/* * Copyright 2021-2022 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include "board.h" #include "streamer_pcm_app.h" #include "fsl_codec_common.h" #include "fsl_wm8960.h" #include "app_definitions.h" #include "fsl_cache.h" #include "audio_speaker.h" #include "fsl_sai.h" AT_NONCACHEABLE_SECTION_INIT(static pcm_rtos_t pcmHandle) = {0}; extern codec_handle_t codecHandle; extern usb_audio_speaker_struct_t g_UsbDeviceAudioSpeaker; extern uint8_t audioPlayDataBuff[AUDIO_SPEAKER_DATA_WHOLE_BUFFER_COUNT_NORMAL * AUDIO_PLAY_BUFFER_SIZE_ONE_FRAME]; static uint8_t audioPlayDMATempBuff[AUDIO_PLAY_BUFFER_SIZE_ONE_FRAME] = {0}; /*! @brief SAI Transmit IRQ handler. * * This function is used to handle or clear error state. */ static void SAI_UserTxIRQHandler(void) { /* Clear the FEF (Tx FIFO underrun) flag. */ SAI_TxClearStatusFlags(DEMO_SAI, kSAI_FIFOErrorFlag); SAI_TxSoftwareReset(DEMO_SAI, kSAI_ResetTypeFIFO); __DSB(); } /*! @brief SAI IRQ handler. * * This function checks FIFO overrun/underrun errors and clears error state. */ void SAI1_IRQHandler(void) { if (DEMO_SAI->TCSR & kSAI_FIFOErrorFlag) SAI_UserTxIRQHandler(); } /*! @brief SAI EDMA transmit callback * * This function is called by the EDMA interface after a block of data has been * successfully written to the SAI. */ static void saiTxCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData) { pcm_rtos_t *pcm = (pcm_rtos_t *)userData; BaseType_t reschedule = -1; xSemaphoreGiveFromISR(pcm->semaphoreTX, &reschedule); portYIELD_FROM_ISR(reschedule); } void streamer_pcm_init(void) { edma_config_t dmaConfig; /* SAI initialization */ NVIC_SetPriority(LPI2C1_IRQn, 5); NVIC_SetPriority(DEMO_SAI_TX_IRQ, 5U); NVIC_SetPriority(DMA1_DMA17_IRQn, 4U); NVIC_SetPriority(DMA0_DMA16_IRQn, 4U); EDMA_GetDefaultConfig(&dmaConfig); EDMA_Init(DEMO_DMA, &dmaConfig); /* Create DMA handle. */ EDMA_CreateHandle(&pcmHandle.dmaTxHandle, DEMO_DMA, DEMO_TX_CHANNEL); /* SAI init */ SAI_Init(DEMO_SAI); pcmHandle.semaphoreTX = xSemaphoreCreateBinary(); EnableIRQ(DEMO_SAI_TX_IRQ); } pcm_rtos_t *streamer_pcm_open(uint32_t num_buffers) { SAI_TransferTxCreateHandleEDMA(DEMO_SAI, &pcmHandle.saiTxHandle, saiTxCallback, (void *)&pcmHandle, &pcmHandle.dmaTxHandle); return &pcmHandle; } pcm_rtos_t *streamer_pcm_rx_open(uint32_t num_buffers) { return &pcmHandle; } void streamer_pcm_start(pcm_rtos_t *pcm) { /* Interrupts already enabled - nothing to do. * App/streamer can begin writing data to SAI. */ } void streamer_pcm_close(pcm_rtos_t *pcm) { /* Stop playback. This will flush the SAI transmit buffers. */ SAI_TransferTerminateSendEDMA(DEMO_SAI, &pcm->saiTxHandle); vSemaphoreDelete(pcmHandle.semaphoreTX); } void streamer_pcm_rx_close(pcm_rtos_t *pcm) { return; } int streamer_pcm_write(pcm_rtos_t *pcm, uint8_t *data, uint32_t size) { /* Ensure write size is a multiple of 32, otherwise EDMA will assert * failure. Round down for the last chunk of a file/stream. */ pcm->saiTx.dataSize = size - (size % 32); pcm->saiTx.data = data; DCACHE_CleanByRange((uint32_t)pcm->saiTx.data, pcm->saiTx.dataSize); /* Start the consecutive transfer */ while (SAI_TransferSendEDMA(DEMO_SAI, &pcm->saiTxHandle, &pcm->saiTx) == kStatus_SAI_QueueFull) { /* Wait for transfer to finish */ if (xSemaphoreTake(pcm->semaphoreTX, portMAX_DELAY) != pdTRUE) { return -1; } } return 0; } int streamer_pcm_read(pcm_rtos_t *pcm, uint8_t *data, uint32_t size) { uint32_t audioSpeakerPreReadDataCount = 0U; uint32_t preAudioSendCount = 0U; sai_transfer_t xfer = {0}; if ((USB_AudioSpeakerBufferSpaceUsed() < (g_UsbDeviceAudioSpeaker.audioPlayTransferSize)) && (g_UsbDeviceAudioSpeaker.startPlayFlag == 1U)) { g_UsbDeviceAudioSpeaker.startPlayFlag = 0; g_UsbDeviceAudioSpeaker.speakerDetachOrNoInput = 1; } if (0U != g_UsbDeviceAudioSpeaker.startPlayFlag) { USB_DeviceCalculateFeedback(); xfer.dataSize = g_UsbDeviceAudioSpeaker.audioPlayTransferSize; xfer.data = audioPlayDataBuff + g_UsbDeviceAudioSpeaker.tdReadNumberPlay; preAudioSendCount = g_UsbDeviceAudioSpeaker.audioSendCount[0]; g_UsbDeviceAudioSpeaker.audioSendCount[0] += g_UsbDeviceAudioSpeaker.audioPlayTransferSize; if (preAudioSendCount > g_UsbDeviceAudioSpeaker.audioSendCount[0]) { g_UsbDeviceAudioSpeaker.audioSendCount[1] += 1U; } g_UsbDeviceAudioSpeaker.audioSendTimes++; g_UsbDeviceAudioSpeaker.tdReadNumberPlay += g_UsbDeviceAudioSpeaker.audioPlayTransferSize; if (g_UsbDeviceAudioSpeaker.tdReadNumberPlay >= g_UsbDeviceAudioSpeaker.audioPlayBufferSize) { g_UsbDeviceAudioSpeaker.tdReadNumberPlay = 0; } audioSpeakerPreReadDataCount = g_UsbDeviceAudioSpeaker.audioSpeakerReadDataCount[0]; g_UsbDeviceAudioSpeaker.audioSpeakerReadDataCount[0] += g_UsbDeviceAudioSpeaker.audioPlayTransferSize; if (audioSpeakerPreReadDataCount > g_UsbDeviceAudioSpeaker.audioSpeakerReadDataCount[0]) { g_UsbDeviceAudioSpeaker.audioSpeakerReadDataCount[1] += 1U; } } else { if (0U != g_UsbDeviceAudioSpeaker.audioPlayTransferSize) { xfer.dataSize = g_UsbDeviceAudioSpeaker.audioPlayTransferSize; } else { xfer.dataSize = AUDIO_PLAY_BUFFER_SIZE_ONE_FRAME / 8U; } xfer.data = audioPlayDMATempBuff; } DCACHE_InvalidateByRange((uint32_t)data, size); memcpy(data, xfer.data, size); return 0; } /*! @brief Map an integer sample rate (Hz) to internal SAI enum */ static sai_sample_rate_t _pcm_map_sample_rate(uint32_t sample_rate) { switch (sample_rate) { case 8000: return kSAI_SampleRate8KHz; case 11025: return kSAI_SampleRate11025Hz; case 12000: return kSAI_SampleRate12KHz; case 16000: return kSAI_SampleRate16KHz; case 24000: return kSAI_SampleRate24KHz; case 22050: return kSAI_SampleRate22050Hz; case 32000: return kSAI_SampleRate32KHz; case 44100: return kSAI_SampleRate44100Hz; case 48000: default: return kSAI_SampleRate48KHz; } } /*! @brief Map an integer bit width (bits) to internal SAI enum */ static sai_word_width_t _pcm_map_word_width(uint32_t bit_width) { switch (bit_width) { case 8: return kSAI_WordWidth8bits; case 16: return kSAI_WordWidth16bits; case 24: return kSAI_WordWidth24bits; case 32: return kSAI_WordWidth32bits; default: return kSAI_WordWidth16bits; } } /*! @brief Map an integer number of channels to internal SAI enum */ static sai_mono_stereo_t _pcm_map_channels(uint8_t num_channels) { if (num_channels >= 2) return kSAI_Stereo; else return kSAI_MonoRight; } int streamer_pcm_setparams(pcm_rtos_t *pcm, uint32_t sample_rate, uint32_t bit_width, uint8_t num_channels, bool transfer, bool dummy_tx, int volume) { sai_transfer_format_t format = {0}; sai_transceiver_t saiConfig; uint32_t masterClockHz = 0U; pcm->sample_rate = sample_rate; pcm->bit_width = bit_width; pcm->num_channels = num_channels; if (sample_rate % 8000 == 0 || sample_rate % 6000 == 0) { /* Configure Audio PLL clock to 786.432 MHz to to be divisible by 48000 Hz */ const clock_audio_pll_config_t audioPllConfig48 = { .loopDivider = 32, /* PLL loop divider. Valid range for DIV_SELECT divider value: 27~54. */ .postDivider = 1, /* Divider after the PLL, should only be 1, 2, 4, 8, 16. */ .numerator = 96, /* 30 bit numerator of fractional loop divider. */ .denominator = 125, /* 30 bit denominator of fractional loop divider */ }; CLOCK_InitAudioPll(&audioPllConfig48); } else { /* Configure Audio PLL clock to 722.5344 MHz to be divisible by 44100 Hz */ const clock_audio_pll_config_t audioPllConfig = { .loopDivider = 30, /* PLL loop divider. Valid range for DIV_SELECT divider value: 27~54. */ .postDivider = 1, /* Divider after the PLL, should only be 1, 2, 4, 8, 16. */ .numerator = 66, /* 30 bit numerator of fractional loop divider. */ .denominator = 625, /* 30 bit denominator of fractional loop divider */ }; CLOCK_InitAudioPll(&audioPllConfig); } #if (defined FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER && FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) || \ (defined FSL_FEATURE_PCC_HAS_SAI_DIVIDER && FSL_FEATURE_PCC_HAS_SAI_DIVIDER) masterClockHz = OVER_SAMPLE_RATE * format.sampleRate_Hz; #else masterClockHz = DEMO_SAI_CLK_FREQ; #endif format.channel = 0U; format.bitWidth = _pcm_map_word_width(bit_width); format.sampleRate_Hz = _pcm_map_sample_rate(sample_rate); format.stereo = _pcm_map_channels(num_channels); #if (defined FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER && FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) format.masterClockHz = masterClockHz; #endif /* I2S transfer mode configurations */ if (transfer) { SAI_TransferTerminateSendEDMA(DEMO_SAI, &pcm->saiTxHandle); SAI_GetClassicI2SConfig(&saiConfig, _pcm_map_word_width(bit_width), format.stereo, 1U << DEMO_SAI_CHANNEL); saiConfig.syncMode = kSAI_ModeAsync; saiConfig.masterSlave = kSAI_Master; SAI_TransferTxSetConfigEDMA(DEMO_SAI, &pcmHandle.saiTxHandle, &saiConfig); /* set bit clock divider */ SAI_TxSetBitClockRate(DEMO_SAI, masterClockHz, _pcm_map_sample_rate(sample_rate), _pcm_map_word_width(bit_width), DEMO_CHANNEL_NUM); /* Enable SAI transmit and FIFO error interrupts. */ SAI_TxEnableInterrupts(DEMO_SAI, kSAI_FIFOErrorInterruptEnable); streamer_pcm_mute(pcm, true); CODEC_SetFormat(&codecHandle, masterClockHz, format.sampleRate_Hz, format.bitWidth); streamer_pcm_set_volume(pcm, volume); } return 0; } void streamer_pcm_getparams(pcm_rtos_t *pcm, uint32_t *sample_rate, uint32_t *bit_width, uint8_t *num_channels) { *sample_rate = pcm->sample_rate; *bit_width = pcm->bit_width; *num_channels = pcm->num_channels; } int streamer_pcm_mute(pcm_rtos_t *pcm, bool mute) { CODEC_SetMute(&codecHandle, kCODEC_PlayChannelHeadphoneRight | kCODEC_PlayChannelHeadphoneLeft, mute); return 0; } int streamer_pcm_set_volume(pcm_rtos_t *pcm, int volume) { if (volume <= 0) CODEC_SetMute(&codecHandle, kCODEC_PlayChannelHeadphoneRight | kCODEC_PlayChannelHeadphoneLeft, true); else CODEC_SetVolume(&codecHandle, kCODEC_PlayChannelHeadphoneRight | kCODEC_PlayChannelHeadphoneLeft, volume > CODEC_VOLUME_MAX_VALUE ? CODEC_VOLUME_MAX_VALUE : volume); return 0; }