Skip to content
choco-technologiesPublic

About

DMOD Sai

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

dmsai

License CI

dmsai is a PCM SAI device driver for DMOD. It implements the dmdrvi DIF used by dmdevfs, following the same device model as dmeth: an INI section selects the driver and each configured controller appears as /dev/dmsaiN. Applications use ordinary file read/write operations and the standard dmdrvi audio stream ioctl commands. There is no separate stream Module API.

dmdevfs owns device node creation and mounting. dmsai parses the selected INI section, reserves the controller through dmsai_port, and provides exclusive file handles. The STM32F7 port transfers PCM with circular DMA.

Device contract

The dmdrvi DIF supplies create/free, open/close, read/write, ioctl, flush and stat. The core parses the active INI section selected by dmdevfs, allocates the device context and exposes a major number equal to the zero-based instance (/dev/dmsai0, /dev/dmsai1, ...). Unknown ioctls return -ENOTTY.

read and write transfer bytes of interleaved PCM in complete frames. Enabled slots are packed in ascending slot order. The sample representation is explicit: signed 16-bit little-endian, signed 24-bit in the low 24 bits of a 32-bit little-endian word, or signed 32-bit little-endian. For stereo 16-bit audio, one frame is four bytes. A request size must be a multiple of frame size; a successful transfer can return a shorter whole-frame count. The device is non-seekable. Callers pass normal PCM buffers; the port owns its hardware resources and transfer mechanism. An open device has one exclusive handle, so two clients cannot independently start, stop or change the same stream.

The node implements the standard audio stream commands from dmdrvi_ioctl.h (dmdrvi 2.7, DMDRVI_IOCTL_AUDIO_*) - the same contract a codec node such as dmwm8994 implements - so a client plays the same way on both and needs no dmsai header:

Command Argument Purpose
DMDRVI_IOCTL_AUDIO_GET_STREAM dmdrvi_audio_stream_t * Read buffer layout (rate, format, channels = active slots) and link
DMDRVI_IOCTL_AUDIO_CONFIGURE const dmdrvi_audio_config_t * Succeeds only when rate, channels and width match the ini format
DMDRVI_IOCTL_AUDIO_START NULL Start configured directions; succeeds when already running
DMDRVI_IOCTL_AUDIO_STOP NULL Stop transfer and wake pending I/O
DMDRVI_IOCTL_AUDIO_GET_STATUS dmdrvi_audio_status_t * Read rate and error counters
DMDRVI_IOCTL_AUDIO_SET_TIMEOUT const uint32_t * Set this handle's wait limit in ms; zero means no limit
DMDRVI_IOCTL_AUDIO_GET_TIMEOUT uint32_t * Read this handle's wait limit
DMDRVI_IOCTL_AUDIO_DRAIN NULL Wait until queued TX data reaches the output

The SAI has no analog stage: AUDIO_GET_INFO, AUDIO_SET_VOLUME and AUDIO_SET_MUTE return -ENOTSUP. That also keeps dmdevfs from reporting the node as an audio device - the codec in front of it is the one.

Configuration example

dmdevfs discovers the section from driver_name=dmsai and restricts the dmini context to it before calling the driver. Board pin routing and codec initialization are configured separately.

[audio0]
driver_name=dmsai
instance=0
clock_role=master
framing=i2s
pcm_format=s16_le
sample_rate_hz=48000
tolerance_ppm=500
slot_bits=16
slot_count=2
frame_bits=32
active_slots=3
transmit=on
receive=off

The port decides which rate, role, framing and directions its target supports. Invalid values or an already reserved controller prevent node creation.

Board GPIO mappings and pin-agnostic MCU defaults are in configs/README.md. Select one device section per SAI controller; the board examples are based on ST's audio BSP pin definitions.

Usage example

This sequence sends PCM and waits for it to leave the DMA output before STOP.

#include "dmod.h"
#include "dmsai.h"
#include "dmdrvi_ioctl.h"

int play_stereo(const int16_t *pcm, size_t frames)
{
    void *audio = Dmod_FileOpen("/dev/dmsai0", "w");
    if (audio == NULL)
        return -1;

    int result = Dmod_Ioctl(audio, DMDRVI_IOCTL_AUDIO_START, NULL);
    if (result == 0)
    {
        size_t bytes = frames * 2u * sizeof(int16_t);
        if (Dmod_FileWrite(pcm, 1, bytes, audio) != bytes)
            result = -1;
        if (result == 0)
            result = Dmod_Ioctl(audio, DMDRVI_IOCTL_AUDIO_DRAIN, NULL);
        Dmod_Ioctl(audio, DMDRVI_IOCTL_AUDIO_STOP, NULL);
    }
    Dmod_FileClose(audio);
    return result;
}

For capture, enable receive=on, open the node for reading and use Dmod_FileRead. For full duplex, enable both directions and open with r+. The codec and GPIO configuration still belong to their respective drivers.

For a direct port client on STM32F746G-DISCO, configure the SAI2 pins as in configs/board/stm32f746g-disco/sai2.ini and initialize the codec separately. The following code then queues one stereo frame through circular DMA:

#include "dmod.h"
#include "dmsai_port.h"

dmsai_config_t config = {
    .instance = 1, .clock_role = dmsai_clock_master,
    .framing = dmsai_frame_i2s, .pcm_format = dmsai_pcm_s16_le,
    .sample_rate_hz = 48000, .tolerance_ppm = 500,
    .slot_bits = 16, .slot_count = 2, .frame_bits = 32,
    .active_slots = 3, .transmit = true,
};
int16_t frame[2] = { 0, 0 };
size_t queued = 0;
if (dmsai_port_init(&config) == 0) {
    if (dmsai_port_start(1) == 0) {
        dmsai_port_write(1, frame, sizeof(frame), &queued, 100);
        dmsai_port_flush(1, 100);
        dmsai_port_stop(1);
    }
    dmsai_port_deinit(1);
}

Core and port layout

dmsai handles the dmdrvi DIF, INI configuration and byte/frame semantics. dmsai_port handles resource ownership, clocks and SAI registers. The port API uses only portable dmsai_types.h types. Its STM32 definitions are placed in src/port/stm32_common/; src/port/stm32f7/port.c contains only module lifecycle and a small hardware descriptor. Another STM32 family selects the same common source in its config.cmake. A different architecture can select its own common source without changing the public API.

The STM32F7 SAI1 and SAI2 ports support master I2S and TDM, 16-bit PCM or 24/32-bit PCM in 32-bit slots, up to 16 slots, 256 frame bits and 96 kHz. The port asks dmclk_port v1.2 for the SAI kernel clock before enabling the peripheral gate, then releases the clock after stopping and gating SAI. With MCKDIV=2, a 48 kHz stream requests 49.152 MHz and reports the achieved sample rate. SAI A generates the clocks and transmits; SAI B receives synchronously when requested. Board GPIO and codec setup remain separate.

If a synchronous block does not acknowledge a stop within five milliseconds, the port resets that SAI controller, restores its configuration and increments the transfer error counter. This also allows a later start or deinitialization when an external frame signal is absent.

The port leases DMA2 stream 1/channel 0 for SAI1 A TX, stream 5/channel 0 for SAI1 B RX, stream 4/channel 3 for SAI2 A TX and stream 6/channel 3 for SAI2 B RX through dmdma v0.5. STM32 channel 0 is passed as request 8 for compatibility with older dmdma releases; its STM32 port maps the low three request bits to the hardware channel. Circular ping-pong buffers reside in the uncached dmheap DMA heap. DMA half/full callbacks move whole PCM frames between those buffers and bounded software queues; empty TX periods produce silence, while RX queue overflow increments rx_overruns. This keeps DMA memory valid across caller scheduling gaps. A successful write means the port accepted bytes into its queue; DMDRVI_IOCTL_AUDIO_DRAIN waits until the queued audio has reached the output before STOP. The port does not configure board pins or the audio codec.

dmsai_port_board_test is a direct hardware test on STM32F746G-DISCO. It temporarily routes SAI1 pins to AF6 and SAI2 pins to AF10, configures 48 kHz and 44.1 kHz streams on both controllers, checks circular DMA progress, writes and reads PCM through the queues, then checks that the clock and peripheral gate are released. Run it from the firmware shell after bundling dmsai_port, the test module, dmdma v0.5 and dmclk_port v1.2. It restores the previous GPIO settings. The test does not initialize the WM8994 codec or verify audio quality.

Build and tests

cmake -S . -B build -DDMOD_CPU_FAMILY=stm32f7 -DDMOD_DIR=/path/to/dmod
cmake --build build

The existing test_dmsai target is retained. dmsai_driver_board_test opens /dev/dmsai1 through dmdevfs and checks configuration, exclusive open, start/stop, 300-frame DMA write, drain, RX read and status. Run it after mounting configs/board/stm32f746g-disco/sai2.ini. Running Cortex-M7 test modules requires a compatible loader or target board; the Raspberry Pi AArch64 loader cannot execute them. See API reference for the detailed contracts.

About

DMOD Sai

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages