drivers: i2s_nrfx: Support less than block size writes

Calling I2S write with less bytes than I2S TX memory block size makes it
possible to synchronize the time when next block is used against some
other, possibly externally sourced, signal. Example use case includes
USB Audio where audio sink and/or source has to be synchronized against
USB SOF. In Asynchronous synchronization type the rate matching is
achieved by varying the number of samples sent/received by 1 sample
(e.g. for 48 kHz audio, 47 or 49 samples are transmitted during frame
instead of 48).

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
This commit is contained in:
Tomasz Moń 2023-11-10 08:49:39 +01:00 committed by Carles Cufí
commit 07d9e521b8

View file

@ -20,6 +20,11 @@ struct stream_cfg {
nrfx_i2s_config_t nrfx_cfg;
};
struct i2s_buf {
void *mem_block;
size_t size;
};
struct i2s_nrfx_drv_data {
struct onoff_manager *clk_mgr;
struct onoff_client clk_cli;
@ -189,9 +194,14 @@ static void find_suitable_clock(const struct i2s_nrfx_drv_cfg *drv_cfg,
static bool get_next_tx_buffer(struct i2s_nrfx_drv_data *drv_data,
nrfx_i2s_buffers_t *buffers)
{
struct i2s_buf buf;
int ret = k_msgq_get(&drv_data->tx_queue,
&buffers->p_tx_buffer,
&buf,
K_NO_WAIT);
if (ret == 0) {
buffers->p_tx_buffer = buf.mem_block;
buffers->buffer_size = buf.size / sizeof(uint32_t);
}
return (ret == 0);
}
@ -226,21 +236,22 @@ static void free_rx_buffer(struct i2s_nrfx_drv_data *drv_data, void *buffer)
static bool supply_next_buffers(struct i2s_nrfx_drv_data *drv_data,
nrfx_i2s_buffers_t *next)
{
uint32_t block_size = (drv_data->active_dir == I2S_DIR_TX)
? drv_data->tx.cfg.block_size
: drv_data->rx.cfg.block_size;
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(drv_data->p_i2s);
return false;
}
/* Set buffer size if there is no TX buffer (which effectively
* controls how many bytes will be received).
*/
if (drv_data->active_dir == I2S_DIR_RX) {
next->buffer_size =
drv_data->rx.cfg.block_size / sizeof(uint32_t);
}
}
next->buffer_size = block_size / sizeof(uint32_t);
drv_data->last_tx_buffer = next->p_tx_buffer;
LOG_DBG("Next buffers: %p/%p", next->p_tx_buffer, next->p_rx_buffer);
nrfx_i2s_next_buffers_set(drv_data->p_i2s, next);
@ -300,8 +311,12 @@ static void data_handler(const struct device *dev,
if (drv_data->discard_rx) {
free_rx_buffer(drv_data, released->p_rx_buffer);
} else {
struct i2s_buf buf = {
.mem_block = released->p_rx_buffer,
.size = released->buffer_size * sizeof(uint32_t)
};
int ret = k_msgq_put(&drv_data->rx_queue,
&released->p_rx_buffer,
&buf,
K_NO_WAIT);
if (ret < 0) {
LOG_ERR("No room in RX queue");
@ -351,6 +366,7 @@ static void data_handler(const struct device *dev,
* before this buffer would be started again).
*/
next.p_tx_buffer = drv_data->last_tx_buffer;
next.buffer_size = 1;
} else if (get_next_tx_buffer(drv_data, &next)) {
/* Next TX buffer successfully retrieved from
* the queue, nothing more to do here.
@ -367,6 +383,7 @@ static void data_handler(const struct device *dev,
* will be stopped earlier.
*/
next.p_tx_buffer = drv_data->last_tx_buffer;
next.buffer_size = 1;
} else {
/* Next TX buffer cannot be supplied now.
* Defer it to when the user writes more data.
@ -383,21 +400,21 @@ static void data_handler(const struct device *dev,
static void purge_queue(const struct device *dev, enum i2s_dir dir)
{
struct i2s_nrfx_drv_data *drv_data = dev->data;
void *mem_block;
struct i2s_buf buf;
if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) {
while (k_msgq_get(&drv_data->tx_queue,
&mem_block,
&buf,
K_NO_WAIT) == 0) {
free_tx_buffer(drv_data, mem_block);
free_tx_buffer(drv_data, buf.mem_block);
}
}
if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) {
while (k_msgq_get(&drv_data->rx_queue,
&mem_block,
&buf,
K_NO_WAIT) == 0) {
free_rx_buffer(drv_data, mem_block);
free_rx_buffer(drv_data, buf.mem_block);
}
}
}
@ -560,6 +577,7 @@ static int i2s_nrfx_read(const struct device *dev,
void **mem_block, size_t *size)
{
struct i2s_nrfx_drv_data *drv_data = dev->data;
struct i2s_buf buf;
int ret;
if (!drv_data->rx_configured) {
@ -568,7 +586,7 @@ static int i2s_nrfx_read(const struct device *dev,
}
ret = k_msgq_get(&drv_data->rx_queue,
mem_block,
&buf,
(drv_data->state == I2S_STATE_ERROR)
? K_NO_WAIT
: SYS_TIMEOUT_MS(drv_data->rx.cfg.timeout));
@ -576,10 +594,11 @@ static int i2s_nrfx_read(const struct device *dev,
return -EIO;
}
LOG_DBG("Released RX %p", *mem_block);
LOG_DBG("Released RX %p", buf.mem_block);
if (ret == 0) {
*size = drv_data->rx.cfg.block_size;
*mem_block = buf.mem_block;
*size = buf.size;
}
return ret;
@ -589,6 +608,7 @@ static int i2s_nrfx_write(const struct device *dev,
void *mem_block, size_t size)
{
struct i2s_nrfx_drv_data *drv_data = dev->data;
struct i2s_buf buf = { .mem_block = mem_block, .size = size };
int ret;
if (!drv_data->tx_configured) {
@ -602,14 +622,14 @@ static int i2s_nrfx_write(const struct device *dev,
return -EIO;
}
if (size != drv_data->tx.cfg.block_size) {
LOG_ERR("This device can only write blocks of %u bytes",
if (size > drv_data->tx.cfg.block_size || size < sizeof(uint32_t)) {
LOG_ERR("This device can only write blocks up to %u bytes",
drv_data->tx.cfg.block_size);
return -EIO;
}
ret = k_msgq_put(&drv_data->tx_queue,
&mem_block,
&buf,
SYS_TIMEOUT_MS(drv_data->tx.cfg.timeout));
if (ret < 0) {
return ret;
@ -662,12 +682,17 @@ static int start_transfer(struct i2s_nrfx_drv_data *drv_data)
/* 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;
initial_buffers.buffer_size = block_size / sizeof(uint32_t);
/* It is necessary to set buffer size here only for I2S_DIR_RX,
* because only then the get_next_tx_buffer() call in the if
* condition above gets short-circuited.
*/
if (drv_data->active_dir == I2S_DIR_RX) {
initial_buffers.buffer_size =
drv_data->rx.cfg.block_size / sizeof(uint32_t);
}
drv_data->last_tx_buffer = initial_buffers.p_tx_buffer;
err = nrfx_i2s_start(drv_data->p_i2s, &initial_buffers, 0);
@ -904,8 +929,8 @@ static const struct i2s_driver_api i2s_nrf_drv_api = {
#define I2S_CLK_SRC(idx) DT_STRING_TOKEN(I2S(idx), clock_source)
#define I2S_NRFX_DEVICE(idx) \
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_buf tx_msgs##idx[CONFIG_I2S_NRFX_TX_BLOCK_COUNT]; \
static struct i2s_buf rx_msgs##idx[CONFIG_I2S_NRFX_RX_BLOCK_COUNT]; \
static void data_handler##idx(nrfx_i2s_buffers_t const *p_released, \
uint32_t status) \
{ \
@ -941,10 +966,10 @@ static const struct i2s_driver_api i2s_nrf_drv_api = {
return err; \
} \
k_msgq_init(&i2s_nrfx_data##idx.tx_queue, \
(char *)tx_msgs##idx, sizeof(void *), \
(char *)tx_msgs##idx, sizeof(struct i2s_buf), \
ARRAY_SIZE(tx_msgs##idx)); \
k_msgq_init(&i2s_nrfx_data##idx.rx_queue, \
(char *)rx_msgs##idx, sizeof(void *), \
(char *)rx_msgs##idx, sizeof(struct i2s_buf), \
ARRAY_SIZE(rx_msgs##idx)); \
init_clock_manager(dev); \
return 0; \