You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by je...@apache.org on 2021/02/01 07:20:39 UTC

[mynewt-core] branch master updated: hw/drivers/i2s: Add I2S driver for DA1469x family

This is an automated email from the ASF dual-hosted git repository.

jerzy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git


The following commit(s) were added to refs/heads/master by this push:
     new c3991db  hw/drivers/i2s: Add I2S driver for DA1469x family
c3991db is described below

commit c3991db548a707a58e7be1b43b1b1cc4f8a408ed
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Wed Oct 28 15:24:02 2020 +0100

    hw/drivers/i2s: Add I2S driver for DA1469x family
    
    I2S driver implementation for DA1469X MCUs.
    Only TX part for now (speaker).
    
    Three data formats supported 16/16 16/32 and 32/32.
---
 .../i2s_da1469x/include/i2s_da1469x/i2s_da1469x.h  |  69 ++++
 hw/drivers/i2s/i2s_da1469x/pkg.yml                 |  29 ++
 hw/drivers/i2s/i2s_da1469x/src/i2s_da1469x.c       | 426 +++++++++++++++++++++
 3 files changed, 524 insertions(+)

diff --git a/hw/drivers/i2s/i2s_da1469x/include/i2s_da1469x/i2s_da1469x.h b/hw/drivers/i2s/i2s_da1469x/include/i2s_da1469x/i2s_da1469x.h
new file mode 100644
index 0000000..f8d361d
--- /dev/null
+++ b/hw/drivers/i2s/i2s_da1469x/include/i2s_da1469x/i2s_da1469x.h
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef _I2S_DA1469X_H
+#define _I2S_DA1469X_H
+
+#include <stdint.h>
+
+struct da1469x_dma_buffer {
+    uint16_t size;
+    uint8_t *buffer;
+};
+
+#define I2S_DA1469X_DMA_BUFFER_DEF(_name, _size) \
+    static uint8_t _Alignas(uint32_t) _name ## _dma_mem[2][_size]; \
+    static struct da1469x_dma_buffer _name ## _buffers = {         \
+        .size = _size,                                             \
+        .buffer = &_name ## _dma_mem[0][0],                        \
+    }
+
+#define I2S_DA1469X_DMA_BUFFER(name) (struct da1469x_dma_buffer *)(&name ## _buffers)
+
+typedef enum {
+    I2S_DATA_FRAME_16_16,
+    I2S_DATA_FRAME_16_32,
+    I2S_DATA_FRAME_32_32,
+} i2s_data_format_t;
+
+struct i2s_cfg {
+    /* Data pin from I2S microphone. */
+    int8_t sdin_pin;
+    /* Data pin to I2S speaker(s). */
+    int8_t sdout_pin;
+    /* Left right clock pin. */
+    int8_t lrcl_pin;
+    /* Bit clock pin. */
+    int8_t bclk_pin;
+
+    /* Samples per second. */
+    uint32_t sample_rate;
+    /* Bits per sample. */
+    uint8_t sample_bits;
+    /* I2S data format. */
+    i2s_data_format_t data_format;
+
+    /* Standard I2S buffer pool, should be set with I2S_BUFFER_POOL() macro. */
+    struct i2s_buffer_pool *pool;
+
+    /* DMA buffers, should be set with I2S_DA1469X_DMA_BUFFER(). */
+    struct da1469x_dma_buffer *dma_memory;
+};
+
+#endif /* _I2S_DA1469X_H */
diff --git a/hw/drivers/i2s/i2s_da1469x/pkg.yml b/hw/drivers/i2s/i2s_da1469x/pkg.yml
new file mode 100644
index 0000000..5b65542
--- /dev/null
+++ b/hw/drivers/i2s/i2s_da1469x/pkg.yml
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: hw/drivers/i2s/i2s_da1469x
+pkg.description: I2S driver for DA1469x
+pkg.author: "Apache Mynewt <de...@mynewt.apache.org>"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+
+pkg.apis:
+    - I2S_HW_IMPL
+pkg.deps:
+    - "@apache-mynewt-core/hw/drivers/i2s"
diff --git a/hw/drivers/i2s/i2s_da1469x/src/i2s_da1469x.c b/hw/drivers/i2s/i2s_da1469x/src/i2s_da1469x.c
new file mode 100644
index 0000000..57dd902
--- /dev/null
+++ b/hw/drivers/i2s/i2s_da1469x/src/i2s_da1469x.c
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <os/mynewt.h>
+#include <bsp/bsp.h>
+#include <i2s/i2s.h>
+#include <i2s/i2s_driver.h>
+#include <i2s_da1469x/i2s_da1469x.h>
+#include <mcu/da1469x_dma.h>
+#include <mcu/da1469x_pd.h>
+
+struct da1469x_i2s {
+    struct i2s_cfg cfg;
+
+    /* DMA register pairs 0,2 RX, 1,3 TX */
+    struct da1469x_dma_regs *dma_regs[4];
+    /* Currently active DMA buffer half. */
+    uint8_t active_half;
+    /* Number of buffers that driver has data from. */
+    uint8_t full_buffer_count;
+
+    bool running;
+
+    struct i2s *i2s;
+};
+
+static struct da1469x_i2s da1469x_i2s;
+
+static int
+da1469x_i2s_init(struct i2s *i2s, const struct i2s_cfg *cfg)
+{
+    int rc;
+
+    da1469x_i2s.i2s = i2s;
+    da1469x_i2s.cfg = *cfg;
+
+    mcu_gpio_set_pin_function(cfg->bclk_pin, MCU_GPIO_MODE_OUTPUT, MCU_GPIO_FUNC_PCM_CLK);
+    mcu_gpio_set_pin_function(cfg->lrcl_pin, MCU_GPIO_MODE_OUTPUT, MCU_GPIO_FUNC_PCM_FSC);
+    i2s->direction = I2S_INVALID;
+    if (cfg->sdout_pin >= 0) {
+        mcu_gpio_set_pin_function(cfg->sdout_pin, MCU_GPIO_MODE_OUTPUT, MCU_GPIO_FUNC_PCM_DO);
+        i2s->direction = I2S_OUT;
+    }
+    if (cfg->sdin_pin >= 0) {
+        mcu_gpio_set_pin_function(cfg->sdin_pin, MCU_GPIO_MODE_INPUT, MCU_GPIO_FUNC_PCM_DI);
+        i2s->direction |= I2S_IN;
+    }
+    i2s->sample_size_in_bytes = cfg->sample_bits / 8;
+
+    rc = i2s_init(i2s, cfg->pool);
+
+    if (rc != OS_OK) {
+        goto end;
+    }
+
+    i2s->sample_rate = cfg->sample_rate;
+    i2s->driver_data = &da1469x_i2s;
+end:
+    return rc;
+}
+
+int
+i2s_create(struct i2s *i2s, const char *name, const struct i2s_cfg *cfg)
+{
+    return os_dev_create(&i2s->dev, name, OS_DEV_INIT_PRIMARY,
+                         100, (os_dev_init_func_t)da1469x_i2s_init, (void *)cfg);
+}
+
+int
+i2s_driver_stop(struct i2s *i2s)
+{
+    struct da1469x_i2s *i2s_data = (struct da1469x_i2s *)i2s->driver_data;
+    struct i2s_sample_buffer *buffer;
+
+    if (i2s_data->running) {
+        da1469x_i2s.dma_regs[0]->DMA_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+        da1469x_i2s.dma_regs[1]->DMA_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+
+        i2s_data->full_buffer_count = 0;
+        while ((buffer = i2s_driver_buffer_get(i2s)) != NULL) {
+            i2s_driver_buffer_put(i2s, buffer);
+        }
+        da1469x_pd_release_nowait(MCU_PD_DOMAIN_PER);
+    }
+
+    return 0;
+}
+
+
+static void
+copy_and_swap_channels_16(const int16_t *lr, int16_t *rl, uint32_t count)
+{
+    const int16_t *limit = lr + count;
+    while (lr < limit) {
+        *rl++ = lr[1];
+        *rl++ = lr[0];
+        lr += 2;
+    }
+}
+
+/* Split 16 bit samples to left and right data for DMA. */
+static void
+split_channels_16(const int16_t *lr, int16_t *l, int16_t *r, uint32_t count)
+{
+    const int16_t *limit = lr + count;
+
+    while (lr < limit) {
+        *l++ = *lr++;
+        *r++ = *lr++;
+    }
+}
+
+/* Split 16 bit samples to left and right data for DMA, extend to 32 bits left aligned. */
+static void
+split_channels_16_32(const int16_t *lr, int32_t *l, int32_t *r, uint32_t count)
+{
+    const int16_t *limit = lr + count;
+
+    while (lr < limit) {
+        *l++ = (*lr++) << 16;
+        *r++ = (*lr++) << 16;
+    }
+}
+
+/* Split 32 bit samples to left and right data for DMA. */
+static void
+split_channels_32_32(const int32_t *lr, int32_t *l, int32_t *r, uint32_t count)
+{
+    const int32_t *limit = lr + count;
+
+    while (lr < limit) {
+        *l++ = *lr++;
+        *r++ = *lr++;
+    }
+}
+
+static inline bool
+tx_dma_is_active(void)
+{
+    return (da1469x_i2s.dma_regs[1]->DMA_CTRL_REG & DMA_DMA0_CTRL_REG_DMA_ON_Msk) != 0;
+}
+
+static void
+da1469x_i2s_fill_dma_buffer(const void *buffer, uint32_t sample_count, uint32_t sample_size_in_bytes)
+{
+    uint32_t offset = 0;
+    const int16_t *src = (const int16_t *)buffer;
+    struct da1469x_dma_buffer *dma_mem = da1469x_i2s.cfg.dma_memory;
+    uint8_t inactive_half = tx_dma_is_active() ? (da1469x_i2s.active_half ^ 1) : da1469x_i2s.full_buffer_count;
+
+    if (da1469x_i2s.cfg.data_format == I2S_DATA_FRAME_16_16) {
+
+        assert(sample_size_in_bytes == 2);
+        assert(sample_count * sample_size_in_bytes == dma_mem->size);
+
+        if (inactive_half != 0) {
+            offset = dma_mem->size;
+        }
+        copy_and_swap_channels_16(src, (int16_t *)(dma_mem->buffer + offset), sample_count);
+    } else if (da1469x_i2s.cfg.data_format == I2S_DATA_FRAME_16_32) {
+
+        assert(sample_size_in_bytes == 2);
+        assert(sample_count * sample_size_in_bytes == dma_mem->size);
+
+        if (inactive_half != 0) {
+            offset = dma_mem->size / 2;
+        }
+        split_channels_16(src, (int16_t *)(dma_mem->buffer + offset),
+                          (int16_t *)(dma_mem->buffer + offset + dma_mem->size),
+                          sample_count);
+    } else if (da1469x_i2s.cfg.data_format == I2S_DATA_FRAME_32_32) {
+
+        if (inactive_half != 0) {
+            offset = dma_mem->size / 2;
+        }
+        if (sample_size_in_bytes == 2) {
+            assert(sample_count * 4 == dma_mem->size);
+            split_channels_16_32((const int16_t *)buffer, (int32_t *)(dma_mem->buffer + offset),
+                                 (int32_t *)(dma_mem->buffer + offset + dma_mem->size),
+                                 sample_count);
+        } else {
+            assert(sample_count * sample_size_in_bytes == dma_mem->size);
+
+            split_channels_32_32((const int32_t *)buffer, (int32_t *)(dma_mem->buffer + offset),
+                                 (int32_t *)(dma_mem->buffer + offset + dma_mem->size),
+                                 sample_count);
+        }
+    }
+    da1469x_i2s.full_buffer_count++;
+}
+
+static void
+da1469x_i2s_dma_tx_start(void)
+{
+    uint32_t number_of_transfers;
+    struct da1469x_dma_regs *lregs = da1469x_i2s.dma_regs[1];
+    struct da1469x_dma_regs *rregs = da1469x_i2s.dma_regs[3];
+    struct da1469x_dma_buffer *dma_mem = da1469x_i2s.cfg.dma_memory;
+
+    da1469x_i2s.active_half = 0;
+
+    if (da1469x_i2s.cfg.data_format == I2S_DATA_FRAME_32_32) {
+        /* Left and right channels data are not interleaved, DMA transfer size 4B */
+        number_of_transfers = (dma_mem->size >> 2) - 1;
+    } else {
+        /* Number of transferred valid for I2S_DATA_FRAME_16_16 and I2S_DATA_FRAME_16_32 */
+        number_of_transfers = (dma_mem->size >> 1) - 1;
+    }
+
+    /* First interrupt at half buffer */
+    lregs->DMA_INT_REG = number_of_transfers / 2;
+    lregs->DMA_LEN_REG = number_of_transfers;
+    if (rregs) {
+        /* DMA is handled in one interrupt, not need for right channel int */
+        rregs->DMA_INT_REG = 0xFFFF;
+        rregs->DMA_LEN_REG = number_of_transfers;
+        rregs->DMA_CTRL_REG |= DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+    }
+    lregs->DMA_CTRL_REG |= DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+}
+
+struct pcm_div {
+    uint16_t div;
+    uint16_t fdiv;
+};
+
+static void
+da1469x_i2s_compute_pcm_div(uint32_t system_clock, uint32_t bit_rate, struct pcm_div *div)
+{
+    int32_t denominator = 0;
+
+    uint32_t initial_deviation;
+    int32_t deviation;
+    int32_t minimum_deviation = INT32_MAX;
+    uint32_t bit_accumulator;
+    uint32_t frac_accumulator;
+    uint16_t fdiv = 1;
+
+    div->div = system_clock / bit_rate;
+    div->fdiv = 0;
+    bit_accumulator = bit_rate;
+
+    initial_deviation = system_clock - (bit_rate * div->div);
+    deviation = initial_deviation;
+    frac_accumulator = initial_deviation;
+
+    while (deviation != 0) {
+        if (deviation > 0) {
+            if (denominator == 16) {
+                break;
+            }
+            ++denominator;
+            fdiv <<= 1;
+            frac_accumulator += initial_deviation;
+        } else if (deviation < 0) {
+            fdiv |= 1;
+            bit_accumulator += bit_rate;
+        }
+        deviation = bit_accumulator - frac_accumulator;
+        if (abs(deviation) < minimum_deviation) {
+            minimum_deviation = abs(deviation);
+            div->fdiv = fdiv;
+        }
+    }
+}
+
+static void
+da1469x_i2s_dma_tx_isr(void *arg)
+{
+    struct i2s *i2s = da1469x_i2s.i2s;
+    struct i2s_sample_buffer *buffer;
+    (void)arg;
+
+    /* Change interrupt time to the end or half the circular buffer size, depending on which half is active. */
+    da1469x_i2s.dma_regs[1]->DMA_INT_REG = da1469x_i2s.dma_regs[1]->DMA_LEN_REG >> (da1469x_i2s.active_half);
+
+    /* DMA is already in other half, let active_half be consistent. */
+    da1469x_i2s.active_half ^= 1;
+
+    assert(da1469x_i2s.full_buffer_count > 0 && da1469x_i2s.full_buffer_count < 3);
+    da1469x_i2s.full_buffer_count--;
+    buffer = i2s_driver_buffer_get(i2s);
+    if (buffer) {
+        da1469x_i2s_fill_dma_buffer(buffer->sample_data, buffer->sample_count, i2s->sample_size_in_bytes);
+        i2s_driver_buffer_put(i2s, buffer);
+    } else {
+        da1469x_i2s.dma_regs[1]->DMA_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+        if (da1469x_i2s.cfg.data_format != I2S_DATA_FRAME_16_16) {
+            da1469x_i2s.dma_regs[3]->DMA_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
+        }
+        i2s_driver_state_changed(i2s, I2S_STATE_OUT_OF_BUFFERS);
+    }
+}
+
+int
+i2s_driver_start(struct i2s *i2s)
+{
+    int rc = 0;
+    uint32_t bit_rate;
+    uint32_t bits_per_frame;
+    struct i2s_sample_buffer *buffer;
+    struct da1469x_dma_buffer *dma_mem = da1469x_i2s.cfg.dma_memory;
+    struct pcm_div div;
+    const i2s_data_format_t data_format = da1469x_i2s.cfg.data_format;
+    struct da1469x_dma_config tx_cfg = {
+        .src_inc = true,
+        .dst_inc = false,
+        .priority = 0,
+        .burst_mode = 0,
+        .bus_width = data_format == I2S_DATA_FRAME_16_32 ? MCU_DMA_BUS_WIDTH_2B : MCU_DMA_BUS_WIDTH_4B,
+    };
+
+    if (!da1469x_i2s.running) {
+        da1469x_pd_acquire(MCU_PD_DOMAIN_PER);
+        if (da1469x_i2s.dma_regs[0] == NULL && da1469x_i2s.dma_regs[1] == NULL) {
+            rc = da1469x_dma_acquire_periph(-1, MCU_DMA_PERIPH_PCM, da1469x_i2s.dma_regs);
+            if (rc) {
+                return rc;
+            }
+
+            da1469x_dma_configure(da1469x_i2s.dma_regs[1], &tx_cfg,
+                                  (da1469x_dma_interrupt_cb)da1469x_i2s_dma_tx_isr, NULL);
+            da1469x_i2s.dma_regs[1]->DMA_A_START_REG = (uint32_t)dma_mem->buffer;
+            da1469x_i2s.dma_regs[1]->DMA_B_START_REG = (uint32_t)&APU->PCM1_OUT1_REG +
+                                                       ((data_format == I2S_DATA_FRAME_16_32) ? 2 : 0);
+            da1469x_i2s.dma_regs[1]->DMA_CTRL_REG |= DMA_DMA0_CTRL_REG_CIRCULAR_Msk;
+        }
+        if (data_format != I2S_DATA_FRAME_16_16) {
+            rc = da1469x_dma_acquire_periph(-1, MCU_DMA_PERIPH_PCM, &da1469x_i2s.dma_regs[2]);
+            if (rc) {
+                da1469x_dma_release_channel(da1469x_i2s.dma_regs[0]);
+                da1469x_i2s.dma_regs[1] = NULL;
+                return rc;
+            }
+
+            da1469x_dma_configure(da1469x_i2s.dma_regs[3], &tx_cfg,
+                                  (da1469x_dma_interrupt_cb)da1469x_i2s_dma_tx_isr, NULL);
+            da1469x_i2s.dma_regs[3]->DMA_A_START_REG = (uint32_t)dma_mem->buffer + da1469x_i2s.cfg.dma_memory->size;
+            da1469x_i2s.dma_regs[3]->DMA_B_START_REG = (uint32_t)&APU->PCM1_OUT2_REG +
+                                                       ((data_format == I2S_DATA_FRAME_16_32) ? 2 : 0);
+            da1469x_i2s.dma_regs[3]->DMA_CTRL_REG |= DMA_DMA0_CTRL_REG_CIRCULAR_Msk;
+        }
+        bits_per_frame = data_format == I2S_DATA_FRAME_16_16 ? 32 : 64;
+        bit_rate = i2s->sample_rate * bits_per_frame;
+        da1469x_i2s_compute_pcm_div(SystemCoreClock, bit_rate, &div);
+        CRG_PER->PCM_DIV_REG = (1 << CRG_PER_PCM_DIV_REG_PCM_SRC_SEL_Pos) |
+                               (1 << CRG_PER_PCM_DIV_REG_CLK_PCM_EN_Pos) |
+                               (div.div << CRG_PER_PCM_DIV_REG_PCM_DIV_Pos);
+        CRG_PER->PCM_FDIV_REG = div.fdiv;
+
+        APU->PCM1_CTRL_REG = ((bits_per_frame - 1) << APU_PCM1_CTRL_REG_PCM_FSC_DIV_Pos) |
+                             (((data_format == I2S_DATA_FRAME_16_16) ? 0 : 1) << APU_PCM1_CTRL_REG_PCM_FSC_EDGE_Pos) |
+                             ((bits_per_frame / 16) << APU_PCM1_CTRL_REG_PCM_FSCLEN_Pos) |
+                             (0 << APU_PCM1_CTRL_REG_PCM_FSCDEL_Pos) |
+                             (1 << APU_PCM1_CTRL_REG_PCM_CLKINV_Pos) |
+                             (0 << APU_PCM1_CTRL_REG_PCM_FSCINV_Pos) |
+                             (0 << APU_PCM1_CTRL_REG_PCM_CH_DEL_Pos) |
+                             (0 << APU_PCM1_CTRL_REG_PCM_PPOD_Pos) |
+                             (1 << APU_PCM1_CTRL_REG_PCM_MASTER_Pos);
+        APU->PCM1_CTRL_REG |= (1 << APU_PCM1_CTRL_REG_PCM_EN_Pos);
+
+        if (i2s->direction == I2S_OUT) {
+            APU->APU_MUX_REG = 2u << APU_APU_MUX_REG_PCM1_MUX_IN_Pos;
+        }
+
+        da1469x_i2s.running = true;
+
+        if (i2s->direction == I2S_OUT) {
+            while (da1469x_i2s.full_buffer_count < 2 && (buffer = i2s_driver_buffer_get(i2s)) != NULL) {
+                da1469x_i2s_fill_dma_buffer(buffer->sample_data, buffer->sample_count, i2s->sample_size_in_bytes);
+                i2s_driver_buffer_put(i2s, buffer);
+            }
+        } else {
+            /* TODO: Implement handling of I2S_IN */
+        }
+        if (da1469x_i2s.full_buffer_count == 0) {
+            i2s->state = I2S_STATE_OUT_OF_BUFFERS;
+            rc = I2S_ERR_NO_BUFFER;
+        } else {
+            i2s->state = I2S_STATE_RUNNING;
+            if (i2s->direction == I2S_OUT) {
+                da1469x_i2s_dma_tx_start();
+            }
+        }
+    }
+    return rc;
+}
+
+void
+i2s_driver_buffer_queued(struct i2s *i2s)
+{
+    struct i2s_sample_buffer *buffer;
+
+    while (da1469x_i2s.full_buffer_count < 2 && (buffer = i2s_driver_buffer_get(i2s)) != NULL) {
+        da1469x_i2s_fill_dma_buffer(buffer->sample_data, buffer->sample_count, i2s->sample_size_in_bytes);
+        i2s_driver_buffer_put(i2s, buffer);
+    }
+}
+
+int
+i2s_driver_suspend(struct i2s *i2s, os_time_t timeout, int arg)
+{
+    return OS_OK;
+}
+
+int
+i2s_driver_resume(struct i2s *i2s)
+{
+    return OS_OK;
+}