2019-08-06 15:00:25 +08:00
|
|
|
/*
|
2019-08-07 14:03:18 +08:00
|
|
|
* Copyright (c) 2006-2018, RT-Thread Development Team
|
|
|
|
*
|
2019-08-06 15:00:25 +08:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*
|
|
|
|
* Date Author Notes
|
|
|
|
* 2019-07-23 Zero-Free first implementation
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <rtthread.h>
|
|
|
|
#include <rtdevice.h>
|
|
|
|
|
|
|
|
#include <drv_sound.h>
|
|
|
|
#include <drv_pl041.h>
|
|
|
|
#include <drv_ac97.h>
|
|
|
|
|
|
|
|
#define DBG_TAG "drv.sound"
|
|
|
|
#define DBG_LVL DBG_INFO
|
|
|
|
#include <rtdbg.h>
|
|
|
|
|
|
|
|
#define TX_FIFO_SIZE (3840)
|
|
|
|
|
|
|
|
struct sound_device
|
|
|
|
{
|
|
|
|
struct rt_audio_device audio;
|
|
|
|
struct rt_audio_configure replay_config;
|
|
|
|
rt_uint8_t *tx_fifo;
|
|
|
|
rt_uint8_t volume;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct sound_device snd_dev = {0};
|
|
|
|
|
|
|
|
static void rt_hw_aaci_isr(rt_uint32_t status, void *user_data)
|
|
|
|
{
|
|
|
|
if (status & AACI_SR_TXHE)
|
|
|
|
{
|
|
|
|
rt_audio_tx_complete(&snd_dev.audio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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_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 = snd_dev->volume;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
result = -RT_ERROR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
result = -RT_ERROR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
struct rt_audio_replay *replay;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
snd_dev->volume = volume;
|
|
|
|
ac97_set_vol(volume);
|
|
|
|
LOG_I("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 */
|
|
|
|
ac97_set_rate(caps->udata.config.samplerate);
|
|
|
|
|
|
|
|
/* update buffer fifo informaition according samplerate */
|
|
|
|
replay = snd_dev->audio.replay;
|
|
|
|
replay->buf_info.total_size = caps->udata.config.samplerate / 50 * 4;
|
|
|
|
replay->buf_info.block_size = replay->buf_info.total_size / 2;
|
|
|
|
|
|
|
|
/* 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:
|
|
|
|
{
|
|
|
|
ac97_set_rate(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:
|
|
|
|
{
|
|
|
|
/* not support */
|
|
|
|
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 result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static rt_err_t sound_init(struct rt_audio_device *audio)
|
|
|
|
{
|
|
|
|
rt_err_t result = RT_EOK;
|
|
|
|
struct sound_device *snd_dev;
|
|
|
|
struct pl041_cfg _cfg;
|
|
|
|
|
|
|
|
RT_ASSERT(audio != RT_NULL);
|
|
|
|
snd_dev = (struct sound_device *)audio->parent.user_data;
|
|
|
|
|
|
|
|
aaci_pl041_init();
|
|
|
|
|
2020-02-17 22:39:32 +08:00
|
|
|
_cfg.itype = PL041_CHANNEL_LEFT_ADC | PL041_CHANNEL_RIGHT_ADC;
|
|
|
|
_cfg.otype = PL041_CHANNEL_LEFT_DAC | PL041_CHANNEL_RIGHT_DAC;
|
2019-08-06 15:00:25 +08:00
|
|
|
_cfg.vol = snd_dev->volume;
|
|
|
|
_cfg.rate = snd_dev->replay_config.samplerate;
|
|
|
|
|
|
|
|
ac97_reset();
|
2020-02-17 22:39:32 +08:00
|
|
|
aaci_pl041_channel_cfg(0, &_cfg);
|
2019-08-06 15:00:25 +08:00
|
|
|
aaci_pl041_irq_register(0, rt_hw_aaci_isr, RT_NULL);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static rt_err_t sound_start(struct rt_audio_device *audio, int stream)
|
|
|
|
{
|
|
|
|
RT_ASSERT(audio != RT_NULL);
|
|
|
|
|
|
|
|
if (stream == AUDIO_STREAM_REPLAY)
|
|
|
|
{
|
|
|
|
LOG_D("open sound device");
|
2020-02-17 22:39:32 +08:00
|
|
|
aaci_pl041_channel_enable(0);
|
2019-08-06 15:00:25 +08:00
|
|
|
aaci_pl041_irq_enable(0, AACI_IE_UR | AACI_IE_TX | AACI_IE_TXC);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
/* wait codec free */
|
|
|
|
rt_thread_mdelay(100);
|
|
|
|
/* disable irq and channels 0 */
|
|
|
|
aaci_pl041_irq_disable(0, AACI_IE_UR | AACI_IE_TX | AACI_IE_TXC);
|
2020-02-17 22:39:32 +08:00
|
|
|
aaci_pl041_channel_disable(0);
|
2019-08-06 15:00:25 +08:00
|
|
|
LOG_D("close sound device");
|
|
|
|
}
|
|
|
|
|
|
|
|
return RT_EOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sound_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info)
|
|
|
|
{
|
|
|
|
struct sound_device *snd_dev;
|
|
|
|
|
|
|
|
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 rt_size_t sound_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size)
|
|
|
|
{
|
|
|
|
RT_ASSERT(audio != RT_NULL);
|
|
|
|
|
|
|
|
/* write data to channel_0 fifo */
|
2020-02-17 22:39:32 +08:00
|
|
|
aaci_pl041_channel_write(0, (rt_uint16_t *)writeBuf, size >> 1);
|
2019-08-06 15:00:25 +08:00
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct rt_audio_ops snd_ops =
|
|
|
|
{
|
|
|
|
.getcaps = sound_getcaps,
|
|
|
|
.configure = sound_configure,
|
|
|
|
.init = sound_init,
|
|
|
|
.start = sound_start,
|
|
|
|
.stop = sound_stop,
|
|
|
|
.transmit = sound_transmit,
|
|
|
|
.buffer_info = sound_buffer_info,
|
|
|
|
};
|
|
|
|
|
|
|
|
int rt_hw_audio_init(void)
|
|
|
|
{
|
|
|
|
rt_uint8_t *tx_fifo;
|
|
|
|
|
|
|
|
if (snd_dev.tx_fifo)
|
|
|
|
return RT_EOK;
|
|
|
|
|
|
|
|
tx_fifo = rt_malloc(TX_FIFO_SIZE);
|
|
|
|
if (tx_fifo == RT_NULL)
|
|
|
|
return -RT_ENOMEM;
|
|
|
|
rt_memset(tx_fifo, 0, TX_FIFO_SIZE);
|
|
|
|
snd_dev.tx_fifo = tx_fifo;
|
|
|
|
|
|
|
|
/* init default configuration */
|
|
|
|
{
|
|
|
|
snd_dev.replay_config.samplerate = 44100;
|
|
|
|
snd_dev.replay_config.channels = 2;
|
|
|
|
snd_dev.replay_config.samplebits = 16;
|
|
|
|
snd_dev.volume = 55;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* register sound device */
|
|
|
|
snd_dev.audio.ops = &snd_ops;
|
|
|
|
rt_audio_register(&snd_dev.audio, "sound0", RT_DEVICE_FLAG_WRONLY, &snd_dev);
|
|
|
|
|
|
|
|
return RT_EOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_DEVICE_EXPORT(rt_hw_audio_init);
|