/* * Copyright (c) 2020-2021, Bluetrum Development Team * * SPDX-License-Identifier: Apache-2.0 * * Date Author Notes * 2020-12-12 greedyhao first implementation */ #include #define DBG_TAG "drv.snd_dev" #define DBG_LVL DBG_INFO #include #define SAI_AUDIO_FREQUENCY_48K ((uint32_t)48000u) #define SAI_AUDIO_FREQUENCY_44K ((uint32_t)44100u) #define SAI_AUDIO_FREQUENCY_38K ((uint32_t)38000u) #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; rt_uint8_t dma_to_aubuf; }; 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 select:3bit 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) { DACDIGCON0 &= ~(0xf << 2); if (frequency == SAI_AUDIO_FREQUENCY_48K) { DACDIGCON0 |= (0 << 2); } else if (frequency == SAI_AUDIO_FREQUENCY_44K) { DACDIGCON0 |= (1 << 2); } else if (frequency == SAI_AUDIO_FREQUENCY_38K) { DACDIGCON0 |= (2 << 2); } 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; } case AUDIO_MIXER_EXTEND: snd_dev->dma_to_aubuf = caps->udata.value; 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); saia_volume_set(snd_dev->volume); 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) AUBUFCON |= BIT(1); } return RT_EOK; } static rt_err_t sound_stop(struct rt_audio_device *audio, int stream) { RT_ASSERT(audio != RT_NULL); if (stream == AUDIO_STREAM_REPLAY) { 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.dma_to_aubuf == RT_FALSE) && (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 1 ); if (snd_dev.thread != RT_NULL) { rt_thread_startup(snd_dev.thread); } /* init default configuration */ { snd_dev.replay_config.samplerate = SAI_AUDIO_FREQUENCY_48K; 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);