59bbcfdb创建于 4月21日历史提交
/****************************************************************************
 * arch/arm/src/samv7/sam_ssc.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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>

#include <arch/board/board.h>

#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/queue.h>
#include <nuttx/wdog.h>
#include <nuttx/wqueue.h>
#include <nuttx/audio/audio.h>
#include <nuttx/audio/i2s.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>

#include "arm_internal.h"
#include "sam_gpio.h"
#include "sam_xdmac.h"
#include "sam_periphclks.h"
#include "sam_ssc.h"
#include "hardware/sam_pmc.h"
#include "hardware/sam_ssc.h"
#include "hardware/sam_pinmap.h"

#if defined(CONFIG_SAMV7_SSC0) || defined(CONFIG_SAMV7_SSC1)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#ifndef CONFIG_SCHED_WORKQUEUE
#  error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
#endif

#ifndef CONFIG_AUDIO
#  error CONFIG_AUDIO required by this driver
#endif

#ifndef CONFIG_SAMV7_SSC_MAXINFLIGHT
#  define CONFIG_SAMV7_SSC_MAXINFLIGHT 16
#endif

#ifndef CONFIG_DEBUG_I2S_INFO
#  undef CONFIG_SAMV7_SSC_REGDEBUG
#endif

/* Assume no RX/TX support until we learn better */

#undef SSC_HAVE_RX
#undef SSC_HAVE_TX

/* Check for SSC0 support */

#if defined(CONFIG_SAMV7_SSC0)

#  ifndef CONFIG_SAMV7_XDMAC
#    error CONFIG_SAMV7_XDMAC required by SSC0
#  endif

  /* The SSC can handle most any bit width from 2 to 32.  However, the DMA
   * logic here is constrained to byte, half-word, and word sizes.
   */

#  ifndef CONFIG_SAMV7_SSC0_DATALEN
#    define CONFIG_SAMV7_SSC0_DATALEN 16
#  endif

#  if CONFIG_SAMV7_SSC0_DATALEN == 8
#    define SAMV7_SSC0_DATAMASK  0
#  elif CONFIG_SAMV7_SSC0_DATALEN == 16
#    define SAMV7_SSC0_DATAMASK  1
#  elif CONFIG_SAMV7_SSC0_DATALEN == 32
#    define SAMV7_SSC0_DATAMASK  3
#  elif  CONFIG_SAMV7_SSC0_DATALEN < 2 || CONFIG_SAMV7_SSC0_DATALEN > 32
#    error Invalid value for CONFIG_SAMV7_SSC0_DATALEN
#  else
#    error Valid but supported value for CONFIG_SAMV7_SSC0_DATALEN
#  endif

/* Check for SSC0 RX support */

#  if defined(CONFIG_SAMV7_SSC0_RX)
#    define SSC_HAVE_RX 1

#    ifndef CONFIG_SSC0_RX_FSLEN
#      define CONFIG_SSC0_RX_FSLEN 1
#    endif

#    if CONFIG_SSC0_RX_FSLEN < 1 || CONFIG_SSC0_RX_FSLEN > 255
#      error Invalid value for CONFIG_SSC0_RX_FSLEN
#    endif

#    ifndef CONFIG_SSC0_RX_STTDLY
#      define CONFIG_SSC0_RX_STTDLY CONFIG_SSC0_RX_FSLEN
#    endif

#    if CONFIG_SSC0_RX_STTDLY < 0 || \
        CONFIG_SSC0_RX_STTDLY < CONFIG_SSC0_RX_FSLEN || \
        CONFIG_SSC0_RX_STTDLY > 255
#      error Invalid value for CONFIG_SSC0_RX_STTDLY
#    endif
#  endif

/* Check for SSC0 TX support */

#  if defined(CONFIG_SAMV7_SSC0_TX)
#    define SSC_HAVE_TX 1

#    ifndef CONFIG_SSC0_TX_FSLEN
#      define CONFIG_SSC0_TX_FSLEN 0
#    endif

#    if CONFIG_SSC0_TX_FSLEN < 0 || CONFIG_SSC0_TX_FSLEN > 255
#      error Invalid value for CONFIG_SSC0_TX_FSLEN
#    endif

#    ifndef CONFIG_SSC0_TX_STTDLY
#      if CONFIG_SSC0_TX_FSLEN > 0
#        define CONFIG_SSC0_TX_STTDLY CONFIG_SSC0_TX_FSLEN
#      else
#        define CONFIG_SSC0_TX_STTDLY 0
#      endif
#    endif

#    if CONFIG_SSC0_TX_STTDLY < 0 || \
        CONFIG_SSC0_TX_STTDLY < CONFIG_SSC0_TX_FSLEN || \
        CONFIG_SSC0_TX_STTDLY > 255
#      error Invalid value for CONFIG_SSC0_TX_STTDLY
#    endif
#  endif

#endif

/* Check for SSC1 support */

#if defined(CONFIG_SAMV7_SSC1)

#  ifndef CONFIG_SAMV7_XDMAC
#    error CONFIG_SAMV7_XDMAC required by SSC1
#  endif

  /* The SSC can handle most any bit width from 2 to 32.  However, the DMA
   * logic here is constrained to byte, half-word, and word sizes.
   */

#  ifndef CONFIG_SAMV7_SSC1_DATALEN
#    define CONFIG_SAMV7_SSC1_DATALEN 16
#  endif

#  if CONFIG_SAMV7_SSC1_DATALEN == 8
#    define SAMV7_SSC1_DATAMASK  0
#  elif CONFIG_SAMV7_SSC1_DATALEN == 16
#    define SAMV7_SSC1_DATAMASK  1
#  elif CONFIG_SAMV7_SSC1_DATALEN == 32
#    define SAMV7_SSC1_DATAMASK  3
#  elif  CONFIG_SAMV7_SSC1_DATALEN < 2 || CONFIG_SAMV7_SSC1_DATALEN > 32
#    error Invalid value for CONFIG_SAMV7_SSC1_DATALEN
#  else
#    error Valid but supported value for CONFIG_SAMV7_SSC1_DATALEN
#  endif

/* Check for SSC1 RX support */

#  if defined(CONFIG_SAMV7_SSC1_RX)
#    define SSC_HAVE_RX 1

#    ifndef CONFIG_SSC1_RX_FSLEN
#      define CONFIG_SSC1_RX_FSLEN 1
#    endif

#    if CONFIG_SSC1_RX_FSLEN < 1 || CONFIG_SSC1_RX_FSLEN > 255
#      error Invalid value for CONFIG_SSC1_RX_FSLEN
#    endif

#    ifndef CONFIG_SSC1_RX_STTDLY
#      define CONFIG_SSC1_RX_STTDLY CONFIG_SSC1_RX_FSLEN
#    endif

#    if CONFIG_SSC1_RX_STTDLY < 0 || \
        CONFIG_SSC1_RX_STTDLY < CONFIG_SSC1_RX_FSLEN || \
        CONFIG_SSC1_RX_STTDLY > 255
#      error Invalid value for CONFIG_SSC1_RX_STTDLY
#    endif

#  endif

/* Check for SSC1 TX support */

#  if defined(CONFIG_SAMV7_SSC1_TX)
#    define SSC_HAVE_TX 1

#    ifndef CONFIG_SSC1_TX_FSLEN
#      define CONFIG_SSC1_TX_FSLEN 0
#    endif

#    if CONFIG_SSC1_TX_FSLEN < 0 || CONFIG_SSC1_TX_FSLEN > 255
#      error Invalid value for CONFIG_SSC1_TX_FSLEN
#    endif

#    ifndef CONFIG_SSC1_TX_STTDLY
#      if CONFIG_SSC1_TX_FSLEN > 0
#        define CONFIG_SSC1_TX_STTDLY CONFIG_SSC1_TX_FSLEN
#      else
#        define CONFIG_SSC1_TX_STTDLY 0
#      endif
#    endif

#    if CONFIG_SSC1_TX_STTDLY < 0 || \
        CONFIG_SSC1_TX_STTDLY < CONFIG_SSC1_TX_FSLEN || \
        CONFIG_SSC1_TX_STTDLY > 255
#      error Invalid value for CONFIG_SSC1_TX_STTDLY
#    endif
#  endif

#endif

/* Check if we need to build RX and/or TX support */

#if defined(SSC_HAVE_RX) || defined(SSC_HAVE_TX)

/* Check if we need the sample rate to set MCK/2 divider */

#undef SSC_HAVE_MCK2
#undef SSC0_HAVE_MCK2
#undef SSC1_HAVE_MCK2

#if (defined(CONFIG_SAMV7_SSC0_RX) && defined(CONFIG_SAMV7_SSC0_RX_MCKDIV)) || \
    (defined(CONFIG_SAMV7_SSC0_TX) && defined(CONFIG_SAMV7_SSC0_TX_MCKDIV))
#  define SSC0_HAVE_MCK2 1
#endif

#if (defined(CONFIG_SAMV7_SSC1_RX) && defined(CONFIG_SAMV7_SSC1_RX_MCKDIV)) || \
    (defined(CONFIG_SAMV7_SSC1_TX) && defined(CONFIG_SAMV7_SSC1_TX_MCKDIV))
#  define SSC1_HAVE_MCK2 1
#endif

#if defined(SSC0_HAVE_MCK2) || defined(SSC1_HAVE_MCK2)
#  define SSC_HAVE_MCK2 1
#endif

/* Waveform:
 *
 *      |<---------------- PERIOD --------------->|
 *  ----+     +-----------------------------------+    +---
 *      |     |                                   |    |
 *      +-----+                                   +----+
 *      |FSLEN|
 *      |<-STTDLY->|<--DATALEN-->|<--DATALEN-->|  |
 *                 |<-----DATALEN * DATNB----->|
 *
 * TK/RK is assumed to be a negative pulse
 * DATALEN is configurable: CONFIG_SAMV7_SSCx_DATALEN
 * FSLEN is configuration:  CONFIG_SAMV7_SSCx_RX/TX_FSLEN
 * FSLEN and STTDLY are fixed at two clocks
 * DATNB is fixed a one work
 *
 * REVISIT:  These will probably need to be configurable
 */

#define SSC_DATNB       (1) /* Number words per per frame */
#define SCC_PERIOD(s,d) ((s) + (d) * SSC_DATNB)

/* Clocking *****************************************************************/

/* Clock source definitions */

#define SSC_CLKSRC_NONE   0 /* No clock */
#define SSC_CLKSRC_MCKDIV 1 /* Clock source is MCK divided down */
#define SSC_CLKSRC_RXOUT  2 /* Transmitter clock source is the receiver clock */
#define SSC_CLKSRC_TXOUT  2 /* Receiver clock source is the transmitter clock */
#define SSC_CLKSRC_TKIN   3 /* Transmitter clock source is TK */
#define SSC_CLKSRC_RKIN   3 /* Receiver clock source is RK */

/* Clock output definitions */

#define SSC_CLKOUT_NONE   0 /* No output clock */
#define SSC_CLKOUT_CONT   1 /* Continuous */
#define SSC_CLKOUT_XFER   2 /* Only output clock during transfers */

/* System Bus Interfaces
 * REVISIT:  I believe that the SAMV1 has only a single APB.
 */

#warning REVISIT
#define DMACH_FLAG_PERIPH_IF DMACH_FLAG_PERIPHAHB_AHB_IF0
#define DMACH_FLAG_MEM_IF    DMACH_FLAG_MEMAHB_AHB_IF0

/* DMA configuration */

#define DMA8_FLAGS \
  (DMACH_FLAG_PERIPH_IF | DMACH_FLAG_PERIPHH2SEL | \
   DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_8BITS | \
   DMACH_FLAG_PERIPHCHUNKSIZE_1 | DMACH_FLAG_MEMPID_MAX | \
   DMACH_FLAG_MEM_IF | DMACH_FLAG_MEMWIDTH_16BITS | \
   DMACH_FLAG_MEMINCREMENT | DMACH_FLAG_MEMCHUNKSIZE_1 | \
   DMACH_FLAG_MEMBURST_4)

#define DMA16_FLAGS \
  (DMACH_FLAG_PERIPH_IF | DMACH_FLAG_PERIPHH2SEL | \
   DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_16BITS | \
   DMACH_FLAG_PERIPHCHUNKSIZE_1 | DMACH_FLAG_MEMPID_MAX | \
   DMACH_FLAG_MEM_IF | DMACH_FLAG_MEMWIDTH_16BITS | \
   DMACH_FLAG_MEMINCREMENT | DMACH_FLAG_MEMCHUNKSIZE_1 | \
   DMACH_FLAG_MEMBURST_4)

#define DMA32_FLAGS \
  (DMACH_FLAG_PERIPH_IF | DMACH_FLAG_PERIPHH2SEL | \
   DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_32BITS | \
   DMACH_FLAG_PERIPHCHUNKSIZE_1 | DMACH_FLAG_MEMPID_MAX | \
   DMACH_FLAG_MEM_IF | DMACH_FLAG_MEMWIDTH_32BITS | \
   DMACH_FLAG_MEMINCREMENT | DMACH_FLAG_MEMCHUNKSIZE_1 | \
   DMACH_FLAG_MEMBURST_4)

/* DMA timeout.  The value is not critical; we just don't want the system to
 * hang in the event that a DMA does not finish.  This is set to
 */

#define DMA_TIMEOUT_MS    (800)
#define DMA_TIMEOUT_TICKS MSEC2TICK(DMA_TIMEOUT_MS)

/* Debug ********************************************************************/

/* Check if SSC debug is enabled */

#ifndef CONFIG_DEBUG_I2S_INFO
#  undef CONFIG_SAMV7_SSC_DMADEBUG
#  undef CONFIG_SAMV7_SSC_REGDEBUG
#  undef CONFIG_SAMV7_SSC_QDEBUG
#  undef CONFIG_SAMV7_SSC_DUMPBUFFERS
#endif

#ifndef CONFIG_DEBUG_DMA
#  undef CONFIG_SAMV7_SSC_DMADEBUG
#endif

#define DMA_INITIAL      0
#define DMA_AFTER_SETUP  1
#define DMA_AFTER_START  2
#define DMA_CALLBACK     3
#define DMA_TIMEOUT      3
#define DMA_END_TRANSFER 4
#define DMA_NSAMPLES     5

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* I2S buffer container */

struct sam_buffer_s
{
  struct sam_buffer_s *flink;  /* Supports a singly linked list */
  i2s_callback_t callback;     /* Function to call when the transfer completes */
  uint32_t timeout;            /* The timeout value to use with DMA transfers */
  void *arg;                   /* The argument to be returned with the callback */
  struct ap_buffer_s *apb;     /* The audio buffer */
  int result;                  /* The result of the transfer */
};

/* This structure describes the state of one receiver or transmitter
 * transport
 */

struct sam_transport_s
{
  DMA_HANDLE dma;             /* SSC DMA handle */
  struct wdog_s dog;          /* Watchdog that handles DMA timeouts */
  sq_queue_t pend;            /* A queue of pending transfers */
  sq_queue_t act;             /* A queue of active transfers */
  sq_queue_t done;            /* A queue of completed transfers */
  struct work_s work;         /* Supports worker thread operations */

#ifdef CONFIG_SAMV7_SSC_DMADEBUG
  struct sam_dmaregs_s dmaregs[DMA_NSAMPLES];
#endif
};

/* The state of the one SSC peripheral */

struct sam_ssc_s
{
  struct i2s_dev_s dev;        /* Externally visible I2S interface */
  uintptr_t base;              /* SSC controller register base address */
  mutex_t lock;                /* Assures mutually exclusive access to SSC */
  uint8_t datalen;             /* Data width (8, 16, or 32) */
  uint8_t align;               /* Log2 of data width (0, 1, or 3) */
  uint8_t pid;                 /* Peripheral ID */
  uint8_t rxfslen;             /* RX frame sync length */
  uint8_t txfslen;             /* TX frame sync length */
  uint8_t rxsttdly;            /* RX start delay */
  uint8_t txsttdly;            /* TX start delay */
  uint8_t rxenab:1;            /* True: RX transfers enabled */
  uint8_t txenab:1;            /* True: TX transfers enabled */
  uint8_t loopback:1;          /* True: Loopback mode */
  uint8_t sscno:1;             /* SSC controller number (0 or 1) */
  uint8_t rxclk:2;             /* Receiver clock source. See SSC_CLKSRC_* definitions */
  uint8_t txclk:2;             /* Transmitter clock source. See SSC_CLKSRC_* definitions */
  uint8_t rxout:2;             /* Receiver clock output. See SSC_CLKOUT_* definitions */
  uint8_t txout:2;             /* Transmitter clock output. See SSC_CLKOUT_* definitions */
  uint32_t frequency;          /* SSC clock frequency */
#ifdef SSC_HAVE_MCK2
  uint32_t samplerate;         /* Data sample rate (determines only MCK/2 divider) */
#endif

#ifdef SSC_HAVE_RX
  struct sam_transport_s rx;   /* RX transport state */
#endif
#ifdef SSC_HAVE_TX
  struct sam_transport_s tx;   /* TX transport state */
#endif

  /* Pre-allocated pool of buffer containers */

  sem_t bufsem;                   /* Buffer wait semaphore */
  struct sam_buffer_s *freelist;  /* A list a free buffer containers */
  struct sam_buffer_s containers[CONFIG_SAMV7_SSC_MAXINFLIGHT];

  /* Debug stuff */

#ifdef CONFIG_SAMV7_SSC_REGDEBUG
  bool     wr;                 /* Last was a write */
  uint32_t regaddr;            /* Last address */
  uint32_t regval;             /* Last value */
  int      count;              /* Number of times */
#endif /* CONFIG_SAMV7_SSC_REGDEBUG */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Register helpers */

#ifdef CONFIG_SAMV7_SSC_REGDEBUG
static bool     ssc_checkreg(struct sam_ssc_s *priv, bool wr,
                  uint32_t regval, uint32_t regaddr);
#else
#  define       ssc_checkreg(priv,wr,regval,regaddr) (false)
#endif

static inline uint32_t ssc_getreg(struct sam_ssc_s *priv,
                  unsigned int offset);
static inline void ssc_putreg(struct sam_ssc_s *priv, unsigned int offset,
                  uint32_t regval);
static inline uintptr_t ssc_regaddr(struct sam_ssc_s *priv,
                  unsigned int offset);

#ifdef CONFIG_DEBUG_I2S_INFO
static void     scc_dump_regs(struct sam_ssc_s *priv, const char *msg);
#else
#  define       scc_dump_regs(s,m)
#endif

#ifdef CONFIG_SAMV7_SSC_QDEBUG
static void     ssc_dump_queues(struct sam_transport_s *xpt,
                  const char *msg);
#  define       ssc_dump_rxqueues(s,m) ssc_dump_queues(&(s)->rx,m)
#  define       ssc_dump_txqueues(s,m) ssc_dump_queues(&(s)->tx,m)
#else
#  define       ssc_dump_rxqueues(s,m)
#  define       ssc_dump_txqueues(s,m)
#endif

#ifdef CONFIG_SAMV7_SSC_DUMPBUFFERS
#  define       ssc_init_buffer(b,s)   memset(b, 0x55, s);
#  define       ssc_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
#else
#  define       ssc_init_buffer(b,s)
#  define       ssc_dump_buffer(m,b,s)
#endif

/* Buffer container helpers */

static struct sam_buffer_s *
                ssc_buf_allocate(struct sam_ssc_s *priv);
static void     ssc_buf_free(struct sam_ssc_s *priv,
                  struct sam_buffer_s *bfcontainer);
static void     ssc_buf_initialize(struct sam_ssc_s *priv);

/* DMA support */

#ifdef CONFIG_SAMV7_SSC_DMADEBUG
static void     ssc_dma_sampleinit(struct sam_ssc_s *priv,
                  struct sam_transport_s *xpt);
#endif

#if defined(CONFIG_SAMV7_SSC_DMADEBUG) && defined(SSC_HAVE_RX)
#  define       ssc_rxdma_sample(s,i) sam_dmasample((s)->rx.dma, &(s)->rx.dmaregs[i])
#  define       ssc_rxdma_sampleinit(s) ssc_dma_sampleinit(s, &(s)->rx)
static void     ssc_rxdma_sampledone(struct sam_ssc_s *priv, int result);

#else
#  define       ssc_rxdma_sample(s,i)
#  define       ssc_rxdma_sampleinit(s)
#  define       ssc_rxdma_sampledone(s,r)

#endif

#if defined(CONFIG_SAMV7_SSC_DMADEBUG) && defined(SSC_HAVE_TX)
#  define       ssc_txdma_sample(s,i) sam_dmasample((s)->tx.dma, &(s)->tx.dmaregs[i])
#  define       ssc_txdma_sampleinit(s) ssc_dma_sampleinit(s, &(s)->tx)
static void     ssc_txdma_sampledone(struct sam_ssc_s *priv, int result);

#else
#  define       ssc_txdma_sample(s,i)
#  define       ssc_txdma_sampleinit(s)
#  define       ssc_txdma_sampledone(s,r)

#endif

#ifdef SSC_HAVE_RX
static void     ssc_rxdma_timeout(wdparm_t arg);
static int      ssc_rxdma_setup(struct sam_ssc_s *priv);
static void     ssc_rx_worker(void *arg);
static void     ssc_rx_schedule(struct sam_ssc_s *priv, int result);
static void     ssc_rxdma_callback(DMA_HANDLE handle, void *arg, int result);
#endif
#ifdef SSC_HAVE_TX
static void     ssc_txdma_timeout(wdparm_t arg);
static int      ssc_txdma_setup(struct sam_ssc_s *priv);
static void     ssc_tx_worker(void *arg);
static void     ssc_tx_schedule(struct sam_ssc_s *priv, int result);
static void     ssc_txdma_callback(DMA_HANDLE handle, void *arg, int result);
#endif

/* I2S methods (and close friends) */

static int      ssc_checkwidth(struct sam_ssc_s *priv, int bits);

static uint32_t ssc_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t ssc_rxdatawidth(struct i2s_dev_s *dev, int bits);
static int      ssc_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
                  i2s_callback_t callback, void *arg, uint32_t timeout);
static uint32_t ssc_txsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t ssc_txdatawidth(struct i2s_dev_s *dev, int bits);
static int      ssc_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
                  i2s_callback_t callback, void *arg,
                  uint32_t timeout);

/* Initialization */

#ifdef SSC_HAVE_RX
static int      ssc_rx_configure(struct sam_ssc_s *priv);
#endif
#ifdef SSC_HAVE_TX
static int      ssc_tx_configure(struct sam_ssc_s *priv);
#endif
static uint32_t ssc_mck2divider(struct sam_ssc_s *priv);
static void     ssc_clocking(struct sam_ssc_s *priv);
static int      ssc_dma_flags(struct sam_ssc_s *priv, uint32_t *dmaflags);
static int      ssc_dma_allocate(struct sam_ssc_s *priv);
static void     ssc_dma_free(struct sam_ssc_s *priv);
#ifdef CONFIG_SAMV7_SSC0
static void     ssc0_configure(struct sam_ssc_s *priv);
#endif
#ifdef CONFIG_SAMV7_SSC1
static void     ssc1_configure(struct sam_ssc_s *priv);
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* I2S device operations */

static const struct i2s_ops_s g_sscops =
{
  /* Receiver methods */

  .i2s_rxsamplerate = ssc_rxsamplerate,
  .i2s_rxdatawidth  = ssc_rxdatawidth,
  .i2s_receive      = ssc_receive,

  /* Transmitter methods */

  .i2s_txsamplerate = ssc_txsamplerate,
  .i2s_txdatawidth  = ssc_txdatawidth,
  .i2s_send         = ssc_send,
};

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: ssc_checkreg
 *
 * Description:
 *   Check if the current register access is a duplicate of the preceding.
 *
 * Input Parameters:
 *   regval  - The value to be written
 *   regaddr - The address of the register to write to
 *
 * Returned Value:
 *   true:  This is the first register access of this type.
 *   flase: This is the same as the preceding register access.
 *
 ****************************************************************************/

#ifdef CONFIG_SAMV7_SSC_REGDEBUG
static bool ssc_checkreg(struct sam_ssc_s *priv, bool wr, uint32_t regval,
                         uint32_t regaddr)
{
  if (wr      == priv->wr &&     /* Same kind of access? */
      regval  == priv->regval && /* Same value? */
      regaddr == priv->regaddr)  /* Same address? */
    {
      /* Yes, then just keep a count of the number of times we did this. */

      priv->count++;
      return false;
    }
  else
    {
      /* Did we do the previous operation more than once? */

      if (priv->count > 0)
        {
          /* Yes... show how many times we did it */

          i2sinfo("...[Repeats %d times]...\n", priv->count);
        }

      /* Save information about the new access */

      priv->wr      = wr;
      priv->regval  = regval;
      priv->regaddr = regaddr;
      priv->count   = 0;
    }

  /* Return true if this is the first time that we have done this operation */

  return true;
}
#endif

/****************************************************************************
 * Name: ssc_getreg
 *
 * Description:
 *  Read an SSC register
 *
 ****************************************************************************/

static inline uint32_t ssc_getreg(struct sam_ssc_s *priv,
                                  unsigned int offset)
{
  uint32_t regaddr = priv->base + offset;
  uint32_t regval = getreg32(regaddr);

#ifdef CONFIG_SAMV7_SSC_REGDEBUG
  if (ssc_checkreg(priv, false, regval, regaddr))
    {
      i2sinfo("%08x->%08x\n", regaddr, regval);
    }
#endif

  return regval;
}

/****************************************************************************
 * Name: ssc_putreg
 *
 * Description:
 *  Write a value to an SSC register
 *
 ****************************************************************************/

static inline void ssc_putreg(struct sam_ssc_s *priv, unsigned int offset,
                              uint32_t regval)
{
  uint32_t regaddr = priv->base + offset;

#ifdef CONFIG_SAMV7_SSC_REGDEBUG
  if (ssc_checkreg(priv, true, regval, regaddr))
    {
      i2sinfo("%08x<-%08x\n", regaddr, regval);
    }
#endif

  putreg32(regval, regaddr);
}

/****************************************************************************
 * Name: ssc_regaddr
 *
 * Description:
 *   Return the address of an SSC register
 *
 ****************************************************************************/

static inline uintptr_t ssc_regaddr(struct sam_ssc_s *priv,
                                    unsigned int offset)
{
  return priv->base + offset;
}

/****************************************************************************
 * Name: scc_dump_regs
 *
 * Description:
 *   Dump the contents of all SSC registers
 *
 * Input Parameters:
 *   priv - The SSC controller to dump
 *   msg - Message to print before the register data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_I2S_INFO
static void scc_dump_regs(struct sam_ssc_s *priv, const char *msg)
{
  i2sinfo("SSC%d: %s\n", priv->sscno, msg);
  i2sinfo("   CMR:%08x RCMR:%08x RFMR:%08x TCMR:%08x\n",
          getreg32(priv->base + SAM_SSC_CMR_OFFSET),
          getreg32(priv->base + SAM_SSC_RCMR_OFFSET),
          getreg32(priv->base + SAM_SSC_RFMR_OFFSET),
          getreg32(priv->base + SAM_SSC_TCMR_OFFSET));
  i2sinfo("  TFMR:%08x RC0R:%08x RC1R:%08x   SR:%08x\n",
          getreg32(priv->base + SAM_SSC_TFMR_OFFSET),
          getreg32(priv->base + SAM_SSC_RC0R_OFFSET),
          getreg32(priv->base + SAM_SSC_RC1R_OFFSET),
          getreg32(priv->base + SAM_SSC_SR_OFFSET));
  i2sinfo("   IMR:%08x WPMR:%08x WPSR:%08x\n",
          getreg32(priv->base + SAM_SSC_IMR_OFFSET),
          getreg32(priv->base + SAM_SSC_WPMR_OFFSET),
          getreg32(priv->base + SAM_SSC_WPSR_OFFSET));
}
#endif

/****************************************************************************
 * Name: ssc_dump_queues
 *
 * Description:
 *   Dump the contents of transport queues
 *
 * Input Parameters:
 *   priv - The SSC controller to dump
 *   xpt - The transport to be dumped
 *   msg - Message to print before the register data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_SAMV7_SSC_QDEBUG
static void ssc_dump_queue(sq_queue_t *queue)
{
  struct sam_buffer_s *bfcontainer;
  struct ap_buffer_s *apb;
  sq_entry_t *entry;

  for (entry = queue->head; entry; entry = entry->flink)
    {
      bfcontainer = (struct sam_buffer_s *)entry;
      apb = bfcontainer->apb;

      if (!apb)
        {
          i2sinfo("    %p: No buffer\n", bfcontainer);
        }
      else
        {
          i2sinfo("    %p: buffer=%p nmaxbytes=%d nbytes=%d\n",
                  bfcontainer, apb, apb->nmaxbytes, apb->nbytes);
        }
    }
}

static void ssc_dump_queues(struct sam_transport_s *xpt, const char *msg)
{
  irqstate_t flags;

  flags = enter_critical_section();
  i2sinfo("%s\n", msg);
  i2sinfo("  Pending:\n");
  ssc_dump_queue(&xpt->pend);
  i2sinfo("  Active:\n");
  ssc_dump_queue(&xpt->act);
  i2sinfo("  Done:\n");
  ssc_dump_queue(&xpt->done);
  leave_critical_section(flags);
}
#endif

/****************************************************************************
 * Name: ssc_buf_allocate
 *
 * Description:
 *   Allocate a buffer container by removing the one at the head of the
 *   free list
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   A non-NULL pointer to the allocate buffer container on success; NULL if
 *   there are no available buffer containers.
 *
 * Assumptions:
 *   The caller does NOT have exclusive access to the SSC state structure.
 *   That would result in a deadlock!
 *
 ****************************************************************************/

static struct sam_buffer_s *ssc_buf_allocate(struct sam_ssc_s *priv)
{
  struct sam_buffer_s *bfcontainer;
  irqstate_t flags;
  int ret;

  /* Set aside a buffer container.  By doing this, we guarantee that we will
   * have at least one free buffer container.
   */

  ret = nxsem_wait_uninterruptible(&priv->bufsem);
  if (ret < 0)
    {
      return NULL;
    }

  /* Get the buffer from the head of the free list */

  flags = enter_critical_section();
  bfcontainer = priv->freelist;
  DEBUGASSERT(bfcontainer);

  /* Unlink the buffer from the freelist */

  priv->freelist = bfcontainer->flink;
  leave_critical_section(flags);
  return bfcontainer;
}

/****************************************************************************
 * Name: ssc_buf_free
 *
 * Description:
 *   Free buffer container by adding it to the head of the free list
 *
 * Input Parameters:
 *   priv - SSC state instance
 *   bfcontainer - The buffer container to be freed
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   The caller has exclusive access to the SSC state structure
 *
 ****************************************************************************/

static void ssc_buf_free(struct sam_ssc_s *priv,
                         struct sam_buffer_s *bfcontainer)
{
  irqstate_t flags;

  /* Put the buffer container back on the free list */

  flags = enter_critical_section();
  bfcontainer->flink  = priv->freelist;
  priv->freelist = bfcontainer;
  leave_critical_section(flags);

  /* Wake up any threads waiting for a buffer container */

  nxsem_post(&priv->bufsem);
}

/****************************************************************************
 * Name: ssc_buf_initialize
 *
 * Description:
 *   Initialize the buffer container allocator by adding all of the
 *   pre-allocated buffer containers to the free list
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Called early in SSC initialization so that there are no issues with
 *   concurrency.
 *
 ****************************************************************************/

static void ssc_buf_initialize(struct sam_ssc_s *priv)
{
  int i;

  priv->freelist = NULL;
  nxsem_init(&priv->bufsem, 0, CONFIG_SAMV7_SSC_MAXINFLIGHT);

  for (i = 0; i < CONFIG_SAMV7_SSC_MAXINFLIGHT; i++)
    {
      ssc_buf_free(priv, &priv->containers[i]);
    }
}

/****************************************************************************
 * Name: ssc_dma_sampleinit
 *
 * Description:
 *   Initialize sampling of DMA registers (if CONFIG_SAMV7_SSC_DMADEBUG)
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(CONFIG_SAMV7_SSC_DMADEBUG) && defined(SSC_HAVE_RX)
static void ssc_dma_sampleinit(struct sam_ssc_s *priv,
                               struct sam_transport_s *xpt)
{
  /* Put contents of register samples into a known state */

  memset(xpt->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct sam_dmaregs_s));

  /* Then get the initial samples */

  sam_dmasample(xpt->dma, &xpt->dmaregs[DMA_INITIAL]);
}
#endif

/****************************************************************************
 * Name: ssc_rxdma_sampledone
 *
 * Description:
 *   Dump sampled RX DMA registers
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(CONFIG_SAMV7_SSC_DMADEBUG) && defined(SSC_HAVE_RX)
static void ssc_rxdma_sampledone(struct sam_ssc_s *priv, int result)
{
  i2llsinfo("result: %d\n", result);

  /* Sample the final registers */

  sam_dmasample(priv->rx.dma, &priv->rx.dmaregs[DMA_END_TRANSFER]);

  /* Then dump the sampled DMA registers */

  /* Initial register values */

  sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_INITIAL],
              "RX: Initial Registers");

  /* Register values after DMA setup */

  sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_SETUP],
              "RX: After DMA Setup");

  /* Register values after DMA start */

  sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_START],
              "RX: After DMA Start");

  /* Register values at the time of the TX and RX DMA callbacks
   * -OR- DMA timeout.
   *
   * If the DMA timedout, then there will not be any RX DMA
   * callback samples.  There is probably no TX DMA callback
   * samples either, but we don't know for sure.
   */

  if (result == -ETIMEDOUT || result == -EINTR)
    {
      sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_TIMEOUT],
                  "RX: At DMA timeout");
    }
  else
    {
      sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_CALLBACK],
                  "RX: At DMA callback");
    }

  sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_END_TRANSFER],
              "RX: At End-of-Transfer");

  scc_dump_regs(priv, "RX: At End-of-Transfer");
}
#endif

/****************************************************************************
 * Name: ssc_txdma_sampledone
 *
 * Description:
 *   Dump sampled DMA registers
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(CONFIG_SAMV7_SSC_DMADEBUG) && defined(SSC_HAVE_TX)
static void ssc_txdma_sampledone(struct sam_ssc_s *priv, int result)
{
  i2sinfo("result: %d\n", result);

  /* Sample the final registers */

  sam_dmasample(priv->tx.dma, &priv->tx.dmaregs[DMA_END_TRANSFER]);

  /* Then dump the sampled DMA registers */

  /* Initial register values */

  sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_INITIAL],
              "TX: Initial Registers");

  /* Register values after DMA setup */

  sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_SETUP],
              "TX: After DMA Setup");

  /* Register values after DMA start */

  sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_START],
              "TX: After DMA Start");

  /* Register values at the time of the TX and RX DMA callbacks
   * -OR- DMA timeout.
   */

  if (result == -ETIMEDOUT || result == -EINTR)
    {
      sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_TIMEOUT],
                  "TX: At DMA timeout");
    }
  else
    {
      sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_CALLBACK],
                  "TX: At DMA callback");
    }

  sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_END_TRANSFER],
              "TX: At End-of-Transfer");

  scc_dump_regs(priv, "TX: At End-of-Transfer");
}
#endif

/****************************************************************************
 * Name: ssc_rxdma_timeout
 *
 * Description:
 *   The RX watchdog timeout without completion of the RX DMA.
 *
 * Input Parameters:
 *   arg    - The argument
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Always called from the interrupt level with interrupts disabled.
 *
 ****************************************************************************/

#ifdef SSC_HAVE_RX
static void ssc_rxdma_timeout(wdparm_t arg)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  DEBUGASSERT(priv != NULL);

  /* Sample DMA registers at the time of the timeout */

  ssc_rxdma_sample(priv, DMA_TIMEOUT);

  /* Cancel the DMA */

  sam_dmastop(priv->rx.dma);

  /* Then schedule completion of the transfer to occur on the worker thread.
   * NOTE: sam_dmastop() will call the DMA complete callback with an error
   * of -EINTR.  So the following is just insurance and should have no
   * effect if the worker is already schedule.
   */

  ssc_rx_schedule(priv, -ETIMEDOUT);
}
#endif

/****************************************************************************
 * Name: ssc_rxdma_setup
 *
 * Description:
 *   Setup and initiate the next RX DMA transfer
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   OK on success; a negated errno value on failure
 *
 * Assumptions:
 *   Interrupts are disabled
 *
 ****************************************************************************/

#ifdef SSC_HAVE_RX
static int ssc_rxdma_setup(struct sam_ssc_s *priv)
{
  struct sam_buffer_s *bfcontainer;
  struct ap_buffer_s *apb;
  uintptr_t regaddr;
  uintptr_t memaddr;
  uint32_t timeout;
  bool notimeout;
  int ret;

  /* If there is already an active transmission in progress, then bail
   * returning success.
   */

  if (!sq_empty(&priv->rx.act))
    {
      return OK;
    }

  /* If there are no pending transfer, then bail returning success */

  if (sq_empty(&priv->rx.pend))
    {
      return OK;
    }

  /* Initialize DMA register sampling */

  ssc_rxdma_sampleinit(priv);

  /* Loop, adding each pending DMA */

  timeout = 0;
  notimeout = false;

  do
    {
      /* Remove the pending RX transfer at the head of the RX pending
       * queue.
       */

      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rx.pend);
      DEBUGASSERT(bfcontainer && bfcontainer->apb);

      apb = bfcontainer->apb;
      DEBUGASSERT(((uintptr_t)apb->samp % priv->align) == 0);

      /* No data received yet */

      apb->nbytes  = 0;
      apb->curbyte = 0;

      /* Physical address of the SSC RHR register and of the buffer location
       * in RAM.
       */

      regaddr = ssc_regaddr(priv, SAM_SSC_RHR_OFFSET);
      memaddr = (uintptr_t)apb->samp;

      /* Configure the RX DMA */

      sam_dmarxsetup(priv->rx.dma, regaddr, memaddr, apb->nmaxbytes);

      /* Increment the DMA timeout */

      if (bfcontainer->timeout > 0)
        {
          timeout += bfcontainer->timeout;
        }
      else
        {
          notimeout = true;
        }

      /* Add the container to the list of active DMAs */

      sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);

      /* Invalidate the data cache so that nothing gets flush into the
       * DMA buffer after starting the DMA transfer.
       */
#warning Not yet supported
#if 0
      up_invalidate_dcache((uintptr_t)apb->samp,
                           (uintptr_t)apb->samp + apb->nmaxbytes);
#endif
    }
#if 1 /* REVISIT: Chained RX transfers */
  while (0);
#else
  while (!sq_empty(&priv->rx.pend));
#endif

  /* Sample DMA registers */

  ssc_rxdma_sample(priv, DMA_AFTER_SETUP);

  /* Start the DMA, saving the container as the current active transfer */

  sam_dmastart(priv->rx.dma, ssc_rxdma_callback, priv);
  ssc_rxdma_sample(priv, DMA_AFTER_START);

  /* Enable the receiver */

  ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXEN);

  /* Start a watchdog to catch DMA timeouts */

  if (!notimeout)
    {
      ret = wd_start(&priv->rx.dog, timeout,
                     ssc_rxdma_timeout, (wdparm_t)priv);

      /* Check if we have successfully started the watchdog timer.  Note
       * that we do nothing in the case of failure to start the timer.  We
       * are already committed to the DMA anyway.  Let's just hope that the
       * DMA does not hang.
       */

      if (ret < 0)
        {
          i2serr("ERROR: wd_start failed: %d\n", ret);
        }
    }

  ssc_dump_rxqueues(priv, "RX DMA started");
  return OK;
}
#endif

/****************************************************************************
 * Name: ssc_rx_worker
 *
 * Description:
 *   RX transfer done worker
 *
 * Input Parameters:
 *   arg - the SSC device instance cast to void*
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SSC_HAVE_RX
static void ssc_rx_worker(void *arg)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  struct sam_buffer_s *bfcontainer;
  struct ap_buffer_s *apb;
  irqstate_t flags;

  DEBUGASSERT(priv);

  /* When the transfer was started, the active buffer containers were
   * removed from the rx.pend queue and saved in the rx.act queue.  We get
   * here when the DMA is finished... either successfully, with a DMA
   * error, or with a DMA timeout.
   *
   * In any case, the buffer containers in rx.act will be moved to the end
   * of the rx.done queue and rx.act queue will be emptied before this worker
   * is started.
   *
   * REVISIT: Normal DMA callback processing should restart the DMA
   * immediately to avoid audio artifacts at the boundaries between DMA
   * transfers.  Unfortunately, the DMA callback occurs at the interrupt
   * level and we cannot call dma_rxsetup() from the interrupt level.
   * So we have to start the next DMA here.
   */

  i2sinfo("rx.act.head=%p rx.done.head=%p\n",
          priv->rx.act.head, priv->rx.done.head);
  ssc_dump_rxqueues(priv, "RX worker start");

  /* Check if the DMA is IDLE */

  if (sq_empty(&priv->rx.act))
    {
#ifdef CONFIG_SAMV7_SSC_DMADEBUG
      bfcontainer = (struct sam_buffer_s *)sq_peek(&priv->rx.done);
      if (bfcontainer)
        {
          /* Dump the DMA registers */

          ssc_rxdma_sampledone(priv, bfcontainer->result);
        }
#endif

      /* Then start the next DMA.  This must be done with interrupts
       * disabled.
       */

      flags = enter_critical_section();
      ssc_rxdma_setup(priv);
      leave_critical_section(flags);
    }

  /* Process each buffer in the rx.done queue */

  while (sq_peek(&priv->rx.done) != NULL)
    {
      /* Remove the buffer container from the rx.done queue.  NOTE that
       * interrupts must be enabled to do this because the rx.done queue is
       * also modified from the interrupt level.
       */

      flags = enter_critical_section();
      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rx.done);
      leave_critical_section(flags);

      DEBUGASSERT(bfcontainer && bfcontainer->apb && bfcontainer->callback);
      apb = bfcontainer->apb;

      /* If the DMA was successful, then update the number of valid bytes in
       * the audio buffer.
       */

      if (bfcontainer->result == OK)
        {
          apb->nbytes = apb->nmaxbytes;
        }

      ssc_dump_buffer("Received", apb->samp, apb->nbytes);

      /* Perform the RX transfer done callback */

      bfcontainer->callback(&priv->dev, apb, bfcontainer->arg,
                            bfcontainer->result);

      /* Release our reference on the audio buffer.  This may very likely
       * cause the audio buffer to be freed.
       */

      apb_free(apb);

      /* And release the buffer container */

      ssc_buf_free(priv, bfcontainer);
    }

  ssc_dump_rxqueues(priv, "RX worker done");
}
#endif

/****************************************************************************
 * Name: ssc_rx_schedule
 *
 * Description:
 *   An RX DMA completion or timeout has occurred.  Schedule processing on
 *   the working thread.
 *
 * Input Parameters:
 *   handle - The DMA handler
 *   arg - A pointer to the chip select struction
 *   result - The result of the DMA transfer
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Interrupts are disabled
 *
 ****************************************************************************/

#ifdef SSC_HAVE_RX
static void ssc_rx_schedule(struct sam_ssc_s *priv, int result)
{
  struct sam_buffer_s *bfcontainer;
  int ret;

  /* Upon entry, the transfer(s) that just completed are the ones in the
   * priv->rx.act queue.  NOTE: In certain conditions, this function may
   * be called an additional time, hence, we can't assert this to be true.
   * For example, in the case of a timeout, this function will be called by
   * both indirectly via the sam_dmastop() logic and directly via the
   * ssc_rxdma_timeout() logic.
   */

  ssc_dump_rxqueues(priv, "RX schedule");

  /* Move all entries from the rx.act queue to the rx.done queue */

  while (!sq_empty(&priv->rx.act))
    {
      /* Remove the next buffer container from the rx.act list */

      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rx.act);

      /* Report the result of the transfer */

      bfcontainer->result = result;

      /* Add the completed buffer container to the tail of the rx.done
       * queue
       */

      sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done);
    }

  /* If the worker has completed running, then reschedule the working thread.
   * REVISIT:  There may be a race condition here.  So we do nothing is the
   * worker is not available.
   */

  if (work_available(&priv->rx.work))
    {
      /* Schedule the TX DMA done processing to occur on the worker thread. */

      ret = work_queue(HPWORK, &priv->rx.work, ssc_rx_worker, priv, 0);
      if (ret != 0)
        {
          i2serr("ERROR: Failed to queue RX work: %d\n", ret);
        }
    }
}
#endif

/****************************************************************************
 * Name: ssc_rxdma_callback
 *
 * Description:
 *   This callback function is invoked at the completion of the SSC RX DMA.
 *
 * Input Parameters:
 *   handle - The DMA handler
 *   arg - A pointer to the chip select struction
 *   result - The result of the DMA transfer
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SSC_HAVE_RX
static void ssc_rxdma_callback(DMA_HANDLE handle, void *arg, int result)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  DEBUGASSERT(priv != NULL);

  /* Cancel the watchdog timeout */

  wd_cancel(&priv->rx.dog);

  /* Sample DMA registers at the time of the DMA completion */

  ssc_rxdma_sample(priv, DMA_CALLBACK);

  /* REVISIT:  We would like to the next DMA started here so that we do not
   * get audio glitches at the boundaries between DMA transfers.
   * Unfortunately, we cannot call sam_dmasetup() from an interrupt handler!
   */

  /* Then schedule completion of the transfer to occur on the worker thread */

  ssc_rx_schedule(priv, result);
}
#endif

/****************************************************************************
 * Name: ssc_txdma_timeout
 *
 * Description:
 *   The RX watchdog timeout without completion of the RX DMA.
 *
 * Input Parameters:
 *   arg    - The argument
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Always called from the interrupt level with interrupts disabled.
 *
 ****************************************************************************/

#ifdef SSC_HAVE_TX
static void ssc_txdma_timeout(wdparm_t arg)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  DEBUGASSERT(priv != NULL);

  /* Sample DMA registers at the time of the timeout */

  ssc_txdma_sample(priv, DMA_TIMEOUT);

  /* Cancel the DMA */

  sam_dmastop(priv->tx.dma);

  /* Then schedule completion of the transfer to occur on the worker thread.
   * NOTE: sam_dmastop() will call the DMA complete callback with an error
   * of -EINTR.  So the following is just insurance and should have no
   * effect if the worker is already schedule.
   */

  ssc_tx_schedule(priv, -ETIMEDOUT);
}
#endif

/****************************************************************************
 * Name: ssc_txdma_setup
 *
 * Description:
 *   Setup and initiate the next TX DMA transfer
 *
 * Input Parameters:
 *   priv - SSC state instance
 *
 * Returned Value:
 *   OK on success; a negated errno value on failure
 *
 * Assumptions:
 *   Interrupts are disabled
 *
 ****************************************************************************/

#ifdef SSC_HAVE_TX
static int ssc_txdma_setup(struct sam_ssc_s *priv)
{
  struct sam_buffer_s *bfcontainer;
  struct ap_buffer_s *apb;
  uintptr_t samp;
  uintptr_t regaddr;
  uintptr_t memaddr;
  uint32_t timeout;
  apb_samp_t nbytes;
  bool notimeout;
  int ret;

  /* If there is already an active transmission in progress, then bail
   * returning success.
   */

  if (!sq_empty(&priv->tx.act))
    {
      return OK;
    }

  /* If there are no pending transfer, then bail returning success */

  if (sq_empty(&priv->tx.pend))
    {
      return OK;
    }

  /* Initialize DMA register sampling */

  ssc_txdma_sampleinit(priv);

  /* Loop, adding each pending DMA */

  timeout = 0;
  notimeout = false;

  do
    {
      /* Remove the pending TX transfer at the head of the TX pending
       * queue.
       */

      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.pend);
      DEBUGASSERT(bfcontainer && bfcontainer->apb);

      apb = bfcontainer->apb;

      /* Get the transfer information, accounting for any data offset */

      samp   = (uintptr_t)&apb->samp[apb->curbyte];
      nbytes = apb->nbytes - apb->curbyte;
      DEBUGASSERT((samp & priv->align) == 0 && (nbytes & priv->align) == 0);

      /* Physical address of the SSC THR register and of the buffer location
       * in RAM.
       */

      regaddr = ssc_regaddr(priv, SAM_SSC_THR_OFFSET);
      memaddr = (uintptr_t)samp;

      /* Configure the TX DMA */

      sam_dmatxsetup(priv->tx.dma, regaddr, memaddr, nbytes);

      /* Increment the DMA timeout */

      if (bfcontainer->timeout > 0)
        {
          timeout += bfcontainer->timeout;
        }
      else
        {
          notimeout = true;
        }

      /* Add the container to the list of active DMAs */

      sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);

      /* Flush the data cache so that everything is in the physical memory
       * before starting the DMA.
       */
#warning REVISIT
#if 1
      up_invalidate_dcache_all();
#else
      up_clean_dcache(samp, samp + nbytes);
#endif
    }
#if 1 /* REVISIT: Chained TX transfers */
  while (0);
#else
  while (!sq_empty(&priv->tx.pend));
#endif

  /* Sample DMA registers */

  ssc_txdma_sample(priv, DMA_AFTER_SETUP);

  /* Start the DMA, saving the container as the current active transfer */

  sam_dmastart(priv->tx.dma, ssc_txdma_callback, priv);
  ssc_txdma_sample(priv, DMA_AFTER_START);

  /* Enable the transmitter */

  ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_TXEN);

  /* Start a watchdog to catch DMA timeouts */

  if (!notimeout)
    {
      ret = wd_start(&priv->tx.dog, timeout,
                     ssc_txdma_timeout, (wdparm_t)priv);

      /* Check if we have successfully started the watchdog timer.  Note
       * that we do nothing in the case of failure to start the timer.  We
       * are already committed to the DMA anyway.  Let's just hope that the
       * DMA does not hang.
       */

      if (ret < 0)
        {
          i2serr("ERROR: wd_start failed: %d\n", ret);
        }
    }

  ssc_dump_txqueues(priv, "TX DMA started");
  return OK;
}
#endif

/****************************************************************************
 * Name: ssc_tx_worker
 *
 * Description:
 *   TX transfer done worker
 *
 * Input Parameters:
 *   arg - the SSC device instance cast to void*
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SSC_HAVE_TX
static void ssc_tx_worker(void *arg)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  struct sam_buffer_s *bfcontainer;
  irqstate_t flags;

  DEBUGASSERT(priv);

  /* When the transfer was started, the active buffer containers were
   * removed from the tx.pend queue and saved in the tx.act queue.  We get
   * here when the DMA is finished... either successfully, with a DMA
   * error, or with a DMA timeout.
   *
   * In any case, the buffer containers in tx.act will be moved to the end
   * of the tx.done queue and tx.act will be emptied before this worker is
   * started.
   *
   * REVISIT: Normal DMA callback processing should restart the DMA
   * immediately to avoid audio artifacts at the boundaries between DMA
   * transfers.  Unfortunately, the DMA callback occurs at the interrupt
   * level and we cannot call dma_txsetup() from the interrupt level.
   * So we have to start the next DMA here.
   */

  i2sinfo("tx.act.head=%p tx.done.head=%p\n",
           priv->tx.act.head, priv->tx.done.head);
  ssc_dump_txqueues(priv, "TX worker start");

  /* Check if the DMA is IDLE */

  if (sq_empty(&priv->tx.act))
    {
#ifdef CONFIG_SAMV7_SSC_DMADEBUG
      bfcontainer = (struct sam_buffer_s *)sq_peek(&priv->tx.done);
      if (bfcontainer)
        {
          /* Dump the DMA registers */

          ssc_txdma_sampledone(priv, bfcontainer->result);
        }
#endif

      /* Then start the next DMA.  This must be done with interrupts
       * disabled.
       */

      flags = enter_critical_section();
      ssc_txdma_setup(priv);
      leave_critical_section(flags);
    }

  /* Process each buffer in the tx.done queue */

  while (sq_peek(&priv->tx.done) != NULL)
    {
      /* Remove the buffer container from the tx.done queue.  NOTE that
       * interrupts must be enabled to do this because the tx.done queue is
       * also modified from the interrupt level.
       */

      flags = enter_critical_section();
      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.done);
      leave_critical_section(flags);

      /* Perform the TX transfer done callback */

      DEBUGASSERT(bfcontainer && bfcontainer->callback);
      bfcontainer->callback(&priv->dev, bfcontainer->apb,
                            bfcontainer->arg, bfcontainer->result);

      /* Release our reference on the audio buffer.  This may very likely
       * cause the audio buffer to be freed.
       */

      apb_free(bfcontainer->apb);

      /* And release the buffer container */

      ssc_buf_free(priv, bfcontainer);
    }

  ssc_dump_txqueues(priv, "TX worker done");
}
#endif

/****************************************************************************
 * Name: ssc_tx_schedule
 *
 * Description:
 *   An TX DMA completion or timeout has occurred.  Schedule processing on
 *   the working thread.
 *
 * Input Parameters:
 *   handle - The DMA handler
 *   arg - A pointer to the chip select struction
 *   result - The result of the DMA transfer
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   - Interrupts are disabled
 *   - The TX timeout has been canceled.
 *
 ****************************************************************************/

#ifdef SSC_HAVE_TX
static void ssc_tx_schedule(struct sam_ssc_s *priv, int result)
{
  struct sam_buffer_s *bfcontainer;
  int ret;

  /* Upon entry, the transfer(s) that just completed are the ones in the
   * priv->tx.act queue.  NOTE: In certain conditions, this function may
   * be called an additional time, hence, we can't assert this to be true.
   * For example, in the case of a timeout, this function will be called by
   * both indirectly via the sam_dmastop() logic and directly via the
   * ssc_txdma_timeout() logic.
   */

  ssc_dump_txqueues(priv, "TX schedule");

  /* Move all entries from the tx.act queue to the tx.done queue */

  while (!sq_empty(&priv->tx.act))
    {
      /* Remove the next buffer container from the tx.act list */

      bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.act);

      /* Report the result of the transfer */

      bfcontainer->result = result;

      /* Add the completed buffer container to the tail of the tx.done
       * queue
       */

      sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
    }

  /* If the worker has completed running, then reschedule the working thread.
   * REVISIT:  There may be a race condition here.  So we do nothing is the
   * worker is not available.
   */

  if (work_available(&priv->tx.work))
    {
      /* Schedule the TX DMA done processing to occur on the worker thread. */

      ret = work_queue(HPWORK, &priv->tx.work, ssc_tx_worker, priv, 0);
      if (ret != 0)
        {
          i2serr("ERROR: Failed to queue TX work: %d\n", ret);
        }
    }
}
#endif

/****************************************************************************
 * Name: ssc_txdma_callback
 *
 * Description:
 *   This callback function is invoked at the completion of the SSC TX DMA.
 *
 * Input Parameters:
 *   handle - The DMA handler
 *   arg - A pointer to the chip select struction
 *   result - The result of the DMA transfer
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SSC_HAVE_TX
static void ssc_txdma_callback(DMA_HANDLE handle, void *arg, int result)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)arg;
  DEBUGASSERT(priv != NULL);

  /* Cancel the watchdog timeout */

  wd_cancel(&priv->tx.dog);

  /* Sample DMA registers at the time of the DMA completion */

  ssc_txdma_sample(priv, DMA_CALLBACK);

  /* REVISIT:  We would like to the next DMA started here so that we do not
   * get audio glitches at the boundaries between DMA transfers.
   * Unfortunately, we cannot call sam_dmasetup() from an interrupt handler!
   */

  /* Then schedule completion of the transfer to occur on the worker thread */

  ssc_tx_schedule(priv, result);
}
#endif

/****************************************************************************
 * Name: ssc_checkwidth
 *
 * Description:
 *   Check for a valid bit width.  The SSC is capable of handling most any
 *   bit width from 2 to 32, but the DMA logic in this driver is constrained
 *   to 8-, 16-, and 32-bit data widths
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   rate - The I2S sample rate in samples (not bits) per second
 *
 * Returned Value:
 *   Returns the resulting bitrate
 *
 ****************************************************************************/

static int ssc_checkwidth(struct sam_ssc_s *priv, int bits)
{
  /* The SSC can handle most any bit width from 2 to 32.  However, the DMA
   * logic here is constrained to byte, half-word, and word sizes.
   */

  switch (bits)
    {
    case 8:
      priv->align = 0;
      break;

    case 16:
      priv->align = 1;
      break;

    case 32:
      priv->align = 3;
      break;

    default:
      i2serr("ERROR: Unsupported or invalid data width: %d\n", bits);
      return (bits < 2 || bits > 32) ? -EINVAL : -ENOSYS;
    }

  /* Save the new data width */

  priv->datalen = bits;
  return OK;
}

/****************************************************************************
 * Name: ssc_rxsamplerate
 *
 * Description:
 *   Set the I2S RX sample rate.  NOTE:  This will have no effect if (1) the
 *   driver does not support an I2C receiver or if (2) the sample rate is
 *   driven by the I2S frame clock.  This may also have unexpected side-
 *   effects of the RX sample is coupled with the TX sample rate.
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   rate - The I2S sample rate in samples (not bits) per second
 *
 * Returned Value:
 *   Returns the resulting bitrate
 *
 ****************************************************************************/

static uint32_t ssc_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
#if defined(SSC_HAVE_RX) && defined(SSC_HAVE_MCK2)
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
  DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0);

  /* Check if the receiver is driven by the MCK/2 */

  if (priv->rxclk == SSC_CLKSRC_MCKDIV)
    {
      /* Save the new sample rate and update the MCK/2 divider */

      priv->samplerate = rate;
      return ssc_mck2divider(priv);
    }
#endif

  return 0;
}

/****************************************************************************
 * Name: ssc_rxdatawidth
 *
 * Description:
 *   Set the I2S RX data width.  The RX bitrate is determined by
 *   sample_rate * data_width.
 *
 * Input Parameters:
 *   dev   - Device-specific state data
 *   width - The I2S data with in bits.
 *
 * Returned Value:
 *   Returns the resulting bitrate
 *
 ****************************************************************************/

static uint32_t ssc_rxdatawidth(struct i2s_dev_s *dev, int bits)
{
#ifdef SSC_HAVE_RX
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
  uint32_t dmaflags;
  int ret;

  DEBUGASSERT(priv && bits > 1);

  /* Check if this is a bit width that we are configured to handle */

  ret = ssc_checkwidth(priv, bits);
  if (ret < 0)
    {
      i2serr("ERROR: ssc_checkwidth failed: %d\n", ret);
      return 0;
    }

  /* Update the DMA flags */

  ret = ssc_dma_flags(priv, &dmaflags);
  if (ret < 0)
    {
      i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
      return 0;
    }

  /* Reconfigure the RX DMA (and TX DMA if applicable) */

  sam_dmaconfig(priv->rx.dma, dmaflags);
#ifdef SSC_HAVE_RX
  if (priv->txenab)
    {
      sam_dmaconfig(priv->tx.dma, dmaflags);
    }
#endif

#ifdef SSC_HAVE_MCK2
  /* Check if the receiver is driven by the MCK/2 */

  if (priv->rxclk == SSC_CLKSRC_MCKDIV)
    {
      /* Update the MCK/2 divider.  bitrate is samplerate * datawidth. */

      return ssc_mck2divider(priv);
    }
#endif
#endif

  return 0;
}

/****************************************************************************
 * Name: ssc_receive
 *
 * Description:
 *   Receive a block of data from I2S.
 *
 * Input Parameters:
 *   dev      - Device-specific state data
 *   apb      - A pointer to the audio buffer in which to receive data
 *   callback - A user provided callback function that will be called at
 *              the completion of the transfer.  The callback will be
 *              performed in the context of the worker thread.
 *   arg      - An opaque argument that will be provided to the callback
 *              when the transfer complete
 *   timeout  - The timeout value to use.  The transfer will be canceled
 *              and an ETIMEDOUT error will be reported if this timeout
 *              elapsed without completion of the DMA transfer.  Units
 *              are system clock ticks.  Zero means no timeout.
 *
 * Returned Value:
 *   OK on success; a negated errno value on failure.  NOTE:  This function
 *   only enqueues the transfer and returns immediately.  Success here only
 *   means that the transfer was enqueued correctly.
 *
 *   When the transfer is complete, a 'result' value will be provided as
 *   an argument to the callback function that will indicate if the transfer
 *   failed.
 *
 ****************************************************************************/

static int ssc_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
                       i2s_callback_t callback, void *arg, uint32_t timeout)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
#ifdef SSC_HAVE_RX
  struct sam_buffer_s *bfcontainer;
  irqstate_t flags;
  int ret;
#endif

  DEBUGASSERT(priv && apb && ((uintptr_t)apb->samp & priv->align) == 0);
  i2sinfo("apb=%p nmaxbytes=%d arg=%p timeout=%d\n",
          apb, apb->nmaxbytes, arg, timeout);

  ssc_init_buffer(apb->samp, apb->nmaxbytes);

#ifdef SSC_HAVE_RX
  /* Allocate a buffer container in advance */

  bfcontainer = ssc_buf_allocate(priv);
  if (bfcontainer == NULL)
    {
      return -ENOMEM;
    }

  /* Get exclusive access to the SSC driver data */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      goto errout_with_buf;
    }

  /* Has the RX channel been enabled? */

  if (!priv->rxenab)
    {
      i2serr("ERROR: SSC%d has no receiver\n", priv->sscno);
      ret = -EAGAIN;
      goto errout_with_lock;
    }

  /* Add a reference to the audio buffer */

  apb_reference(apb);

  /* Initialize the buffer container structure */

  bfcontainer->callback = (void *)callback;
  bfcontainer->timeout  = timeout;
  bfcontainer->arg      = arg;
  bfcontainer->apb      = apb;
  bfcontainer->result   = -EBUSY;

  /* Add the buffer container to the end of the RX pending queue */

  flags = enter_critical_section();
  sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
  ssc_dump_rxqueues(priv, "Receiving");

  /* Then start the next transfer.  If there is already a transfer in
   * progress, then this will do nothing.
   */

  ret = ssc_rxdma_setup(priv);
  DEBUGASSERT(ret == OK);
  leave_critical_section(flags);
  nxmutex_unlock(&priv->lock);
  return OK;

errout_with_lock:
  nxmutex_unlock(&priv->lock);

errout_with_buf:
  ssc_buf_free(priv, bfcontainer);
  return ret;

#else
  i2serr("ERROR: SSC%d has no receiver\n", priv->sscno);
  UNUSED(priv);
  return -ENOSYS;
#endif
}

/****************************************************************************
 * Name: ssc_txsamplerate
 *
 * Description:
 *   Set the I2S TX sample rate.  NOTE:  This will have no effect if (1) the
 *   driver does not support an I2S transmitter or if (2) the sample rate is
 *   driven by the I2S frame clock.  This may also have unexpected side-
 *   effects of the TX sample is coupled with the RX sample rate.
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   rate - The I2S sample rate in samples (not bits) per second
 *
 * Returned Value:
 *   Returns the resulting bitrate
 *
 ****************************************************************************/

static uint32_t ssc_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
#if defined(SSC_HAVE_TX) && defined(SSC_HAVE_MCK2)
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
  DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0);

  /* Check if the receiver is driven by the MCK/2 */

  if (priv->txclk == SSC_CLKSRC_MCKDIV)
    {
      /* Save the new sample rate and update the MCK/2 divider */

      priv->samplerate = rate;
      return ssc_mck2divider(priv);
    }
#endif

  return 0;
}

/****************************************************************************
 * Name: ssc_txdatawidth
 *
 * Description:
 *   Set the I2S TX data width.  The TX bitrate is determined by
 *   sample_rate * data_width.
 *
 * Input Parameters:
 *   dev   - Device-specific state data
 *   width - The I2S data with in bits.
 *
 * Returned Value:
 *   Returns the resulting bitrate
 *
 ****************************************************************************/

static uint32_t ssc_txdatawidth(struct i2s_dev_s *dev, int bits)
{
#ifdef SSC_HAVE_TX
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
  uint32_t dmaflags;
  int ret;

  DEBUGASSERT(priv && bits > 1);

  /* Check if this is a bit width that we are configured to handle */

  ret = ssc_checkwidth(priv, bits);
  if (ret < 0)
    {
      i2serr("ERROR: ssc_checkwidth failed: %d\n", ret);
      return 0;
    }

  /* Update the DMA flags */

  ret = ssc_dma_flags(priv, &dmaflags);
  if (ret < 0)
    {
      i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
      return 0;
    }

  /* Reconfigure the RX DMA (and RX DMA if applicable) */

  sam_dmaconfig(priv->tx.dma, dmaflags);
#ifdef SSC_HAVE_RX
  if (priv->rxenab)
    {
      sam_dmaconfig(priv->rx.dma, dmaflags);
    }
#endif

#ifdef SSC_HAVE_MCK2
  /* Check if the transmitter is driven by the MCK/2 */

  if (priv->txclk == SSC_CLKSRC_MCKDIV)
    {
      /* Update the MCK/2 divider.  bitrate is samplerate * datawidth. */

      return ssc_mck2divider(priv);
    }
#endif
#endif

  return 0;
}

/****************************************************************************
 * Name: ssc_send
 *
 * Description:
 *   Send a block of data on I2S.
 *
 * Input Parameters:
 *   dev      - Device-specific state data
 *   apb      - A pointer to the audio buffer from which to send data
 *   callback - A user provided callback function that will be called at
 *              the completion of the transfer.  The callback will be
 *              performed in the context of the worker thread.
 *   arg      - An opaque argument that will be provided to the callback
 *              when the transfer complete
 *   timeout  - The timeout value to use.  The transfer will be canceled
 *              and an ETIMEDOUT error will be reported if this timeout
 *              elapsed without completion of the DMA transfer.  Units
 *              are system clock ticks.  Zero means no timeout.
 *
 * Returned Value:
 *   OK on success; a negated errno value on failure.  NOTE:  This function
 *   only enqueues the transfer and returns immediately.  Success here only
 *   means that the transfer was enqueued correctly.
 *
 *   When the transfer is complete, a 'result' value will be provided as
 *   an argument to the callback function that will indicate if the transfer
 *   failed.
 *
 ****************************************************************************/

static int ssc_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
                    i2s_callback_t callback, void *arg, uint32_t timeout)
{
  struct sam_ssc_s *priv = (struct sam_ssc_s *)dev;
#ifdef SSC_HAVE_TX
  struct sam_buffer_s *bfcontainer;
  irqstate_t flags;
  int ret;
#endif

  /* Make sure that we have valid pointers that that the data has uint32_t
   * alignment.
   */

  DEBUGASSERT(priv && apb);
  i2sinfo("apb=%p nbytes=%d arg=%p timeout=%d\n",
          apb, apb->nbytes - apb->curbyte, arg, timeout);

  ssc_dump_buffer("Sending", &apb->samp[apb->curbyte],
                  apb->nbytes - apb->curbyte);
  DEBUGASSERT(((uintptr_t)&apb->samp[apb->curbyte] & priv->align) == 0);

#ifdef SSC_HAVE_TX
  /* Allocate a buffer container in advance */

  bfcontainer = ssc_buf_allocate(priv);
  if (bfcontainer == NULL)
    {
      return -ENOMEM;
    }

  /* Get exclusive access to the SSC driver data */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      goto errout_with_buf;
    }

  /* Has the TX channel been enabled? */

  if (!priv->txenab)
    {
      i2serr("ERROR: SSC%d has no transmitter\n", priv->sscno);
      ret = -EAGAIN;
      goto errout_with_lock;
    }

  /* Add a reference to the audio buffer */

  apb_reference(apb);

  /* Initialize the buffer container structure */

  bfcontainer->callback = (void *)callback;
  bfcontainer->timeout  = timeout;
  bfcontainer->arg      = arg;
  bfcontainer->apb      = apb;
  bfcontainer->result   = -EBUSY;

  /* Add the buffer container to the end of the TX pending queue */

  flags = enter_critical_section();
  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
  ssc_dump_txqueues(priv, "Transmitting");

  /* Then start the next transfer.  If there is already a transfer in
   * progress, then this will do nothing.
   */

  ret = ssc_txdma_setup(priv);
  DEBUGASSERT(ret == OK);
  leave_critical_section(flags);
  nxmutex_unlock(&priv->lock);
  return OK;

errout_with_lock:
  nxmutex_unlock(&priv->lock);

errout_with_buf:
  ssc_buf_free(priv, bfcontainer);
  return ret;

#else
  i2serr("ERROR: SSC%d has no transmitter\n", priv->sscno);
  UNUSED(priv);
  return -ENOSYS;
#endif
}

/****************************************************************************
 * Name: ssc_rx/tx_configure
 *
 * Description:
 *   Configure the SSC receiver and transmitter.
 *
 * Input Parameters:
 *   priv - Fully initialized SSC device structure.
 *
 * Returned Value:
 *   OK is returned on failure.  A negated errno value is returned on
 *   failure.
 *
 ****************************************************************************/

static int ssc_rx_configure(struct sam_ssc_s *priv)
{
#ifdef SSC_HAVE_RX
  uint32_t regval;
  uint32_t fslen;

  /* Get the RX sync time (in RX clocks) */

  DEBUGASSERT(priv->rxfslen > 0);
  fslen  = priv->rxfslen - 1;

  /* RCMR settings */

  /* Configure the receiver input clock */

  regval = 0;
  switch (priv->rxclk)
    {
    case SSC_CLKSRC_RKIN:    /* Receiver clock source is RK */
      regval = SSC_RCMR_CKS_RK;
      break;

    case SSC_CLKSRC_TXOUT:   /* Receiver clock source is the transmitter clock */
      regval = SSC_RCMR_CKS_TK;
      break;

    case SSC_CLKSRC_MCKDIV:  /* Clock source is MCK divided down */
#ifdef SSC_HAVE_MCK2
      DEBUGASSERT(priv->samplerate > 0);
      regval = SSC_RCMR_CKS_MCK;
      break;
#endif

    case SSC_CLKSRC_NONE: /* No clock */
    default:
      i2serr("ERROR:  No receiver clock\n");
      return -EINVAL;
    }

  /* Configure the receiver output clock */

  switch (priv->rxout)
    {
    case SSC_CLKOUT_CONT: /* Continuous */
      regval |= SSC_RCMR_CKO_CONT;
      break;

    case SSC_CLKOUT_XFER: /* Only output clock during transfers */
      regval |= SSC_RCMR_CKO_TRANSFER;
      break;

    case SSC_CLKOUT_NONE: /* No output clock */
      regval |= SSC_RCMR_CKO_NONE;
      break;

    default:
      i2serr("ERROR: Invalid clock output selection\n");
      return -EINVAL;
    }

  /* REVISIT:  Some of these settings will need to be configurable as well.
   * Currently hardcoded to:
   *
   *   SSC_RCMR_CKI         Receive clock inversion
   *   SSC_RCMR_CKG_CONT    No receive clock gating
   *   SSC_RCMR_START_EDGE  Detection of any edge on RF signal
   *   SSC_RCMR_STOP        Not selected
   *   SSC_RCMR_STTDLY(1)   Receive start delay = 1 (same as FSLEN)
   *   SSC_RCMR_PERIOD(0)   Receive period divider = 0
   *
   * REVISIT:  This implementation assumes that on the transmitter
   * can be the master (i.e, can generate the TK/RK clocking.
   */

  regval |= (SSC_RCMR_CKI | SSC_RCMR_CKG_CONT | SSC_RCMR_START_EDGE |
             SSC_RCMR_STTDLY(priv->rxsttdly) | SSC_RCMR_PERIOD(0));
  ssc_putreg(priv, SAM_SSC_RCMR_OFFSET, regval);

  /* RFMR settings. Some of these settings will need to be configurable as
   * well.  Currently hardcoded to:
   *
   *  SSC_RFMR_DATLEN(n)    'n' determined by configuration
   *  SSC_RFMR_LOOP         Determined by configuration
   *  SSC_RFMR_MSBF         Most significant bit first
   *  SSC_RFMR_DATNB(n)     Data number 'n' per frame (hard-coded)
   *  SSC_RFMR_FSLEN        Set to LS 4 bits of (CONFIG_SSCx_RX_FSLEN-1)
   *  SSC_RFMR_FSLEN(1)     Pulse length = FSLEN + (FSLEN_EXT * 16) + 1 =
   *                        2 clocks
   *  SSC_RFMR_FSOS_NONE    RF pin is always in input
   *  SSC_RFMR_FSEDGE_POS   Positive frame sync edge detection
   *  SSC_RFMR_FSLENEXT   I Set to MS 4 bits of (CONFIG_SSCx_TX_FSLEN-1)
   */

  regval = (SSC_RFMR_DATLEN(CONFIG_SAMV7_SSC0_DATALEN - 1) | SSC_RFMR_MSBF |
            SSC_RFMR_DATNB(SSC_DATNB - 1) | SSC_RFMR_FSOS_NONE);

  /* Set the RX frame synch  */

  regval |= (SSC_RFMR_FSLEN(fslen & 0x0f) |
            SSC_RFMR_FSLENEXT((fslen >> 4) & 0x0f));

  /* Loopback mode? */

  if (priv->loopback)
    {
      regval |= SSC_RFMR_LOOP;
    }

  ssc_putreg(priv, SAM_SSC_RFMR_OFFSET, regval);

#else
  ssc_putreg(priv, SAM_SSC_RCMR_OFFSET, 0);
  ssc_putreg(priv, SAM_SSC_RFMR_OFFSET, 0);

#endif

  /* Disable the receiver */

  ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXDIS);
  return OK;
}

static int ssc_tx_configure(struct sam_ssc_s *priv)
{
#ifdef SSC_HAVE_TX
  uint32_t regval;
  uint32_t fslen;
  uint32_t period;

  /* Get the TX synch in (in TX clocks) */

  fslen  = priv->txfslen > 0 ? priv->txfslen - 1 : 0;

  /* From the start delay and the datalength , we can get the full
   * period of the waveform.
   */

  period = SCC_PERIOD(priv->txsttdly, priv->datalen);

  /* TCMR settings */

  /* Configure the transmitter input clock */

  regval = 0;
  switch (priv->txclk)
    {
    case SSC_CLKSRC_TKIN:    /* Transmitter clock source is TK */
      regval = SSC_TCMR_CKS_TK;
      break;

    case SSC_CLKSRC_RXOUT:   /* Transmitter clock source is the receiver clock */
      regval = SSC_TCMR_CKS_RK;
      break;

    case SSC_CLKSRC_MCKDIV:  /* Clock source is MCK divided down */
#ifdef SSC_HAVE_MCK2
      DEBUGASSERT(priv->samplerate > 0);
      regval = SSC_TCMR_CKS_MCK;
      break;
#endif

    case SSC_CLKSRC_NONE: /* No clock */
    default:
      i2serr("ERROR:  No transmitter clock\n");
      return -EINVAL;
    }

  /* Configure the receiver output clock */

  switch (priv->txout)
    {
    case SSC_CLKOUT_CONT: /* Continuous */
      regval |= SSC_TCMR_CKO_CONT;
      break;

    case SSC_CLKOUT_XFER: /* Only output clock during transfers */
      regval |= SSC_TCMR_CKO_TRANSFER;
      break;

    case SSC_CLKOUT_NONE: /* No output clock */
      regval |= SSC_TCMR_CKO_NONE;
      break;

    default:
      i2serr("ERROR: Invalid clock output selection\n");
      return -EINVAL;
    }

  /* REVISIT:  Some of these settings will need to be configurable as well.
   * Currently hard-coded to:
   *
   *   SSC_RCMR_CKI           No transmitter clock inversion
   *   SSC_RCMR_CKG_CONT      No transmit clock gating
   *   SSC_TCMR_STTDLY(1)     Receive start delay = 2 clocks (same as FSLEN)
   *
   * If master (i.e., provides clocking):
   *   SSC_TCMR_START_CONT    When data written to THR
   *   SSC_TCMR_PERIOD(n)     'n' depends on the datawidth
   *
   * If slave (i.e., receives clocking):
   *   SSC_TCMR_START_EDGE    Detection of any edge on TF signal
   *   SSC_TCMR_PERIOD(0)     Receive period divider = 0
   *
   * The period signal is generated at clocks = 2 x (PERIOD+1), or
   * PERIOD = (clocks / 2) - 1.
   */

  if (priv->txclk == SSC_CLKSRC_MCKDIV)
    {
      regval |= (SSC_TCMR_CKG_CONT | SSC_TCMR_START_CONT |
                 SSC_TCMR_STTDLY(priv->txsttdly) |
                 SSC_TCMR_PERIOD(period / 2 - 1));
    }
  else
    {
      regval |= (SSC_TCMR_CKG_CONT | SSC_TCMR_START_EDGE |
                 SSC_TCMR_STTDLY(priv->txsttdly) |
                 SSC_TCMR_PERIOD(0));
    }

  ssc_putreg(priv, SAM_SSC_TCMR_OFFSET, regval);

  /* TFMR settings. Some of these settings will need to be configurable as
   * well.  Currently set to:
   *
   *  SSC_TFMR_DATLEN(n)    'n' determined by configuration
   *  SSC_TFMR_DATDEF        Data default = 0
   *  SSC_TFMR_MSBF          Most significant bit first
   *  SSC_TFMR_DATNB(n)      Data number 'n' per frame (hard-coded)
   *  SSC_TFMR_FSDEN         Enabled if CONFIG_SSCx_TX_FSLEN > 0
   *  SSC_TFMR_FSLEN         If enabled, set to LS 4 bits of
   *                         (CONFIG_SSCx_TX_FSLEN-1)
   *  SSC_TFMR_FSLENEXT      If enabled, set to MS 4 bits of
   *                         (CONFIG_SSCx_TX_FSLEN-1)
   *
   * If master (i.e., provides clocking):
   *  SSC_TFMR_FSOS_NEGATIVE Negative pulse TF output
   *
   * If slave (i.e, receives clocking):
   *  SSC_TFMR_FSOS_NONE     TF is an output
   */

  if (priv->txclk == SSC_CLKSRC_MCKDIV)
    {
      regval = (SSC_TFMR_DATLEN(priv->datalen - 1) |
                SSC_TFMR_MSBF | SSC_TFMR_DATNB(SSC_DATNB - 1) |
                SSC_TFMR_FSOS_NEGATIVE);
    }
  else
    {
      regval = (SSC_TFMR_DATLEN(priv->datalen - 1) |
                SSC_TFMR_MSBF | SSC_TFMR_DATNB(SSC_DATNB - 1) |
                SSC_TFMR_FSOS_NONE);
    }

  /* Is the TX frame synch enabled? */

  if (priv->txfslen > 0)
    {
      /* Yes.. Set the FSDEN bit and the FSLEN field */

      regval |= (SSC_TFMR_FSDEN | SSC_TFMR_FSLEN(fslen & 0x0f) |
                 SSC_TFMR_FSLENEXT((fslen >> 4) & 0x0f));
    }

  ssc_putreg(priv, SAM_SSC_TFMR_OFFSET, regval);

#else
  ssc_putreg(priv, SAM_SSC_TCMR_OFFSET, 0);
  ssc_putreg(priv, SAM_SSC_TFMR_OFFSET, 0);

#endif

  /* Disable the transmitter */

  ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_TXDIS);
  return OK;
}

/****************************************************************************
 * Name: ssc_mck2divider
 *
 * Description:
 *   Setup the MCK/2 divider based on the currently selected data width and
 *   the sample rate
 *
 * Input Parameters:
 *   priv - I2C device structure (only the sample rate and data length is
 *          needed at this point).
 *
 * Returned Value:
 *  The current bitrate
 *
 ****************************************************************************/

static uint32_t ssc_mck2divider(struct sam_ssc_s *priv)
{
#ifdef SSC_HAVE_MCK2
  uint32_t bitrate;
  uint32_t regval;
  DEBUGASSERT(priv && priv->samplerate > 0 && priv->datalen > 0);

  /* A zero sample rate means to disable the MCK/2 clock */

  if (priv->samplerate == 0)
    {
      bitrate = 0;
      regval  = 0;
    }
  else
    {
      /* Calculate the new bitrate in Hz */

      bitrate = priv->samplerate * priv->datalen;

      /* Calculate the new MCK/2 divider from the bitrate. The divided clock
       * equals:
       *
       *   bitrate = MCK / (2 * div)
       *   div     = MCK / (2 * bitrate)
       *
       * The maximum bit rate is MCK/2. The minimum bit rate is
       * MCK/2 x 4095 = MCK/8190.
       */

      regval =  (BOARD_MCK_FREQUENCY + bitrate) / (bitrate << 1);
    }

  /* Configure MCK/2 divider */

  ssc_putreg(priv, SAM_SSC_CMR_OFFSET, regval);
  return bitrate;
#else
  return 0;
#endif
}

/****************************************************************************
 * Name: ssc_clocking
 *
 * Description:
 *   Enable and configure clocking to the SSC
 *
 * Input Parameters:
 *   priv - Partially initialized I2C device structure (only the PID is
 *          needed at this point).
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void ssc_clocking(struct sam_ssc_s *priv)
{
  uint32_t regval;
  uint32_t mck;

  /* Determine the maximum SSC peripheral clock frequency */

  mck = BOARD_MCK_FREQUENCY;
#ifdef SAMV7_HAVE_PMC_PCR_DIV
  DEBUGASSERT((mck >> 3) <= SAM_SSC_MAXPERCLK);

  if (mck <= SAM_SSC_MAXPERCLK)
    {
      priv->frequency = mck;
      regval          = PMC_PCR_DIV1;
    }
  else if ((mck >> 1) <= SAM_SSC_MAXPERCLK)
    {
      priv->frequency = (mck >> 1);
      regval          = PMC_PCR_DIV2;
    }
  else if ((mck >> 2) <= SAM_SSC_MAXPERCLK)
    {
      priv->frequency = (mck >> 2);
      regval          = PMC_PCR_DIV4;
    }
  else /* if ((mck >> 3) <= SAM_SSC_MAXPERCLK) */
    {
      priv->frequency = (mck >> 3);
      regval          = PMC_PCR_DIV8;
    }

#else
  /* No PCR_DIV field */

  priv->frequency     = mck;
  regval              = 0;
#endif

  /* Set the maximum SSC peripheral clock frequency */

  regval |= PMC_PCR_PID(priv->pid) | PMC_PCR_CMD | PMC_PCR_EN;
  putreg32(regval, SAM_PMC_PCR);

  /* Reset, disable receiver & transmitter */

  ssc_putreg(priv, SAM_SSC_CR_OFFSET,
             SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST);

  /* Configure MCK/2 divider */

  ssc_mck2divider(priv);

  /* Enable peripheral clocking */

  sam_enableperiph1(priv->pid);

  i2sinfo("PCSR1=%08x PCR=%08x CMR=%08x\n",
          getreg32(SAM_PMC_PCSR1), regval,
          ssc_getreg(priv, SAM_SSC_CMR_OFFSET));
}

/****************************************************************************
 * Name: ssc_dma_flags
 *
 * Description:
 *   Determine DMA FLAGS based on PID and data width
 *
 * Input Parameters:
 *   priv - Partially initialized I2C device structure.
 *   dmaflags - Location to return the DMA flags.
 *
 * Returned Value:
 *   OK on success; a negated errno value on failure
 *
 ****************************************************************************/

static int ssc_dma_flags(struct sam_ssc_s *priv, uint32_t *dmaflags)
{
  uint32_t flags;

  switch (priv->datalen)
    {
    case 8:
      flags = DMA8_FLAGS;
      break;

    case 16:
      flags = DMA16_FLAGS;
      break;

    case 32:
      flags = DMA32_FLAGS;
      break;

    default:
      i2serr("ERROR: Unsupported data width: %d\n", priv->datalen);
      return -ENOSYS;
    }

  *dmaflags = (flags | DMACH_FLAG_PERIPHPID(priv->pid));
  return OK;
}

/****************************************************************************
 * Name: ssc_dma_allocate
 *
 * Description:
 *   Allocate SCC DMA channels
 *
 * Input Parameters:
 *   priv - Partially initialized I2C device structure.  This function
 *          will complete the DMA specific portions of the initialization
 *
 * Returned Value:
 *   OK on success; A negated errno value on failure.
 *
 ****************************************************************************/

static int ssc_dma_allocate(struct sam_ssc_s *priv)
{
  uint32_t dmaflags;
  int ret;

  /* Get the DMA flags for this channel */

  ret = ssc_dma_flags(priv, &dmaflags);
  if (ret < 0)
    {
      i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
      return ret;
    }

  /* Allocate DMA channels. */

#ifdef SSC_HAVE_RX
  if (priv->rxenab)
    {
      /* Allocate an RX DMA channel */

      priv->rx.dma = sam_dmachannel(0, dmaflags);
      if (!priv->rx.dma)
        {
          i2serr("ERROR: Failed to allocate the RX DMA channel\n");
          goto errout;
        }
    }
#endif

#ifdef SSC_HAVE_TX
  if (priv->txenab)
    {
      /* Allocate a TX DMA channel */

      priv->tx.dma = sam_dmachannel(0, dmaflags);
      if (!priv->tx.dma)
        {
          i2serr("ERROR: Failed to allocate the TX DMA channel\n");
          goto errout;
        }
    }
#endif

  /* Success exit */

  return OK;

  /* Error exit */

errout:
  ssc_dma_free(priv);
  return -ENOMEM;
}

/****************************************************************************
 * Name: ssc_dma_free
 *
 * Description:
 *   Release DMA-related resources allocated by ssc_dma_allocate()
 *
 * Input Parameters:
 *   priv - Partially initialized I2C device structure.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void ssc_dma_free(struct sam_ssc_s *priv)
{
#ifdef SSC_HAVE_TX
  wd_cancel(&priv->tx.dog);
  if (priv->tx.dma)
    {
      sam_dmafree(priv->tx.dma);
    }
#endif

#ifdef SSC_HAVE_RX
  wd_cancel(&priv->rx.dog);
  if (priv->rx.dma)
    {
      sam_dmafree(priv->rx.dma);
    }
#endif
}

/****************************************************************************
 * Name: ssc0/1_configure
 *
 * Description:
 *   Configure SSC0 and/or SSC1
 *
 * Input Parameters:
 *   priv - Partially initialized I2C device structure.  These functions
 *          will complete the SSC specific portions of the initialization
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_SAMV7_SSC0
static void ssc0_configure(struct sam_ssc_s *priv)
{
  /* Configure multiplexed pins as connected on the board.  Chip
   * select pins must be selected by board-specific logic.
   */

#ifdef CONFIG_SAMV7_SSC0_RX
  priv->rxenab = true;

  /* Configure the receiver data (RD) and receiver frame synchro (RF) pins */

  sam_configgpio(GPIO_SSC0_RD);
  sam_configgpio(GPIO_SSC0_RF);

#if defined(CONFIG_SAMV7_SSC0_RX_RKINPUT)
  /* Configure the RK pin only if we are using an external clock to drive
   * the receiver clock.
   *
   * REVISIT:  The SSC is also capable of generated the receiver clock
   * output on the RK pin.
   */

  sam_configgpio(GPIO_SSC0_RK); /* External clock received on the RK I/O pad */
  priv->rxclk = SSC_CLKSRC_RKIN;

#elif defined(CONFIG_SAMV7_SSC0_RX_TXCLK)
  priv->rxclk = SSC_CLKSRC_TXOUT;

#elif defined(CONFIG_SAMV7_SSC0_RX_MCKDIV)
  priv->rxclk = SSC_CLKSRC_MCKDIV;

#else
  priv->rxclk = SSC_CLKSRC_NONE;

#endif

  /* Remember parameters of the configured waveform */

  priv->rxfslen  = CONFIG_SSC0_RX_FSLEN;
  priv->rxsttdly = CONFIG_SSC0_RX_STTDLY;

  /* Remember the configured RX clock output */

#if defined(CONFIG_SAMV7_SSC0_RX_RKOUTPUT_CONT)
  priv->rxout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMV7_SSC0_RX_RKOUTPUT_XFR)
  priv->rxout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMV7_SSC0_RX_RKOUTPUT_NONE) */
  priv->rxout = SSC_CLKOUT_NONE; /* No output clock */
#endif

#else
  priv->rxenab = false;
  priv->rxclk  = SSC_CLKSRC_NONE; /* No input clock */
  priv->rxout  = SSC_CLKOUT_NONE; /* No output clock */

#endif /* CONFIG_SAMV7_SSC0_RX */

#ifdef CONFIG_SAMV7_SSC0_TX
  priv->txenab = true;

  /* Configure the transmitter data (TD) and transmitter frame synchro (TF)
   * pins
   */

  sam_configgpio(GPIO_SSC0_TD);
  sam_configgpio(GPIO_SSC0_TF);

#if defined(CONFIG_SAMV7_SSC0_TX_TKINPUT)
  /* Configure the TK pin only if we are using an external clock to drive
   * the transmitter clock.
   *
   * REVISIT:  The SSC is also capable of generated the transmitter clock
   * output on the TK pin.
   */

  sam_configgpio(GPIO_SSC0_TK); /* External clock received on the TK I/O pad */
  priv->txclk = SSC_CLKSRC_TKIN;

#elif defined(CONFIG_SAMV7_SSC0_TX_RXCLK)
  priv->txclk = SSC_CLKSRC_RXOUT;

#elif defined(CONFIG_SAMV7_SSC0_TX_MCKDIV)
  priv->txclk = SSC_CLKSRC_MCKDIV;

#else
  priv->txclk = SSC_CLKSRC_NONE;

#endif

  /* Remember the configured TX clock output */

#if defined(CONFIG_SAMV7_SSC0_TX_TKOUTPUT_CONT)
  priv->txout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMV7_SSC0_TX_TKOUTPUT_XFR)
  priv->txout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMV7_SSC0_TX_TKOUTPUT_NONE) */
  priv->txout = SSC_CLKOUT_NONE; /* No output clock */
#endif

#else
  priv->txenab = false;
  priv->txclk  = SSC_CLKSRC_NONE; /* No input clock */
  priv->txout  = SSC_CLKOUT_NONE; /* No output clock */

#endif /* CONFIG_SAMV7_SSC0_TX */

  /* Remember parameters of the configured waveform */

  priv->txfslen  = CONFIG_SSC0_TX_FSLEN;
  priv->txsttdly = CONFIG_SSC0_TX_STTDLY;

  /* Set/clear loopback mode */

#if defined(CONFIG_SAMV7_SSC0_RX) && defined(CONFIG_SAMV7_SSC0_TX) && \
    defined(CONFIG_SAMV7_SSC0_LOOPBACK)
  priv->loopback = true;
#else
  priv->loopback = false;
#endif

  /* Does the receiver or transmitter need to have the MCK divider set up? */

#if defined(SSC0_HAVE_MCK2)
  priv->samplerate = CONFIG_SAMV7_SSC0_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
  priv->samplerate = 0;
#endif

  /* Configure driver state specific to this SSC peripheral */

  priv->base    = SAM_SSC0_BASE;
  priv->datalen = CONFIG_SAMV7_SSC0_DATALEN;
  priv->align   = SAMV7_SSC0_DATAMASK;
  priv->pid     = SAM_PID_SSC0;
}
#endif

#ifdef CONFIG_SAMV7_SSC1
static void ssc1_configure(struct sam_ssc_s *priv)
{
  /* Configure multiplexed pins as connected on the board.  Chip
   * select pins must be selected by board-specific logic.
   */

#ifdef CONFIG_SAMV7_SSC1_RX
  priv->rxenab = true;

  /* Configure the receiver data (RD) and receiver frame synchro (RF) pins */

  sam_configgpio(GPIO_SSC1_RD);
  sam_configgpio(GPIO_SSC1_RF);

#ifdef CONFIG_SAMV7_SSC1_RX_RKINPUT
  /* Configure the RK pin only if we are using an external clock to drive
   * the receiver clock.
   *
   * REVISIT:  The SSC is also capable of generated the receiver clock
   * output on the RK pin.
   */

  sam_configgpio(GPIO_SSC1_RK); /* External clock received on the RK I/O pad */
  priv->rxclk = SSC_CLKSRC_RKIN;

#elif defined(CONFIG_SAMV7_SSC1_RX_TXCLK)
  priv->rxclk = SSC_CLKSRC_TXOUT;

#elif defined(CONFIG_SAMV7_SSC1_RX_MCKDIV)
  priv->rxclk = SSC_CLKSRC_MCKDIV;

#else
  priv->rxclk = SSC_CLKSRC_NONE;

#endif

  /* Remember parameters of the configured waveform */

  priv->rxfslen  = CONFIG_SSC1_RX_FSLEN;
  priv->rxsttdly = CONFIG_SSC1_RX_STTDLY;

  /* Remember the configured RX clock output */

#if defined(CONFIG_SAMV7_SSC1_RX_RKOUTPUT_CONT)
  priv->rxout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMV7_SSC1_RX_RKOUTPUT_XFR)
  priv->rxout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMV7_SSC1_RX_RKOUTPUT_NONE) */
  priv->rxout = SSC_CLKOUT_NONE; /* No output clock */
#endif

#else
  priv->rxenab = false;
  priv->rxclk  = SSC_CLKSRC_NONE; /* No input clock */
  priv->rxout  = SSC_CLKOUT_NONE; /* No output clock */

#endif /* CONFIG_SAMV7_SSC1_RX */

#ifdef CONFIG_SAMV7_SSC1_TX
  priv->txenab = true;

  /* Configure the transmitter data (TD) and transmitter frame synchro (TF)
   * pins
   */

  sam_configgpio(GPIO_SSC1_TD);
  sam_configgpio(GPIO_SSC1_TF);

#if defined(CONFIG_SAMV7_SSC1_TX_TKINPUT)
  /* Configure the TK pin only if we are using an external clock to drive
   * the transmitter clock.
   *
   * REVISIT:  The SSC is also capable of generated the transmitter clock
   * output on the TK pin.
   */

  sam_configgpio(GPIO_SSC1_TK); /* External clock received on the TK I/O pad */
  priv->txclk = SSC_CLKSRC_TKIN;

#elif defined(CONFIG_SAMV7_SSC1_TX_RXCLK)
  priv->txclk = SSC_CLKSRC_RXOUT;

#elif defined(CONFIG_SAMV7_SSC1_TX_MCKDIV)
  priv->txclk = SSC_CLKSRC_MCKDIV;

#else
  priv->txclk = SSC_CLKSRC_NONE;

#endif

  /* Remember the configured TX clock output */

#if defined(CONFIG_SAMV7_SSC1_TX_TKOUTPUT_CONT)
  priv->txout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMV7_SSC1_TX_TKOUTPUT_XFR)
  priv->txout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMV7_SSC1_TX_TKOUTPUT_NONE) */
  priv->txout = SSC_CLKOUT_NONE; /* No output clock */
#endif

#else
  priv->txenab = false;
  priv->txclk  = SSC_CLKSRC_NONE; /* No input clock */
  priv->txout  = SSC_CLKOUT_NONE; /* No output clock */

#endif /* CONFIG_SAMV7_SSC1_TX */

  /* Remember parameters of the configured waveform */

  priv->txfslen  = CONFIG_SSC1_TX_FSLEN;
  priv->txsttdly = CONFIG_SSC1_TX_STTDLY;

  /* Set/clear loopback mode */

#if defined(CONFIG_SAMV7_SSC1_RX) && defined(CONFIG_SAMV7_SSC1_TX) && \
    defined(CONFIG_SAMV7_SSC1_LOOPBACK)
  priv->loopback = true;
#else
  priv->loopback = false;
#endif

  /* Does the receiver or transmitter need to have the MCK divider set up? */

#if defined(SSC1_HAVE_MCK2)
  priv->samplerate = CONFIG_SAMV7_SSC1_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
  priv->samplerate = 0;
#endif

  /* Configure driver state specific to this SSC peripheral */

  priv->base    = SAM_SSC1_BASE;
  priv->datalen = CONFIG_SAMV7_SSC1_DATALEN;
  priv->align   = SAMV7_SSC1_DATAMASK;
  priv->pid     = SAM_PID_SSC1;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: sam_ssc_initialize
 *
 * Description:
 *   Initialize the selected SSC port
 *
 * Input Parameters:
 *   port - I2S "port" number (identifying the "logical" SSC port)
 *
 * Returned Value:
 *   Valid SSC device structure reference on success; a NULL on failure
 *
 ****************************************************************************/

struct i2s_dev_s *sam_ssc_initialize(int port)
{
  struct sam_ssc_s *priv;
  irqstate_t flags;
  int ret;

  /* The support SAM parts have only a single SSC port */

  i2sinfo("port: %d\n", port);

  /* Allocate a new state structure for this chip select.  NOTE that there
   * is no protection if the same chip select is used in two different
   * chip select structures.
   */

  priv = kmm_zalloc(sizeof(struct sam_ssc_s));
  if (!priv)
    {
      i2serr("ERROR: Failed to allocate a chip select structure\n");
      return NULL;
    }

  /* Set up the initial state for this chip select structure.  Other fields
   * were zeroed by kmm_zalloc().
   */

  /* Initialize the common parts for the SSC device structure  */

  nxmutex_init(&priv->lock);
  priv->dev.ops = &g_sscops;
  priv->sscno   = port;

  /* Initialize buffering */

  ssc_buf_initialize(priv);

  flags = enter_critical_section();
#ifdef CONFIG_SAMV7_SSC0
  if (port == 0)
    {
      ssc0_configure(priv);
    }
  else
#endif /* CONFIG_SAMV7_SSC0 */
#ifdef CONFIG_SAMV7_SSC1
  if (port == 1)
    {
      ssc1_configure(priv);
    }
  else
#endif /* CONFIG_SAMV7_SSC1 */
    {
      i2serr("ERROR:  Unsupported I2S port: %d\n", port);
      goto errout_with_alloc;
    }

  /* Allocate DMA channels */

  ret = ssc_dma_allocate(priv);
  if (ret < 0)
    {
      goto errout_with_alloc;
    }

  /* Configure and enable clocking */

  ssc_clocking(priv);

  /* Configure the receiver */

  ret = ssc_rx_configure(priv);
  if (ret < 0)
    {
      i2serr("ERROR: Failed to configure the receiver: %d\n", ret);
      goto errout_with_clocking;
    }

  /* Configure the transmitter */

  ret = ssc_tx_configure(priv);
  if (ret < 0)
    {
      i2serr("ERROR: Failed to configure the transmitter: %d\n", ret);
      goto errout_with_clocking;
    }

  leave_critical_section(flags);
  scc_dump_regs(priv, "After initialization");

  /* Success exit */

  return &priv->dev;

  /* Failure exits */

errout_with_clocking:
  sam_disableperiph1(priv->pid);
  ssc_dma_free(priv);

errout_with_alloc:
  nxmutex_destroy(&priv->lock);
  nxsem_destroy(&priv->bufsem);
  kmm_free(priv);
  return NULL;
}

#endif /* SSC_HAVE_RX || SSC_HAVE_TX */
#endif /* CONFIG_SAMV7_SSC0 || CONFIG_SAMV7_SSC1 */