// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "RT1051Audiocodec.hpp"
#include "board.h"
#include "dma_config.h"
#include "log/log.hpp"
#include "bsp/BoardDefinitions.hpp"
#include <mutex.hpp>
namespace bsp
{
using namespace drivers;
std::shared_ptr<drivers::DriverPLL> RT1051Audiocodec::pllAudio;
std::shared_ptr<drivers::DriverDMAMux> RT1051Audiocodec::dmamux;
std::shared_ptr<drivers::DriverDMA> RT1051Audiocodec::dma;
std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::rxDMAHandle;
std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::txDMAHandle;
sai_config_t RT1051Audiocodec::config = {};
std::uint32_t RT1051Audiocodec::mclkSourceClockHz = 0;
sai_edma_handle_t RT1051Audiocodec::txHandle = {};
sai_edma_handle_t RT1051Audiocodec::rxHandle = {};
int16_t RT1051Audiocodec::inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
int16_t RT1051Audiocodec::outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
RT1051Audiocodec::RT1051Audiocodec(bsp::AudioDevice::audioCallback_t callback)
: AudioDevice(callback), saiInFormat{}, saiOutFormat{}, codecParams{}, codec{}
{
isInitialized = true;
}
RT1051Audiocodec::~RT1051Audiocodec()
{
Stop();
}
AudioDevice::RetCode RT1051Audiocodec::Start(const bsp::AudioDevice::Format &format)
{
cpp_freertos::LockGuard lock(mutex);
if (state == State::Running) {
return AudioDevice::RetCode::Failure;
}
saiInFormat.bitWidth = format.bitWidth;
saiInFormat.sampleRate_Hz = format.sampleRate_Hz;
saiOutFormat.bitWidth = format.bitWidth;
saiOutFormat.sampleRate_Hz = format.sampleRate_Hz;
if (format.flags & static_cast<uint32_t>(AudioDevice::Flags::InputLeft)) {
saiInFormat.stereo = kSAI_MonoLeft;
InStart();
}
else if (format.flags & static_cast<uint32_t>(AudioDevice::Flags::InputRight)) {
saiInFormat.stereo = kSAI_MonoRight;
InStart();
}
else if (format.flags & static_cast<uint32_t>(AudioDevice::Flags::InputStereo)) {
saiInFormat.stereo = kSAI_Stereo;
InStart();
}
if (format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputMono)) {
saiOutFormat.stereo = kSAI_MonoLeft;
OutStart();
}
else if (format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputStereo)) {
saiOutFormat.stereo = kSAI_Stereo;
OutStart();
}
codecParams.sampleRate = CodecParamsMAX98090::ValToSampleRate(format.sampleRate_Hz);
if (codecParams.sampleRate == CodecParamsMAX98090::SampleRate::Invalid) {
LOG_ERROR("Unsupported sample rate");
}
codecParams.inputPath = static_cast<CodecParamsMAX98090::InputPath>(format.inputPath);
codecParams.outputPath = static_cast<CodecParamsMAX98090::OutputPath>(format.outputPath);
codecParams.outVolume = format.outputVolume;
codecParams.inGain = format.inputGain;
codec.Start(codecParams);
// Store format passed
currentFormat = format;
state = State::Running;
return AudioDevice::RetCode::Success;
}
AudioDevice::RetCode RT1051Audiocodec::Stop()
{
cpp_freertos::LockGuard lock(mutex);
if (state == State::Stopped) {
return AudioDevice::RetCode::Failure;
}
codec.Stop();
InStop();
OutStop();
if (outWorkerThread) {
xTaskNotify(outWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
outWorkerThread = nullptr;
}
if (inWorkerThread) {
xTaskNotify(inWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
inWorkerThread = nullptr;
}
state = State::Stopped;
vTaskDelay(codecSettleTime);
return AudioDevice::RetCode::Success;
}
AudioDevice::RetCode RT1051Audiocodec::OutputVolumeCtrl(float vol)
{
currentFormat.outputVolume = vol;
CodecParamsMAX98090 params;
params.outVolume = vol;
params.opCmd = CodecParamsMAX98090::Cmd::SetOutVolume;
codec.Ioctrl(params);
return AudioDevice::RetCode::Success;
}
AudioDevice::RetCode RT1051Audiocodec::InputGainCtrl(float gain)
{
currentFormat.inputGain = gain;
CodecParamsMAX98090 params;
params.inGain = gain;
params.opCmd = CodecParamsMAX98090::Cmd::SetInGain;
codec.Ioctrl(params);
return AudioDevice::RetCode::Success;
}
AudioDevice::RetCode RT1051Audiocodec::InputPathCtrl(InputPath inputPath)
{
currentFormat.inputPath = inputPath;
CodecParamsMAX98090 params;
params.inputPath = static_cast<CodecParamsMAX98090::InputPath>(inputPath);
params.opCmd = CodecParamsMAX98090::Cmd::SetInput;
codec.Ioctrl(params);
return AudioDevice::RetCode::Success;
}
AudioDevice::RetCode RT1051Audiocodec::OutputPathCtrl(OutputPath outputPath)
{
currentFormat.outputPath = outputPath;
CodecParamsMAX98090 params;
params.outputPath = static_cast<CodecParamsMAX98090::OutputPath>(outputPath);
params.opCmd = CodecParamsMAX98090::Cmd::SetOutput;
codec.Ioctrl(params);
return AudioDevice::RetCode::Success;
}
bool RT1051Audiocodec::IsFormatSupported(const bsp::AudioDevice::Format &format)
{
if (CodecParamsMAX98090::ValToSampleRate(format.sampleRate_Hz) == CodecParamsMAX98090::SampleRate::Invalid) {
return false;
}
return true;
}
// INTERNALS
void RT1051Audiocodec::Init()
{
pllAudio = DriverPLL::Create(static_cast<PLLInstances>(BoardDefinitions ::AUDIO_PLL), DriverPLLParams{});
dmamux = DriverDMAMux::Create(static_cast<DMAMuxInstances>(BoardDefinitions ::AUDIOCODEC_DMAMUX),
DriverDMAMuxParams{});
dma = DriverDMA::Create(static_cast<DMAInstances>(BoardDefinitions ::AUDIOCODEC_DMA), DriverDMAParams{});
// Enable MCLK clock
IOMUXC_GPR->GPR1 |= BOARD_AUDIOCODEC_SAIx_MCLK_MASK;
txDMAHandle = dma->CreateHandle(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_TX_DMA_CHANNEL));
rxDMAHandle = dma->CreateHandle(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_RX_DMA_CHANNEL));
dmamux->Enable(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_TX_DMA_CHANNEL),
BSP_AUDIOCODEC_SAIx_DMA_TX_SOURCE);
dmamux->Enable(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_RX_DMA_CHANNEL),
BSP_AUDIOCODEC_SAIx_DMA_RX_SOURCE);
mclkSourceClockHz = GetPerphSourceClock(PerphClock_SAI2);
// Initialize SAI Tx module
SAI_TxGetDefaultConfig(&config);
config.masterSlave = kSAI_Slave;
SAI_TxInit(BOARD_AUDIOCODEC_SAIx, &config);
// Initialize SAI Rx module
SAI_RxGetDefaultConfig(&config);
config.masterSlave = kSAI_Slave;
SAI_RxInit(BOARD_AUDIOCODEC_SAIx, &config);
}
void RT1051Audiocodec::Deinit()
{
memset(&config, 0, sizeof config);
SAI_Deinit(BOARD_AUDIOCODEC_SAIx);
if (dmamux) {
dmamux->Disable(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_TX_DMA_CHANNEL));
dmamux->Disable(static_cast<uint32_t>(BoardDefinitions ::AUDIOCODEC_RX_DMA_CHANNEL));
}
// force order of destruction
txDMAHandle.reset();
rxDMAHandle.reset();
dma.reset();
dmamux.reset();
pllAudio.reset();
}
void RT1051Audiocodec::InStart()
{
sai_transfer_format_t sai_format = {0};
sai_transfer_t xfer = {0};
saiInFormat.data = (uint8_t *)inBuffer;
saiInFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiInFormat.bitWidth;
sai_format.channel = 0U;
sai_format.sampleRate_Hz = saiInFormat.sampleRate_Hz;
sai_format.masterClockHz = mclkSourceClockHz;
sai_format.isFrameSyncCompact = false;
sai_format.protocol = config.protocol;
sai_format.stereo = saiInFormat.stereo;
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
sai_format.watermark = FSL_FEATURE_SAI_FIFO_COUNT / 2U;
#endif
SAI_TransferRxCreateHandleEDMA(BOARD_AUDIOCODEC_SAIx,
&rxHandle,
rxAudioCodecCallback,
this,
reinterpret_cast<edma_handle_t *>(rxDMAHandle->GetHandle()));
SAI_TransferRxSetFormatEDMA(
BOARD_AUDIOCODEC_SAIx, &rxHandle, &sai_format, mclkSourceClockHz, mclkSourceClockHz);
DisableIRQ(BOARD_AUDIOCODEC_SAIx_RX_IRQ);
/* Reset SAI Rx internal logic */
SAI_RxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
xfer.data = saiInFormat.data;
xfer.dataSize = saiInFormat.dataSize;
SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);
if (xTaskCreate(inAudioCodecWorkerTask, "inaudiocodec", 1024, this, 0, &inWorkerThread) != pdPASS) {
LOG_ERROR("Error during creating input audiocodec task");
}
}
void RT1051Audiocodec::OutStart()
{
sai_transfer_format_t sai_format = {0};
sai_transfer_t xfer = {0};
saiOutFormat.data = (uint8_t *)outBuffer;
saiOutFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiOutFormat.bitWidth;
sai_format.channel = 0U;
sai_format.sampleRate_Hz = saiOutFormat.sampleRate_Hz;
sai_format.masterClockHz = mclkSourceClockHz;
sai_format.isFrameSyncCompact = false;
sai_format.protocol = config.protocol;
sai_format.stereo = saiOutFormat.stereo;
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
sai_format.watermark = FSL_FEATURE_SAI_FIFO_COUNT / 2U;
#endif
SAI_TransferTxCreateHandleEDMA(BOARD_AUDIOCODEC_SAIx,
&txHandle,
txAudioCodecCallback,
this,
reinterpret_cast<edma_handle_t *>(txDMAHandle->GetHandle()));
SAI_TransferTxSetFormatEDMA(
BOARD_AUDIOCODEC_SAIx, &txHandle, &sai_format, mclkSourceClockHz, mclkSourceClockHz);
DisableIRQ(BOARD_AUDIOCODEC_SAIx_TX_IRQ);
/* Reset SAI Tx internal logic */
SAI_TxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
xfer.data = saiOutFormat.data;
xfer.dataSize = saiOutFormat.dataSize;
SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);
if (xTaskCreate(outAudioCodecWorkerTask, "outaudiocodec", 1024, this, 0, &outWorkerThread) != pdPASS) {
LOG_ERROR("Error during creating output audiocodec task");
}
// Fill out buffer with data
GetAudioCallback()(nullptr, outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE * 2);
}
void RT1051Audiocodec::OutStop()
{
SAI_TxDisableInterrupts(BOARD_AUDIOCODEC_SAIx, kSAI_FIFOErrorInterruptEnable);
if (txHandle.dmaHandle) {
SAI_TransferTerminateSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle);
}
memset(&txHandle, 0, sizeof(txHandle));
}
void RT1051Audiocodec::InStop()
{
SAI_RxDisableInterrupts(BOARD_AUDIOCODEC_SAIx, kSAI_FIFOErrorInterruptEnable);
if (rxHandle.dmaHandle) {
SAI_TransferAbortReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle);
}
memset(&rxHandle, 0, sizeof(rxHandle));
}
void inAudioCodecWorkerTask(void *pvp)
{
std::uint32_t ulNotificationValue = 0;
RT1051Audiocodec *inst = reinterpret_cast<RT1051Audiocodec *>(pvp);
while (true) {
xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
UINT32_MAX, /* Clear all bits on exit. */
&ulNotificationValue, /* Receives the notification value. */
portMAX_DELAY); /* Block indefinitely. */
{
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
break;
}
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
auto framesFetched = inst->GetAudioCallback()(
inst->inBuffer, nullptr, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
memset(&inst->inBuffer[framesFetched],
0,
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
}
}
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
auto framesFetched =
inst->GetAudioCallback()(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
nullptr,
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
memset(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
0,
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
}
}
}
}
vTaskDelete(nullptr);
}
void outAudioCodecWorkerTask(void *pvp)
{
std::uint32_t ulNotificationValue = 0;
RT1051Audiocodec *inst = reinterpret_cast<RT1051Audiocodec *>(pvp);
while (true) {
xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
UINT32_MAX, /* Clear all bits on exit. */
&ulNotificationValue, /* Receives the notification value. */
portMAX_DELAY); /* Block indefinitely. */
{
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
break;
}
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
auto framesFetched = inst->GetAudioCallback()(
nullptr, inst->outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
memset(&inst->outBuffer[framesFetched],
0,
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
}
}
if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
auto framesFetched =
inst->GetAudioCallback()(nullptr,
&inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
memset(&inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
0,
RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
}
}
}
}
vTaskDelete(nullptr);
}
void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
RT1051Audiocodec *inst = (RT1051Audiocodec *)userData;
sai_transfer_t xfer = {0};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (inst->state == RT1051Audiocodec::State::Stopped) {
return;
}
if (state == RT1051Audiocodec::TransferState::HalfTransfer) {
xfer.dataSize = inst->saiInFormat.dataSize;
xfer.data = inst->saiInFormat.data + (inst->saiInFormat.dataSize);
SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);
/* Notify the task that the transmission is complete. */
if (inst->inWorkerThread) {
xTaskNotifyFromISR(
inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
}
state = RT1051Audiocodec::TransferState::FullTransfer;
}
else {
xfer.dataSize = inst->saiInFormat.dataSize;
xfer.data = inst->saiInFormat.data;
SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);
/* Notify the task that the transmission is complete. */
xTaskNotifyFromISR(inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
state = RT1051Audiocodec::TransferState::HalfTransfer;
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
RT1051Audiocodec *inst = (RT1051Audiocodec *)userData;
sai_transfer_t xfer = {0};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (inst->state == RT1051Audiocodec::State::Stopped) {
return;
}
if (state == RT1051Audiocodec::TransferState::HalfTransfer) {
xfer.dataSize = inst->saiOutFormat.dataSize;
xfer.data = inst->saiOutFormat.data + (inst->saiOutFormat.dataSize);
SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);
/* Notify the task that the transmission is complete. */
xTaskNotifyFromISR(
inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
state = RT1051Audiocodec::TransferState::FullTransfer;
}
else {
xfer.dataSize = inst->saiOutFormat.dataSize;
xfer.data = inst->saiOutFormat.data;
SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);
/* Notify the task that the transmission is complete. */
xTaskNotifyFromISR(
inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
state = RT1051Audiocodec::TransferState::HalfTransfer;
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
} // namespace bsp