mirror of
https://github.com/drasko/open-ameba.git
synced 2025-07-31 12:31:04 +00:00
273 lines
8.6 KiB
C
273 lines
8.6 KiB
C
/******************************************************************************
|
|
*
|
|
* FileName: i2s_freertos.c
|
|
*
|
|
* Description: I2S output routines for a FreeRTOS system.
|
|
*
|
|
* Modification history:
|
|
* 2015/10, RTL8710 kissste, pvvx
|
|
*******************************************************************************/
|
|
|
|
/*
|
|
How does this work? Basically, to get sound, you need to:
|
|
- Connect an I2S codec to the I2S pins on the RTL.
|
|
- Start up a thread that's going to do the sound output
|
|
- Call I2sInit()
|
|
- Call I2sSetRate() with the sample rate you want.
|
|
- Generate sound and call i2sPushSample() with 32-bit samples.
|
|
The 32bit samples basically are 2 16-bit signed values (the analog values for
|
|
the left and right channel) concatenated as (Rout<<16)+Lout
|
|
|
|
I2sPushSample will block when you're sending data too quickly, so you can just
|
|
generate and push data as fast as you can and I2sPushSample will regulate the
|
|
speed.
|
|
*/
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "semphr.h"
|
|
#include "queue.h"
|
|
#include "user/playerconfig.h"
|
|
|
|
#include "osdep_api.h"
|
|
#include "i2s_api.h"
|
|
#include "driver/i2s_freertos.h"
|
|
|
|
#define USE_RTL_I2S_API 0 // speed
|
|
|
|
PI2S_OBJS pi2s[MAX_I2S_OBJS]; // I2S0, I2S1
|
|
xSemaphoreHandle I2sTxSema;
|
|
|
|
// i2s interrupt callback
|
|
static void i2s_test_tx_complete(void *data, char *pbuf)
|
|
{
|
|
i2s_t *i2s_obj = (i2s_t *)data;
|
|
int idx = i2s_obj->InitDat.I2SIdx;
|
|
#if I2S_DEBUG_LEVEL > 1
|
|
int reg = HAL_I2S_READ32(idx, REG_I2S_TX_PAGE0_OWN);
|
|
reg |= HAL_I2S_READ32(idx, REG_I2S_TX_PAGE1_OWN);
|
|
reg |= HAL_I2S_READ32(idx, REG_I2S_TX_PAGE2_OWN);
|
|
reg |= HAL_I2S_READ32(idx, REG_I2S_TX_PAGE3_OWN);
|
|
if(!(reg & BIT_PAGE_I2S_OWN_BIT)) pi2s[idx]->underrunCnt++;
|
|
#endif
|
|
if(!idx) xSemaphoreGive(I2sTxSema);
|
|
}
|
|
|
|
void i2sClose(int mask) {
|
|
int i;
|
|
for(i = 0; i < MAX_I2S_OBJS; i++) {
|
|
if(mask & (1 << i)) {
|
|
if(pi2s[i] != NULL) {
|
|
if(pi2s[i]->i2s_obj.InitDat.I2SEn != I2S_DISABLE) {
|
|
i2s_disable(&pi2s[i]->i2s_obj); // HalI2SDisable(&pi2s[i]->i2s_obj.I2SAdapter);
|
|
i2s_deinit(&pi2s[i]->i2s_obj); // HalI2SDeInit(&pi2s[i]->i2s_obj.I2SAdapter);
|
|
#if I2S_DEBUG_LEVEL > 0
|
|
DBG_8195A("I2S%d: i2s_disable (%d)\n", i, pi2s[i]->i2s_obj.InitDat.I2SEn);
|
|
#endif
|
|
}
|
|
if(pi2s[i]->i2s_obj.InitDat.I2STxData != NULL) {
|
|
vPortFree(pi2s[i]->i2s_obj.InitDat.I2STxData);
|
|
pi2s[i]->i2s_obj.InitDat.I2STxData = NULL;
|
|
}
|
|
vPortFree(pi2s[i]);
|
|
pi2s[i] = NULL;
|
|
if(i==0) {
|
|
vSemaphoreDelete(I2sTxSema);
|
|
HalPinCtrlRtl8195A(JTAG, 0, 1);
|
|
}
|
|
DBG_8195A("I2S%d: Closed.\n", i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Initialize I2S subsystem for DMA circular buffer use
|
|
int i2sInit(int mask, int bufsize, int word_len) { // word_len = WL_16b or WL_24b
|
|
#if I2S_DEBUG_LEVEL > 2
|
|
DBG_ERR_MSG_ON(_DBG_I2S_ | _DBG_GDMA_);
|
|
DBG_INFO_MSG_ON(_DBG_I2S_ | _DBG_GDMA_);
|
|
DBG_WARN_MSG_ON(_DBG_I2S_ | _DBG_GDMA_);
|
|
#endif
|
|
if(bufsize < I2S_DMA_PAGE_SIZE_MS_96K*2) {
|
|
DBG_8195A("I2S: Min buffer %d bytes!\n", I2S_DMA_PAGE_SIZE_MS_96K*2);
|
|
return 0;
|
|
}
|
|
int page_size = bufsize * sizeof(u32);
|
|
if(word_len != WL_16b) page_size <<= 1; //bufsize *2;
|
|
int i;
|
|
for(i = 0; i < MAX_I2S_OBJS; i++) {
|
|
if (mask & (1 << i)) {
|
|
if(pi2s[i] != NULL) i2sClose(1 << i);
|
|
PI2S_OBJS pi2s_new = pvPortMalloc(sizeof(I2S_OBJS));
|
|
if(pi2s_new == NULL) {
|
|
DBG_8195A("I2S%d: Not heap buffer %d bytes!\n", i, sizeof(i2s_t) + page_size * I2S_DMA_PAGE_NUM);
|
|
return 0;
|
|
}
|
|
rtl_memset(pi2s_new, 0, sizeof(i2s_t));
|
|
u8 * i2s_tx_buf = (u8 *) pvPortMalloc(page_size * I2S_DMA_PAGE_NUM);
|
|
if (i2s_tx_buf == NULL) {
|
|
vPortFree(pi2s_new);
|
|
DBG_8195A("I2S%d: Not heap buffer %d bytes!\n", i, sizeof(i2s_t) + page_size * I2S_DMA_PAGE_NUM);
|
|
return 0;
|
|
}
|
|
pi2s[i] = pi2s_new;
|
|
#if I2S_DEBUG_LEVEL > 1
|
|
pi2s_new->underrunCnt = 0;
|
|
#endif
|
|
pi2s[i]->sampl_err = 0;
|
|
pi2s_new->currDMABuffPos = 0;
|
|
pi2s_new->currDMABuff = NULL;
|
|
|
|
i2s_t * pi2s_obj = &pi2s_new->i2s_obj;
|
|
|
|
pi2s_obj->channel_num = CH_STEREO;
|
|
pi2s_obj->sampling_rate = SR_96KHZ;
|
|
pi2s_obj->word_length = word_len;
|
|
pi2s_obj->direction = I2S_DIR_TX; //consider switching to TX only
|
|
if(i == 0) {
|
|
HalPinCtrlRtl8195A(JTAG, 0, 0);
|
|
i2s_init(pi2s_obj, I2S0_SCLK_PIN, I2S0_WS_PIN, I2S0_SD_PIN);
|
|
// Create a Semaphone
|
|
RtlInitSema(&I2sTxSema, 1);
|
|
}
|
|
else i2s_init(pi2s_obj, I2S1_SCLK_PIN, I2S1_WS_PIN, I2S1_SD_PIN);
|
|
i2s_set_param(pi2s_obj, pi2s_obj->channel_num, pi2s_obj->sampling_rate, pi2s_obj->word_length);
|
|
i2s_set_dma_buffer(pi2s_obj, i2s_tx_buf, NULL, I2S_DMA_PAGE_NUM, page_size);
|
|
i2s_tx_irq_handler(pi2s_obj, i2s_test_tx_complete, (uint32_t)pi2s_obj);
|
|
// i2s_rx_irq_handler(pi2s_obj, (i == 0)? (i2s_irq_handler)i2s1_test_rx_complete : (i2s_irq_handler)i2s2_test_rx_complete, i); // TX only!
|
|
i2s_enable(pi2s_obj);
|
|
DBG_8195A("I2S%d: Alloc DMA buf %d bytes (%d x %d samples %d bits)\n", i, page_size * I2S_DMA_PAGE_NUM, I2S_DMA_PAGE_NUM, bufsize, (word_len == WL_16b)? 32 : 96);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Set the I2S sample rate, in HZ
|
|
char i2sSetRate(int mask, int rate) {
|
|
|
|
int sample_rate;
|
|
char result = 1;
|
|
#if defined(OVERSAMPLES) && defined(PWM_HACK96BIT)
|
|
rate <<= 1;
|
|
while (rate <= 48000) {
|
|
rate <<= 1;
|
|
result++;
|
|
}
|
|
#endif
|
|
if (rate>=96000) sample_rate = SR_96KHZ;
|
|
else if (rate>=88200) sample_rate = SR_88p2KHZ;
|
|
else if (rate>=48000) sample_rate = SR_48KHZ;
|
|
else if (rate>=44100) sample_rate = SR_44p1KHZ;
|
|
else if (rate>=32000) sample_rate = SR_32KHZ;
|
|
else if (rate>=24000) sample_rate = SR_24KHZ;
|
|
else if (rate>=22050) sample_rate = SR_22p05KHZ;
|
|
else if (rate>=16000) sample_rate = SR_16KHZ;
|
|
else if (rate>=11020) sample_rate = SR_11p02KHZ;
|
|
else if (rate>= 8000) sample_rate = SR_8KHZ;
|
|
else sample_rate = SR_7p35KHZ;
|
|
int i;
|
|
for(i = 0; i < MAX_I2S_OBJS; i++) {
|
|
if (mask & (1 << i)) {
|
|
i2s_t * pi2s_obj = &pi2s[i]->i2s_obj;
|
|
pi2s[i]->sampl_err = 0;
|
|
pi2s_obj->sampling_rate = sample_rate;
|
|
#if USE_RTL_I2S_API
|
|
i2s_set_param(pi2s_obj, pi2s_obj->channel_num, pi2s_obj->sampling_rate, pi2s_obj->word_length);
|
|
#else
|
|
pi2s_obj->I2SAdapter.pInitDat->I2SRate = sample_rate;
|
|
HalI2SSetRate(pi2s_obj->I2SAdapter.pInitDat);
|
|
#endif
|
|
}
|
|
}
|
|
DBG_8195A("I2S: Set Sample Rate %d (x%d)\n", rate, result);
|
|
return result;
|
|
}
|
|
|
|
#if defined(PWM_HACK96BIT)
|
|
//This routine pushes a single, 32-bit sample to the I2S buffers. Call this at (on average)
|
|
//at least the current sample rate. You can also call it quicker: it will suspend the calling
|
|
//thread if the buffer is full and resume when there's room again.
|
|
u32 i2sPushPWMSamples(u32 sample) {
|
|
int i;
|
|
for(i = 0; i < MAX_I2S_OBJS; i++) {
|
|
PI2S_OBJS pi2s_cur = pi2s[i];
|
|
PHAL_I2S_ADAPTER I2SAdapter = &pi2s_cur->i2s_obj.I2SAdapter;
|
|
while(pi2s_cur->currDMABuff == NULL){
|
|
#if USE_RTL_I2S_API
|
|
pi2s_cur->currDMABuff = i2s_get_tx_page(&pi2s_cur->i2s_obj);
|
|
if(pi2s_cur->currDMABuff == NULL) RtlDownSema(&I2sTxSema);
|
|
#else
|
|
u8 page_idx = HalI2SGetTxPage((VOID*)I2SAdapter->pInitDat);
|
|
if(page_idx < I2S_DMA_PAGE_NUM) pi2s_cur->currDMABuff = ((u32 *)I2SAdapter->TxPageList[page_idx]);
|
|
else xSemaphoreTake(I2sTxSema, portMAX_DELAY);
|
|
// RtlDownSema(&I2sTxSema);
|
|
#endif
|
|
pi2s_cur->currDMABuffPos = 0;
|
|
}
|
|
u32 *p = &pi2s_cur->currDMABuff[pi2s_cur->currDMABuffPos];
|
|
if(i) sample >>= 16;
|
|
s32 smp = (s16)sample + 0x8000 + pi2s_cur->sampl_err;
|
|
if (smp > 0xffff) smp = 0xffff;
|
|
else if (smp < 0) smp = 0;
|
|
u8 x = smp/(u16)(0x10000/97);
|
|
pi2s_cur->sampl_err = smp - x * (u16)(0x10000/97);
|
|
if(x < 24) {
|
|
*p++ = (1 << x) -1;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p = 0;
|
|
}
|
|
else if (x < 48) {
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = (1 << (x - 24)) -1;
|
|
*p++ = 0;
|
|
*p = 0;
|
|
}
|
|
else if (x < 72) {
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = (1 << (x - 48)) -1;
|
|
*p = 0;
|
|
}
|
|
else if (x < 96) {
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = 0xFFFFFFFF;
|
|
*p = (1 << (x - 72)) -1;
|
|
}
|
|
else {
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = 0xFFFFFFFF;
|
|
*p++ = 0xFFFFFFFF;
|
|
*p = 0xFFFFFFFF;
|
|
}
|
|
pi2s_cur->currDMABuffPos += 4;
|
|
}
|
|
portENTER_CRITICAL();
|
|
for(i = 0; i < MAX_I2S_OBJS; i++) {
|
|
PI2S_OBJS pi2s_cur = pi2s[i];
|
|
if (pi2s_cur->currDMABuffPos > pi2s_cur->i2s_obj.InitDat.I2SPageSize) {
|
|
#if USE_RTL_I2S_API
|
|
i2s_send_page(&pi2s_cur->i2s_obj, pi2s_cur->currDMABuff);
|
|
#else
|
|
PHAL_I2S_ADAPTER I2SAdapter = &pi2s_cur->i2s_obj.I2SAdapter;
|
|
int n;
|
|
for (n = 0; n < I2S_DMA_PAGE_NUM; n++) {
|
|
if (I2SAdapter->TxPageList[n] == pi2s_cur->currDMABuff) {
|
|
HalI2SPageSend(I2SAdapter->pInitDat, n);
|
|
HAL_I2S_WRITE32(i, REG_I2S_TX_PAGE0_OWN + 4 * n, BIT_PAGE_I2S_OWN_BIT);
|
|
break; // break the for loop
|
|
}
|
|
}
|
|
#endif
|
|
pi2s_cur->currDMABuff = NULL;
|
|
}
|
|
}
|
|
portEXIT_CRITICAL();
|
|
}
|
|
#endif
|
|
|
|
#if I2S_DEBUG_LEVEL > 1
|
|
long i2s1GetUnderrunCnt(int num) {
|
|
return pi2s[num]->underrunCnt;
|
|
}
|
|
#endif
|