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 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 committed Jan 8, 2024
1 parent 30650ab commit b95b314
Showing 1 changed file with 46 additions and 30 deletions.
76 changes: 46 additions & 30 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 All @@ -29,6 +34,7 @@ struct i2s_nrfx_drv_data {
struct k_msgq rx_queue;
const nrfx_i2s_t *p_i2s;
const uint32_t *last_tx_buffer;
uint16_t last_buffer_size;
enum i2s_state state;
enum i2s_dir active_dir;
bool stop; /* stop after the current (TX or RX) block */
Expand Down Expand Up @@ -189,9 +195,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 +237,20 @@ 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;
}
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;
drv_data->last_buffer_size = next->buffer_size;

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 @@ -274,6 +284,7 @@ static void data_handler(const struct device *dev,
drv_data->last_tx_buffer);
}
drv_data->last_tx_buffer = NULL;
drv_data->last_buffer_size = 0;
}
nrfx_i2s_uninit(drv_data->p_i2s);
if (drv_data->request_clock) {
Expand All @@ -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 = drv_data->last_buffer_size;
} 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 = drv_data->last_buffer_size;
} 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 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);
free_tx_buffer(drv_data, mem_block.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);
free_rx_buffer(drv_data, mem_block.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,8 @@ 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",
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,13 +676,15 @@ 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);
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;
drv_data->last_buffer_size = initial_buffers.buffer_size;

err = nrfx_i2s_start(drv_data->p_i2s, &initial_buffers, 0);
if (err == NRFX_SUCCESS) {
Expand Down Expand Up @@ -904,8 +920,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 +957,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 b95b314

Please sign in to comment.