/**************************************************************************** * 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 */ 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((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; }