blob: 38c301747e9fdf4a53107b1480fd76a4592f66f7 [file]
// Copyright 2023 Google LLC
//
// Licensed 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
//
// https://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.
#![no_std]
use cantrip_os_common::camkes::semaphore::seL4_Semaphore;
#[allow(unused_imports)]
use log::{error, info, trace};
use sdk_interface::SDKError;
use spin::Mutex;
mod buffer;
use buffer::Buffer; // NB: buffer holds 32-bit values
#[allow(dead_code)]
mod i2s;
use i2s::*;
extern "Rust" {
static RX_NONEMPTY: seL4_Semaphore;
static TX_EMPTY: seL4_Semaphore;
}
use reg_constants::platform::TOP_MATCHA_SMC_I2S_CLOCK_FREQ_PERIPHERAL_HZ as CLK_FIXED_FREQ_HZ;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum WhichBuffer {
A,
B,
}
struct DoubleBuffer {
pub buffer_a: Buffer,
pub buffer_b: Buffer,
// Front is the current buffer to read received data
pub front: WhichBuffer,
// Back is the current buffer to write received data (from the RX FIFO)
pub back: WhichBuffer,
}
impl DoubleBuffer {
pub const fn new() -> Self {
Self {
buffer_a: Buffer::new(),
buffer_b: Buffer::new(),
front: WhichBuffer::B,
back: WhichBuffer::A,
}
}
pub fn front(&mut self) -> &mut Buffer {
if self.front == WhichBuffer::A {
&mut self.buffer_a
} else {
&mut self.buffer_b
}
}
pub fn back(&mut self) -> &mut Buffer {
if self.back == WhichBuffer::A {
&mut self.buffer_a
} else {
&mut self.buffer_b
}
}
pub fn flip(&mut self) -> bool {
assert!(self.back().available_space() == 0);
if self.front().is_empty() {
let next = self.front;
self.front = self.back;
self.back = next;
true
} else {
false // NB: will lose data, maybe count
}
}
pub fn clear(&mut self) {
self.front().clear();
self.back().clear();
}
}
static RX_BUFFER: Mutex<DoubleBuffer> = Mutex::new(DoubleBuffer::new());
static mut RX_STOP_ON_FULL: bool = false; // NB: protected by RX_BUFFER
static TX_BUFFER: Mutex<Buffer> = Mutex::new(Buffer::new());
/// Resets the audio hardware according to |rxrst| and |txrst| and
/// sets the tx/rx FIFO watermark levels. Any recording or playing
/// is terminated.
pub fn audio_reset(rxrst: bool, txrst: bool, rxilvl: u8, txilvl: u8) -> Result<(), SDKError> {
// XXX worth making errors distinct?
fn cvt_rxilvl(rxilvl: u8) -> Result<RxILvl, SDKError> {
match rxilvl {
1 => Ok(RxILvl::RxLvl1),
4 => Ok(RxILvl::RxLvl4),
8 => Ok(RxILvl::RxLvl8),
16 => Ok(RxILvl::RxLvl16),
30 => Ok(RxILvl::RxLvl30),
_ => Err(SDKError::InvalidAudioParameter),
}
}
fn cvt_txilvl(txilvl: u8) -> Result<TxILvl, SDKError> {
match txilvl {
1 => Ok(TxILvl::TxLvl1),
4 => Ok(TxILvl::TxLvl4),
8 => Ok(TxILvl::TxLvl8),
16 => Ok(TxILvl::TxLvl16),
_ => Err(SDKError::InvalidAudioParameter),
}
}
trace!("audio_reset {rxrst} {txrst} {rxilvl} {txilvl}");
if txrst {
let mut buf = RX_BUFFER.lock();
audio_stop_recording(&mut buf);
}
if rxrst {
let mut buf = TX_BUFFER.lock();
audio_stop_playing(&mut buf);
}
set_fifo_ctrl(
FifoCtrl::new()
.with_rxrst(rxrst)
.with_txrst(txrst)
.with_rxilvl(cvt_rxilvl(rxilvl)?)
.with_txilvl(cvt_txilvl(txilvl)?),
);
Ok(())
}
fn audio_drain_rx_fifo() {
// NB: must be called with RX_BUFFER lock held
trace!("audio_drain_rx_fifo begin");
while rx_fifo_level() > 0 {
let _ = get_rdata();
}
trace!("audio_drain_rx_fifo end");
}
fn audio_stop_recording(buf: &mut DoubleBuffer) {
trace!("audio_stop_recording");
// NB: must be called with RX_BUFFER lock held
set_ctrl(get_ctrl().with_rx(false));
set_fifo_ctrl(get_fifo_ctrl().with_rxrst(true)); // Flush RX FIFO
set_intr_enable(get_intr_enable().with_rx_watermark(false));
set_intr_state(get_intr_state().with_rx_watermark(false));
audio_drain_rx_fifo();
buf.clear();
}
pub fn audio_record_start(
rate: usize,
_buffer_size: usize,
stop_on_full: bool,
) -> Result<(), SDKError> {
fn nz(x: usize) -> usize {
if x == 0 {
1
} else {
x
}
}
trace!("audio_record_start rate {rate} stop_on_full {stop_on_full}");
let mut _buf = RX_BUFFER.lock();
let nco_rx = CLK_FIXED_FREQ_HZ / (nz(2 * rate) as u64);
if nco_rx > reg_constants::i2s::I2S_CTRL_NCO_RX_MASK as u64 {
error!("bad nco_rx {nco_rx} for rate {rate}");
return Err(SDKError::InvalidAudioParameter);
}
// XXX or force client to stop?
// audio_stop_recording(buf);
unsafe {
RX_STOP_ON_FULL = stop_on_full;
}
set_intr_state(get_intr_state().with_rx_watermark(true));
set_intr_enable(get_intr_enable().with_rx_watermark(true));
set_ctrl(get_ctrl().with_rx(true).with_nco_rx(nco_rx as u8));
Ok(())
}
pub fn audio_record_stop() -> Result<(), SDKError> {
trace!("audio_record_stop");
let mut buf = RX_BUFFER.lock();
audio_stop_recording(&mut buf);
Ok(())
}
pub fn audio_record_collect(data: &mut [u32], wait_if_empty: bool) -> Result<usize, SDKError> {
let mut guard = RX_BUFFER.lock();
let mut buf = guard.front();
let mut count = 0;
while count < data.len() {
if let Some(b) = buf.pop() {
data[count] = b;
count += 1;
} else {
// Optionally block until data is present. Note this may
// block the caller which may block the runtime interface
// thread which in turn may block other apps/clients.
if wait_if_empty {
// XXX maybe check count < data.len / 2 or similar?
trace!("wait for flip");
while buf.is_empty() {
drop(guard);
unsafe {
RX_NONEMPTY.wait();
}
guard = RX_BUFFER.lock();
buf = guard.front();
}
} else {
break;
}
}
}
Ok(count)
}
pub fn audio_play_start(rate: usize, _buffer_size: usize) -> Result<(), SDKError> {
fn nz(x: usize) -> usize {
if x == 0 {
1
} else {
x
}
}
trace!("audio_play_start {rate}");
let mut buf = TX_BUFFER.lock();
let nco_tx = CLK_FIXED_FREQ_HZ / (nz(2 * rate) as u64);
if nco_tx > reg_constants::i2s::I2S_CTRL_NCO_TX_MASK as u64 {
error!("bad nco_tx {nco_tx} for rate {rate}");
return Err(SDKError::InvalidAudioParameter);
}
// XXX or force client to stop?
buf.clear();
// audio_stop_playing(&mut buf);
set_intr_state(get_intr_state().with_tx_watermark(true));
set_intr_enable(get_intr_enable().with_tx_watermark(true));
set_ctrl(get_ctrl().with_tx(true).with_nco_tx(nco_tx as u8));
Ok(())
}
pub fn audio_play_stop() -> Result<(), SDKError> {
trace!("audio_play_stop");
let mut buf = TX_BUFFER.lock();
// XXX client may want to flush instead of waiting
while !buf.is_empty() || tx_fifo_level() > 0 {
fill_tx_fifo(&mut buf);
drop(buf);
unsafe {
// XXX TxWatermark posts when buf is empty
TX_EMPTY.wait();
}
buf = TX_BUFFER.lock();
}
audio_stop_playing(&mut buf);
Ok(())
}
fn tx_fifo_level() -> u32 { get_fifo_status().txlvl().into() }
fn rx_fifo_level() -> u32 { get_fifo_status().rxlvl().into() }
pub fn audio_play_write(data: &[u32]) -> Result<(), SDKError> {
trace!("play write {}", data.len());
let mut buf = TX_BUFFER.lock();
for ix in 0..data.len() {
while buf.available_space() == 0 {
trace!(
"wait for tx_watermark {ix} avail {} fifo {}",
buf.available_space(),
tx_fifo_level()
);
drop(buf);
unsafe {
TX_EMPTY.wait();
}
buf = TX_BUFFER.lock();
trace!(
"tx_watermark wakeup avail {} fifo {}",
buf.available_space(),
tx_fifo_level()
);
}
buf.push(data[ix]);
}
if !buf.is_empty() {
fill_tx_fifo(&mut buf);
}
Ok(())
}
/// Copies from TX_BUFFER into the transmit FIFO.
///
/// This stops when the transmit FIFO is full or when TX_BUFFER is empty,
/// whichever comes first.
fn fill_tx_fifo(buf: &mut Buffer) {
const I2S_TX_FIFO_CAPACITY: u32 = 32;
trace!("fill_tx_fifo {} buf {}", tx_fifo_level(), buf.available_data());
while tx_fifo_level() < I2S_TX_FIFO_CAPACITY {
if let Some(b) = buf.pop() {
set_wdata(b);
} else {
break;
}
}
}
fn audio_stop_playing(buf: &mut Buffer) {
// NB: caller must drain buffer
assert!(buf.is_empty());
set_ctrl(get_ctrl().with_tx(false));
set_fifo_ctrl(get_fifo_ctrl().with_txrst(true)); // Flush TX FIFO
set_intr_state(get_intr_state().with_tx_watermark(false));
set_intr_enable(get_intr_enable().with_tx_watermark(false));
}
// IRQ Support.
// NB: glue'd into irq framework by I2SRxWatermarkInterfaceThread
pub struct RxWatermarkInterfaceThread;
impl RxWatermarkInterfaceThread {
pub fn handler() {
trace!("rx_watermark begin");
// Drain the RX fifo; data goes to the RX_BUFFER.
let mut guard = RX_BUFFER.lock();
if unsafe { RX_STOP_ON_FULL } {
let buf = guard.back();
while rx_fifo_level() > 0 && buf.available_space() > 0 {
buf.push(get_rdata());
}
} else {
let buf = guard.back();
while rx_fifo_level() > 0 {
buf.push(get_rdata());
}
}
if guard.back().available_space() == 0 {
if guard.flip() {
trace!("buffer flip");
// Notify any waiters of the buffer flip.
unsafe {
RX_NONEMPTY.post();
}
}
}
set_intr_state(get_intr_state().with_rx_watermark(true));
trace!(
"rx_watermark end, fifo {} buf {}",
rx_fifo_level(),
guard.back().available_data()
);
}
}
pub struct TxWatermarkInterfaceThread;
impl TxWatermarkInterfaceThread {
pub fn handler() {
trace!("handle tx_watermark");
let mut buf = TX_BUFFER.lock();
fill_tx_fifo(&mut buf);
if buf.available_space() >= 16 {
unsafe {
TX_EMPTY.post();
}
}
set_intr_state(get_intr_state().with_tx_watermark(true));
trace!(
"tx_watermark end, fifo {} buf {}",
tx_fifo_level(),
buf.available_data()
);
}
}