| /* |
| * Copyright (c) 2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdlib.h> |
| #include <zephyr/drivers/i2s.h> |
| #include <zephyr/drivers/clock_control/nrf_clock_control.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <soc.h> |
| #include <nrfx_i2s.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| LOG_MODULE_REGISTER(i2s_nrfx, CONFIG_I2S_LOG_LEVEL); |
| |
| struct stream_cfg { |
| struct i2s_config cfg; |
| nrfx_i2s_config_t nrfx_cfg; |
| }; |
| |
| struct i2s_nrfx_drv_data { |
| struct onoff_manager *clk_mgr; |
| struct onoff_client clk_cli; |
| struct stream_cfg tx; |
| struct k_msgq tx_queue; |
| struct stream_cfg rx; |
| struct k_msgq rx_queue; |
| const uint32_t *last_tx_buffer; |
| enum i2s_state state; |
| enum i2s_dir active_dir; |
| bool stop; /* stop after the current (TX or RX) block */ |
| bool discard_rx; /* discard further RX blocks */ |
| volatile bool next_tx_buffer_needed; |
| bool tx_configured : 1; |
| bool rx_configured : 1; |
| bool request_clock : 1; |
| }; |
| |
| struct i2s_nrfx_drv_cfg { |
| nrfx_i2s_data_handler_t data_handler; |
| nrfx_i2s_config_t nrfx_def_cfg; |
| #ifdef CONFIG_PINCTRL |
| const struct pinctrl_dev_config *pcfg; |
| #endif |
| enum clock_source { |
| PCLK32M, |
| PCLK32M_HFXO, |
| ACLK |
| } clk_src; |
| }; |
| |
| /* Finds the clock settings that give the frame clock frequency closest to |
| * the one requested, taking into account the hardware limitations. |
| */ |
| static void find_suitable_clock(const struct i2s_nrfx_drv_cfg *drv_cfg, |
| nrfx_i2s_config_t *config, |
| const struct i2s_config *i2s_cfg) |
| { |
| static const struct { |
| uint16_t ratio_val; |
| nrf_i2s_ratio_t ratio_enum; |
| } ratios[] = { |
| { 32, NRF_I2S_RATIO_32X }, |
| { 48, NRF_I2S_RATIO_48X }, |
| { 64, NRF_I2S_RATIO_64X }, |
| { 96, NRF_I2S_RATIO_96X }, |
| { 128, NRF_I2S_RATIO_128X }, |
| { 192, NRF_I2S_RATIO_192X }, |
| { 256, NRF_I2S_RATIO_256X }, |
| { 384, NRF_I2S_RATIO_384X }, |
| { 512, NRF_I2S_RATIO_512X } |
| }; |
| const uint32_t src_freq = |
| (NRF_I2S_HAS_CLKCONFIG && drv_cfg->clk_src == ACLK) |
| /* The I2S_NRFX_DEVICE() macro contains build assertions that |
| * make sure that the ACLK clock source is only used when it is |
| * available and only with the "hfclkaudio-frequency" property |
| * defined, but the default value of 0 here needs to be used to |
| * prevent compilation errors when the property is not defined |
| * (this expression will be eventually optimized away then). |
| */ |
| ? DT_PROP_OR(DT_NODELABEL(clock), hfclkaudio_frequency, 0) |
| : 32*1000*1000UL; |
| uint32_t bits_per_frame = 2 * i2s_cfg->word_size; |
| uint32_t best_diff = UINT32_MAX; |
| uint8_t r, best_r = 0; |
| nrf_i2s_mck_t best_mck_cfg = 0; |
| uint32_t best_mck = 0; |
| |
| for (r = 0; r < ARRAY_SIZE(ratios); ++r) { |
| /* Only multiples of the frame width can be used as ratios. */ |
| if ((ratios[r].ratio_val % bits_per_frame) != 0) { |
| continue; |
| } |
| |
| if (IS_ENABLED(CONFIG_SOC_SERIES_NRF53X)) { |
| uint32_t requested_mck = |
| i2s_cfg->frame_clk_freq * ratios[r].ratio_val; |
| /* As specified in the nRF5340 PS: |
| * |
| * MCKFREQ = 4096 * floor(f_MCK * 1048576 / |
| * (f_source + f_MCK / 2)) |
| * f_actual = f_source / |
| * floor(1048576 * 4096 / MCKFREQ) |
| */ |
| uint32_t mck_factor = |
| (uint32_t)((requested_mck * 1048576ULL) / |
| (src_freq + requested_mck / 2)); |
| uint32_t actual_mck = src_freq / (1048576 / mck_factor); |
| |
| uint32_t lrck_freq = actual_mck / ratios[r].ratio_val; |
| uint32_t diff = lrck_freq >= i2s_cfg->frame_clk_freq |
| ? (lrck_freq - i2s_cfg->frame_clk_freq) |
| : (i2s_cfg->frame_clk_freq - lrck_freq); |
| |
| if (diff < best_diff) { |
| best_mck_cfg = mck_factor * 4096; |
| best_mck = actual_mck; |
| best_r = r; |
| /* Stop if an exact match is found. */ |
| if (diff == 0) { |
| break; |
| } |
| |
| best_diff = diff; |
| } |
| } else { |
| static const struct { |
| uint8_t divider_val; |
| nrf_i2s_mck_t divider_enum; |
| } dividers[] = { |
| { 8, NRF_I2S_MCK_32MDIV8 }, |
| { 10, NRF_I2S_MCK_32MDIV10 }, |
| { 11, NRF_I2S_MCK_32MDIV11 }, |
| { 15, NRF_I2S_MCK_32MDIV15 }, |
| { 16, NRF_I2S_MCK_32MDIV16 }, |
| { 21, NRF_I2S_MCK_32MDIV21 }, |
| { 23, NRF_I2S_MCK_32MDIV23 }, |
| { 30, NRF_I2S_MCK_32MDIV30 }, |
| { 31, NRF_I2S_MCK_32MDIV31 }, |
| { 32, NRF_I2S_MCK_32MDIV32 }, |
| { 42, NRF_I2S_MCK_32MDIV42 }, |
| { 63, NRF_I2S_MCK_32MDIV63 }, |
| { 125, NRF_I2S_MCK_32MDIV125 } |
| }; |
| |
| for (uint8_t d = 0; d < ARRAY_SIZE(dividers); ++d) { |
| uint32_t mck_freq = |
| src_freq / dividers[d].divider_val; |
| uint32_t lrck_freq = |
| mck_freq / ratios[r].ratio_val; |
| uint32_t diff = |
| lrck_freq >= i2s_cfg->frame_clk_freq |
| ? (lrck_freq - i2s_cfg->frame_clk_freq) |
| : (i2s_cfg->frame_clk_freq - lrck_freq); |
| |
| if (diff < best_diff) { |
| best_mck_cfg = dividers[d].divider_enum; |
| best_mck = mck_freq; |
| best_r = r; |
| /* Stop if an exact match is found. */ |
| if (diff == 0) { |
| break; |
| } |
| |
| best_diff = diff; |
| } |
| |
| /* Since dividers are in ascending order, stop |
| * checking next ones for the current ratio |
| * after resulting LRCK frequency falls below |
| * the one requested. |
| */ |
| if (lrck_freq < i2s_cfg->frame_clk_freq) { |
| break; |
| } |
| } |
| } |
| } |
| |
| config->mck_setup = best_mck_cfg; |
| config->ratio = ratios[best_r].ratio_enum; |
| LOG_INF("I2S MCK frequency: %u, actual PCM rate: %u", |
| best_mck, best_mck / ratios[best_r].ratio_val); |
| } |
| |
| static bool get_next_tx_buffer(struct i2s_nrfx_drv_data *drv_data, |
| nrfx_i2s_buffers_t *buffers) |
| { |
| int ret = k_msgq_get(&drv_data->tx_queue, |
| &buffers->p_tx_buffer, |
| K_NO_WAIT); |
| return (ret == 0); |
| } |
| |
| static bool get_next_rx_buffer(struct i2s_nrfx_drv_data *drv_data, |
| nrfx_i2s_buffers_t *buffers) |
| { |
| int ret = k_mem_slab_alloc(drv_data->rx.cfg.mem_slab, |
| (void **)&buffers->p_rx_buffer, |
| K_NO_WAIT); |
| if (ret < 0) { |
| LOG_ERR("Failed to allocate next RX buffer: %d", |
| ret); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void free_tx_buffer(struct i2s_nrfx_drv_data *drv_data, |
| const void *buffer) |
| { |
| k_mem_slab_free(drv_data->tx.cfg.mem_slab, (void **)&buffer); |
| LOG_DBG("Freed TX %p", buffer); |
| } |
| |
| static void free_rx_buffer(struct i2s_nrfx_drv_data *drv_data, void *buffer) |
| { |
| k_mem_slab_free(drv_data->rx.cfg.mem_slab, &buffer); |
| LOG_DBG("Freed RX %p", buffer); |
| } |
| |
| static bool supply_next_buffers(struct i2s_nrfx_drv_data *drv_data, |
| nrfx_i2s_buffers_t *next) |
| { |
| drv_data->last_tx_buffer = next->p_tx_buffer; |
| |
| if (drv_data->active_dir != I2S_DIR_TX) { /* -> RX active */ |
| if (!get_next_rx_buffer(drv_data, next)) { |
| drv_data->state = I2S_STATE_ERROR; |
| nrfx_i2s_stop(); |
| return false; |
| } |
| } |
| |
| LOG_DBG("Next buffers: %p/%p", next->p_tx_buffer, next->p_rx_buffer); |
| nrfx_i2s_next_buffers_set(next); |
| return true; |
| } |
| |
| static void data_handler(const struct device *dev, |
| const nrfx_i2s_buffers_t *released, uint32_t status) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| bool stop_transfer = false; |
| |
| if (status & NRFX_I2S_STATUS_TRANSFER_STOPPED) { |
| if (drv_data->state == I2S_STATE_STOPPING) { |
| drv_data->state = I2S_STATE_READY; |
| } |
| if (drv_data->last_tx_buffer) { |
| /* Usually, these pointers are equal, i.e. the last TX |
| * buffer that were to be transferred is released by the |
| * driver after it stops. The last TX buffer pointer is |
| * then set to NULL here so that the buffer can be freed |
| * below, just as any other TX buffer released by the |
| * driver. However, it may happen that the buffer is not |
| * released this way, for example, when the transfer |
| * ends with an error because an RX buffer allocation |
| * fails. In such case, the last TX buffer needs to be |
| * freed here. |
| */ |
| if (drv_data->last_tx_buffer != released->p_tx_buffer) { |
| free_tx_buffer(drv_data, |
| drv_data->last_tx_buffer); |
| } |
| drv_data->last_tx_buffer = NULL; |
| } |
| nrfx_i2s_uninit(); |
| if (drv_data->request_clock) { |
| (void)onoff_release(drv_data->clk_mgr); |
| } |
| } |
| |
| if (released == NULL) { |
| /* This means that buffers for the next part of the transfer |
| * were not supplied and the previous ones cannot be released |
| * yet, as pointers to them were latched in the I2S registers. |
| * It is not an error when the transfer is to be stopped (those |
| * buffers will be released after the transfer actually stops). |
| */ |
| if (drv_data->state != I2S_STATE_STOPPING) { |
| LOG_ERR("Next buffers not supplied on time"); |
| drv_data->state = I2S_STATE_ERROR; |
| } |
| nrfx_i2s_stop(); |
| return; |
| } |
| |
| if (released->p_rx_buffer) { |
| if (drv_data->discard_rx) { |
| free_rx_buffer(drv_data, released->p_rx_buffer); |
| } else { |
| int ret = k_msgq_put(&drv_data->rx_queue, |
| &released->p_rx_buffer, |
| K_NO_WAIT); |
| if (ret < 0) { |
| LOG_ERR("No room in RX queue"); |
| drv_data->state = I2S_STATE_ERROR; |
| stop_transfer = true; |
| |
| free_rx_buffer(drv_data, released->p_rx_buffer); |
| } else { |
| LOG_DBG("Queued RX %p", released->p_rx_buffer); |
| |
| /* If the TX direction is not active and |
| * the transfer should be stopped after |
| * the current block, stop the reception. |
| */ |
| if (drv_data->active_dir == I2S_DIR_RX && |
| drv_data->stop) { |
| drv_data->discard_rx = true; |
| stop_transfer = true; |
| } |
| } |
| } |
| } |
| |
| if (released->p_tx_buffer) { |
| /* If the last buffer that was to be transferred has just been |
| * released, it is time to stop the transfer. |
| */ |
| if (released->p_tx_buffer == drv_data->last_tx_buffer) { |
| drv_data->discard_rx = true; |
| stop_transfer = true; |
| } else { |
| free_tx_buffer(drv_data, released->p_tx_buffer); |
| } |
| } |
| |
| if (stop_transfer) { |
| nrfx_i2s_stop(); |
| } else if (status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED) { |
| nrfx_i2s_buffers_t next = { 0 }; |
| |
| if (drv_data->active_dir != I2S_DIR_RX) { /* -> TX active */ |
| if (drv_data->stop) { |
| /* If the stream is to be stopped, don't get |
| * the next TX buffer from the queue, instead |
| * supply the one used last time (it won't be |
| * transferred, the stream will stop right |
| * before this buffer would be started again). |
| */ |
| next.p_tx_buffer = drv_data->last_tx_buffer; |
| } else if (get_next_tx_buffer(drv_data, &next)) { |
| /* Next TX buffer successfully retrieved from |
| * the queue, nothing more to do here. |
| */ |
| } else if (drv_data->state == I2S_STATE_STOPPING) { |
| /* If there are no more TX blocks queued and |
| * the current state is STOPPING (so the DRAIN |
| * command was triggered) it is time to finish |
| * the transfer. |
| */ |
| drv_data->stop = true; |
| /* Supply the same buffer as last time; it will |
| * not be transferred anyway, as the transfer |
| * will be stopped earlier. |
| */ |
| next.p_tx_buffer = drv_data->last_tx_buffer; |
| } else { |
| /* Next TX buffer cannot be supplied now. |
| * Defer it to when the user writes more data. |
| */ |
| drv_data->next_tx_buffer_needed = true; |
| return; |
| } |
| } |
| |
| (void)supply_next_buffers(drv_data, &next); |
| } |
| } |
| |
| static void purge_queue(const struct device *dev, enum i2s_dir dir) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| void *mem_block; |
| |
| if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { |
| while (k_msgq_get(&drv_data->tx_queue, |
| &mem_block, |
| K_NO_WAIT) == 0) { |
| free_tx_buffer(drv_data, mem_block); |
| } |
| } |
| |
| if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { |
| while (k_msgq_get(&drv_data->rx_queue, |
| &mem_block, |
| K_NO_WAIT) == 0) { |
| free_rx_buffer(drv_data, mem_block); |
| } |
| } |
| } |
| |
| static int i2s_nrfx_configure(const struct device *dev, enum i2s_dir dir, |
| const struct i2s_config *i2s_cfg) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| const struct i2s_nrfx_drv_cfg *drv_cfg = dev->config; |
| nrfx_i2s_config_t nrfx_cfg; |
| |
| if (drv_data->state != I2S_STATE_READY) { |
| LOG_ERR("Cannot configure in state: %d", drv_data->state); |
| return -EINVAL; |
| } |
| |
| if (i2s_cfg->frame_clk_freq == 0) { /* -> reset state */ |
| purge_queue(dev, dir); |
| if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { |
| drv_data->tx_configured = false; |
| memset(&drv_data->tx, 0, sizeof(drv_data->tx)); |
| } |
| if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { |
| drv_data->rx_configured = false; |
| memset(&drv_data->rx, 0, sizeof(drv_data->rx)); |
| } |
| return 0; |
| } |
| |
| __ASSERT_NO_MSG(i2s_cfg->mem_slab != NULL && |
| i2s_cfg->block_size != 0); |
| |
| if ((i2s_cfg->block_size % sizeof(uint32_t)) != 0) { |
| LOG_ERR("This device can transfer only full 32-bit words"); |
| return -EINVAL; |
| } |
| |
| nrfx_cfg = drv_cfg->nrfx_def_cfg; |
| |
| switch (i2s_cfg->word_size) { |
| case 8: |
| nrfx_cfg.sample_width = NRF_I2S_SWIDTH_8BIT; |
| break; |
| case 16: |
| nrfx_cfg.sample_width = NRF_I2S_SWIDTH_16BIT; |
| break; |
| case 24: |
| nrfx_cfg.sample_width = NRF_I2S_SWIDTH_24BIT; |
| break; |
| #if defined(I2S_CONFIG_SWIDTH_SWIDTH_32Bit) |
| case 32: |
| nrfx_cfg.sample_width = NRF_I2S_SWIDTH_32BIT; |
| break; |
| #endif |
| default: |
| LOG_ERR("Unsupported word size: %u", i2s_cfg->word_size); |
| return -EINVAL; |
| } |
| |
| switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) { |
| case I2S_FMT_DATA_FORMAT_I2S: |
| nrfx_cfg.alignment = NRF_I2S_ALIGN_LEFT; |
| nrfx_cfg.format = NRF_I2S_FORMAT_I2S; |
| break; |
| case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED: |
| nrfx_cfg.alignment = NRF_I2S_ALIGN_LEFT; |
| nrfx_cfg.format = NRF_I2S_FORMAT_ALIGNED; |
| break; |
| case I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED: |
| nrfx_cfg.alignment = NRF_I2S_ALIGN_RIGHT; |
| nrfx_cfg.format = NRF_I2S_FORMAT_ALIGNED; |
| break; |
| default: |
| LOG_ERR("Unsupported data format: 0x%02x", i2s_cfg->format); |
| return -EINVAL; |
| } |
| |
| if ((i2s_cfg->format & I2S_FMT_DATA_ORDER_LSB) || |
| (i2s_cfg->format & I2S_FMT_BIT_CLK_INV) || |
| (i2s_cfg->format & I2S_FMT_FRAME_CLK_INV)) { |
| LOG_ERR("Unsupported stream format: 0x%02x", i2s_cfg->format); |
| return -EINVAL; |
| } |
| |
| if (i2s_cfg->channels == 2) { |
| nrfx_cfg.channels = NRF_I2S_CHANNELS_STEREO; |
| } else if (i2s_cfg->channels == 1) { |
| nrfx_cfg.channels = NRF_I2S_CHANNELS_LEFT; |
| } else { |
| LOG_ERR("Unsupported number of channels: %u", |
| i2s_cfg->channels); |
| return -EINVAL; |
| } |
| |
| if ((i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) && |
| (i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE)) { |
| nrfx_cfg.mode = NRF_I2S_MODE_SLAVE; |
| } else if (!(i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) && |
| !(i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE)) { |
| nrfx_cfg.mode = NRF_I2S_MODE_MASTER; |
| } else { |
| LOG_ERR("Unsupported operation mode: 0x%02x", i2s_cfg->options); |
| return -EINVAL; |
| } |
| |
| /* If the master clock generator is needed (i.e. in Master mode or when |
| * the MCK output is used), find a suitable clock configuration for it. |
| */ |
| if (nrfx_cfg.mode == NRF_I2S_MODE_MASTER || |
| nrfx_cfg.mck_pin != NRFX_I2S_PIN_NOT_USED) { |
| find_suitable_clock(drv_cfg, &nrfx_cfg, i2s_cfg); |
| /* Unless the PCLK32M source is used with the HFINT oscillator |
| * (which is always available without any additional actions), |
| * it is required to request the proper clock to be running |
| * before starting the transfer itself. |
| */ |
| drv_data->request_clock = (drv_cfg->clk_src != PCLK32M); |
| } else { |
| nrfx_cfg.mck_setup = NRF_I2S_MCK_DISABLED; |
| drv_data->request_clock = false; |
| } |
| |
| if ((i2s_cfg->options & I2S_OPT_LOOPBACK) || |
| (i2s_cfg->options & I2S_OPT_PINGPONG)) { |
| LOG_ERR("Unsupported options: 0x%02x", i2s_cfg->options); |
| return -EINVAL; |
| } |
| |
| if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { |
| drv_data->tx.cfg = *i2s_cfg; |
| drv_data->tx.nrfx_cfg = nrfx_cfg; |
| drv_data->tx_configured = true; |
| } |
| |
| if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { |
| drv_data->rx.cfg = *i2s_cfg; |
| drv_data->rx.nrfx_cfg = nrfx_cfg; |
| drv_data->rx_configured = true; |
| } |
| |
| return 0; |
| } |
| |
| static const struct i2s_config *i2s_nrfx_config_get(const struct device *dev, |
| enum i2s_dir dir) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| |
| if (dir == I2S_DIR_TX && drv_data->tx_configured) { |
| return &drv_data->tx.cfg; |
| } |
| if (dir == I2S_DIR_RX && drv_data->rx_configured) { |
| return &drv_data->rx.cfg; |
| } |
| |
| return NULL; |
| } |
| |
| static int i2s_nrfx_read(const struct device *dev, |
| void **mem_block, size_t *size) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| int ret; |
| |
| if (!drv_data->rx_configured) { |
| LOG_ERR("Device is not configured"); |
| return -EIO; |
| } |
| |
| ret = k_msgq_get(&drv_data->rx_queue, |
| mem_block, |
| (drv_data->state == I2S_STATE_ERROR) |
| ? K_NO_WAIT |
| : SYS_TIMEOUT_MS(drv_data->rx.cfg.timeout)); |
| if (ret == -ENOMSG) { |
| return -EIO; |
| } |
| |
| LOG_DBG("Released RX %p", *mem_block); |
| |
| if (ret == 0) { |
| *size = drv_data->rx.cfg.block_size; |
| } |
| |
| return ret; |
| } |
| |
| static int i2s_nrfx_write(const struct device *dev, |
| void *mem_block, size_t size) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| |
| if (!drv_data->tx_configured) { |
| LOG_ERR("Device is not configured"); |
| return -EIO; |
| } |
| |
| if (drv_data->state != I2S_STATE_RUNNING && |
| drv_data->state != I2S_STATE_READY) { |
| LOG_ERR("Cannot write in state: %d", drv_data->state); |
| return -EIO; |
| } |
| |
| if (size != drv_data->tx.cfg.block_size) { |
| LOG_ERR("This device can only write blocks of %u bytes", |
| drv_data->tx.cfg.block_size); |
| return -EIO; |
| } |
| |
| if (drv_data->state == I2S_STATE_RUNNING && |
| drv_data->next_tx_buffer_needed) { |
| nrfx_i2s_buffers_t next = { .p_tx_buffer = mem_block }; |
| |
| drv_data->next_tx_buffer_needed = false; |
| |
| LOG_DBG("Next TX %p", mem_block); |
| |
| if (!supply_next_buffers(drv_data, &next)) { |
| return -EIO; |
| } |
| } else { |
| int ret = k_msgq_put(&drv_data->tx_queue, |
| &mem_block, |
| SYS_TIMEOUT_MS(drv_data->tx.cfg.timeout)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| LOG_DBG("Queued TX %p", mem_block); |
| } |
| |
| return 0; |
| } |
| |
| static int start_transfer(struct i2s_nrfx_drv_data *drv_data) |
| { |
| nrfx_i2s_buffers_t initial_buffers = { 0 }; |
| int ret; |
| |
| if (drv_data->active_dir != I2S_DIR_RX && /* -> TX to be started */ |
| !get_next_tx_buffer(drv_data, &initial_buffers)) { |
| LOG_ERR("No TX buffer available"); |
| ret = -ENOMEM; |
| } else if (drv_data->active_dir != I2S_DIR_TX && /* -> RX to be started */ |
| !get_next_rx_buffer(drv_data, &initial_buffers)) { |
| /* Failed to allocate next RX buffer */ |
| ret = -ENOMEM; |
| } else { |
| uint32_t block_size = (drv_data->active_dir == I2S_DIR_TX) |
| ? drv_data->tx.cfg.block_size |
| : drv_data->rx.cfg.block_size; |
| nrfx_err_t err; |
| |
| drv_data->last_tx_buffer = initial_buffers.p_tx_buffer; |
| |
| err = nrfx_i2s_start(&initial_buffers, |
| block_size / sizeof(uint32_t), 0); |
| if (err == NRFX_SUCCESS) { |
| return 0; |
| } |
| |
| LOG_ERR("Failed to start I2S transfer: 0x%08x", err); |
| ret = -EIO; |
| } |
| |
| nrfx_i2s_uninit(); |
| if (drv_data->request_clock) { |
| (void)onoff_release(drv_data->clk_mgr); |
| } |
| |
| if (initial_buffers.p_tx_buffer) { |
| free_tx_buffer(drv_data, initial_buffers.p_tx_buffer); |
| } |
| if (initial_buffers.p_rx_buffer) { |
| free_rx_buffer(drv_data, initial_buffers.p_rx_buffer); |
| } |
| |
| drv_data->state = I2S_STATE_ERROR; |
| return ret; |
| } |
| |
| static void clock_started_callback(struct onoff_manager *mgr, |
| struct onoff_client *cli, |
| uint32_t state, |
| int res) |
| { |
| struct i2s_nrfx_drv_data *drv_data = |
| CONTAINER_OF(cli, struct i2s_nrfx_drv_data, clk_cli); |
| |
| /* The driver state can be set back to READY at this point if the DROP |
| * command was triggered before the clock has started. Do not start |
| * the actual transfer in such case. |
| */ |
| if (drv_data->state == I2S_STATE_READY) { |
| nrfx_i2s_uninit(); |
| (void)onoff_release(drv_data->clk_mgr); |
| } else { |
| (void)start_transfer(drv_data); |
| } |
| } |
| |
| static int trigger_start(const struct device *dev) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| const struct i2s_nrfx_drv_cfg *drv_cfg = dev->config; |
| nrfx_err_t err; |
| int ret; |
| const nrfx_i2s_config_t *nrfx_cfg = (drv_data->active_dir == I2S_DIR_TX) |
| ? &drv_data->tx.nrfx_cfg |
| : &drv_data->rx.nrfx_cfg; |
| |
| err = nrfx_i2s_init(nrfx_cfg, drv_cfg->data_handler); |
| if (err != NRFX_SUCCESS) { |
| LOG_ERR("Failed to initialize I2S: 0x%08x", err); |
| return -EIO; |
| } |
| |
| drv_data->state = I2S_STATE_RUNNING; |
| |
| #if NRF_I2S_HAS_CLKCONFIG |
| nrf_i2s_clk_configure(NRF_I2S0, |
| drv_cfg->clk_src == ACLK ? NRF_I2S_CLKSRC_ACLK |
| : NRF_I2S_CLKSRC_PCLK32M, |
| false); |
| #endif |
| |
| /* If it is required to use certain HF clock, request it to be running |
| * first. If not, start the transfer directly. |
| */ |
| if (drv_data->request_clock) { |
| sys_notify_init_callback(&drv_data->clk_cli.notify, |
| clock_started_callback); |
| ret = onoff_request(drv_data->clk_mgr, &drv_data->clk_cli); |
| if (ret < 0) { |
| nrfx_i2s_uninit(); |
| drv_data->state = I2S_STATE_READY; |
| |
| LOG_ERR("Failed to request clock: %d", ret); |
| return -EIO; |
| } |
| } else { |
| ret = start_transfer(drv_data); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int i2s_nrfx_trigger(const struct device *dev, |
| enum i2s_dir dir, enum i2s_trigger_cmd cmd) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| bool configured = false; |
| bool cmd_allowed; |
| |
| /* This driver does not use the I2S_STATE_NOT_READY value. |
| * Instead, if a given stream is not configured, the respective |
| * flag (tx_configured or rx_configured) is cleared. |
| */ |
| if (dir == I2S_DIR_BOTH) { |
| configured = drv_data->tx_configured && drv_data->rx_configured; |
| } else if (dir == I2S_DIR_TX) { |
| configured = drv_data->tx_configured; |
| } else if (dir == I2S_DIR_RX) { |
| configured = drv_data->rx_configured; |
| } |
| |
| if (!configured) { |
| LOG_ERR("Device is not configured"); |
| return -EIO; |
| } |
| |
| if (dir == I2S_DIR_BOTH && |
| (memcmp(&drv_data->tx.nrfx_cfg, |
| &drv_data->rx.nrfx_cfg, |
| sizeof(drv_data->rx.nrfx_cfg)) != 0 |
| || |
| (drv_data->tx.cfg.block_size != drv_data->rx.cfg.block_size))) { |
| LOG_ERR("TX and RX configurations are different"); |
| return -EIO; |
| } |
| |
| switch (cmd) { |
| case I2S_TRIGGER_START: |
| cmd_allowed = (drv_data->state == I2S_STATE_READY); |
| break; |
| case I2S_TRIGGER_STOP: |
| case I2S_TRIGGER_DRAIN: |
| cmd_allowed = (drv_data->state == I2S_STATE_RUNNING); |
| break; |
| case I2S_TRIGGER_DROP: |
| cmd_allowed = configured; |
| break; |
| case I2S_TRIGGER_PREPARE: |
| cmd_allowed = (drv_data->state == I2S_STATE_ERROR); |
| break; |
| default: |
| LOG_ERR("Invalid trigger: %d", cmd); |
| return -EINVAL; |
| } |
| |
| if (!cmd_allowed) { |
| return -EIO; |
| } |
| |
| /* For triggers applicable to the RUNNING state (i.e. STOP, DRAIN, |
| * and DROP), ensure that the command is applied to the streams |
| * that are currently active (this device cannot e.g. stop only TX |
| * without stopping RX). |
| */ |
| if (drv_data->state == I2S_STATE_RUNNING && |
| drv_data->active_dir != dir) { |
| LOG_ERR("Inappropriate trigger (%d/%d), active stream(s): %d", |
| cmd, dir, drv_data->active_dir); |
| return -EINVAL; |
| } |
| |
| switch (cmd) { |
| case I2S_TRIGGER_START: |
| drv_data->stop = false; |
| drv_data->discard_rx = false; |
| drv_data->active_dir = dir; |
| drv_data->next_tx_buffer_needed = false; |
| return trigger_start(dev); |
| |
| case I2S_TRIGGER_STOP: |
| drv_data->state = I2S_STATE_STOPPING; |
| drv_data->stop = true; |
| return 0; |
| |
| case I2S_TRIGGER_DRAIN: |
| drv_data->state = I2S_STATE_STOPPING; |
| /* If only RX is active, DRAIN is equivalent to STOP. */ |
| drv_data->stop = (drv_data->active_dir == I2S_DIR_RX); |
| return 0; |
| |
| case I2S_TRIGGER_DROP: |
| if (drv_data->state != I2S_STATE_READY) { |
| drv_data->discard_rx = true; |
| nrfx_i2s_stop(); |
| } |
| purge_queue(dev, dir); |
| drv_data->state = I2S_STATE_READY; |
| return 0; |
| |
| case I2S_TRIGGER_PREPARE: |
| purge_queue(dev, dir); |
| drv_data->state = I2S_STATE_READY; |
| return 0; |
| |
| default: |
| LOG_ERR("Invalid trigger: %d", cmd); |
| return -EINVAL; |
| } |
| } |
| |
| static void init_clock_manager(const struct device *dev) |
| { |
| struct i2s_nrfx_drv_data *drv_data = dev->data; |
| clock_control_subsys_t subsys; |
| |
| #if NRF_CLOCK_HAS_HFCLKAUDIO |
| const struct i2s_nrfx_drv_cfg *drv_cfg = dev->config; |
| |
| if (drv_cfg->clk_src == ACLK) { |
| subsys = CLOCK_CONTROL_NRF_SUBSYS_HFAUDIO; |
| } else |
| #endif |
| { |
| subsys = CLOCK_CONTROL_NRF_SUBSYS_HF; |
| } |
| |
| drv_data->clk_mgr = z_nrf_clock_control_get_onoff(subsys); |
| __ASSERT_NO_MSG(drv_data->clk_mgr != NULL); |
| } |
| |
| static const struct i2s_driver_api i2s_nrf_drv_api = { |
| .configure = i2s_nrfx_configure, |
| .config_get = i2s_nrfx_config_get, |
| .read = i2s_nrfx_read, |
| .write = i2s_nrfx_write, |
| .trigger = i2s_nrfx_trigger, |
| }; |
| |
| #define I2S(idx) DT_NODELABEL(i2s##idx) |
| #define I2S_PIN(idx, name) DT_PROP_OR(I2S(idx), name##_pin, \ |
| NRFX_I2S_PIN_NOT_USED) |
| #define I2S_CLK_SRC(idx) DT_STRING_TOKEN(I2S(idx), clock_source) |
| |
| #define I2S_NRFX_DEVICE(idx) \ |
| NRF_DT_CHECK_PIN_ASSIGNMENTS(I2S(idx), 0, sck_pin, lrck_pin, \ |
| mck_pin, sdout_pin, sdin_pin); \ |
| static void *tx_msgs##idx[CONFIG_I2S_NRFX_TX_BLOCK_COUNT]; \ |
| static void *rx_msgs##idx[CONFIG_I2S_NRFX_RX_BLOCK_COUNT]; \ |
| static struct i2s_nrfx_drv_data i2s_nrfx_data##idx = { \ |
| .state = I2S_STATE_READY, \ |
| }; \ |
| static int i2s_nrfx_init##idx(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_IRQN(I2S(idx)), DT_IRQ(I2S(idx), priority), \ |
| nrfx_isr, nrfx_i2s_irq_handler, 0); \ |
| IF_ENABLED(CONFIG_PINCTRL, ( \ |
| const struct i2s_nrfx_drv_cfg *drv_cfg = dev->config;\ |
| int err = pinctrl_apply_state(drv_cfg->pcfg, \ |
| PINCTRL_STATE_DEFAULT);\ |
| if (err < 0) { \ |
| return err; \ |
| } \ |
| )) \ |
| k_msgq_init(&i2s_nrfx_data##idx.tx_queue, \ |
| (char *)tx_msgs##idx, sizeof(void *), \ |
| ARRAY_SIZE(tx_msgs##idx)); \ |
| k_msgq_init(&i2s_nrfx_data##idx.rx_queue, \ |
| (char *)rx_msgs##idx, sizeof(void *), \ |
| ARRAY_SIZE(rx_msgs##idx)); \ |
| init_clock_manager(dev); \ |
| return 0; \ |
| } \ |
| static void data_handler##idx(nrfx_i2s_buffers_t const *p_released, \ |
| uint32_t status) \ |
| { \ |
| data_handler(DEVICE_DT_GET(I2S(idx)), p_released, status); \ |
| } \ |
| IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_DEFINE(I2S(idx)))); \ |
| static const struct i2s_nrfx_drv_cfg i2s_nrfx_cfg##idx = { \ |
| .data_handler = data_handler##idx, \ |
| .nrfx_def_cfg = NRFX_I2S_DEFAULT_CONFIG(I2S_PIN(idx, sck), \ |
| I2S_PIN(idx, lrck), \ |
| I2S_PIN(idx, mck), \ |
| I2S_PIN(idx, sdout), \ |
| I2S_PIN(idx, sdin)), \ |
| IF_ENABLED(CONFIG_PINCTRL, \ |
| (.nrfx_def_cfg.skip_gpio_cfg = true, \ |
| .nrfx_def_cfg.skip_psel_cfg = true, \ |
| .pcfg = PINCTRL_DT_DEV_CONFIG_GET(I2S(idx)),)) \ |
| .clk_src = I2S_CLK_SRC(idx), \ |
| }; \ |
| BUILD_ASSERT(I2S_CLK_SRC(idx) != ACLK || NRF_I2S_HAS_CLKCONFIG, \ |
| "Clock source ACLK is not available."); \ |
| BUILD_ASSERT(I2S_CLK_SRC(idx) != ACLK || \ |
| DT_NODE_HAS_PROP(DT_NODELABEL(clock), \ |
| hfclkaudio_frequency), \ |
| "Clock source ACLK requires the hfclkaudio-frequency " \ |
| "property to be defined in the nordic,nrf-clock node."); \ |
| DEVICE_DT_DEFINE(I2S(idx), i2s_nrfx_init##idx, NULL, \ |
| &i2s_nrfx_data##idx, &i2s_nrfx_cfg##idx, \ |
| POST_KERNEL, CONFIG_I2S_INIT_PRIORITY, \ |
| &i2s_nrf_drv_api); |
| |
| /* Existing SoCs only have one I2S instance. */ |
| I2S_NRFX_DEVICE(0); |