text: /****************************************************************************
   * drivers/audio/vs1053.c
   *
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.  The
   * ASF licenses this file to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance with the
   * License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
   * License for the specific language governing permissions and limitations
   * under the License.
   *
   ****************************************************************************/
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  #include <sys/types.h>
  #include <sys/ioctl.h>
  
  #include <inttypes.h>
  #include <stdint.h>
  #include <stdbool.h>
  #include <stdio.h>
  #include <fcntl.h>
  #include <string.h>
  #include <assert.h>
  #include <debug.h>
  #include <errno.h>
  #include <queue.h>
  
  #include <nuttx/kmalloc.h>
  #include <nuttx/signal.h>
  #include <nuttx/mqueue.h>
  #include <nuttx/fs/fs.h>
  #include <nuttx/fs/ioctl.h>
  #include <nuttx/audio/audio.h>
  #include <nuttx/audio/vs1053.h>
  
  #include "vs1053.h"
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  #ifndef CONFIG_VS1053_SPIMODE
  #  define CONFIG_VS1053_SPIMODE SPIDEV_MODE0
  #endif
  
  #ifndef CONFIG_VS1053_XTALI
  #  define CONFIG_VS1053_XTALI             12288000
  #endif
  
  #ifndef CONFIG_VS1053_MP3_DECODE_FREQ
  #  define CONFIG_VS1053_MP3_DECODE_FREQ   43000000
  #endif
  
  #ifndef CONFIG_VS1053_MSG_PRIO
  #  define CONFIG_VS1053_MSG_PRIO          1
  #endif
  
  #ifndef CONFIG_VS1053_BUFFER_SIZE
  #  define CONFIG_VS1053_BUFFER_SIZE       8192
  #endif
  
  #ifndef CONFIG_VS1053_NUM_BUFFERS
  #  define CONFIG_VS1053_NUM_BUFFERS       2
  #endif
  
  #ifndef CONFIG_VS1053_WORKER_STACKSIZE
  #  define CONFIG_VS1053_WORKER_STACKSIZE  768
  #endif
  
  #define VS1053_DUMMY                      0xFF
  #define VS1053_DEFAULT_XTALI              12288000
  #define VS1053_DATA_FREQ                  20000000
  #define VS1053_RST_USECS                  2000
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  struct vs1053_struct_s
  {
    /* We are an audio lower half driver */
  
    struct audio_lowerhalf_s lower;
  
    /* Our specific driver data goes here */
  
    FAR const struct vs1053_lower_s *hw_lower; /* Pointer to the hardware lower functions */
    FAR struct spi_dev_s    *spi;              /* Pointer to the SPI bus */
    FAR struct ap_buffer_s  *apb;              /* Pointer to the buffer we are processing */
    struct dq_queue_s       apbq;              /* Our queue for enqueued buffers */
    unsigned long           spi_freq;          /* Frequency to run the SPI bus at. */
    unsigned long           chip_freq;         /* Current chip frequency */
    struct file             mq;                /* Message queue for receiving messages */
    char                    mqname[16];        /* Our message queue name */
    pthread_t               threadid;          /* ID of our thread */
    sem_t                   apbq_sem;          /* Audio Pipeline Buffer Queue sem access */
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
    int16_t                 volume;            /* Current volume level */
  #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE
    int16_t                 balance;           /* Current balance level */
  #endif /* CONFIG_AUDIO_EXCLUDE_BALANCE */
  #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */
  #ifndef CONFIG_AUDIO_EXCLUDE_TONE
    uint8_t                 bass;              /* Bass level */
    uint8_t                 treble;            /* Bass level */
  #endif
    uint16_t                endfillbytes;
    uint8_t                 endfillchar;       /* Fill char to send when no more data */
    bool                    running;
    bool                    paused;
    bool                    endmode;
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
    bool                    cancelmode;
  #endif
    bool                    busy;              /* Set true when device reserved */
  };
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  
  static int     vs1053_getcaps(FAR struct audio_lowerhalf_s *lower, int type,
                   FAR struct audio_caps_s *caps);
  static int     vs1053_shutdown(FAR struct audio_lowerhalf_s *lower);
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int     vs1053_configure(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session, FAR const struct audio_caps_s *caps);
  static int     vs1053_start(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session);
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
  static int     vs1053_stop(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session);
  #endif
  #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
  static int     vs1053_pause(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session);
  static int     vs1053_resume(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session);
  #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */
  static int     vs1053_reserve(FAR struct audio_lowerhalf_s *lower,
                   FAR void** session);
  static int     vs1053_release(FAR struct audio_lowerhalf_s *lower,
                   FAR void *session);
  #else
  static int     vs1053_configure(FAR struct audio_lowerhalf_s *lower,
                   FAR const struct audio_caps_s *caps);
  static int     vs1053_start(FAR struct audio_lowerhalf_s *lower);
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
  static int     vs1053_stop(FAR struct audio_lowerhalf_s *lower);
  #endif
  #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
  static int     vs1053_pause(FAR struct audio_lowerhalf_s *lower);
  static int     vs1053_resume(FAR struct audio_lowerhalf_s *lower);
  #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */
  static int     vs1053_reserve(FAR struct audio_lowerhalf_s *lower);
  static int     vs1053_release(FAR struct audio_lowerhalf_s *lower);
  #endif /* CONFIG_AUDIO_MULTI_SESION */
  static int     vs1053_enqueuebuffer(FAR struct audio_lowerhalf_s *lower,
                   FAR struct ap_buffer_s *apb);
  static int     vs1053_cancelbuffer(FAR struct audio_lowerhalf_s *lower,
                   FAR struct ap_buffer_s *apb);
  static int     vs1053_ioctl(FAR struct audio_lowerhalf_s *lower, int cmd,
                   unsigned long arg);
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  static const struct audio_ops_s g_audioops =
  {
    vs1053_getcaps,       /* getcaps        */
    vs1053_configure,     /* configure      */
    vs1053_shutdown,      /* shutdown       */
    vs1053_start,         /* start          */
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
    vs1053_stop,          /* stop           */
  #endif
  #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
    vs1053_pause,         /* pause          */
    vs1053_resume,        /* resume         */
  #endif
    NULL,                 /* alloc_buffer   */
    NULL,                 /* free_buffer    */
    vs1053_enqueuebuffer, /* enqueue_buffer */
    vs1053_cancelbuffer,  /* cancel_buffer  */
    vs1053_ioctl,         /* ioctl          */
    NULL,                 /* read           */
    NULL,                 /* write          */
    vs1053_reserve,       /* reserve        */
    vs1053_release        /* release        */
  };
  
  /* Volume control log table.  This table is in increments of 2% of
   * requested volume level and is the register value that should be
   * programmed to the VS1053 to achieve that volume percentage.
   */
  
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
  static const uint8_t   g_logtable [] =
  {
    254, 170, 140, 122, 110,    /* 0  - 8 */
    100, 92,  85,  80,  74,     /* 10 - 18 */
    70,  66,  62,  59,  55,     /* 20 - 28 */
    52,  49,  47,  44,  42,     /* 30 - 38 */
    40,  38,  36,  34,  32,     /* 40 - 48 */
    30,  28,  27,  25,  24,     /* 50 - 58 */
    22,  21,  19,  18,  17,     /* 60 - 68 */
    15,  14,  13,  12,  11,     /* 70 - 78 */
    10,   9,   8,   7,   6,     /* 80 - 88 */
    5,    4,   3,   2,   1,     /* 90 - 98 */
    0                           /* 100     */
  };
  #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: vs1053_spi_lock
   ****************************************************************************/
  
  static void vs1053_spi_lock(FAR struct spi_dev_s *dev,
                              unsigned long freq_mhz)
  {
    /* On SPI buses where there are multiple devices, it will be necessary to
     * lock SPI to have exclusive access to the buses for a sequence of
     * transfers.  The bus should be locked before the chip is selected.
     *
     * This is a blocking call and will not return until we have exclusive
     * access to the SPI bus.
     * We will retain that exclusive access until the bus is unlocked.
     */
  
    SPI_LOCK(dev, true);
  
    /* After locking the SPI bus, the we also need call the setfrequency,
     * setbits, and setmode methods to make sure that the SPI is properly
     * configured for the device.
     * If the SPI bus is being shared, then it may have been left in an
     * incompatible state.
     */
  
    SPI_SETMODE(dev, CONFIG_VS1053_SPIMODE);
    SPI_SETBITS(dev, 8);
    SPI_HWFEATURES(dev, 0);
    SPI_SETFREQUENCY(dev, freq_mhz);
  }
  
  /****************************************************************************
   * Name: vs1053_spi_unlock
   ****************************************************************************/
  
  static inline void vs1053_spi_unlock(FAR struct spi_dev_s *dev)
  {
    SPI_LOCK(dev, false);
  }
  
  /****************************************************************************
   * Name: vs1053_readreg - Read the specified 16-bit register from the
   *                        VS1053 device.  Caller must hold the SPI lock.
   ****************************************************************************/
  
  static uint16_t vs1053_readreg(FAR struct vs1053_struct_s *dev, uint8_t reg)
  {
    uint16_t ret;
    FAR struct spi_dev_s *spi = dev->spi;
  
    /* Select the AUDIO_CTRL device on the SPI bus */
  
    SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), true);
  
    /* Send the WRITE command followed by the address */
  
    SPI_SEND(spi, VS1053_OPCODE_READ);
    SPI_SEND(spi, reg);
  
    /* Now read the 16-bit value */
  
    ret = SPI_SEND(spi, VS1053_DUMMY) << 8;
    ret |= SPI_SEND(spi, VS1053_DUMMY);
  
    /* Deselect the CODEC */
  
    SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), false);
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_writereg - Write the specified 16-bit register to the
   *                         VS1053 device.  Caller must hold the SPI lock.
   ****************************************************************************/
  
  static void vs1053_writereg(FAR struct vs1053_struct_s *dev,
                              uint8_t reg,
                              uint16_t val)
  {
    FAR struct spi_dev_s *spi = dev->spi;
  
    /* Select the AUDIO_CTRL device on the SPI bus */
  
    audinfo("Write Reg %d = 0x%0X\n", reg, val);
  
    SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), true);
  
    /* Send the WRITE command followed by the address */
  
    SPI_SEND(spi, VS1053_OPCODE_WRITE);
    SPI_SEND(spi, reg);
  
    /* Now read the 16-bit value */
  
    SPI_SEND(spi, val >> 8);
    SPI_SEND(spi, val & 0xff);
  
    /* Deselect the CODEC */
  
    SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), false);
  
    /* Short delay after a write for VS1053 processing time */
  
    nxsig_usleep(10);
  }
  
  /****************************************************************************
   * Name: vs1053_setfrequency
   *
   * Description: Get the audio device capabilities
   *
   ****************************************************************************/
  
  static int vs1053_setfrequency(FAR struct vs1053_struct_s *dev,
                                 uint32_t freq)
  {
    double    factor;
    uint16_t  reg;
    uint8_t   timeout;
  
    audinfo("Entry\n");
  
    /* Calculate the clock divisor based on the input frequency */
  
    factor = (double) freq / (double) CONFIG_VS1053_XTALI * 10.0 + 0.5;
  
    /* Check the input frequency against bounds */
  
    if (factor > 50.0)
      {
        audinfo("Frequency too high!  Limiting to XTALI * 5\n");
        factor = 50.0;
        return -EINVAL;
      }
  
    if (factor < 10.0)
      {
        factor = 10.0;
      }
  
    /* Calculate the clock mulit register based on the factor */
  
    if ((int) factor == 10)
      {
        reg = 0;
      }
    else
      {
        reg = (((int) factor - 15) / 5) << VS1053_SC_MULT_SHIFT;
      }
  
    /* Set the MULT_ADD factor to the max to allow the chip to dynamically
     * increase the frequency the maximum amount as needed
     */
  
    reg |= (VS1053_SC_ADD_XTALI_X20 << VS1053_SC_ADD_SHIFT);
  
    /* If we aren't running with a 12.228Mhz input crystal, then we
     * must tell the chip what the frequency is
     */
  
    if (CONFIG_VS1053_XTALI != VS1053_DEFAULT_XTALI)
      {
        /* Calculate register value based on equation: (XTALI - 8000000) / 4000
         * per the datasheet.
         */
  
        reg |= (CONFIG_VS1053_XTALI - 8000000) / 4000;
      }
  
    /* Now set the new clock multiplier register */
  
    vs1053_writereg(dev, VS1053_SCI_CLOCKF, reg);
  
    /* Wait for DREQ to go active */
  
    timeout = 200;
    while (!dev->hw_lower->read_dreq(dev->hw_lower) && timeout)
      {
        nxsig_usleep(1000);
        timeout--;
      }
  
    /* Update our internal variables */
  
    dev->chip_freq = freq;
    dev->spi_freq = freq / 7;
  
    return OK;
  }
  
  /****************************************************************************
   * Name: vs1053_logapprox -
   *           Approximate the register value in .5 dB increments
   *           level based on the percentage using a log table since
   *           math libraries aren't available.
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
  uint8_t vs1053_logapprox(int percent)
  {
    /* Check percentage for bounds */
  
    if (percent >= 100)
      {
        return 0;
      }
  
    return (g_logtable[percent >> 1] + g_logtable[(percent + 1) >> 1]) >> 1;
  }
  #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */
  
  /****************************************************************************
   * Name: vs1053_setvolume -
   *             Set the right and left volume values in the VS1053
   *             device based on the current volume and balance settings.
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
  static void vs1053_setvolume(FAR struct vs1053_struct_s *dev)
  {
    FAR struct spi_dev_s *spi = dev->spi;
    uint32_t              leftlevel;
    uint32_t              rightlevel;
    uint8_t               leftreg;
    uint8_t               rightreg;
  
    /* Constrain balance */
  #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE
    if (dev->balance > 1000)
      {
        dev->balance = 1000;
      }
  
    /* Calculate the left channel volume level */
  
    if (dev->balance <= 500)
      {
        leftlevel = dev->volume;
      }
    else if (dev->balance == 1000)
      {
        leftlevel = 0;
      }
    else
      {
        leftlevel = dev->volume * (1000 - dev->balance) / 500;
      }
  
    /* Calculate the right channel volume level */
  
    if (dev->balance >= 500)
      {
        rightlevel = dev->volume;
      }
    else if (dev->balance == 0)
      {
        rightlevel = 0;
      }
    else
      {
        rightlevel = dev->volume * dev->balance / 500;
      }
  #else
    leftlevel = rightlevel = dev->volume;
  #endif
  
    /* Calculate the left and right register values */
  
    /* The register sets the volume in dB which is a logrithmic scale,
     * so we must use log() to calculate the register value.
     */
  
    leftreg = vs1053_logapprox(leftlevel / 10);
    rightreg = vs1053_logapprox(rightlevel / 10);
  
    /* Lock the SPI bus to get exclusive access to the chip. */
  
    vs1053_spi_lock(spi, dev->spi_freq);
    vs1053_writereg(dev, VS1053_SCI_VOL, (leftreg << 8) | rightreg);
    vs1053_spi_unlock(spi);
  }
  #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */
  
  /****************************************************************************
   * Name: vs1053_setbass - Set the bass and treble level as specified in the
   *                        context's bass and treble variables..
   *
   * The level and range are in whole percentage levels (0-100).
   *
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_TONE
  static void vs1053_setbass(FAR struct vs1053_struct_s *dev)
  {
    FAR struct spi_dev_s *spi = dev->spi;
    int       bass_range;
    int       bass_boost;
    int       treble_range;
    int       treble_boost;
  
    /* Calculate range and boost based on level */
  
    bass_boost = 15 * dev->bass / 100;
    bass_range = 15;
    treble_boost = 15 * dev->treble / 100;
    treble_range = 15;
  
    /* Lock the SPI bus to get exclsive access to the chip. */
  
    vs1053_spi_lock(spi, dev->spi_freq);
    vs1053_writereg(dev, VS1053_SCI_BASS,
                   (treble_boost << 12) | (treble_range << 8) |
                   (bass_boost << 4) | bass_range);
    vs1053_spi_unlock(spi);
  }
  #endif /* CONFIG_AUDIO_EXCLUDE_TONE */
  
  /****************************************************************************
   * Name: vs1053_getcaps
   *
   * Description: Get the audio device capabilities
   *
   ****************************************************************************/
  
  static int vs1053_getcaps(FAR struct audio_lowerhalf_s *lower, int type,
              FAR struct audio_caps_s *caps)
  {
    audinfo("Entry\n");
  
    /* Validate the structure */
  
    DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s));
  
    /* Fill in the caller's structure based on requested info */
  
    caps->ac_format.hw  = 0;
    caps->ac_controls.w = 0;
  
    switch (caps->ac_type)
      {
        /* Caller is querying for the types of units we support */
  
        case AUDIO_TYPE_QUERY:
  
          /* Provide our overall capabilities.  The interfacing software
           * must then call us back for specific info for each capability.
           */
  
          caps->ac_channels = 2;       /* Stereo output */
  
          switch (caps->ac_subtype)
            {
              case AUDIO_TYPE_QUERY:
  
                /* The input formats we can decode / accept */
  
                caps->ac_format.hw = 0
  #ifdef CONFIG_AUDIO_FORMAT_AC3
                      | (1 << (AUDIO_FMT_AC3 - 1))
  #endif
  #ifdef CONFIG_AUDIO_FORMAT_MP3
                      | (1 << (AUDIO_FMT_MP3 - 1))
  #endif
  #ifdef CONFIG_AUDIO_FORMAT_WMA
                      | (1 << (AUDIO_FMT_WMA - 1))
  #endif
  #ifdef CONFIG_AUDIO_FORMAT_MIDI
                      | (1 << (AUDIO_FMT_MIDI - 1))
  #endif
  #ifdef CONFIG_AUDIO_FORMAT_PCM
                      | (1 << (AUDIO_FMT_PCM - 1))
  #endif
  #ifdef CONFIG_AUDIO_FORMAT_OGG_VORBIS
                      | (1 << (AUDIO_FMT_OGG_VORBIS - 1))
  #endif
                  ;
  
                /* The types of audio units we implement */
  
                caps->ac_controls.b[0] = AUDIO_TYPE_OUTPUT |
                                         AUDIO_TYPE_FEATURE |
                                         AUDIO_TYPE_PROCESSING;
  
                break;
  
              /* Report sub-formats for MIDI if requested */
  
  #ifdef CONFIG_AUDIO_FORMAT_MIDI
              case AUDIO_FMT_MIDI:
  
                /* We only support Format 0 */
  
                caps->ac_controls.b[0] = AUDIO_SUBFMT_MIDI_0;
                caps->ac_controls.b[1] = AUDIO_SUBFMT_END;
                break;
  #endif
  
              default:
                caps->ac_controls.b[0] = AUDIO_SUBFMT_END;
                break;
            }
  
          break;
  
        /* Provide capabilities of our OUTPUT unit */
  
        case AUDIO_TYPE_OUTPUT:
  
          caps->ac_channels = 2;
  
          switch (caps->ac_subtype)
            {
              case AUDIO_TYPE_QUERY:
  
                /* Report the Sample rates we support */
  
                caps->ac_controls.b[0] = AUDIO_SAMP_RATE_8K  |
                                         AUDIO_SAMP_RATE_11K |
                                         AUDIO_SAMP_RATE_16K |
                                         AUDIO_SAMP_RATE_22K |
                                         AUDIO_SAMP_RATE_32K |
                                         AUDIO_SAMP_RATE_44K |
                                         AUDIO_SAMP_RATE_48K;
                break;
  
              case AUDIO_FMT_MP3:
              case AUDIO_FMT_WMA:
              case AUDIO_FMT_PCM:
                /* Report the Bit rates we support.
                 * The bit rate support is actually a complex function of the
                 * format and selected sample rate, and the datasheet has
                 * multiple tables to indicate the supported bit rate vs sample
                 * rate vsformat.
                 * The selected sample rate should be provided in the ac_format
                 * field of the query, and only a single sample rate should be
                 * given.
                 */
  
                /* TODO:  Create a table or set of tables to report this! */
  
                break;
  
              default:
                break;
            }
  
          break;
  
        /* Provide capabilities of our FEATURE units */
  
        case AUDIO_TYPE_FEATURE:
  
          /* If the sub-type is UNDEF,
           * then report the Feature Units we support
           */
  
          if (caps->ac_subtype == AUDIO_FU_UNDEF)
            {
              /* Fill in the ac_controls section with the
               * Feature Units we have
               */
  
              caps->ac_controls.b[0] = AUDIO_FU_VOLUME |
                                       AUDIO_FU_BASS |
                                       AUDIO_FU_TREBLE;
              caps->ac_controls.b[1] = AUDIO_FU_BALANCE >> 8;
            }
          else
            {
              /* TODO:
               * Do we need to provide specific info for the Feature Units,
               * such as volume setting ranges, etc.?
               */
            }
  
          break;
  
        /* Provide capabilities of our PROCESSING unit */
  
        case AUDIO_TYPE_PROCESSING:
  
          switch (caps->ac_subtype)
            {
              case AUDIO_PU_UNDEF:
  
                /* Provide the type of Processing Units we support */
  
                caps->ac_controls.b[0] = AUDIO_PU_STEREO_EXTENDER;
                break;
  
              case AUDIO_PU_STEREO_EXTENDER:
  
                /* Proivde capabilities of our Stereo Extender */
  
                caps->ac_controls.b[0] = AUDIO_STEXT_ENABLE |
                                         AUDIO_STEXT_WIDTH;
                break;
  
              default:
  
                /* Other types of processing uint we don't support */
  
                break;
            }
  
          break;
  
        /* All others we don't support */
  
        default:
  
          /* Zero out the fields to indicate no support */
  
          caps->ac_subtype = 0;
          caps->ac_channels = 0;
  
          break;
      }
  
    /* Return the length of the audio_caps_s struct for validation of
     * proper Audio device type.
     */
  
    return caps->ac_len;
  }
  
  /****************************************************************************
   * Name: vs1053_configure
   *
   * Description: Configure the audio device for the specified  mode of
   *              operation.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_configure(FAR struct audio_lowerhalf_s *lower,
              FAR void *session, FAR const struct audio_caps_s *caps)
  #else
  static int vs1053_configure(FAR struct audio_lowerhalf_s *lower,
              FAR const struct audio_caps_s *caps)
  #endif
  {
    int     ret = OK;
  #if !defined(CONFIG_AUDIO_EXCLUDE_VOLUME) || !defined(CONFIG_AUDIO_EXCLUDE_TONE)
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
  #endif
  
    audinfo("Entry\n");
  
    /* Process the configure operation */
  
    switch (caps->ac_type)
      {
        case AUDIO_TYPE_FEATURE:
  
          /* Process based on Feature Unit */
  
          switch (caps->ac_format.hw)
            {
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
              case AUDIO_FU_VOLUME:
  
                /* Set the volume */
  
                dev->volume = caps->ac_controls.hw[0];
                vs1053_setvolume(dev);
  
                break;
  #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */
  
  #if !defined(CONFIG_AUDIO_EXCLUDE_TONE) && !defined(CONFIG_AUDIO_EXCLUDE_VOLUME)
              case AUDIO_FU_BALANCE:
  
                /* Set the volume */
  
                dev->balance = caps->ac_controls.hw[0];
                vs1053_setvolume(dev);
  
                break;
  #endif
  
  #ifndef CONFIG_AUDIO_EXCLUDE_TONE
              case AUDIO_FU_BASS:
  
                /* Set the bass.  The percentage level (0-100) is in the
                 * ac_controls[0] parameter.
                 */
  
                dev->bass = caps->ac_controls.b[0];
                if (dev->bass > 100)
                  dev->bass = 100;
                vs1053_setbass(dev);
  
                break;
  
              case AUDIO_FU_TREBLE:
                /* Set the treble.  The percentage level (0-100) is in the
                 * ac_controls.b[0] parameter.
                 */
  
                dev->treble = caps->ac_controls.b[0];
                if (dev->treble > 100)
                  dev->treble = 100;
                vs1053_setbass(dev);
  
                break;
  #endif /* CONFIG_AUDIO_EXCLUDE_TONE */
  
              default:
  
                /* Others we don't support */
  
                break;
            }
  
          break;
  
        case AUDIO_TYPE_OUTPUT:
          break;
  
        case AUDIO_TYPE_PROCESSING:
  
          /* We only support STEREO_EXTENDER */
  
          if (caps->ac_format.hw == AUDIO_PU_STEREO_EXTENDER)
            {
            }
  
          break;
      }
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_softreset
   *
   * Description: Performs a soft reset on the VS1053 chip by setting the
   *              RESET bit of the MODE register.
   *
   ****************************************************************************/
  
  static int vs1053_softreset(FAR struct vs1053_struct_s *dev)
  {
    uint16_t reg;
    uint16_t timeout;
  
    /* First disable interrupts, lower the frequency and lock the SPI bus */
  
    dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
    vs1053_spi_lock(dev->spi, VS1053_DEFAULT_XTALI / 7);
  
    /* Now issue a reset command */
  
    reg = vs1053_readreg(dev, VS1053_SCI_MODE);
    vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_RESET);
  
    /* Now wait for the SM_RESET to go inactive */
  
    timeout = 1000;
    while (vs1053_readreg(dev, VS1053_SCI_MODE) & VS1053_SM_RESET && timeout)
      {
        timeout--;
      }
  
    /* Switch to low frequency, Unlock the SPI bus and exit */
  
    vs1053_setfrequency(dev, CONFIG_VS1053_XTALI);
    vs1053_spi_unlock(dev->spi);
    return OK;
  }
  
  /****************************************************************************
   * Name: vs1053_hardreset
   *
   * Description: Performs a hardware reset on the VS1053 chip by toggling
   *              the RST line, disabling IRQ, and setting the default
   *              XTALI frequency.
   *
   ****************************************************************************/
  
  static int vs1053_hardreset(FAR struct vs1053_struct_s *dev)
  {
    dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
    dev->hw_lower->reset(dev->hw_lower, false);
    nxsig_usleep(10);
    dev->hw_lower->reset(dev->hw_lower, true);
    nxsig_usleep(VS1053_RST_USECS);
    vs1053_setfrequency(dev, CONFIG_VS1053_XTALI);  /* Slow speed at first */
  
    return OK;
  }
  
  /****************************************************************************
   * Name: vs1053_shutdown
   *
   * Description: Shutdown the VS1053 chip and put it in the lowest power
   *              state possible.
   *
   ****************************************************************************/
  
  static int vs1053_shutdown(FAR struct audio_lowerhalf_s *lower)
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
    FAR struct spi_dev_s  *spi = dev->spi;
  
    audinfo("Entry\n");
    vs1053_spi_lock(spi, dev->spi_freq);            /* Lock the device */
    vs1053_setfrequency(dev, CONFIG_VS1053_XTALI);  /* Reduce speed to minimum */
    vs1053_writereg(dev, VS1053_SCI_VOL, 0xfefe);   /* Power down the DAC outputs */
    vs1053_spi_unlock(spi);                         /* Unlock the device */
    return OK;
  }
  
  /****************************************************************************
   * Name: vs1053_feeddata
   *
   * Description: Feeds more data to the vs1053 chip from the enqueued
   *              buffers.  It will continue feeding data until the DREQ
   *              line indicates it can't accept any more data.
   *
   ****************************************************************************/
  
  static void vs1053_feeddata(FAR struct vs1053_struct_s *dev)
  {
    int                   bytecount;
    int                   ret;
    uint8_t               *samp = NULL;
    uint16_t              reg;
    struct ap_buffer_s    *apb;
    FAR struct spi_dev_s  *spi = dev->spi;
  
    /* Check for false interrupt caused by an SCI transaction */
  
    if (!dev->hw_lower->read_dreq(dev->hw_lower) || dev->paused)
      {
        return;
      }
  
    /* Grab the SPI bus.  We can run at 20Mhz because we increased the
     * chip frequency above 40Mhz for the decode operation.
     */
  
    vs1053_spi_lock(spi, VS1053_DATA_FREQ);   /* Lock the SPI bus */
  
    SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), true); /* Select the VS1053 data bus */
  
    /* Local stack copy of our active buffer */
  
    apb = dev->apb;
  
    /* audinfo("Entry apb=%p, Bytes left=%d\n",
     *         apb, apb->nbytes - apb->curbyte);
     */
  
    /* Setup pointer to the next sample in the buffer */
  
    if (apb)
      {
        samp = &apb->samp[apb->curbyte];
      }
    else if (!dev->endmode)
      {
        SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), false);
        vs1053_spi_unlock(spi);
        return;
      }
  
    /* Loop until the FIFO is full */
  
    while (dev->hw_lower->read_dreq(dev->hw_lower))
      {
        /* If endmode, then send fill characters */
  
        if (dev->endmode)
          {
            bytecount = 32;
            while (bytecount)
              {
                SPI_SEND(spi, dev->endfillchar);
                bytecount--;
              }
  
            /* For the VS1053, after the file has been played, we must
             * send 2052 bytes of endfillchar per the datasheet.
             */
  
            dev->endfillbytes += 32;
  
            /* Process end mode logic.  We send 2080 bytes of endfillchar as
             * directed by the datasheet, then set SM_CANCEL.  Then we wait
             * until the chip clears SM_CANCEL while sending endfillchar
             * 32 bytes at a time.
             */
  
            if (dev->endfillbytes == 32 * 65)
              {
                /* After at least 2052 bytes, we send an SM_CANCEL */
  
                dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
                SPI_SETFREQUENCY(dev->spi, dev->spi_freq);
                reg = vs1053_readreg(dev, VS1053_SCI_MODE);
                vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_CANCEL);
                dev->hw_lower->enable(dev->hw_lower);   /* Enable the DREQ interrupt */
              }
            else if (dev->endfillbytes >= 32 * 130)
              {
                /* Do a hard reset and terminate */
  
                vs1053_hardreset(dev);
                dev->running = false;
                dev->endmode = false;
                break;
              }
            else if (dev->endfillbytes > 32 * 65)
              {
                /* After each 32 byte of endfillchar, check the status
                 * register to see if SM_CANCEL has been cleared.  If
                 * it has been cleared, then we're done.
                 */
  
                if (!(vs1053_readreg(dev, VS1053_SCI_STATUS) &
                      VS1053_SM_CANCEL))
                  {
                    SPI_SETFREQUENCY(dev->spi, dev->spi_freq);
                    dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
  
                    audinfo("HDAT1: 0x%0X   HDAT0: 0x%0X\n",
                            vs1053_readreg(dev, VS1053_SCI_HDAT1),
                            vs1053_readreg(dev, VS1053_SCI_HDAT0));
  
                    vs1053_writereg(dev,
                                    VS1053_SCI_WRAMADDR,
                                    VS1053_END_FILL_BYTE);
                    dev->endfillchar = vs1053_readreg(dev,
                                                      VS1053_SCI_WRAM) >> 8;
  
                    audinfo("EndFillChar: 0x%0X\n", dev->endfillchar);
  
                    reg = vs1053_readreg(dev, VS1053_SCI_MODE);
                    vs1053_writereg(dev,
                                    VS1053_SCI_MODE,
                                    reg | VS1053_SM_RESET);
  
                    dev->running = false;
                    dev->endmode = false;
                    break;
                  }
              }
          }
        else
          {
            /* Send 32 more bytes.  We only send 32 at a time because this is
             * the meaning of DREQ active from the chip ... that it can
             * accept at least 32 more bytes.  After each 32 byte block, we
             * will recheck the DREQ line again.
             */
  
            bytecount = apb->nbytes - apb->curbyte;
            if (bytecount > 32)
              {
                bytecount = 32;
              }
  
  #if 1
            SPI_SNDBLOCK(spi, samp, bytecount);
            samp += bytecount;
  #else
            bytecount = bytecount;
            while (bytecount--)
              {
                /* Send next byte from the buffer */
  
                SPI_SEND(spi, *samp);
                samp++;
              }
  
  #endif
            apb->curbyte += bytecount;
  
            /* Test if we are in cancel mode.  If we are, then we need
             * to continue sending file data and check for the SM_CANCEL
             * bit going inactive.
             */
  
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
            if (dev->cancelmode)
              {
                /* Read the VS1053 MODE register */
  
                dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
                SPI_SETFREQUENCY(dev->spi, dev->spi_freq);
                reg = vs1053_readreg(dev, VS1053_SCI_MODE);
                SPI_SETFREQUENCY(dev->spi, VS1053_DATA_FREQ);
                dev->hw_lower->enable(dev->hw_lower);   /* Enable the DREQ interrupt */
  
                /* Check the SM_CANCEL bit */
  
                if (!(reg & VS1053_SM_CANCEL))
                  {
                    /* Cancel has begun.  Switch to endmode */
  
                    apb->nbytes  = 0;
                    apb->curbyte = 0;
                  }
              }
  #endif /* CONFIG_AUDIO_EXCLUDE_STOP */
  
            /* Test if we are at the end of the buffer */
  
            if (apb->curbyte >= apb->nbytes)
              {
                /* Check if this was the final buffer in stream */
  
                if ((apb->flags & AUDIO_APB_FINAL) != 0)
                  {
                    /* This is the final buffer.  Get the VS1053 endfillchar */
  
                    dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
                    SPI_SETFREQUENCY(dev->spi, dev->spi_freq);
                    vs1053_writereg(dev,
                                    VS1053_SCI_WRAMADDR,
                                    VS1053_END_FILL_BYTE);
                    dev->endfillchar = vs1053_readreg(dev,
                                                      VS1053_SCI_WRAM) >> 8;
                    SPI_SETFREQUENCY(dev->spi, VS1053_DATA_FREQ);
                    dev->hw_lower->enable(dev->hw_lower);   /* Enable the DREQ interrupt */
  
                    /* Mark the device as endmode */
  
                    dev->endmode = true;
  
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
                    if (dev->cancelmode)
                      {
                        /* If we are in cancel mode, then we don't dequeue the
                         * buffer or need to send another SM_CANCEL, so jump
                         * into the middle of the stop sequence.
                         */
  
                        dev->endfillbytes = 32 * 65 + 1;
                        continue;
                      }
                    else
  #endif /* CONFIG_AUDIO_EXCLUDE_STOP */
                      {
                        dev->endfillbytes = 0;
                      }
                  }
  
                /* We referenced the buffer so we must free it */
  
                apb_free(apb);
  #ifdef CONFIG_AUDIO_MULTI_SESSION
                dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_DEQUEUE,
                    apb, OK, NULL);
  #else
                dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_DEQUEUE,
                    apb, OK);
  #endif
  
                /* Lock the buffer queue to pop the next buffer */
  
                if ((ret = nxsem_wait(&dev->apbq_sem)) < 0)
                  {
  #ifdef CONFIG_AUDIO_MULTI_SESSION
                    dev->lower.upper(dev->lower.priv,
                        AUDIO_CALLBACK_IOERR, NULL, ret, NULL);
  #else
                    dev->lower.upper(dev->lower.priv,
                        AUDIO_CALLBACK_IOERR, NULL, ret);
  #endif
                    auderr("ERROR: I/O error!\n");
  
                    goto err_out;
                  }
  
                /* Pop the next entry */
  
                apb = (struct ap_buffer_s *) dq_remfirst(&dev->apbq);
                dev->apb = apb;
  
                /* audinfo("Next Buffer = %p, bytes = %d\n",
                 *          apb, apb ? apb->nbytes : 0);
                 */
  
                if (apb == NULL)
                  {
                    nxsem_post(&dev->apbq_sem);
                    break;
                  }
  
                samp = &apb->samp[apb->curbyte];
                apb_reference(apb);                /* Add our buffer reference */
                nxsem_post(&dev->apbq_sem);
              }
          }
      }
  
    /* Deselect the SPI bus and unlock it */
  
  err_out:
    SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), false);
    vs1053_spi_unlock(spi);
  }
  
  /****************************************************************************
   * Name: vs1053_dreq_isr
   *
   *  This is the ISR that services the DREQ pin from the VS1053, which
   *  indicates the chip is ready to receive additional data.  We use it to
   *  send a message to our workertherad message queue so it knows to wake
   *  up and send more data.
   *
   ****************************************************************************/
  
  static int vs1053_dreq_isr(int irq, FAR void *context, FAR void *arg)
  {
    struct vs1053_struct_s *dev = (struct vs1053_struct_s *)arg;
    struct audio_msg_s      msg;
  
    DEBUGASSERT(dev != NULL);
  
    /* Now create a message and send it to the workerthread */
  
    if (dev->running)
      {
        msg.msg_id = AUDIO_MSG_DATA_REQUEST;
        file_mq_send(&dev->mq, (FAR const char *)&msg, sizeof(msg),
                     CONFIG_VS1053_MSG_PRIO);
      }
    else
      {
        msg.msg_id = AUDIO_MSG_DATA_REQUEST;
      }
  
    return 0;
  }
  
  /****************************************************************************
   * Name: vs1053_workerthread
   *
   *  This is the thread that feeds data to the chip and keeps the audio
   *  stream going.
   *
   ****************************************************************************/
  
  static void *vs1053_workerthread(pthread_addr_t pvarg)
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) pvarg;
    struct audio_msg_s      msg;
    FAR struct ap_buffer_s *apb;
    int                     size;
    unsigned int            prio;
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
    uint16_t                reg;
  #endif
    uint8_t                 timeout;
  
    audinfo("Entry\n");
  
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
    dev->cancelmode   = false;
  #endif
    dev->endmode      = false;
    dev->endfillbytes = 0;
  
    /* Fill the VS1053 FIFO with initial data.  */
  
    vs1053_feeddata(dev);             /* Fill the VS1053 FIFO */
  
    /* Wait for DREQ to go active so we can issue a READ command */
  
    timeout = 200;
    while (!dev->hw_lower->read_dreq(dev->hw_lower) && timeout)
      {
        nxsig_usleep(100);
        timeout--;
      }
  
    /* Loop as long as we are supposed to be running */
  
    dev->running = true;
    dev->hw_lower->enable(dev->hw_lower);   /* Enable the DREQ interrupt */
    while (dev->running || dev->endmode)
      {
        if (dev->hw_lower->read_dreq(dev->hw_lower))
          {
            vs1053_feeddata(dev);    /* Feed more data to the VS1053 FIFO */
          }
  
        /* Wait for messages from our message queue */
  
        size = file_mq_receive(&dev->mq, (FAR char *)&msg, sizeof(msg), &prio);
  
        /* Handle the case when we return with no message */
  
        if (size == 0)
          {
            /* Should we just stop running? */
  
            dev->running = false;
            break;
          }
  
        /* Process the message */
  
        switch (msg.msg_id)
          {
            /* The ISR has requested more data */
  
            case AUDIO_MSG_DATA_REQUEST:
              nxsig_usleep(500);
              vs1053_feeddata(dev);   /* Feed more data to the VS1053 FIFO */
              break;
  
            /* Stop the playback */
  
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
            case AUDIO_MSG_STOP:
              if (!dev->hw_lower->read_dreq(dev->hw_lower))
                {
                  nxsig_usleep(300);
                }
  
              /* Send CANCEL message to VS1053 */
  
              dev->hw_lower->disable(dev->hw_lower);
              vs1053_spi_lock(dev->spi, dev->spi_freq);
              reg = vs1053_readreg(dev, VS1053_SCI_MODE);
              vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_CANCEL);
              vs1053_spi_unlock(dev->spi);
              dev->hw_lower->enable(dev->hw_lower);
  
              /* Set cancelmode */
  
              dev->cancelmode = true;
  
              break;
  #endif
  
            /* We will wake up when a new buffer enqueued just in case */
  
            case AUDIO_MSG_ENQUEUE:
              break;
  
            default:
              break;
          }
      }
  
    /* Disable the DREQ interrupt */
  
    dev->hw_lower->disable(dev->hw_lower);
  
    /* Cancel any leftover buffer in our queue */
  
    if (nxsem_wait(&dev->apbq_sem) == OK)
      {
        /* Get the next buffer from the queue */
  
        while ((apb = (FAR struct ap_buffer_s *) dq_remfirst(&dev->apbq))
                 != NULL)
          ;
      }
  
    nxsem_post(&dev->apbq_sem);
  
    /* Free the active buffer */
  
    if (dev->apb != NULL)
      {
        apb_free(dev->apb);
        dev->apb = NULL;
      }
  
    /* Close the message queue */
  
    file_mq_close(&dev->mq);
    file_mq_unlink(dev->mqname);
  
    /* Send an AUDIO_MSG_COMPLETE message to the client */
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
    dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL);
  #else
    dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK);
  #endif
  
    audinfo("Exit\n");
    return NULL;
  }
  
  /****************************************************************************
   * Name: vs1053_start
   *
   * Description: Start the configured operation (audio streaming, volume
   *              enabled, etc.).
   *
   ****************************************************************************/
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_start(FAR struct audio_lowerhalf_s *lower,
                          FAR void *session)
  #else
  static int vs1053_start(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
    struct mq_attr      attr;
    struct sched_param  sparam;
    pthread_attr_t      tattr;
    int                 ret;
    void                *value;
  
    audinfo("Entry\n");
  
    vs1053_spi_lock(dev->spi, dev->spi_freq);     /* Lock the device */
    audinfo("Entry HDAT1=0x%0X  HDAT0=0x%0X\n",
            vs1053_readreg(dev, VS1053_SCI_HDAT1),
            vs1053_readreg(dev, VS1053_SCI_HDAT0));
    vs1053_spi_unlock(dev->spi);
  
    /* Do a soft reset, just in case */
  
    vs1053_softreset(dev);
  
    /* Increase the frequency of the part during processing */
  
    vs1053_spi_lock(dev->spi, dev->spi_freq);     /* Lock the device */
    vs1053_setfrequency(dev, CONFIG_VS1053_MP3_DECODE_FREQ);
    audinfo("Reset HDAT1=0x%0X  HDAT0=0x%0X\n",
            vs1053_readreg(dev, VS1053_SCI_HDAT1),
            vs1053_readreg(dev, VS1053_SCI_HDAT0));
    vs1053_spi_unlock(dev->spi);
  
    /* Create a message queue for the worker thread */
  
    snprintf(dev->mqname, sizeof(dev->mqname), "/tmp/%" PRIXPTR,
             (uintptr_t)dev);
    attr.mq_maxmsg = 16;
    attr.mq_msgsize = sizeof(struct audio_msg_s);
    attr.mq_curmsgs = 0;
    attr.mq_flags = 0;
    ret = file_mq_open(&dev->mq, dev->mqname,
                       O_RDWR | O_CREAT, 0644, &attr);
    if (ret < 0)
      {
        /* Error creating message queue! */
  
        auderr("ERROR: Couldn't allocate message queue\n");
        return ret;
      }
  
    /* Pop the first enqueued buffer */
  
    if ((ret = nxsem_wait(&dev->apbq_sem)) == OK)
      {
        dev->apb = (FAR struct ap_buffer_s *) dq_remfirst(&dev->apbq);
        apb_reference(dev->apb);               /* Add our buffer reference */
        nxsem_post(&dev->apbq_sem);
      }
    else
      {
        auderr("ERROR: Error getting APB Queue sem\n");
        return ret;
      }
  
    /* Join any old worker thread we had created to prevent a memory leak */
  
    if (dev->threadid != 0)
      {
        audinfo("Joining old thread\n");
        pthread_join(dev->threadid, &value);
      }
  
    /* Start our thread for sending data to the device */
  
    pthread_attr_init(&tattr);
    sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3;
    pthread_attr_setschedparam(&tattr, &sparam);
    pthread_attr_setstacksize(&tattr, CONFIG_VS1053_WORKER_STACKSIZE);
  
    audinfo("Starting workerthread\n");
    ret = pthread_create(&dev->threadid, &tattr, vs1053_workerthread,
        (pthread_addr_t) dev);
    if (ret != OK)
      {
        auderr("ERROR: Can't create worker thread, ret=%d\n", ret);
      }
    else
      {
        pthread_setname_np(dev->threadid, "vs1053");
        audinfo("Created worker thread\n");
      }
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_stop
   *
   * Description: Stop the configured operation (audio streaming, volume
   *              disabled, etc.).
   *
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_STOP
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_stop(FAR struct audio_lowerhalf_s *lower,
                         FAR void *session)
  #else
  static int vs1053_stop(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
    struct audio_msg_s term_msg;
    FAR void *value;
  
    /* Send a message to stop all audio streaming */
  
    term_msg.msg_id = AUDIO_MSG_STOP;
    term_msg.u.data = 0;
    file_mq_send(&dev->mq, (FAR const char *)&term_msg, sizeof(term_msg),
                 CONFIG_VS1053_MSG_PRIO);
  
    /* Join the worker thread */
  
    pthread_join(dev->threadid, &value);
    dev->threadid = 0;
  
    /* Reduce the decoder's operating frequency to save power */
  
    vs1053_spi_lock(dev->spi, dev->spi_freq);       /* Lock the device */
    vs1053_setfrequency(dev, CONFIG_VS1053_XTALI);
    vs1053_spi_unlock(dev->spi);
  
    /* Wait for a bit */
  
    up_mdelay(40);
  
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: vs1053_pause
   *
   * Description: Pauses the playback.
   *
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_pause(FAR struct audio_lowerhalf_s *lower,
                          FAR void *session)
  #else
  static int vs1053_pause(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
  
    if (!dev->running)
      {
        return OK;
      }
  
    /* Disable interrupts to prevent us from supplying any more data */
  
    dev->paused = true;
    dev->hw_lower->disable(dev->hw_lower);   /* Disable the DREQ interrupt */
    return OK;
  }
  #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */
  
  /****************************************************************************
   * Name: vs1053_resume
   *
   * Description: Resuems the playback.
   *
   ****************************************************************************/
  
  #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_resume(FAR struct audio_lowerhalf_s *lower,
                           FAR void *session)
  #else
  static int vs1053_resume(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
  
    if (!dev->running)
      {
        return OK;
      }
  
    /* Enable interrupts to allow suppling data */
  
    dev->paused = false;
    vs1053_feeddata(dev);
    dev->hw_lower->enable(dev->hw_lower);   /* Enable the DREQ interrupt */
    return OK;
  }
  #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */
  
  /****************************************************************************
   * Name: vs1053_enqueuebuffer
   *
   * Description: Enqueue an Audio Pipeline Buffer for playback/ processing.
   *
   ****************************************************************************/
  
  static int vs1053_enqueuebuffer(FAR struct audio_lowerhalf_s *lower,
                                  FAR struct ap_buffer_s *apb)
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *)lower;
    struct audio_msg_s  term_msg;
    int ret;
  
    audinfo("Entry\n");
  
    /* Lock access to the apbq */
  
    if ((ret = nxsem_wait(&dev->apbq_sem)) == OK)
      {
        /* We can now safely add the buffer to the queue */
  
        apb->curbyte = 0;
        apb->flags  |= AUDIO_APB_OUTPUT_ENQUEUED;
        dq_addlast(&apb->dq_entry, &dev->apbq);
        nxsem_post(&dev->apbq_sem);
  
        /* Send a message indicating a new buffer enqueued */
  
        if (dev->mq.f_inode != NULL)
          {
            term_msg.msg_id = AUDIO_MSG_ENQUEUE;
            term_msg.u.data = 0;
            file_mq_send(&dev->mq, (FAR const char *)&term_msg,
                         sizeof(term_msg), CONFIG_VS1053_MSG_PRIO);
          }
      }
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_cancelbuffer
   *
   * Description: Called when an enqueued buffer is being cancelled.
   *
   ****************************************************************************/
  
  static int vs1053_cancelbuffer(FAR struct audio_lowerhalf_s *lower,
                                 FAR struct ap_buffer_s *apb)
  {
    return OK;
  }
  
  /****************************************************************************
   * Name: vs1053_ioctl
   *
   * Description: Perform a device ioctl
   *
   ****************************************************************************/
  
  static int vs1053_ioctl(FAR struct audio_lowerhalf_s *lower, int cmd,
                    unsigned long arg)
  {
    int ret = OK;
  #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS
    FAR struct ap_buffer_info_s *bufinfo;
  #endif
  
    /* Deal with ioctls passed from the upper-half driver */
  
    switch (cmd)
      {
        /* Check for AUDIOIOC_HWRESET ioctl.  This ioctl is passed straight
         * through from the upper-half audio driver.
         */
  
        case AUDIOIOC_HWRESET:
          vs1053_hardreset((FAR struct vs1053_struct_s *) lower);
          break;
  
         /* Report our preferred buffer size and quantity */
  
  #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS
        case AUDIOIOC_GETBUFFERINFO:
  
          bufinfo = (FAR struct ap_buffer_info_s *) arg;
          bufinfo->buffer_size = CONFIG_VS1053_BUFFER_SIZE;
          bufinfo->nbuffers = CONFIG_VS1053_NUM_BUFFERS;
          break;
  #endif
  
        default:
          ret = -ENOTTY;
          break;
      }
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_reserve
   *
   * Description: Reserves a session (the only one we have).
   *
   ****************************************************************************/
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower,
                    FAR void **psession)
  #else
  static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
    int   ret;
  
    /* Borrow the APBQ semaphore for thread sync */
  
    ret = nxsem_wait(&dev->apbq_sem);
    if (ret < 0)
      {
        return ret;
      }
  
    if (dev->busy)
      {
        ret = -EBUSY;
      }
    else
      {
        /* Initialize the session context.  We don't really use it. */
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
        *psession = NULL;
  #endif
        dev->busy    = true;
        dev->running = false;
        dev->paused  = false;
      }
  
    nxsem_post(&dev->apbq_sem);
  
    return ret;
  }
  
  /****************************************************************************
   * Name: vs1053_release
   *
   * Description: Releases the session (the only one we have).
   *
   ****************************************************************************/
  
  #ifdef CONFIG_AUDIO_MULTI_SESSION
  static int vs1053_release(FAR struct audio_lowerhalf_s *lower,
                    FAR void *psession)
  #else
  static int vs1053_release(FAR struct audio_lowerhalf_s *lower)
  #endif
  {
    FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower;
    void  *value;
    int ret;
  
    /* Join any old worker thread we had created to prevent a memory leak */
  
    if (dev->threadid != 0)
      {
        pthread_join(dev->threadid, &value);
        dev->threadid = 0;
      }
  
    /* Borrow the APBQ semaphore for thread sync */
  
    ret = nxsem_wait(&dev->apbq_sem);
    if (ret < 0)
      {
        return ret;
      }
  
    /* Really we should free any queued buffers here */
  
    dev->busy = false;
    nxsem_post(&dev->apbq_sem);
  
    return OK;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: vs1053_initialize
   *
   * Description:
   *   Initialize the VS1053 device
   *
   * Input Parameters:
   *   spidevice - This is a placeholder argument until the Audio interface
   *      has been flushed out a bit.
   *
   ****************************************************************************/
  
  struct audio_lowerhalf_s *vs1053_initialize(FAR struct spi_dev_s *spi,
                              FAR const struct vs1053_lower_s *lower,
                              unsigned int devno)
  {
    FAR struct vs1053_struct_s *dev;
    uint16_t                    status;
    uint8_t                     id;
    uint8_t                     retry;
  
    /* Sanity check */
  
    DEBUGASSERT(spi != NULL);
    DEBUGASSERT(lower != NULL);
    DEBUGASSERT(lower->reset != NULL);
  
    /* Allocate a VS1053 device structure */
  
    dev = (struct vs1053_struct_s *)kmm_zalloc(sizeof(struct vs1053_struct_s));
    if (dev)
      {
        /* Initialize the VS1053 device structure */
  
        dev->lower.ops   = &g_audioops;
        dev->hw_lower    = lower;
        dev->spi_freq    = CONFIG_VS1053_XTALI / 7;
        dev->spi         = spi;
  
  #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
        dev->volume      = 250;           /* 25% volume as default */
  #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE
        dev->balance     = 500;           /* Center balance */
  #endif
  #endif
  
        nxsem_init(&dev->apbq_sem, 0, 1);
        dq_init(&dev->apbq);
  
        /* Reset the VS1053 chip */
  
        lower->reset(lower, false);
        up_udelay(10);
        lower->reset(lower, true);
        up_udelay(VS1053_RST_USECS);
  
  #if CONFIG_VS1053_XTALI == VS1053_DEFAULT_XTALI
  
        /* If we have a standard crystal, then wait extra time
         * for the DREQ to be active indicating the device is ready
         */
  
        retry = 200;
        while (!lower->read_dreq(lower) && retry)
          {
            up_udelay(10);
            retry--;
          }
  #endif
  
        /* Do device detection to validate the chip is there.
         * We have to hold the SPI lock during reads / writes.
         */
  
        vs1053_spi_lock(spi, dev->spi_freq);
        status = vs1053_readreg(dev, VS1053_SCI_STATUS);
        vs1053_spi_unlock(spi);
  
        /* Validate the device ID read from the chip */
  
        id = (status & VS1053_SS_VER) >> VS1053_VER_SHIFT;
        if (id != VS1053_VER_VS1053)
          {
            auderr("ERROR: Unexpected VER bits: 0x%0X\n", id);
            kmm_free(dev);
            return NULL;
          }
        else
          {
            audinfo("VS1053 Detected!\n");
          }
  
        /* Attach our ISR to this device */
  
        dev->hw_lower->attach(dev->hw_lower, vs1053_dreq_isr, dev);
  
        /* Do some initialization of the codec */
  
        vs1053_shutdown(&dev->lower);                   /* Go to shutdown state */
      }
  
    return &dev->lower;
  }
  