Skip to content

Commit

Permalink
drivers: i2s_nrfx: Support less than block size writes
Browse files Browse the repository at this point in the history
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ń <[email protected]>
  • Loading branch information
tmon-nordic authored and carlescufi committed Jan 15, 2024
1 parent 2dee952 commit 07d9e52
Showing 1 changed file with 53 additions and 28 deletions.
81 changes: 53 additions & 28 deletions drivers/i2s/i2s_nrfx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -568,18 +586,19 @@ 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));
if (ret == -ENOMSG) {
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;
Expand All @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) \
{ \
Expand Down Expand Up @@ -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; \
Expand Down

0 comments on commit 07d9e52

Please sign in to comment.