d499ac9d58
Signed-off-by: Petro Karashchenko <petro.karashchenko@gmail.com>
599 lines
17 KiB
C
599 lines
17 KiB
C
/****************************************************************************
|
|
* 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 <nuttx/config.h>
|
|
#include <nuttx/audio/audio_dma.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/queue.h>
|
|
|
|
#include <debug.h>
|
|
|
|
/****************************************************************************
|
|
* 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 */
|
|
|
|
if (caps->ac_subtype == 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);
|
|
}
|
|
|
|
caps->ac_controls.b[0] = AUDIO_SUBFMT_END;
|
|
break;
|
|
|
|
/* Provide capabilities of our OUTPUT unit */
|
|
|
|
case AUDIO_TYPE_OUTPUT:
|
|
case AUDIO_TYPE_INPUT:
|
|
|
|
caps->ac_channels = 2;
|
|
|
|
if (caps->ac_subtype == AUDIO_TYPE_QUERY)
|
|
{
|
|
/* Report the Sample rates we support */
|
|
|
|
caps->ac_controls.hw[0] = AUDIO_SAMP_RATE_DEF_ALL;
|
|
}
|
|
|
|
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((FAR void *)fifo_addr);
|
|
else
|
|
audio_dma->src_addr = up_addrenv_va_to_pa((FAR 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;
|
|
}
|