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