/**************************************************************************** * audio/audio_comp.c * * SPDX-License-Identifier: Apache-2.0 * * 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 /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the internal state of the audio composite */ struct audio_comp_priv_s { /* This is is our appearance to the outside world. This *MUST* be the * first element of the structure so that we can freely cast between * types struct audio_lowerhalf and struct audio_comp_dev_s. */ struct audio_lowerhalf_s export; /* This is the contained, low-level audio device array and count. */ FAR struct audio_lowerhalf_s **lower; int count; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int audio_comp_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps); #else static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps); #endif static int audio_comp_shutdown(FAR struct audio_lowerhalf_s *dev); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_start(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_start(FAR struct audio_lowerhalf_s *dev); #endif #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev); #endif #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session); static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev); static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev); #endif #endif static int audio_comp_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc); static int audio_comp_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc); static int audio_comp_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); static int audio_comp_cancelbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); static int audio_comp_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg); static int audio_comp_read(FAR struct audio_lowerhalf_s *dev, FAR char *buffer, size_t buflen); static int audio_comp_write(FAR struct audio_lowerhalf_s *dev, FAR const char *buffer, size_t buflen); #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session); #else static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev); #endif #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_release(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int audio_comp_release(FAR struct audio_lowerhalf_s *dev); #endif #ifdef CONFIG_AUDIO_MULTI_SESSION static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status, FAR void *session); #else static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status); #endif /**************************************************************************** * Private Data ****************************************************************************/ static const struct audio_ops_s g_audio_comp_ops = { audio_comp_getcaps, /* getcaps */ audio_comp_configure, /* configure */ audio_comp_shutdown, /* shutdown */ audio_comp_start, /* start */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP audio_comp_stop, /* stop */ #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME audio_comp_pause, /* pause */ audio_comp_resume, /* resume */ #endif audio_comp_allocbuffer, /* allocbuffer */ audio_comp_freebuffer, /* freebuffer */ audio_comp_enqueuebuffer, /* enqueue_buffer */ audio_comp_cancelbuffer, /* cancel_buffer */ audio_comp_ioctl, /* ioctl */ audio_comp_read, /* read */ audio_comp_write, /* write */ audio_comp_reserve, /* reserve */ audio_comp_release /* release */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: audio_comp_getcaps * * Description: Get the audio device capabilities * ****************************************************************************/ static int audio_comp_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int max = UINT8_MAX; int min = 0; int i; caps->ac_channels = UINT8_MAX; caps->ac_format.hw = UINT16_MAX; caps->ac_controls.w = UINT32_MAX; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->getcaps) { FAR struct audio_caps_s dup = *caps; int tmp = lower[i]->ops->getcaps(lower[i], type, &dup); if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } if (max > (dup.ac_channels & 0x0f)) { max = dup.ac_channels & 0x0f; } if (min < (dup.ac_channels & 0xf0)) { min = dup.ac_channels & 0xf0; } caps->ac_format.hw &= dup.ac_format.hw; caps->ac_controls.w &= dup.ac_controls.w; } } caps->ac_channels = max | min; return ret; } /**************************************************************************** * Name: audio_comp_configure * * Description: * Configure the audio device for the specified mode of operation. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps) #else static int audio_comp_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->configure) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->configure(lower[i], sess[i], caps); #else int tmp = lower[i]->ops->configure(lower[i], caps); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_shutdown * * Description: * Shutdown the driver and put it in the lowest power state possible. * ****************************************************************************/ static int audio_comp_shutdown(FAR struct audio_lowerhalf_s *dev) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->shutdown) { int tmp = lower[i]->ops->shutdown(lower[i]); if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } /**************************************************************************** * Name: audio_comp_start * * Description: * Start the configured operation (audio streaming, volume enabled, etc.). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_start(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_start(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->start) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->start(lower[i], sess[i]); #else int tmp = lower[i]->ops->start(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->stop) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->stop(lower[i], sess[i]); #else lower[i]->ops->stop(lower[i]); #endif } } break; } } return ret; } /**************************************************************************** * Name: audio_comp_stop * * Description: Stop the configured operation (audio streaming, volume * disabled, etc.). * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_stop(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->stop) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->stop(lower[i], sess[i]); #else int tmp = lower[i]->ops->stop(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } #endif /**************************************************************************** * Name: audio_comp_pause * * Description: Pauses the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_pause(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->pause) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->pause(lower[i], sess[i]); #else int tmp = lower[i]->ops->pause(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret == -ENOTTY || ret >= 0) { ret = tmp; } } } return ret; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: audio_comp_resume * * Description: Resumes the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_resume(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->resume) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->resume(lower[i], sess[i]); #else int tmp = lower[i]->ops->resume(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->pause) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->pause(lower[i], sess[i]); #else lower[i]->ops->pause(lower[i]); #endif } } break; } } return ret; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: audio_comp_allocbuffer * * Description: Allocate an audio pipeline buffer. * ****************************************************************************/ static int audio_comp_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->allocbuffer) { ret = lower[i]->ops->allocbuffer(lower[i], bufdesc); if (ret != -ENOTTY) { break; } } } if (ret == -ENOTTY) { ret = apb_alloc(bufdesc); } return ret; } /**************************************************************************** * Name: audio_comp_freebuffer * * Description: Free an audio pipeline buffer. * ****************************************************************************/ static int audio_comp_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *bufdesc) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->freebuffer) { ret = lower[i]->ops->freebuffer(lower[i], bufdesc); if (ret != -ENOTTY) { break; } } } if (ret == -ENOTTY) { apb_free(bufdesc->u.buffer); ret = sizeof(*bufdesc); } return ret; } /**************************************************************************** * Name: audio_comp_enqueuebuffer * * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. * ****************************************************************************/ static int audio_comp_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->enqueuebuffer) { ret = lower[i]->ops->enqueuebuffer(lower[i], apb); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_cancelbuffer * * Description: Called when an enqueued buffer is being cancelled. * ****************************************************************************/ static int audio_comp_cancelbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->cancelbuffer) { ret = lower[i]->ops->cancelbuffer(lower[i], apb); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_ioctl * * Description: Perform a device ioctl * ****************************************************************************/ static int audio_comp_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->ioctl) { int tmp = lower[i]->ops->ioctl(lower[i], cmd, arg); if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret < 0) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_read * * Description: Lower-half logic for read commands. * ****************************************************************************/ static int audio_comp_read(FAR struct audio_lowerhalf_s *dev, FAR char *buffer, size_t buflen) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->read) { ret = lower[i]->ops->read(lower[i], buffer, buflen); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_write * * Description: Lower-half logic for write commands. * ****************************************************************************/ static int audio_comp_write(FAR struct audio_lowerhalf_s *dev, FAR const char *buffer, size_t buflen) { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; int ret = -ENOTTY; int i; for (i = 0; i < priv->count; i++) { if (lower[i]->ops->write) { ret = lower[i]->ops->write(lower[i], buffer, buflen); if (ret != -ENOTTY) { break; } } } return ret; } /**************************************************************************** * Name: audio_comp_reserve * * Description: Reserves a session. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session) #else static int audio_comp_reserve(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess; #endif int ret = OK; int i; #ifdef CONFIG_AUDIO_MULTI_SESSION sess = kmm_calloc(priv->count, sizeof(*sess)); if (sess == NULL) { return -ENOMEM; } #endif for (i = 0; i < priv->count; i++) { if (lower[i]->ops->reserve) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->reserve(lower[i], &sess[i]); #else int tmp = lower[i]->ops->reserve(lower[i]); #endif if (tmp == -ENOTTY) { continue; } ret = tmp; if (ret >= 0) { continue; } while (--i >= 0) { if (lower[i]->ops->release) { #ifdef CONFIG_AUDIO_MULTI_SESSION lower[i]->ops->release(lower[i], sess[i]); #else lower[i]->ops->release(lower[i]); #endif } } #ifdef CONFIG_AUDIO_MULTI_SESSION kmm_free(sess); sess = NULL; #endif break; } } #ifdef CONFIG_AUDIO_MULTI_SESSION *session = sess; #endif return ret; } /**************************************************************************** * Name: audio_comp_release * * Description: Releases the session. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int audio_comp_release(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int audio_comp_release(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct audio_comp_priv_s *priv = (FAR struct audio_comp_priv_s *)dev; FAR struct audio_lowerhalf_s **lower = priv->lower; #ifdef CONFIG_AUDIO_MULTI_SESSION FAR void **sess = session; #endif int ret = OK; int i; for (i = priv->count - 1; i >= 0; i--) { if (lower[i]->ops->release) { #ifdef CONFIG_AUDIO_MULTI_SESSION int tmp = lower[i]->ops->release(lower[i], sess[i]); #else int tmp = lower[i]->ops->release(lower[i]); #endif if (tmp == -ENOTTY) { continue; } if (tmp < 0 || ret >= 0) { ret = tmp; } } } #ifdef CONFIG_AUDIO_MULTI_SESSION kmm_free(sess); #endif return ret; } /**************************************************************************** * Name: audio_comp_callback * * Description: * Lower-to-upper level callback for buffer dequeueing. * * Input Parameters: * arg - The value of the 'priv' field from audio_lowerhalf_s. * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status, FAR void *session) #else static void audio_comp_callback(FAR void *arg, uint16_t reason, FAR struct ap_buffer_s *apb, uint16_t status) #endif { FAR struct audio_comp_priv_s *priv = arg; #ifdef CONFIG_AUDIO_MULTI_SESSION priv->export.upper(priv->export.priv, reason, apb, status, session); #else priv->export.upper(priv->export.priv, reason, apb, status); #endif } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: audio_comp_initialize * * Description: * Initialize the composite audio device. * * Input Parameters: * name - The name of the audio device. * ... - The list of the lower half audio driver. * * Returned Value: * struct audio_lowerhalf_s* on success; NULL on failure. * * Note * The variable argument list must be NULL terminated. * ****************************************************************************/ FAR struct audio_lowerhalf_s *audio_comp_initialize(FAR const char *name, ...) { FAR struct audio_comp_priv_s *priv; va_list ap; int ret = -ENOMEM; int i; priv = kmm_zalloc(sizeof(struct audio_comp_priv_s)); if (priv == NULL) { return NULL; } priv->export.ops = &g_audio_comp_ops; va_start(ap, name); while (va_arg(ap, FAR struct audio_lowerhalf_s *)) { priv->count++; } va_end(ap); priv->lower = kmm_calloc(priv->count, sizeof(FAR struct audio_lowerhalf_s *)); if (priv->lower == NULL) { goto free_priv; } va_start(ap, name); for (i = 0; i < priv->count; i++) { FAR struct audio_lowerhalf_s *tmp; tmp = va_arg(ap, FAR struct audio_lowerhalf_s *); tmp->upper = audio_comp_callback; tmp->priv = priv; priv->lower[i] = tmp; } va_end(ap); if (name != NULL) { ret = audio_register(name, &priv->export); if (ret < 0) { goto free_lower; } } return &priv->export; free_lower: kmm_free(priv->lower); free_priv: kmm_free(priv); return NULL; }