rt-thread/bsp/bluetrum/ab32vg1-ab-prougen/board/ports/audio/drv_sound.c

534 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020-2021, Bluetrum Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Date Author Notes
* 2020-12-12 greedyhao first implementation
*/
#include <board.h>
#define DBG_TAG "drv.snd_dev"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#define SAI_AUDIO_FREQUENCY_44K ((uint32_t)44100u)
#define SAI_AUDIO_FREQUENCY_48K ((uint32_t)48000u)
#define TX_FIFO_SIZE (1024)
struct sound_device
{
struct rt_audio_device audio;
struct rt_audio_configure replay_config;
rt_sem_t semaphore;
rt_thread_t thread;
rt_uint8_t *tx_fifo;
rt_uint8_t *rx_fifo;
rt_uint8_t volume;
};
static struct sound_device snd_dev = {0};
//apll = 采样率*ADPLL_DIV*512
//audio pll init
void adpll_init(uint8_t out_spr)
{
PLL1CON &= ~(BIT(16) | BIT(17)); //PLL1 refclk select xosc26m
CLKCON2 &= ~(BIT(4)| BIT(5) | BIT(6) | BIT(7));
PLL1CON &= ~(BIT(3) | BIT(4) | BIT(5));
PLL1CON |= BIT(3); //Select PLL/VCO frequency band (PLL大于206M vcos = 0x01, 否则为0)
PLL1CON |= BIT(12); //enable pll1 ldo
hal_mdelay(1);
PLL1CON |= BIT(18); //pll1 sdm enable
if (out_spr) {
CLKCON2 |= BIT(4) | BIT(7); //adpll_div = 10
PLL1DIV = (245.76 * 65536) / 26; //245.76Mhz for 48K
// sys.aupll_type = 1;
} else {
CLKCON2 |= BIT(5) | BIT(7); //adpll_div = 11
PLL1DIV = (248.3712 * 65536) / 26; //248.3712MHz for 44.1k
// sys.aupll_type = 0;
}
hal_mdelay(1);
PLL1CON |= BIT(20); //update pll1div
PLL1CON |= BIT(6); //enable analog pll1
hal_mdelay(1); //wait pll1 stable
}
void dac_start(void)
{
AUANGCON0 |= BIT(0) | BIT(1) | BIT(3); // bg ldoh bias enable
AUANGCON0 &= ~(BIT(6)|BIT(5)|BIT(4)); // LDOH voltage select3bit
AUANGCON0 |= (3<<4); // 2.4/2.5/2.7/2.9/3.1/3.2
AUANGCON0 |= BIT(2); // LDOL enable
AUANGCON0 |= BIT(9); //VCM enable
AUANGCON0 &= ~(BIT(13)|BIT(12)); // VCM voltage select, 2bit
AUANGCON0 |= (2<<12);
AUANGCON0 |= BIT(15) | BIT(16) | BIT(17) | BIT(18); // d2a lpf audpa audpa_dly
AUANGCON0 &= ~BIT(11); //VCM type: 0-->res divider with off-chip cap; 1-->internal VCM
//AUANGCON0 |= BIT(11);
AUANGCON0 &= ~BIT(19); // dac type: 0-->SC; 1-->SR
//AUANGCON0 |= BIT(19);
AUANGCON0 |= BIT(20); // pa type: 0-->diff; 1-->3.3V single
AUANGCON3 &= ~(0x7<<4); //BIT[6:4]=PA_GF[2:0]
AUANGCON3 |= (0<<4);
AUANGCON3 &= ~(0xf); //BIT[3:0]=PA_GX[3:0]
AUANGCON3 |= 0;
AUANGCON3 &= ~(0xF<<8); //BIT[11:8]=PA2_GX[3:0]
AUANGCON3 |= (0<<8);
AUANGCON3 &= ~(0x7<<12); //BIT[14:12]=PA2_GF[2:0]
AUANGCON3 |= (0<<12);
AUANGCON1 |= BIT(0) | BIT(1); // dac enable: BIT(0)-->right channel; BIT(1)-->left channel
//AUANGCON1 &= ~BIT(1); //disable left channel
AUANGCON1 |= BIT(12); // lpf2pa enable
AUANGCON1 &= ~BIT(29); // vcmbuf enable: 0-->disable
//AUANGCON1 |= BIT(29);
//AUANGCON1 |= BIT(30); // mirror enable
//AUANGCON2 |= BIT(29) | BIT(30); // adc mute
//AUANGCON1 |= BIT(3); // pa mute
}
RT_SECTION(".irq.audio")
void audio_sem_post(void)
{
rt_sem_release(snd_dev.semaphore);
}
void audio_sem_pend(void)
{
rt_sem_take(snd_dev.semaphore, RT_WAITING_FOREVER);
}
void saia_frequency_set(uint32_t frequency)
{
if (frequency == SAI_AUDIO_FREQUENCY_48K) {
DACDIGCON0 |= BIT(1);
DACDIGCON0 &= ~(0xf << 2);
DACDIGCON0 |= BIT(6);
} else if (frequency == SAI_AUDIO_FREQUENCY_44K) {
DACDIGCON0 &= ~BIT(1);
DACDIGCON0 &= ~(0xf << 2);
DACDIGCON0 |= BIT(1);
DACDIGCON0 |= BIT(6);
}
}
void saia_channels_set(uint8_t channels)
{
LOG_D("saia_channels_set=%d", channels);
if (channels == 1) {
AU0LMIXCOEF = 0x00007FFF;
AU1LMIXCOEF = 0x00007FFF;
DACDIGCON0 |= BIT(7);
DACDIGCON0 |= BIT(8);
AUANGCON1 &= ~BIT(0);
} else {
AUANGCON1 |= BIT(0);
DACDIGCON0 &= ~BIT(7);
DACDIGCON0 &= ~BIT(8);
}
}
void saia_volume_set(rt_uint8_t volume)
{
if (volume > 100)
volume = 100;
uint32_t dvol = volume * 327; // max is 0x7ffff
LOG_D("dvol=0x%x", dvol);
DACVOLCON = dvol | (0x02 << 16); // dac fade in
}
uint8_t saia_volume_get(void)
{
return ((DACVOLCON & 0xffff) / 327);
}
static rt_err_t sound_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps)
{
rt_err_t result = RT_EOK;
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
switch (caps->main_type)
{
case AUDIO_TYPE_QUERY: /* qurey the types of hw_codec device */
{
switch (caps->sub_type)
{
case AUDIO_TYPE_QUERY:
caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER;
break;
default:
result = -RT_ERROR;
break;
}
break;
}
case AUDIO_TYPE_OUTPUT: /* Provide capabilities of OUTPUT unit */
{
switch (caps->sub_type)
{
case AUDIO_DSP_PARAM:
caps->udata.config.samplerate = snd_dev->replay_config.samplerate;
caps->udata.config.channels = snd_dev->replay_config.channels;
caps->udata.config.samplebits = snd_dev->replay_config.samplebits;
break;
case AUDIO_DSP_SAMPLERATE:
caps->udata.config.samplerate = snd_dev->replay_config.samplerate;
break;
case AUDIO_DSP_CHANNELS:
caps->udata.config.channels = snd_dev->replay_config.channels;
break;
case AUDIO_DSP_SAMPLEBITS:
caps->udata.config.samplebits = snd_dev->replay_config.samplebits;
break;
default:
result = -RT_ERROR;
break;
}
break;
}
case AUDIO_TYPE_MIXER: /* report the Mixer Units */
{
switch (caps->sub_type)
{
case AUDIO_MIXER_QUERY:
caps->udata.mask = AUDIO_MIXER_VOLUME;
break;
case AUDIO_MIXER_VOLUME:
caps->udata.value = saia_volume_get();
break;
default:
result = -RT_ERROR;
break;
}
break;
}
default:
result = -RT_ERROR;
break;
}
return RT_EOK;
}
static rt_err_t sound_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps)
{
rt_err_t result = RT_EOK;
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
switch (caps->main_type)
{
case AUDIO_TYPE_MIXER:
{
switch (caps->sub_type)
{
case AUDIO_MIXER_VOLUME:
{
rt_uint8_t volume = caps->udata.value;
saia_volume_set(volume);
snd_dev->volume = volume;
LOG_D("set volume %d", volume);
break;
}
default:
result = -RT_ERROR;
break;
}
break;
}
case AUDIO_TYPE_OUTPUT:
{
switch (caps->sub_type)
{
case AUDIO_DSP_PARAM:
{
/* set samplerate */
saia_frequency_set(caps->udata.config.samplerate);
/* set channels */
saia_channels_set(caps->udata.config.channels);
/* save configs */
snd_dev->replay_config.samplerate = caps->udata.config.samplerate;
snd_dev->replay_config.channels = caps->udata.config.channels;
snd_dev->replay_config.samplebits = caps->udata.config.samplebits;
LOG_D("set samplerate %d", snd_dev->replay_config.samplerate);
break;
}
case AUDIO_DSP_SAMPLERATE:
{
saia_frequency_set(caps->udata.config.samplerate);
snd_dev->replay_config.samplerate = caps->udata.config.samplerate;
LOG_D("set samplerate %d", snd_dev->replay_config.samplerate);
break;
}
case AUDIO_DSP_CHANNELS:
{
saia_channels_set(caps->udata.config.channels);
snd_dev->replay_config.channels = caps->udata.config.channels;
LOG_D("set channels %d", snd_dev->replay_config.channels);
break;
}
case AUDIO_DSP_SAMPLEBITS:
{
/* not support */
snd_dev->replay_config.samplebits = caps->udata.config.samplebits;
break;
}
default:
result = -RT_ERROR;
break;
}
break;
}
default:
break;
}
return RT_EOK;
}
static rt_err_t sound_init(struct rt_audio_device *audio)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
adpll_init(0);
dac_start();
/* set default params */
saia_frequency_set(snd_dev->replay_config.samplerate);
saia_channels_set(snd_dev->replay_config.channels);
return RT_EOK;
}
static rt_err_t sound_start(struct rt_audio_device *audio, int stream)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
if (stream == AUDIO_STREAM_REPLAY)
{
LOG_D("open sound device");
AUBUFSIZE = (TX_FIFO_SIZE / 4 - 1);
AUBUFSIZE |= (TX_FIFO_SIZE / 8) << 16;
AUBUFSTARTADDR = DMA_ADR(snd_dev->rx_fifo);
DACDIGCON0 = BIT(0) | BIT(10); // (0x01<<2)
DACVOLCON = 0x7fff; // -60DB
DACVOLCON |= BIT(20);
AUBUFCON |= BIT(1);
}
return RT_EOK;
}
static rt_err_t sound_stop(struct rt_audio_device *audio, int stream)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
if (stream == AUDIO_STREAM_REPLAY)
{
AUBUFCON &= ~BIT(4);
LOG_D("close sound device");
}
return RT_EOK;
}
rt_size_t sound_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size)
{
struct sound_device *snd_dev = RT_NULL;
rt_size_t tmp_size = size / 4;
rt_size_t count = 0;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
while (tmp_size-- > 0) {
if (AUBUFCON & BIT(8)) { // aubuf full
AUBUFCON |= BIT(1) | BIT(4);
audio_sem_pend();
}
AUBUFDATA = ((const uint32_t *)writeBuf)[count++];
}
return size;
}
static void sound_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info)
{
struct sound_device *snd_dev = RT_NULL;
RT_ASSERT(audio != RT_NULL);
snd_dev = (struct sound_device *)audio->parent.user_data;
/**
* TX_FIFO
* +----------------+----------------+
* | block1 | block2 |
* +----------------+----------------+
* \ block_size /
*/
info->buffer = snd_dev->tx_fifo;
info->total_size = TX_FIFO_SIZE;
info->block_size = TX_FIFO_SIZE / 2;
info->block_count = 2;
}
static struct rt_audio_ops ops =
{
.getcaps = sound_getcaps,
.configure = sound_configure,
.init = sound_init,
.start = sound_start,
.stop = sound_stop,
.transmit = sound_transmit,
.buffer_info = sound_buffer_info,
};
RT_SECTION(".irq.audio")
static void audio_isr(int vector, void *param)
{
rt_interrupt_enter();
//Audio buffer pend
if (AUBUFCON & BIT(5)) {
AUBUFCON |= BIT(1); //Audio Buffer Pend Clear
AUBUFCON &= ~BIT(4);
audio_sem_post();
}
rt_interrupt_leave();
}
static void audio_thread_entry(void *parameter)
{
while (1)
{
if (snd_dev.audio.replay->activated == RT_TRUE) {
rt_audio_tx_complete(&snd_dev.audio);
} else {
rt_thread_mdelay(50);
}
}
}
static int rt_hw_sound_init(void)
{
rt_uint8_t *tx_fifo = RT_NULL;
rt_uint8_t *rx_fifo = RT_NULL;
/* 分配 DMA 搬运 buffer */
tx_fifo = rt_calloc(1, TX_FIFO_SIZE);
if(tx_fifo == RT_NULL)
{
return -RT_ENOMEM;
}
snd_dev.tx_fifo = tx_fifo;
/* 分配 DMA 搬运 buffer */
rx_fifo = rt_calloc(1, TX_FIFO_SIZE);
if(rx_fifo == RT_NULL)
{
return -RT_ENOMEM;
}
snd_dev.rx_fifo = rx_fifo;
snd_dev.semaphore = rt_sem_create("snd", 0, RT_IPC_FLAG_FIFO);
if (snd_dev.semaphore == RT_NULL)
{
return -RT_ENOMEM;
}
snd_dev.thread = rt_thread_create(
"audio",
audio_thread_entry,
RT_NULL,
1024,
20, // must equal or lower than tshell priority
5
);
if (snd_dev.thread != RT_NULL)
{
rt_thread_startup(snd_dev.thread);
}
/* init default configuration */
{
snd_dev.replay_config.samplerate = 48000;
snd_dev.replay_config.channels = 2;
snd_dev.replay_config.samplebits = 16;
snd_dev.volume = 55;
}
/* register snd_dev device */
snd_dev.audio.ops = &ops;
rt_audio_register(&snd_dev.audio, "sound0", RT_DEVICE_FLAG_WRONLY, &snd_dev);
rt_hw_interrupt_install(IRQ_AUBUF0_1_VECTOR, audio_isr, RT_NULL, "au_isr");
return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_sound_init);