| /**************************************************************************** |
| * drivers/virtio/virtio-snd.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 <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <sys/param.h> |
| |
| #include <nuttx/audio/audio.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/virtio/virtio.h> |
| #include <nuttx/semaphore.h> |
| |
| #include "virtio-snd.h" |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* Map for converting VirtIO frame rate to nuttx frame rate */ |
| |
| struct virtio_snd_rate_map_s |
| { |
| unsigned int nrate; |
| unsigned int sps; |
| }; |
| |
| /* Map for nuttx bps to virtio pcm subformat */ |
| |
| struct virtio_snd_format_map_s |
| { |
| uint8_t nformat; |
| unsigned int bps; |
| }; |
| |
| /* Buffer for pcm data tx/rx transfer */ |
| |
| struct virtio_snd_buffer_s |
| { |
| struct ap_buffer_s apb; |
| struct virtio_snd_pcm_xfer xfer; |
| struct virtio_snd_pcm_status status; |
| FAR struct audio_lowerhalf_s *dev; |
| }; |
| |
| /* Include struct audio_lowerhalf_s, use to get struct virtio_snd_s */ |
| |
| struct virtio_snd_dev_s |
| { |
| struct audio_lowerhalf_s dev; |
| uint32_t period_bytes; |
| uint32_t index; |
| bool running; |
| FAR void *priv; |
| }; |
| |
| /* Virtio snd card struct for virtio driver */ |
| |
| struct virtio_snd_s |
| { |
| FAR struct virtio_device *vdev; |
| FAR struct virtio_snd_dev_s *dev; |
| FAR struct virtio_snd_pcm_info *info; |
| struct virtio_snd_config config; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static void virtio_snd_pcm_notify_cb(FAR struct virtqueue *vqueue); |
| static void virtio_snd_ctl_notify_cb(FAR struct virtqueue *vqueue); |
| static void virtio_snd_event_notify_cb(FAR struct virtqueue *vqueue); |
| |
| static unsigned int virtio_snd_get_period_bytes(unsigned int rate, |
| unsigned int ch, |
| unsigned int bps, |
| unsigned int period_time); |
| static unsigned int |
| virtio_snd_get_support_rates(FAR const struct virtio_snd_pcm_info *info); |
| static void |
| virtio_snd_get_support_formats(FAR const struct virtio_snd_pcm_info *info, |
| FAR struct audio_caps_s *caps); |
| |
| static int virtio_snd_send_pcm(FAR struct virtio_snd_dev_s *sdev, |
| FAR struct virtio_snd_buffer_s *buf); |
| static int virtio_snd_send_ctl(FAR struct virtio_snd_s *priv, |
| FAR struct virtqueue_buf *vb, |
| int readable, |
| int writable); |
| static int virtio_snd_query_info(FAR struct virtio_snd_s *priv, |
| int cmd, |
| size_t size, |
| size_t count, |
| FAR struct virtio_snd_pcm_info *info); |
| static int virtio_snd_set_params(FAR struct virtio_snd_dev_s *sdev, |
| unsigned int ch, |
| unsigned int rate, |
| unsigned int bps); |
| static int virtio_snd_send_cmd(FAR struct virtio_snd_dev_s *sdev, |
| int cmd); |
| |
| static int virtio_snd_getcaps(FAR struct audio_lowerhalf_s *dev, |
| int type, |
| FAR struct audio_caps_s *caps); |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session, |
| FAR const struct audio_caps_s *caps); |
| static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session); |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session); |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session); |
| static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session); |
| #endif |
| static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev, |
| FAR void **session); |
| static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session); |
| #else |
| static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, |
| FAR const struct audio_caps_s *caps); |
| static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev); |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev); |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev); |
| static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev); |
| #endif |
| static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev); |
| static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev); |
| #endif |
| static int virtio_snd_allocbuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct audio_buf_desc_s *apb); |
| static int virtio_snd_freebuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct audio_buf_desc_s *apb); |
| static int virtio_snd_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct ap_buffer_s *apb); |
| static int virtio_snd_ioctl(FAR struct audio_lowerhalf_s *dev, |
| int cmd, |
| unsigned long arg); |
| static int virtio_snd_shutdown(FAR struct audio_lowerhalf_s *dev); |
| |
| static int virtio_snd_probe(FAR struct virtio_device *vdev); |
| static void virtio_snd_remove(FAR struct virtio_device *vdev); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const struct virtio_snd_rate_map_s g_rate_map[] = |
| { |
| [VIRTIO_SND_PCM_RATE_8000] = |
| { |
| AUDIO_SAMP_RATE_8K, 8000 |
| }, |
| [VIRTIO_SND_PCM_RATE_11025] = |
| { |
| AUDIO_SAMP_RATE_11K, 11025 |
| }, |
| [VIRTIO_SND_PCM_RATE_16000] = |
| { |
| AUDIO_SAMP_RATE_16K, 16000 |
| }, |
| [VIRTIO_SND_PCM_RATE_22050] = |
| { |
| AUDIO_SAMP_RATE_22K, 22050 |
| }, |
| [VIRTIO_SND_PCM_RATE_32000] = |
| { |
| AUDIO_SAMP_RATE_32K, 32000 |
| }, |
| [VIRTIO_SND_PCM_RATE_44100] = |
| { |
| AUDIO_SAMP_RATE_44K, 44100 |
| }, |
| [VIRTIO_SND_PCM_RATE_48000] = |
| { |
| AUDIO_SAMP_RATE_48K, 48000 |
| }, |
| [VIRTIO_SND_PCM_RATE_96000] = |
| { |
| AUDIO_SAMP_RATE_96K, 96000 |
| }, |
| [VIRTIO_SND_PCM_RATE_192000] = |
| { |
| AUDIO_SAMP_RATE_192K, 192000 |
| } |
| }; |
| |
| static const struct virtio_snd_format_map_s g_format_map[] = |
| { |
| [VIRTIO_SND_PCM_FMT_S8] = |
| { |
| AUDIO_SUBFMT_PCM_S8, 8 |
| }, |
| [VIRTIO_SND_PCM_FMT_S16] = |
| { |
| AUDIO_SUBFMT_PCM_S16_LE, 16 |
| }, |
| [VIRTIO_SND_PCM_FMT_S32] = |
| { |
| AUDIO_SUBFMT_PCM_S32_LE, 32 |
| } |
| }; |
| |
| static struct virtio_driver g_virtio_snd_driver = |
| { |
| LIST_INITIAL_VALUE(g_virtio_snd_driver.node), /* node */ |
| VIRTIO_ID_SOUND, /* device id */ |
| virtio_snd_probe, /* probe */ |
| virtio_snd_remove, /* remove */ |
| }; |
| |
| static const struct audio_ops_s g_virtio_snd_ops = |
| { |
| virtio_snd_getcaps, /* getcaps */ |
| virtio_snd_configure, /* configure */ |
| virtio_snd_shutdown, /* shutdown */ |
| virtio_snd_start, /* start */ |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| virtio_snd_stop, /* stop */ |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| virtio_snd_pause, /* pause */ |
| virtio_snd_resume, /* resume */ |
| #endif |
| virtio_snd_allocbuffer, /* allocbuffer */ |
| virtio_snd_freebuffer, /* freebuffer */ |
| virtio_snd_enqueuebuffer, /* enqueuebuffer */ |
| NULL, /* cancelbuffer */ |
| virtio_snd_ioctl, /* ioctl */ |
| NULL, /* read */ |
| NULL, /* write */ |
| virtio_snd_reserve, /* reserve */ |
| virtio_snd_release /* release */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: virtio_snd_get_period_bytes |
| ****************************************************************************/ |
| |
| static unsigned int virtio_snd_get_period_bytes(unsigned int rate, |
| unsigned int ch, |
| unsigned int bps, |
| unsigned int period_time) |
| { |
| return rate * ch * (bps / 8) * period_time / 1000; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_get_support_rates |
| ****************************************************************************/ |
| |
| static unsigned int |
| virtio_snd_get_support_rates(FAR const struct virtio_snd_pcm_info *info) |
| { |
| unsigned int rates = 0; |
| int i; |
| |
| for (i = VIRTIO_SND_PCM_RATE_5512; i < VIRTIO_SND_PCM_RATE_384000; i++) |
| { |
| if (info->rates & (1 << i)) |
| { |
| rates |= g_rate_map[i].nrate; |
| } |
| } |
| |
| return rates; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_get_support_formats |
| ****************************************************************************/ |
| |
| static void |
| virtio_snd_get_support_formats(FAR const struct virtio_snd_pcm_info *info, |
| FAR struct audio_caps_s *caps) |
| { |
| size_t subformats = 0; |
| size_t i; |
| |
| for (i = 0; i < nitems(g_format_map); i++) |
| { |
| if (info->formats & (1 << i)) |
| { |
| caps->ac_controls.b[subformats++] = g_format_map[i].nformat; |
| if (subformats >= nitems(caps->ac_controls.b)) |
| break; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_pcm_notify_cb |
| ****************************************************************************/ |
| |
| static void virtio_snd_pcm_notify_cb(FAR struct virtqueue *vq) |
| { |
| for (; ; ) |
| { |
| FAR struct virtio_snd_buffer_s *buf; |
| buf = virtqueue_get_buffer(vq, NULL, NULL); |
| if (buf == NULL) |
| { |
| break; |
| } |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| buf->dev->upper(buf->dev->priv, AUDIO_CALLBACK_DEQUEUE, |
| &buf->apb, OK, NULL); |
| #else |
| buf->dev->upper(buf->dev->priv, AUDIO_CALLBACK_DEQUEUE, |
| &buf->apb, OK); |
| #endif |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_ctl_notify_cb |
| ****************************************************************************/ |
| |
| static void virtio_snd_ctl_notify_cb(FAR struct virtqueue *vq) |
| { |
| FAR sem_t *ctl_sem; |
| |
| ctl_sem = virtqueue_get_buffer(vq, NULL, NULL); |
| nxsem_post(ctl_sem); |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_event_notify_cb |
| ****************************************************************************/ |
| |
| static void virtio_snd_event_notify_cb(FAR struct virtqueue *vqueue) |
| { |
| vrtinfo("recvive jack/pcm event\n"); |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_send_pcm |
| ****************************************************************************/ |
| |
| static int virtio_snd_send_pcm(FAR struct virtio_snd_dev_s *sdev, |
| FAR struct virtio_snd_buffer_s *buf) |
| { |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_snd_pcm_info *info = &priv->info[sdev->index]; |
| int idx = info->direction == VIRTIO_SND_D_INPUT ? |
| VIRTIO_SND_VQ_RX : VIRTIO_SND_VQ_TX; |
| FAR struct virtqueue *vq = priv->vdev->vrings_info[idx].vq; |
| struct virtqueue_buf vb[3]; |
| |
| vb[0].buf = &buf->xfer; |
| vb[0].len = sizeof(buf->xfer); |
| vb[1].buf = buf->apb.samp; |
| vb[1].len = sdev->period_bytes; |
| vb[2].buf = &buf->status; |
| vb[2].len = sizeof(buf->status); |
| |
| if (idx == VIRTIO_SND_VQ_RX) |
| { |
| virtqueue_add_buffer(vq, vb, 1, 2, buf); |
| } |
| else |
| { |
| virtqueue_add_buffer(vq, vb, 2, 1, buf); |
| } |
| |
| virtqueue_kick(vq); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_send_ctl |
| ****************************************************************************/ |
| |
| static int virtio_snd_send_ctl(FAR struct virtio_snd_s *priv, |
| FAR struct virtqueue_buf *vb, |
| int readable, |
| int writable) |
| { |
| FAR struct virtqueue *vq = |
| priv->vdev->vrings_info[VIRTIO_SND_VQ_CONTROL].vq; |
| sem_t ctl_sem; |
| int ret; |
| |
| nxsem_init(&ctl_sem, 0, 0); |
| |
| virtqueue_add_buffer(vq, vb, readable, writable, &ctl_sem); |
| virtqueue_kick(vq); |
| |
| ret = nxsem_wait_uninterruptible(&ctl_sem); |
| nxsem_destroy(&ctl_sem); |
| if (ret < 0) |
| { |
| vrterr("nxsem wait error:%d\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_query_info |
| ****************************************************************************/ |
| |
| static int virtio_snd_query_info(FAR struct virtio_snd_s *priv, |
| int cmd, |
| size_t size, |
| size_t count, |
| FAR struct virtio_snd_pcm_info *info) |
| { |
| FAR struct virtio_snd_query_info *req; |
| FAR struct virtio_snd_hdr *resp; |
| struct virtqueue_buf vb[3]; |
| int ret; |
| |
| req = virtio_zalloc_buf(priv->vdev, sizeof(*req), 16); |
| if (req == NULL) |
| { |
| vrterr("virtio audio driver cmd request alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| req->hdr.code = cmd; |
| req->count = count; |
| req->size = size; |
| |
| resp = virtio_alloc_buf(priv->vdev, sizeof(*resp), 16); |
| if (resp == NULL) |
| { |
| vrterr("virtio audio driver cmd response alloc failed\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| resp->code = VIRTIO_SND_S_IO_ERR; |
| |
| vb[0].buf = req; |
| vb[0].len = sizeof(*req); |
| vb[1].buf = resp; |
| vb[1].len = sizeof(*resp); |
| vb[2].buf = info; |
| vb[2].len = count * size; |
| |
| ret = virtio_snd_send_ctl(priv, vb, 1, 2); |
| if (ret < 0) |
| { |
| vrterr("send msg error:%d\n", ret); |
| goto out; |
| } |
| |
| ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; |
| vrtinfo("send cmd:0x%x and resp:0x%"PRIu32"\n", cmd, resp->code); |
| |
| out: |
| virtio_free_buf(priv->vdev, req); |
| virtio_free_buf(priv->vdev, resp); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_dev_set_params |
| ****************************************************************************/ |
| |
| static int virtio_snd_set_params(FAR struct virtio_snd_dev_s *sdev, |
| unsigned int ch, |
| unsigned int rate, |
| unsigned int bps) |
| { |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_snd_pcm_set_params *req; |
| FAR struct virtio_snd_hdr *resp; |
| struct virtqueue_buf vb[2]; |
| size_t i; |
| int ret; |
| |
| req = virtio_zalloc_buf(priv->vdev, sizeof(*req), 16); |
| if (req == NULL) |
| { |
| vrterr("zalloc for request error\n"); |
| return -ENOMEM; |
| } |
| |
| req->hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS; |
| req->hdr.stream_id = sdev->index; |
| req->channels = ch; |
| |
| req->rate = VIRTIO_SND_PCM_RATE_44100; |
| for (i = 0; i < nitems(g_rate_map); i++) |
| { |
| if (rate == g_rate_map[i].sps) |
| { |
| req->rate = i; |
| break; |
| } |
| } |
| |
| req->format = VIRTIO_SND_PCM_FMT_S16; |
| for (i = 0; i < nitems(g_format_map); i++) |
| { |
| if (bps == g_format_map[i].bps) |
| { |
| req->format = i; |
| break; |
| } |
| } |
| |
| req->period_bytes = sdev->period_bytes; |
| req->buffer_bytes = req->period_bytes * |
| CONFIG_DRIVERS_VIRTIO_SND_BUFFER_COUNT; |
| |
| resp = virtio_alloc_buf(priv->vdev, sizeof(*resp), 16); |
| if (resp == NULL) |
| { |
| vrterr("zalloc for request error\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| resp->code = VIRTIO_SND_S_IO_ERR; |
| |
| vb[0].buf = req; |
| vb[0].len = sizeof(*req); |
| vb[1].buf = resp; |
| vb[1].len = sizeof(*resp); |
| |
| ret = virtio_snd_send_ctl(priv, vb, 1, 1); |
| if (ret < 0) |
| { |
| vrterr("send msg error:%d\n", ret); |
| goto out; |
| } |
| |
| ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; |
| |
| vrtinfo("send cmd:0x%x and resp:0x%"PRIu32"\n", |
| VIRTIO_SND_R_PCM_SET_PARAMS, resp->code); |
| |
| out: |
| virtio_free_buf(priv->vdev, req); |
| virtio_free_buf(priv->vdev, resp); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_send_cmd |
| ****************************************************************************/ |
| |
| static int virtio_snd_send_cmd(FAR struct virtio_snd_dev_s *sdev, |
| int cmd) |
| { |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_device *vdev = priv->vdev; |
| FAR struct virtio_snd_pcm_hdr *req; |
| FAR struct virtio_snd_hdr *resp; |
| struct virtqueue_buf vb[2]; |
| int ret; |
| |
| req = virtio_alloc_buf(vdev, sizeof(*req), 16); |
| if (req == NULL) |
| { |
| vrterr("zalloc for request error\n"); |
| return -ENOMEM; |
| } |
| |
| req->hdr.code = cmd; |
| req->stream_id = sdev->index; |
| |
| resp = virtio_alloc_buf(vdev, sizeof(*resp), 16); |
| if (resp == NULL) |
| { |
| vrterr("zalloc for request error\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| resp->code = VIRTIO_SND_S_IO_ERR; |
| |
| vb[0].buf = req; |
| vb[0].len = sizeof(*req); |
| vb[1].buf = resp; |
| vb[1].len = sizeof(*resp); |
| |
| ret = virtio_snd_send_ctl(priv, vb, 1, 1); |
| if (ret < 0) |
| { |
| vrterr("send msg error:%d\n", ret); |
| goto out; |
| } |
| |
| ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; |
| if (ret < 0) |
| { |
| vrterr("check response error:%d\n", ret); |
| } |
| |
| vrtinfo("send cmd:0x%x and resp:0x%"PRIu32"\n", cmd, resp->code); |
| |
| out: |
| virtio_free_buf(vdev, req); |
| virtio_free_buf(vdev, resp); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_getcaps |
| ****************************************************************************/ |
| |
| static int virtio_snd_getcaps(FAR struct audio_lowerhalf_s *dev, |
| int type, |
| FAR struct audio_caps_s *caps) |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_snd_pcm_info *info = &priv->info[sdev->index]; |
| |
| DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s)); |
| |
| caps->ac_format.hw = 0; |
| caps->ac_controls.w = 0; |
| |
| switch (caps->ac_type) |
| { |
| case AUDIO_TYPE_QUERY: |
| switch (caps->ac_subtype) |
| { |
| case AUDIO_TYPE_QUERY: |
| caps->ac_controls.b[0] = |
| info->direction == VIRTIO_SND_D_INPUT ? |
| AUDIO_TYPE_INPUT : AUDIO_TYPE_OUTPUT; |
| caps->ac_format.hw = 1 << (AUDIO_FMT_PCM - 1); |
| break; |
| |
| case AUDIO_FMT_PCM: |
| virtio_snd_get_support_formats(info, caps); |
| break; |
| |
| default: |
| caps->ac_controls.b[0] = AUDIO_SUBFMT_END; |
| break; |
| } |
| break; |
| |
| case AUDIO_TYPE_OUTPUT: |
| case AUDIO_TYPE_INPUT: |
| { |
| caps->ac_channels = (info->channels_min << 4) | |
| (info->channels_max & 0x0f); |
| switch (caps->ac_subtype) |
| { |
| case AUDIO_TYPE_QUERY: |
| caps->ac_controls.b[0] = virtio_snd_get_support_rates(info); |
| break; |
| |
| default: |
| break; |
| } |
| break; |
| } |
| |
| default: |
| caps->ac_subtype = 0; |
| caps->ac_channels = 0; |
| break; |
| } |
| |
| return caps->ac_len; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_configure |
| * |
| * Description: |
| * Configure the audio device for the specified mode of operation. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session, |
| FAR const struct audio_caps_s *caps) |
| #else |
| static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, |
| FAR const struct audio_caps_s *caps) |
| #endif |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| int ret = -ENOTTY; |
| uint32_t rate; |
| uint8_t bps; |
| uint8_t ch; |
| |
| switch (caps->ac_type) |
| { |
| case AUDIO_TYPE_OUTPUT: |
| case AUDIO_TYPE_INPUT: |
| vrtinfo("Number of channels: %"PRIu8"\n", caps->ac_channels); |
| vrtinfo("Sample rate: %"PRIu16"\n", caps->ac_controls.hw[0]); |
| vrtinfo("Sample width: %"PRIu8"\n", caps->ac_controls.b[2]); |
| vrtinfo("channel map: 0x%x\n", caps->ac_chmap); |
| rate = caps->ac_controls.hw[0] | (caps->ac_controls.b[3] << 16); |
| bps = caps->ac_controls.b[2]; |
| ch = caps->ac_channels; |
| sdev->period_bytes = |
| virtio_snd_get_period_bytes(rate, ch, bps, |
| CONFIG_DRIVERS_VIRTIO_SOUND_PERIOD_TIME); |
| vrtinfo("period_bytes:%"PRIu32"\n", sdev->period_bytes); |
| ret = virtio_snd_set_params(sdev, ch, rate, bps); |
| if (ret < 0) |
| { |
| break; |
| } |
| |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_PREPARE); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_start |
| * |
| * Description: |
| * Start the configured operation (audio streaming, volume enabled, etc.). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session) |
| #else |
| static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| int ret = OK; |
| |
| if (!sdev->running) |
| { |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| sdev->running = true; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_stop |
| * |
| * Description: Stop the configured operation (audio streaming, volume |
| * disabled, etc.). |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session) |
| #else |
| static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| int ret = OK; |
| |
| if (sdev->running) |
| { |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| sdev->running = false; |
| } |
| |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_RELEASE); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); |
| #else |
| dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); |
| #endif |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: virtio_snd_pause |
| * |
| * Description: Pauses the playback. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session) |
| #else |
| static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| int ret = OK; |
| |
| if (sdev->running) |
| { |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| sdev->running = false; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: virtio_snd_resume |
| * |
| * Description: Resumes the playback. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session) |
| #else |
| static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| int ret = OK; |
| |
| if (!sdev->running) |
| { |
| ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| sdev->running = true; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static int virtio_snd_allocbuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct audio_buf_desc_s *desc) |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_snd_buffer_s *buf; |
| |
| DEBUGASSERT(desc->u.pbuffer != NULL); |
| |
| buf = virtio_zalloc_buf(priv->vdev, sizeof(*buf) + desc->numbytes, 16); |
| if (buf == NULL) |
| { |
| vrterr("failed to allocate apb buffer\n"); |
| return -ENOMEM; |
| } |
| |
| *desc->u.pbuffer = &buf->apb; |
| |
| buf->apb.crefs = 1; |
| buf->apb.nmaxbytes = desc->numbytes; |
| buf->apb.samp = (FAR uint8_t *)(buf + 1); |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| buf->apb.session = desc->session; |
| #endif |
| buf->xfer.stream_id = sdev->index; |
| buf->status.status = VIRTIO_SND_S_IO_ERR; |
| buf->dev = dev; |
| nxmutex_init(&buf->apb.lock); |
| |
| return sizeof(struct audio_buf_desc_s); |
| } |
| |
| static int virtio_snd_freebuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct audio_buf_desc_s *desc) |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| FAR struct virtio_snd_s *priv = sdev->priv; |
| FAR struct virtio_device *vdev = priv->vdev; |
| FAR struct ap_buffer_s *apb = desc->u.buffer; |
| int refcount; |
| |
| nxmutex_lock(&apb->lock); |
| refcount = apb->crefs--; |
| nxmutex_unlock(&apb->lock); |
| if (refcount <= 1) |
| { |
| nxmutex_destroy(&apb->lock); |
| virtio_free_buf(vdev, apb); |
| } |
| |
| return sizeof(struct audio_buf_desc_s); |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_enqueuebuffer |
| * |
| * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. |
| * |
| ****************************************************************************/ |
| |
| static int virtio_snd_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, |
| FAR struct ap_buffer_s *apb) |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| FAR struct virtio_snd_buffer_s *buf = |
| (FAR struct virtio_snd_buffer_s *)apb; |
| |
| return virtio_snd_send_pcm(sdev, buf); |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_ioctl |
| * |
| * Description: Perform a device ioctl |
| * |
| ****************************************************************************/ |
| |
| static int virtio_snd_ioctl(FAR struct audio_lowerhalf_s *dev, |
| int cmd, |
| unsigned long arg) |
| { |
| FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; |
| FAR struct ap_buffer_info_s *bufinfo; |
| |
| switch (cmd) |
| { |
| case AUDIOIOC_GETBUFFERINFO: |
| bufinfo = (FAR struct ap_buffer_info_s *)arg; |
| bufinfo->nbuffers = CONFIG_DRIVERS_VIRTIO_SND_BUFFER_COUNT; |
| bufinfo->buffer_size = sdev->period_bytes; |
| break; |
| |
| default: |
| return -ENOTTY; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_shutdown |
| * |
| * Description: |
| * Shutdown the driver and put it in the lowest power state possible. |
| * |
| ****************************************************************************/ |
| |
| static int virtio_snd_shutdown(FAR struct audio_lowerhalf_s *dev) |
| { |
| vrtinfo("Return OK\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_reserve |
| * |
| * Description: Reserves a session (the only one we have). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev, |
| FAR void **session) |
| #else |
| static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| vrtinfo("Return OK\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_release |
| * |
| * Description: Releases the session (the only one we have). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev, |
| FAR void *session) |
| #else |
| static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| vrtinfo("Return OK\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_init |
| ****************************************************************************/ |
| |
| static int virtio_snd_init(FAR struct virtio_snd_s *priv) |
| { |
| FAR const char *vqnames[VIRTIO_SND_VQ_MAX]; |
| vq_callback callbacks[VIRTIO_SND_VQ_MAX]; |
| int ret; |
| int i; |
| |
| vqnames[VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl"; |
| vqnames[VIRTIO_SND_VQ_EVENT] = "virtsnd-event"; |
| vqnames[VIRTIO_SND_VQ_TX] = "virtsnd-tx"; |
| vqnames[VIRTIO_SND_VQ_RX] = "virtsnd-rx"; |
| |
| callbacks[VIRTIO_SND_VQ_CONTROL] = virtio_snd_ctl_notify_cb; |
| callbacks[VIRTIO_SND_VQ_EVENT] = virtio_snd_event_notify_cb; |
| callbacks[VIRTIO_SND_VQ_TX] = virtio_snd_pcm_notify_cb; |
| callbacks[VIRTIO_SND_VQ_RX] = virtio_snd_pcm_notify_cb; |
| |
| ret = virtio_create_virtqueues(priv->vdev, 0, |
| VIRTIO_SND_VQ_MAX, vqnames, callbacks); |
| if (ret < 0) |
| { |
| vrterr("virtio_device_create_virtqueue failed, ret=%d\n", ret); |
| return ret; |
| } |
| |
| for (i = 0; i < VIRTIO_SND_VQ_MAX; i++) |
| { |
| virtqueue_enable_cb(priv->vdev->vrings_info[i].vq); |
| } |
| |
| virtio_set_status(priv->vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK); |
| |
| virtio_read_config(priv->vdev, 0, &priv->config, |
| sizeof(struct virtio_snd_config)); |
| vrtinfo("jacks:%"PRIu32" streams:%"PRIu32" chmap:%"PRIu32"\n", |
| priv->config.jacks, priv->config.streams, priv->config.chmaps); |
| |
| priv->info = virtio_alloc_buf(priv->vdev, |
| priv->config.streams * |
| sizeof(struct virtio_snd_pcm_info), |
| 16); |
| if (priv->info == NULL) |
| { |
| vrterr("virtio audio driver query pcm info alloc failed\n"); |
| ret = -ENOMEM; |
| goto err_with_vq; |
| } |
| |
| /* Query pcm info */ |
| |
| ret = virtio_snd_query_info(priv, VIRTIO_SND_R_PCM_INFO, |
| sizeof(struct virtio_snd_pcm_info), |
| priv->config.streams, |
| priv->info); |
| if (ret < 0) |
| { |
| vrterr("virtio snd query pcm info failed,ret:%d\n", ret); |
| goto err_with_info; |
| } |
| |
| return ret; |
| |
| err_with_info: |
| virtio_free_buf(priv->vdev, priv->info); |
| err_with_vq: |
| virtio_delete_virtqueues(priv->vdev); |
| return ret; |
| } |
| |
| static int |
| virtio_snd_register_audio_driver(FAR struct virtio_snd_s *priv) |
| { |
| char devname[32]; |
| int tx_minor = 0; |
| int rx_minor = 0; |
| int ret = -ENODEV; |
| uint32_t i; |
| |
| priv->dev = kmm_zalloc(priv->config.streams * |
| sizeof(struct virtio_snd_dev_s)); |
| if (priv->dev == NULL) |
| { |
| vrterr("zalloc for virtio audio device failed\n"); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < priv->config.streams; i++) |
| { |
| switch (priv->info[i].direction) |
| { |
| case VIRTIO_SND_D_OUTPUT: |
| snprintf(devname, 32, "pcm%dp", tx_minor++); |
| break; |
| |
| case VIRTIO_SND_D_INPUT: |
| snprintf(devname, 32, "pcm%dc", rx_minor++); |
| break; |
| } |
| |
| priv->dev[i].index = i; |
| priv->dev[i].running = false; |
| priv->dev[i].priv = priv; |
| priv->dev[i].dev.ops = &g_virtio_snd_ops; |
| ret = audio_register(devname, &priv->dev[i].dev); |
| if (ret < 0) |
| { |
| vrterr("failed to register /dev/%s device: %d\n", |
| devname, ret); |
| break; |
| } |
| } |
| |
| if (i == 0) |
| { |
| kmm_free(priv->dev); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_probe |
| ****************************************************************************/ |
| |
| static int virtio_snd_probe(FAR struct virtio_device *vdev) |
| { |
| FAR struct virtio_snd_s *priv; |
| int ret; |
| |
| priv = kmm_zalloc(sizeof(*priv)); |
| if (priv == NULL) |
| { |
| vrterr("virtio net driver priv alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| priv->vdev = vdev; |
| vdev->priv = priv; |
| |
| ret = virtio_snd_init(priv); |
| if (ret < 0) |
| { |
| goto err_with_virtsnd; |
| } |
| |
| ret = virtio_snd_register_audio_driver(priv); |
| if (ret < 0) |
| { |
| goto err_with_vq; |
| } |
| |
| return ret; |
| |
| err_with_vq: |
| virtio_reset_device(vdev); |
| virtio_delete_virtqueues(vdev); |
| virtio_free_buf(vdev, priv->info); |
| err_with_virtsnd: |
| kmm_free(priv); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: virtio_snd_remove |
| ****************************************************************************/ |
| |
| static void virtio_snd_remove(FAR struct virtio_device *vdev) |
| { |
| FAR struct virtio_snd_s *priv = vdev->priv; |
| |
| virtio_reset_device(vdev); |
| virtio_delete_virtqueues(vdev); |
| virtio_free_buf(vdev, priv->info); |
| kmm_free(priv->dev); |
| kmm_free(priv); |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: virtio_register_snd_driver |
| ****************************************************************************/ |
| |
| int virtio_register_snd_driver(void) |
| { |
| return virtio_register_driver(&g_virtio_snd_driver); |
| } |