Atmosic I2S Driver

Overview

The Atmosic I2S driver (atmosic,atm-i2s) implements the standard Zephyr I2S API on ATM33, ATM34 and ATM5. Applications use the standard calls (i2s_configure(), i2s_write(), i2s_trigger(), i2s_config_get()). This page covers only the Atmosic-specific behavior: how the frame clock is selected and the related configuration.

Key Features

  • Standard Zephyr I2S API: No Atmosic-specific calls are needed for normal use

  • Controller or Target: Drives SCK/WS, or follows them with I2S_OPT_FRAME_CLK_TARGET

  • Data Formats: I2S, PCM short/long, left-justified and right-justified

  • Word Size: 16-bit or 32-bit WS slot

  • DMA-Driven TX: Audio blocks are streamed to the I2S FIFO by DMA

  • Build-Time Rate Selection: The frame clock is fixed by CONFIG_I2S_LUT_IDX

Supported API

The driver differs from the generic Zephyr I2S API in the following points:

  • Only I2S_DIR_TX is supported. i2s_read() returns -ENOTSUP.

  • i2s_configure() can be called once. The driver keeps a pointer to the struct i2s_config, so it must remain valid (for example, static).

  • frame_clk_freq and word_size in struct i2s_config must match the rate table entry selected by CONFIG_I2S_LUT_IDX; otherwise i2s_configure() returns -EINVAL (and asserts when asserts are enabled).

  • Samples are always 16-bit in memory. With a 32-bit WS slot the hardware pads the remaining bit clocks with zeros.

Clock Architecture

The I2S source clock is one of 16 MHz, 32 MHz, or the PLL divided by 1 to 8. The driver never reprograms clocks: it only selects a source that the system clock configuration already provides. The system clock is chosen at build time with INIT_BP_FREQ (see How to set BP Clock Frequency).

CONFIG_I2S_CLK_FREQ_MAX (default 32 MHz) is the highest PLL-derived source the driver uses. Raise it to use the 64, 128 or 192 MHz entries.

The source is selected by I2S_SEL in CLK_AUD_CTRL: 0 is 16 MHz, 1 is 32 MHz, and 2 to 9 is the I2S PLL input divided by N, where I2S_SEL is N + 1. Below, each PLL source is written as the I2S PLL input, the divider /N and the resulting I2S_SEL, followed by the INIT_BP_FREQ that provides it.

ATM33

  • 16 MHz: I2S_SEL 0, always available.

  • 32 MHz: PLL 64 MHz, /2, I2S_SEL 3 (INIT_BP_FREQ 64000000).

  • 64, 128 and 192 MHz: not available.

ATM33 cannot use the PLL undivided (I2S_SEL 2 selects 16 MHz), so the driver starts PLL dividers at /2 (CONFIG_I2S_PLL_DIV_MIN). PLL 32 MHz /1 at INIT_BP_FREQ 32000000 is therefore not a usable 32 MHz source.

ATM34

  • 16 MHz: I2S_SEL 0, always available.

  • 32 MHz, any of:

    • 32 MHz doubler, I2S_SEL 1 (INIT_BP_FREQ 32000000)

    • PLL 64 MHz, /2, I2S_SEL 3 (INIT_BP_FREQ 64000000)

    • PLL 128 MHz, /4, I2S_SEL 5 (INIT_BP_FREQ 64000001)

    • PLL 192 MHz, /6, I2S_SEL 7 (INIT_BP_FREQ 48000001)

  • 64 MHz: PLL 64 MHz, /1, I2S_SEL 2 (INIT_BP_FREQ 64000000), CONFIG_I2S_CLK_FREQ_MAX >= 64000000.

  • 128 MHz: PLL 128 MHz, /1, I2S_SEL 2 (INIT_BP_FREQ 64000001), CONFIG_I2S_CLK_FREQ_MAX >= 128000000.

  • 192 MHz: PLL 192 MHz, /1, I2S_SEL 2 (INIT_BP_FREQ 48000001), CONFIG_I2S_CLK_FREQ_MAX >= 192000000.

ATM5

  • 16 MHz: I2S_SEL 0, always available.

  • 32 MHz, either of:

    • PLL 64 MHz / DIV_BP_VAL 2 = 32 MHz, /1, I2S_SEL 2 (INIT_BP_FREQ 32000000)

    • PLL 64 MHz, /2, I2S_SEL 3 (INIT_BP_FREQ 64000000)

  • 64 MHz: PLL 64 MHz, /1, I2S_SEL 2 (INIT_BP_FREQ 64000000), CONFIG_I2S_CLK_FREQ_MAX >= 64000000.

  • 128 and 192 MHz: not available.

ATM5 runs the backplane from PLL 64 MHz (INIT_BP_FREQ 32000000: DIV_BP_VAL 2; INIT_BP_FREQ 64000000: DIV_BP_VAL 1). When DIV_BP_VAL is greater than 1, the I2S PLL input is taken after the backplane divider, so it is PLL 64 MHz / DIV_BP_VAL instead of 64 MHz.

Rate Lookup Table

Each supported frame clock is an entry of i2s_rate_lut[] in openair/drivers/i2s/i2s_atm.c:

struct i2s_rate_cfg {
    uint32_t src_freq;        /* I2S source clock (Hz) */
    uint32_t frame_clk_freq;  /* Requested frame clock (Hz) */
    uint16_t ck2sck;          /* Source clock to SCK divider */
    uint8_t sck2ws_rt;        /* SCK cycles per WS slot; 32 = 32-bit slot */
};

The resulting frame clock is src_freq / (ck2sck x sck2ws_rt x 2). Entries with sck2ws_rt 32 are used with word_size 32; all other entries are used with word_size 16. Some rates (for example 44.1 kHz) cannot be produced exactly; the comment of each entry gives the actual rate and its error.

Examples:

Index

Entry

Frame clock

Requirements

18 (default)

{32000000, 8000, 125, 16}

8000 Hz, 16-bit

32 MHz source

3

{192000000, 48000, 125, 16}

48000 Hz, 16-bit

ATM34, INIT_BP_FREQ 48000001, CONFIG_I2S_CLK_FREQ_MAX=192000000

Custom Entry

A rate that is not in the table can be added without changing the driver. CONFIG_I2S_LUT_CUSTOM appends one entry built from four options as index 30 and makes it the default CONFIG_I2S_LUT_IDX:

CONFIG_I2S_LUT_CUSTOM=y
CONFIG_I2S_LUT_CUSTOM_SRC_FREQ=16000000
CONFIG_I2S_LUT_CUSTOM_FRAME_CLK_FREQ=10000
CONFIG_I2S_LUT_CUSTOM_CK2SCK=50
CONFIG_I2S_LUT_CUSTOM_SCK2WS_RT=16

This example gives exactly 16000000 / (50 x 16 x 2) = 10000 Hz with 16-bit words. CK2SCK is 1-65535 and SCK2WS_RT is 16-32 (32 selects a 32-bit slot). The build fails if the four values produce a frame clock more than 5% away from FRAME_CLK_FREQ. The source clock must already be available, as for any other entry.

Configuration

The I2S driver is enabled with CONFIG_I2S when the i2s node is enabled in the device tree:

CONFIG_I2S=y
CONFIG_I2S_LUT_IDX=18

Option

Default

Description

CONFIG_I2S_LUT_IDX

18 (30 with CONFIG_I2S_LUT_CUSTOM)

Index of the i2s_rate_lut[] entry to use (0-29, or 0-30 with CONFIG_I2S_LUT_CUSTOM)

CONFIG_I2S_LUT_CUSTOM

n

Append a custom entry; see Custom Entry

CONFIG_I2S_CLK_FREQ_MAX

32000000

Highest PLL-derived I2S source clock the driver may use

CONFIG_I2S_PLL_DIV_MIN

2 (ATM33), 1 (others)

Smallest PLL divider used for the I2S source clock; not user-settable

CONFIG_I2S_TX_BLOCK_NUM

20

Number of blocks in the driver TX memory slab

CONFIG_I2S_WSCNT

0

Forced word select count; 0 means not forced

CONFIG_ATM_FIFO_TX_ISR enables a low-latency TX mode with additional Atmosic-specific calls. Contact Atmosic support before using it.

Device Tree

The i2s node is defined in the SoC device tree. Enable it and assign the pins in a board overlay. The binding (atmosic,atm-i2s) provides sck-out-pin, ws-out-pin and sd-out-pin for output, and sck-in-pin, ws-in-pin and sd-in-pin for input:

&i2s {
    sck-out-pin = <18>;
    ws-out-pin = <19>;
    sd-out-pin = <20>;
    status = "okay";
};

/ {
    aliases {
        i2s-tx = &i2s;
    };
};

Usage Examples

Basic TX

The configuration must match the selected entry, here index 18 (8000 Hz, 16-bit):

#include <zephyr/drivers/i2s.h>

#define BLOCK_SIZE 128
#define BLOCK_NUM  4

K_MEM_SLAB_DEFINE_STATIC(tx_slab, BLOCK_SIZE, BLOCK_NUM, 4);

int i2s_tx_example(void)
{
    const struct device *dev = DEVICE_DT_GET(DT_ALIAS(i2s_tx));
    static const struct i2s_config cfg = {
        .word_size = 16,
        .channels = 2,
        .format = I2S_FMT_DATA_FORMAT_I2S,
        .options = I2S_OPT_BIT_CLK_CONTROLLER | I2S_OPT_FRAME_CLK_CONTROLLER,
        .frame_clk_freq = 8000,
        .mem_slab = &tx_slab,
        .block_size = BLOCK_SIZE,
        .timeout = 1000,
    };
    void *block;
    int ret;

    ret = i2s_configure(dev, I2S_DIR_TX, &cfg);
    if (ret < 0) {
        return ret;
    }

    /* Queue the first block, then start */
    k_mem_slab_alloc(&tx_slab, &block, K_FOREVER);
    /* fill block with audio samples */
    i2s_write(dev, block, BLOCK_SIZE);
    i2s_trigger(dev, I2S_DIR_TX, I2S_TRIGGER_START);

    /* keep calling i2s_write() with new blocks */

    return i2s_trigger(dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
}

Selecting Another Rate

To output 48 kHz with 16-bit words on ATM34, select index 3 and make the 192 MHz source available:

west build -p always -b <BOARD> <APP> --sysbuild -- \
    -DCONFIG_I2S_LUT_IDX=3 -DCONFIG_I2S_CLK_FREQ_MAX=192000000 \
    -DSB_ATM_DTS_EXTRA_CPPFLAGS="-DINIT_BP_FREQ=48000001;"

Then set .frame_clk_freq = 48000 and .word_size = 16 in struct i2s_config.

Limitations

  • TX only

  • One frame clock per build, selected by CONFIG_I2S_LUT_IDX

  • Some frame clocks are approximate (see the entry comments in i2s_rate_lut[])

  • 64, 128 and 192 MHz sources depend on the platform (see Clock Architecture)

Troubleshooting

“does not match i2s_rate_lut[N]”: frame_clk_freq or word_size differs from the selected entry. Change CONFIG_I2S_LUT_IDX or the configuration so that they match.

“source … Hz is not available”: The source clock of the selected entry is not provided by the current clock configuration. Use an INIT_BP_FREQ from Clock Architecture, and raise CONFIG_I2S_CLK_FREQ_MAX for sources above 32 MHz.

Debug logging: CONFIG_I2S_LOG_LEVEL_DBG=y prints the selected source clock, dividers and I2S_SEL value.

References

  • openair/samples/drivers/i2s/output: I2S output sample