Skip to main content

ove/
i2s.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! I²S audio bus driver.
8//!
9//! Safe wrappers around the oveRTOS I²S API for DMA-based audio
10//! streaming with double-buffered (ping-pong) operation.
11//!
12//! Mirrors the [`crate::uart`], [`crate::spi`], and [`crate::i2c`]
13//! modules: free functions over an opaque handle.  Lifecycle
14//! (`ove_i2s_init` / `ove_i2s_create`) and callback registration
15//! (`ove_i2s_set_rx_callback` / `ove_i2s_set_tx_callback`) are left to
16//! direct calls against [`crate::ffi`] so the safe surface stays small
17//! and the unsafe boundary matches the sibling drivers.
18//!
19//! # Example
20//!
21//! ```ignore
22//! use ove::ffi;
23//! // Heap-mode lifecycle stays at the FFI level — same as UART/SPI/I²C.
24//! let mut handle: ffi::ove_i2s_t = core::ptr::null_mut();
25//! let cfg = ffi::ove_i2s_cfg { /* ... */ };
26//! ove::error::Error::from_code(unsafe { ffi::ove_i2s_create(&mut handle, &cfg) })?;
27//!
28//! ove::i2s::start(handle)?;
29//!
30//! // In your RX callback (registered via raw FFI), pull the just-filled half:
31//! if let Some(p) = ove::i2s::rx_buf(handle) {
32//!     let n = ove::i2s::half_buf_size(handle);
33//!     let samples = unsafe { core::slice::from_raw_parts(p, n) };
34//!     process(samples);
35//! }
36//! # Ok::<(), ove::error::Error>(())
37//! ```
38//!
39//! Requires `CONFIG_OVE_I2S`.
40
41use crate::bindings;
42use crate::error::{Error, Result};
43
44// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
45// calls below): any handle passed to the C API is non-null and refers to a
46// live RTOS object — wrapper constructors establish validity via
47// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
48// a handle is released. Pointer and slice arguments reference caller-owned
49// memory valid for the duration of the call; the C side copies whatever it
50// retains and does not alias them past return (verified against the
51// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
52// pointer casts from user data, slice reconstruction via `from_raw_parts`,
53// or storing a callback across the FFI boundary — carry their own
54// `// SAFETY:` comment.
55
56/// I²S stream direction — mirrors `ove_i2s_dir_t`.
57///
58/// Pass the underlying `u32` value into `ove_i2s_cfg::direction` via
59/// `as bindings::ove_i2s_dir_t`.
60#[repr(u32)]
61#[derive(Debug, Copy, Clone, PartialEq, Eq)]
62pub enum Direction {
63    /// Transmit only (playback).
64    Tx = 0x01,
65    /// Receive only (capture).
66    Rx = 0x02,
67    /// Full-duplex (simultaneous TX + RX).
68    TxRx = 0x03,
69}
70
71/// Start I²S DMA streaming.
72///
73/// Begins circular DMA transfers.  TX is started first to generate
74/// clocks for a synchronous RX slave when running full-duplex.
75#[inline]
76pub fn start(i2s: bindings::ove_i2s_t) -> Result<()> {
77    Error::from_code(unsafe { bindings::ove_i2s_start(i2s) })
78}
79
80/// Stop I²S DMA streaming.
81#[inline]
82pub fn stop(i2s: bindings::ove_i2s_t) -> Result<()> {
83    Error::from_code(unsafe { bindings::ove_i2s_stop(i2s) })
84}
85
86/// Pause I²S DMA streaming.  Can be resumed with [`resume`].
87#[inline]
88pub fn pause(i2s: bindings::ove_i2s_t) -> Result<()> {
89    Error::from_code(unsafe { bindings::ove_i2s_pause(i2s) })
90}
91
92/// Resume I²S DMA streaming after [`pause`].
93#[inline]
94pub fn resume(i2s: bindings::ove_i2s_t) -> Result<()> {
95    Error::from_code(unsafe { bindings::ove_i2s_resume(i2s) })
96}
97
98/// Pointer to the most recently completed RX half-buffer, or `None`
99/// if the handle is invalid / no buffer has been filled yet.
100///
101/// Call from within the RX callback.  The pointed-to memory is
102/// `[half_buf_size]` bytes long and remains valid until the next
103/// [`stop`] or destroy on the same handle.  To read the samples:
104///
105/// ```ignore
106/// if let Some(p) = ove::i2s::rx_buf(i2s) {
107///     let n = ove::i2s::half_buf_size(i2s);
108///     let samples = unsafe { core::slice::from_raw_parts(p, n) };
109///     /* ... */
110/// }
111/// ```
112#[inline]
113pub fn rx_buf(i2s: bindings::ove_i2s_t) -> Option<*const u8> {
114    let p = unsafe { bindings::ove_i2s_rx_buf(i2s) };
115    if p.is_null() { None } else { Some(p.cast()) }
116}
117
118/// Pointer to the TX half-buffer safe to write, or `None` if the
119/// handle is invalid.
120///
121/// Returns the half of the DMA TX buffer that DMA is *not* currently
122/// transmitting from.  Fill this buffer before the next TX callback.
123/// The pointed-to memory is `[half_buf_size]` bytes long and remains
124/// valid until the next [`stop`] or destroy on the same handle.
125#[inline]
126pub fn tx_buf(i2s: bindings::ove_i2s_t) -> Option<*mut u8> {
127    let p = unsafe { bindings::ove_i2s_tx_buf(i2s) };
128    if p.is_null() { None } else { Some(p.cast()) }
129}
130
131/// Size of one half-buffer in bytes.  Pair with [`rx_buf`] / [`tx_buf`]
132/// to construct a slice via `core::slice::from_raw_parts{,_mut}`.
133#[inline]
134pub fn half_buf_size(i2s: bindings::ove_i2s_t) -> usize {
135    unsafe { bindings::ove_i2s_half_buf_size(i2s) }
136}