From 7eeba713666dbc4a6c31fa3d730073b97a753fdd Mon Sep 17 00:00:00 2001 From: ZhongAn Date: Mon, 25 Jun 2018 20:22:32 +0800 Subject: [PATCH] audio: add audio_dma device driver. Signed-off-by: ZhongAn --- drivers/audio/Kconfig | 6 + drivers/audio/Make.defs | 4 + drivers/audio/audio_dma.c | 613 ++++++++++++++++++++++++++++++++ include/nuttx/audio/audio_dma.h | 41 +++ 4 files changed, 664 insertions(+) create mode 100644 drivers/audio/audio_dma.c create mode 100644 include/nuttx/audio/audio_dma.h diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index dd6c628a64..6437d7e317 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -492,4 +492,10 @@ config AUDIO_I2S depends on AUDIO depends on I2S +config AUDIO_DMA + bool "Audio DMA" + select AUDIO_DRIVER_SPECIFIC_BUFFERS + depends on AUDIO + depends on DMA + endif # DRIVERS_AUDIO diff --git a/drivers/audio/Make.defs b/drivers/audio/Make.defs index 33109cba24..31de28713c 100644 --- a/drivers/audio/Make.defs +++ b/drivers/audio/Make.defs @@ -93,6 +93,10 @@ ifeq ($(CONFIG_AUDIO_I2S),y) CSRCS += audio_i2s.c endif +ifeq ($(CONFIG_AUDIO_DMA),y) +CSRCS += audio_dma.c +endif + # Include Audio driver support DEPPATH += --dep-path audio diff --git a/drivers/audio/audio_dma.c b/drivers/audio/audio_dma.c new file mode 100644 index 0000000000..700a393a8c --- /dev/null +++ b/drivers/audio/audio_dma.c @@ -0,0 +1,613 @@ +/**************************************************************************** + * drivers/audio/audio_dma.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct audio_dma_s +{ + struct audio_lowerhalf_s dev; + struct dma_chan_s *chan; + uintptr_t src_addr; + uintptr_t dst_addr; + uint8_t *alloc_addr; + uint8_t alloc_index; + uint8_t fifo_width; + bool playback; + bool xrun; + struct dq_queue_s pendq; + apb_samp_t buffer_size; + apb_samp_t buffer_num; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int audio_dma_getcaps(struct audio_lowerhalf_s *dev, int type, + struct audio_caps_s *caps); +static int audio_dma_shutdown(struct audio_lowerhalf_s *dev); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_configure(struct audio_lowerhalf_s *dev, + void *session, + const struct audio_caps_s *caps); +static int audio_dma_start(struct audio_lowerhalf_s *dev, + void *session); +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +static int audio_dma_stop(struct audio_lowerhalf_s *dev, void *session); +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +static int audio_dma_pause(struct audio_lowerhalf_s *dev, + void *session); +static int audio_dma_resume(struct audio_lowerhalf_s *dev, + void *session); +#endif +static int audio_dma_reserve(struct audio_lowerhalf_s *dev, + void **session); +static int audio_dma_release(struct audio_lowerhalf_s *dev, + void *session); +#else +static int audio_dma_configure(struct audio_lowerhalf_s *dev, + const struct audio_caps_s *caps); +static int audio_dma_start(struct audio_lowerhalf_s *dev); +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +static int audio_dma_stop(struct audio_lowerhalf_s *dev); +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +static int audio_dma_pause(struct audio_lowerhalf_s *dev); +static int audio_dma_resume(struct audio_lowerhalf_s *dev); +#endif +static int audio_dma_reserve(struct audio_lowerhalf_s *dev); +static int audio_dma_release(struct audio_lowerhalf_s *dev); +#endif +static int audio_dma_allocbuffer(struct audio_lowerhalf_s *dev, + struct audio_buf_desc_s *bufdesc); +static int audio_dma_freebuffer(struct audio_lowerhalf_s *dev, + struct audio_buf_desc_s *bufdesc); +static int audio_dma_enqueuebuffer(struct audio_lowerhalf_s *dev, + struct ap_buffer_s *apb); +static int audio_dma_ioctl(struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg); +static void audio_dma_callback(struct dma_chan_s *chan, void *arg, + ssize_t len); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct audio_ops_s g_audio_dma_ops = +{ + .getcaps = audio_dma_getcaps, + .configure = audio_dma_configure, + .shutdown = audio_dma_shutdown, + .start = audio_dma_start, +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + .stop = audio_dma_stop, +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME + .pause = audio_dma_pause, + .resume = audio_dma_resume, +#endif + .allocbuffer = audio_dma_allocbuffer, + .freebuffer = audio_dma_freebuffer, + .enqueuebuffer = audio_dma_enqueuebuffer, + .ioctl = audio_dma_ioctl, + .reserve = audio_dma_reserve, + .release = audio_dma_release, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int audio_dma_getcaps(struct audio_lowerhalf_s *dev, int type, + struct audio_caps_s *caps) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + + /* Validate the structure */ + + DEBUGASSERT(caps && caps->ac_len >= sizeof(struct audio_caps_s)); + audinfo("type=%d ac_type=%d\n", type, caps->ac_type); + + /* Fill in the caller's structure based on requested info */ + + caps->ac_format.hw = 0; + caps->ac_controls.w = 0; + + switch (caps->ac_type) + { + /* Caller is querying for the types of units we support */ + + case AUDIO_TYPE_QUERY: + + /* Provide our overall capabilities. The interfacing software + * must then call us back for specific info for each capability. + */ + + caps->ac_channels = 2; /* Stereo output */ + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* We don't decode any formats! Only something above us in + * the audio stream can perform decoding on our behalf. + */ + + /* The types of audio units we implement */ + + if (audio_dma->playback) + caps->ac_controls.b[0] = AUDIO_TYPE_OUTPUT; + else + caps->ac_controls.b[0] = AUDIO_TYPE_INPUT; + caps->ac_format.hw = 1 << (AUDIO_FMT_PCM - 1); + break; + + default: + caps->ac_controls.b[0] = AUDIO_SUBFMT_END; + break; + } + + break; + + /* Provide capabilities of our OUTPUT unit */ + + case AUDIO_TYPE_OUTPUT: + case AUDIO_TYPE_INPUT: + + caps->ac_channels = 2; + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* Report the Sample rates we support */ + + caps->ac_controls.hw[0] = AUDIO_SAMP_RATE_8K | + AUDIO_SAMP_RATE_11K | + AUDIO_SAMP_RATE_16K | + AUDIO_SAMP_RATE_22K | + AUDIO_SAMP_RATE_32K | + AUDIO_SAMP_RATE_44K | + AUDIO_SAMP_RATE_48K | + AUDIO_SAMP_RATE_96K | + AUDIO_SAMP_RATE_128K | + AUDIO_SAMP_RATE_160K | + AUDIO_SAMP_RATE_172K | + AUDIO_SAMP_RATE_192K; + break; + } + + break; + } + + /* Return the length of the audio_caps_s struct for validation of + * proper Audio device type. + */ + + return caps->ac_len; +} + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_configure(struct audio_lowerhalf_s *dev, + void *session, + const struct audio_caps_s *caps) +#else +static int audio_dma_configure(struct audio_lowerhalf_s *dev, + const struct audio_caps_s *caps) +#endif +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + struct dma_config_s cfg; + int ret = -EINVAL; + + DEBUGASSERT(audio_dma && caps); + audinfo("ac_type: %d\n", caps->ac_type); + + /* Process the configure operation */ + + switch (caps->ac_type) + { + case AUDIO_TYPE_OUTPUT: + if (audio_dma->playback) + { + memset(&cfg, 0, sizeof(struct dma_config_s)); + cfg.direction = DMA_MEM_TO_DEV; + if (audio_dma->fifo_width) + cfg.dst_width = audio_dma->fifo_width; + else + cfg.dst_width = caps->ac_controls.b[2] / 8; + ret = DMA_CONFIG(audio_dma->chan, &cfg); + } + break; + case AUDIO_TYPE_INPUT: + if (!audio_dma->playback) + { + memset(&cfg, 0, sizeof(struct dma_config_s)); + cfg.direction = DMA_DEV_TO_MEM; + if (audio_dma->fifo_width) + cfg.src_width = audio_dma->fifo_width; + else + cfg.src_width = caps->ac_controls.b[2] / 8; + ret = DMA_CONFIG(audio_dma->chan, &cfg); + } + break; + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +static int audio_dma_shutdown(struct audio_lowerhalf_s *dev) +{ + /* apps enqueued buffers, but doesn't start. stop here to + * clear audio_dma->pendq. + */ + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION + audio_dma_stop(dev, NULL); +#else + audio_dma_stop(dev); +#endif +#endif + + return OK; +} + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_start(struct audio_lowerhalf_s *dev, void *session) +#else +static int audio_dma_start(struct audio_lowerhalf_s *dev) +#endif +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + + return DMA_START_CYCLIC(audio_dma->chan, audio_dma_callback, audio_dma, + audio_dma->dst_addr, audio_dma->src_addr, + audio_dma->buffer_num * audio_dma->buffer_size, + audio_dma->buffer_size); +} + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_stop(struct audio_lowerhalf_s *dev, void *session) +#else +static int audio_dma_stop(struct audio_lowerhalf_s *dev) +#endif +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + struct ap_buffer_s *apb; + + DMA_STOP(audio_dma->chan); + + while (!dq_empty(&audio_dma->pendq)) + { + apb = (struct ap_buffer_s *)dq_remfirst(&audio_dma->pendq); +#ifdef CONFIG_AUDIO_MULTI_SESSION + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK, NULL); +#else + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK); +#endif + } + +#ifdef CONFIG_AUDIO_MULTI_SESSION + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_COMPLETE, + NULL, OK, NULL); +#else + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_COMPLETE, + NULL, OK); +#endif + audio_dma->xrun = false; + return OK; +} +#endif + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_pause(struct audio_lowerhalf_s *dev, void *session) +#else +static int audio_dma_pause(struct audio_lowerhalf_s *dev) +#endif +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + + return DMA_PAUSE(audio_dma->chan); +} + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_resume(struct audio_lowerhalf_s *dev, void *session) +#else +static int audio_dma_resume(struct audio_lowerhalf_s *dev) +#endif +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + + if (dq_empty(&audio_dma->pendq)) + { + return -EINVAL; + } + + return DMA_RESUME(audio_dma->chan); +} +#endif + +static int audio_dma_allocbuffer(struct audio_lowerhalf_s *dev, + struct audio_buf_desc_s *bufdesc) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + struct ap_buffer_s *apb; + + if (bufdesc->numbytes != audio_dma->buffer_size) + { + return -EINVAL; + } + + if (audio_dma->alloc_index == audio_dma->buffer_num) + { + return -ENOMEM; + } + + if (!audio_dma->alloc_addr) + { + audio_dma->alloc_addr = kumm_memalign(32, + audio_dma->buffer_num * + audio_dma->buffer_size); + if (!audio_dma->alloc_addr) + { + return -ENOMEM; + } + + if (audio_dma->playback) + audio_dma->src_addr = up_addrenv_va_to_pa(audio_dma->alloc_addr); + else + audio_dma->dst_addr = up_addrenv_va_to_pa(audio_dma->alloc_addr); + } + + apb = kumm_zalloc(sizeof(struct ap_buffer_s)); + *bufdesc->u.pbuffer = apb; + + /* Test if the allocation was successful or not */ + + if (*bufdesc->u.pbuffer == NULL) + { + return -ENOMEM; + } + + /* Populate the buffer contents */ + + apb->i.channels = 2; + apb->crefs = 1; + apb->nmaxbytes = audio_dma->buffer_size; + apb->samp = audio_dma->alloc_addr + + audio_dma->alloc_index * + audio_dma->buffer_size; + audio_dma->alloc_index++; + nxmutex_init(&apb->lock); + + return sizeof(struct audio_buf_desc_s); +} + +static int audio_dma_freebuffer(struct audio_lowerhalf_s *dev, + struct audio_buf_desc_s *bufdesc) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + struct ap_buffer_s *apb; + + apb = bufdesc->u.buffer; + audio_dma->alloc_index--; + nxmutex_destroy(&apb->lock); + kumm_free(apb); + + if (audio_dma->alloc_index == 0) + { + kumm_free(audio_dma->alloc_addr); + audio_dma->alloc_addr = NULL; + } + + return sizeof(struct audio_buf_desc_s); +} + +static int audio_dma_enqueuebuffer(struct audio_lowerhalf_s *dev, + struct ap_buffer_s *apb) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + irqstate_t flags; + + if (audio_dma->playback) + up_clean_dcache((uintptr_t)apb->samp, + (uintptr_t)apb->samp + apb->nbytes); + + apb->flags |= AUDIO_APB_OUTPUT_ENQUEUED; + + flags = enter_critical_section(); + dq_addlast(&apb->dq_entry, &audio_dma->pendq); + leave_critical_section(flags); + + if (audio_dma->xrun) + { + audio_dma->xrun = false; + return audio_dma_resume(dev); + } + + return OK; +} + +static int audio_dma_ioctl(struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)dev; + struct ap_buffer_info_s *bufinfo; + + switch (cmd) + { + /* Report our preferred buffer size and quantity */ + + case AUDIOIOC_GETBUFFERINFO: + audinfo("AUDIOIOC_GETBUFFERINFO:\n"); + bufinfo = (struct ap_buffer_info_s *)arg; + bufinfo->buffer_size = audio_dma->buffer_size; + bufinfo->nbuffers = audio_dma->buffer_num; + + return OK; + + case AUDIOIOC_SETBUFFERINFO: + audinfo("AUDIOIOC_GETBUFFERINFO:\n"); + bufinfo = (struct ap_buffer_info_s *)arg; + audio_dma->buffer_size = bufinfo->buffer_size; + audio_dma->buffer_num = bufinfo->nbuffers; + kumm_free(audio_dma->alloc_addr); + audio_dma->alloc_addr = NULL; + audio_dma->alloc_index = 0; + + return OK; + } + + return -ENOTTY; +} + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_reserve(struct audio_lowerhalf_s *dev, void **session) +#else +static int audio_dma_reserve(struct audio_lowerhalf_s *dev) +#endif +{ + return OK; +} + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_dma_release(struct audio_lowerhalf_s *dev, void *session) +#else +static int audio_dma_release(struct audio_lowerhalf_s *dev) +#endif +{ + return OK; +} + +static void audio_dma_callback(struct dma_chan_s *chan, + void *arg, ssize_t len) +{ + struct audio_dma_s *audio_dma = (struct audio_dma_s *)arg; + struct ap_buffer_s *apb; + bool final = false; + + apb = (struct ap_buffer_s *)dq_remfirst(&audio_dma->pendq); + if (!apb) + { + /* xrun */ + + DMA_PAUSE(audio_dma->chan); + audio_dma->xrun = true; + return; + } + + if (!audio_dma->playback) + up_invalidate_dcache((uintptr_t)apb->samp, + (uintptr_t)apb->samp + apb->nbytes); + + if ((apb->flags & AUDIO_APB_FINAL) != 0) + final = true; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK, NULL); +#else + audio_dma->dev.upper(audio_dma->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK); +#endif + if (final) + { +#ifdef CONFIG_AUDIO_MULTI_SESSION + audio_dma_stop(&audio_dma->dev, NULL); +#else + audio_dma_stop(&audio_dma->dev); +#endif + } + else if (dq_empty(&audio_dma->pendq)) + { + /* xrun */ + + DMA_PAUSE(audio_dma->chan); + audio_dma->xrun = true; + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct audio_lowerhalf_s *audio_dma_initialize(struct dma_dev_s *dma_dev, + uint8_t chan_num, + bool playback, + uint8_t fifo_width, + uintptr_t fifo_addr) +{ + struct audio_dma_s *audio_dma; + + if (!dma_dev) + { + return NULL; + } + + audio_dma = kmm_zalloc(sizeof(struct audio_dma_s)); + if (!audio_dma) + { + return NULL; + } + + audio_dma->chan = DMA_GET_CHAN(dma_dev, chan_num); + if (!audio_dma->chan) + { + kmm_free(audio_dma); + return NULL; + } + + audio_dma->playback = playback; + audio_dma->fifo_width = fifo_width; + + if (audio_dma->playback) + audio_dma->dst_addr = up_addrenv_va_to_pa((void *)fifo_addr); + else + audio_dma->src_addr = up_addrenv_va_to_pa((void *)fifo_addr); + + audio_dma->buffer_size = CONFIG_AUDIO_BUFFER_NUMBYTES; + audio_dma->buffer_num = CONFIG_AUDIO_NUM_BUFFERS; + dq_init(&audio_dma->pendq); + + audio_dma->dev.ops = &g_audio_dma_ops; + return &audio_dma->dev; +} diff --git a/include/nuttx/audio/audio_dma.h b/include/nuttx/audio/audio_dma.h new file mode 100644 index 0000000000..4ad79431ab --- /dev/null +++ b/include/nuttx/audio/audio_dma.h @@ -0,0 +1,41 @@ +/**************************************************************************** + * include/nuttx/audio/audio_dma.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_AUDIO_AUDIO_DMA_H +#define __INCLUDE_NUTTX_AUDIO_AUDIO_DMA_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct audio_lowerhalf_s *audio_dma_initialize(struct dma_dev_s *dma_dev, + uint8_t chan_num, + bool playback, + uint8_t fifo_width, + uintptr_t fifo_addr); +#endif