nuttx/arch/arm/src/sama5/sam_ssc.c

3193 lines
88 KiB
C

/****************************************************************************
* arch/arm/src/sama5/sam_ssc.c
*
* Copyright (C) 2013 Gregory Nutt. All rights reserved.
* Authors: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <wdog.h>
#include <errno.h>
#include <assert.h>
#include <queue.h>
#include <debug.h>
#include <arch/board/board.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <nuttx/audio/i2s.h>
#include "up_internal.h"
#include "up_arch.h"
#include "cache.h"
#include "chip.h"
#include "sam_pio.h"
#include "sam_dmac.h"
#include "sam_memories.h"
#include "sam_periphclks.h"
#include "sam_ssc.h"
#include "chip/sam_pmc.h"
#include "chip/sam_ssc.h"
#include "chip/sam_pinmap.h"
#if defined(CONFIG_SAMA5_SSC0) || defined(CONFIG_SAMA5_SSC1)
/****************************************************************************
* Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_SCHED_WORKQUEUE
# error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
#endif
#ifndef SAMA5_SSC_MAXINFLIGHT
# define SAMA5_SSC_MAXINFLIGHT 16
#endif
#if defined(CONFIG_SAMA5_SSC0) && !defined(CONFIG_SAMA5_DMAC0)
# error CONFIG_SAMA5_DMAC0 required by SSC0
#endif
#if defined(CONFIG_SAMA5_SSC1) && !defined(CONFIG_SAMA5_DMAC1)
# error CONFIG_SAMA5_DMAC1 required by SSC1
#endif
#ifndef CONFIG_SAMA5_SSC0_DATALEN
# define CONFIG_SAMA5_SSC0_DATALEN 16
#endif
#if CONFIG_SAMA5_SSC0_DATALEN < 2 || CONFIG_SAMA5_SSC0_DATALEN > 32
# error Invalid value for CONFIG_SAMA5_SSC0_DATALEN
#endif
/* Check if we need to build RX and/or TX support */
#undef SSC_HAVE_RX
#undef SSC_HAVE_TX
#if (defined(CONFIG_SAMA5_SSC0) && defined(CONFIG_SAMA5_SSC0_RX)) || \
(defined(CONFIG_SAMA5_SSC1) && defined(CONFIG_SAMA5_SSC1_RX))
# define SSC_HAVE_RX
#endif
#if (defined(CONFIG_SAMA5_SSC0) && defined(CONFIG_SAMA5_SSC0_TX)) || \
(defined(CONFIG_SAMA5_SSC1) && defined(CONFIG_SAMA5_SSC1_TX))
# define SSC_HAVE_TX
#endif
/* 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_SAMA5_SSC0_RX) && defined(CONFIG_SAMA5_SSC0_RX_MCKDIV)) || \
(defined(CONFIG_SAMA5_SSC0_TX) && defined(CONFIG_SAMA5_SSC0_TX_MCKDIV))
# define SSC0_HAVE_MCK2 1
#endif
#if (defined(CONFIG_SAMA5_SSC1_RX) && defined(CONFIG_SAMA5_SSC1_RX_MCKDIV)) || \
(defined(CONFIG_SAMA5_SSC1_TX) && defined(CONFIG_SAMA5_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_SAMA5_SSC0_DATALEN
* FSLEN and STTDLY are fixed at two clocks
* DATNB is fixed a one work
*
* REVISIT: These will probably need to be configurable
*/
#define SSC_FSLEN (2) /* TF/RF plus width in clocks */
#define SSC_STTDLY (2) /* Delay to data start in clocks (same as FSLEN) */
#define SSC_DATNB (1) /* Number words per per frame */
#define SCC_PERIOD (SSC_FSLEN + CONFIG_SAMA5_SSC0_DATALEN * SSC_DATNB)
/* Clocking *****************************************************************/
/* Select MCU-specific settings
*
* SSC is driven by the main clock, divided down so that the maximum
* peripheral clocking is not exceeded.
*/
#if BOARD_MCK_FREQUENCY <= SAM_SSC_MAXPERCLK
# define SSC_FREQUENCY BOARD_MCK_FREQUENCY
# define SSC_PCR_DIV PMC_PCR_DIV1
#elif (BOARD_MCK_FREQUENCY >> 1) <= SAM_SSC_MAXPERCLK
# define SSC_FREQUENCY (BOARD_MCK_FREQUENCY >> 1)
# define SSC_PCR_DIV PMC_PCR_DIV2
#elif (BOARD_MCK_FREQUENCY >> 2) <= SAM_SSC_MAXPERCLK
# define SSC_FREQUENCY (BOARD_MCK_FREQUENCY >> 2)
# define SSC_PCR_DIV PMC_PCR_DIV4
#elif (BOARD_MCK_FREQUENCY >> 3) <= SAM_SSC_MAXPERCLK
# define SSC_FREQUENCY (BOARD_MCK_FREQUENCY >> 3)
# define SSC_PCR_DIV PMC_PCR_DIV8
#else
# error Cannot realize SSC input frequency
#endif
/* 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 */
/* DMA configuration */
#define DMA_PID(pid) ((pid) << DMACH_FLAG_PERIPHPID_SHIFT)
#define DMA8_FLAGS \
(DMACH_FLAG_PERIPHAHB_AHB_IF2 | DMACH_FLAG_PERIPHH2SEL | \
DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_8BITS | \
DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
((0x3f) << DMACH_FLAG_MEMPID_SHIFT) | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_16BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_4)
#define DMA16_FLAGS \
(DMACH_FLAG_PERIPHAHB_AHB_IF2 | DMACH_FLAG_PERIPHH2SEL | \
DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_16BITS | \
DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
((0x3f) << DMACH_FLAG_MEMPID_SHIFT) | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_16BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_4)
#define DMA32_FLAGS \
(DMACH_FLAG_PERIPHAHB_AHB_IF2 | DMACH_FLAG_PERIPHH2SEL | \
DMACH_FLAG_PERIPHISPERIPH | DMACH_FLAG_PERIPHWIDTH_32BITS | \
DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
((0x3f) << DMACH_FLAG_MEMPID_SHIFT) | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_32BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_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 ((DMA_TIMEOUT_MS + (MSEC_PER_TICK-1)) / MSEC_PER_TICK)
/* Debug *******************************************************************/
/* Check if SSC debut is enabled (non-standard.. no support in
* include/debug.h
*/
#ifndef CONFIG_DEBUG
# undef CONFIG_DEBUG_VERBOSE
# undef CONFIG_DEBUG_I2S
#endif
#ifndef CONFIG_DEBUG_I2S
# undef CONFIG_SAMA5_SSC_DMADEBUG
# undef CONFIG_SAMA5_SSC_REGDEBUG
# undef CONFIG_SAMA5_SSC_QDEBUG
# undef CONFIG_SAMA5_SSC_DUMPBUFFERS
#endif
#ifndef CONFIG_DEBUG_DMA
# undef CONFIG_SAMA5_SSC_DMADEBUG
#endif
#ifdef CONFIG_DEBUG_I2S
# define i2sdbg dbg
# define i2slldbg lldbg
# ifdef CONFIG_DEBUG_VERBOSE
# define i2svdbg dbg
# define i2sllvdbg lldbg
# else
# define i2svdbg(x...)
# endif
#else
# define i2sdbg(x...)
# define i2slldbg(x...)
# define i2svdbg(x...)
# define i2sllvdbg(x...)
#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 */
WDOG_ID 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_SAMA5_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 */
sem_t exclsem; /* Assures mutually exclusive acess to SSC */
uint16_t rxenab:1; /* True: RX transfers enabled */
uint16_t txenab:1; /* True: TX transfers enabled */
uint16_t loopback:1; /* True: Loopback mode */
uint16_t sscno:1; /* SSC controller number (0 or 1) */
uint16_t rxclk:2; /* Receiver clock source. See SSC_CLKSRC_* definitions */
uint16_t txclk:2; /* Transmitter clock source. See SSC_CLKSRC_* definitions */
uint16_t rxout:2; /* Receiver clock output. See SSC_CLKOUT_* definitions */
uint16_t txout:2; /* Transmitter clock output. See SSC_CLKOUT_* definitions */
uint8_t datalen; /* Data width (2-32) */
uint8_t pid; /* Peripheral ID */
#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[SAMA5_SSC_MAXINFLIGHT];
/* Debug stuff */
#ifdef CONFIG_SAMA5_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_SAMA5_SSC_REGDEBUG */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register helpers */
#ifdef CONFIG_SAMA5_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_physregaddr(struct sam_ssc_s *priv,
unsigned int offset);
#if defined(CONFIG_DEBUG_I2S) && defined(CONFIG_DEBUG_VERBOSE)
static void scc_dump_regs(struct sam_ssc_s *priv, const char *msg);
#else
# define scc_dump_regs(s,m)
#endif
#ifdef CONFIG_SAMA5_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_SAMA5_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
/* Semaphore helpers */
static void ssc_exclsem_take(struct sam_ssc_s *priv);
#define ssc_exclsem_give(priv) sem_post(&priv->exclsem)
static void ssc_bufsem_take(struct sam_ssc_s *priv);
#define ssc_bufsem_give(priv) sem_post(&priv->bufsem)
/* 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_SAMA5_SSC_DMADEBUG
static void ssc_dma_sampleinit(struct sam_ssc_s *priv,
struct sam_transport_s *xpt);
#endif
#if defined(CONFIG_SAMA5_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_SAMA5_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(int argc, uint32_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(int argc, uint32_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 */
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_SAMA5_SSC0
static void ssc0_configure(struct sam_ssc_s *priv);
#endif
#ifdef CONFIG_SAMA5_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_SAMA5_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 */
lldbg("...[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_SAMA5_SSC_REGDEBUG
if (ssc_checkreg(priv, false, regval, regaddr))
{
lldbg("%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_SAMA5_SSC_REGDEBUG
if (ssc_checkreg(priv, true, regval, regaddr))
{
lldbg("%08x<-%08x\n", regaddr, regval);
}
#endif
putreg32(regval, regaddr);
}
/****************************************************************************
* Name: ssc_physregaddr
*
* Description:
* Return the physical address of an HSMCI register
*
****************************************************************************/
static inline uintptr_t ssc_physregaddr(struct sam_ssc_s *priv,
unsigned int offset)
{
return sam_physregaddr(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
*
****************************************************************************/
#if defined(CONFIG_DEBUG_I2S) && defined(CONFIG_DEBUG_VERBOSE)
static void scc_dump_regs(struct sam_ssc_s *priv, const char *msg)
{
i2svdbg("SSC%d: %s\n", priv->sscno, msg);
i2svdbg(" 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));
i2svdbg(" 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));
i2svdbg(" 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_SAMA5_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)
{
i2sllvdbg(" %p: No buffer\n", bfcontainer);
}
else
{
i2sllvdbg(" %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 = irqsave();
i2sllvdbg("%s\n", msg);
i2sllvdbg(" Pending:\n");
ssc_dump_queue(&xpt->pend);
i2sllvdbg(" Active:\n");
ssc_dump_queue(&xpt->act);
i2sllvdbg(" Done:\n");
ssc_dump_queue(&xpt->done);
irqrestore(flags);
}
#endif
/****************************************************************************
* Name: ssc_exclsem_take
*
* Description:
* Take the exclusive access semaphore handling any exceptional conditions
*
* Input Parameters:
* priv - A reference to the SSC peripheral state
*
* Returned Value:
* None
*
****************************************************************************/
static void ssc_exclsem_take(struct sam_ssc_s *priv)
{
int ret;
/* Wait until we successfully get the semaphore. EINTR is the only
* expected 'failure' (meaning that the wait for the semaphore was
* interrupted by a signal.
*/
do
{
ret = sem_wait(&priv->exclsem);
DEBUGASSERT(ret == 0 || errno == EINTR);
}
while (ret < 0);
}
/****************************************************************************
* Name: ssc_exclsem_take
*
* Description:
* Take the buffer semaphore handling any exceptional conditions
*
* Input Parameters:
* priv - A reference to the SSC peripheral state
*
* Returned Value:
* None
*
****************************************************************************/
static void ssc_bufsem_take(struct sam_ssc_s *priv)
{
int ret;
/* Wait until we successfully get the semaphore. EINTR is the only
* expected 'failure' (meaning that the wait for the semaphore was
* interrupted by a signal.
*/
do
{
ret = sem_wait(&priv->bufsem);
DEBUGASSERT(ret == 0 || errno == EINTR);
}
while (ret < 0);
}
/****************************************************************************
* 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;
/* Set aside a buffer container. By doing this, we guarantee that we will
* have at least one free buffer container.
*/
ssc_bufsem_take(priv);
/* Get the buffer from the head of the free list */
flags = irqsave();
bfcontainer = priv->freelist;
ASSERT(bfcontainer);
/* Unlink the buffer from the freelist */
priv->freelist = bfcontainer->flink;
irqrestore(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 = irqsave();
bfcontainer->flink = priv->freelist;
priv->freelist = bfcontainer;
irqrestore(flags);
/* Wake up any threads waiting for a buffer container */
ssc_bufsem_give(priv);
}
/****************************************************************************
* 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 intialization so that there are no issues with
* concurrency.
*
****************************************************************************/
static void ssc_buf_initialize(struct sam_ssc_s *priv)
{
int i;
priv->freelist = NULL;
sem_init(&priv->bufsem, 0, SAMA5_SSC_MAXINFLIGHT);
for (i = 0; i < SAMA5_SSC_MAXINFLIGHT; i++)
{
ssc_buf_free(priv, &priv->containers[i]);
}
}
/****************************************************************************
* Name: ssc_dma_sampleinit
*
* Description:
* Initialize sampling of DMA registers (if CONFIG_SAMA5_SSC_DMADEBUG)
*
* Input Parameters:
* priv - SSC state instance
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_SAMA5_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_SAMA5_SSC_DMADEBUG) && defined(SSC_HAVE_RX)
static void ssc_rxdma_sampledone(struct sam_ssc_s *priv, int result)
{
lldbg("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_SAMA5_SSC_DMADEBUG) && defined(SSC_HAVE_TX)
static void ssc_txdma_sampledone(struct sam_ssc_s *priv, int result)
{
lldbg("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:
* argc - The number of arguments (should be 1)
* arg - The argument (state structure reference cast to uint32_t)
*
* Returned Value:
* None
*
* Assumptions:
* Always called from the interrupt level with interrupts disabled.
*
****************************************************************************/
#ifdef SSC_HAVE_RX
static void ssc_rxdma_timeout(int argc, uint32_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 paddr;
uintptr_t maddr;
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 & 3) == 0);
/* No data received yet */
apb->nbytes = 0;
/* Physical address of the SSC RHR register and of the buffer location
* in RAM.
*/
paddr = ssc_physregaddr(priv, SAM_SSC_RHR_OFFSET);
maddr = sam_physramaddr((uintptr_t)apb->samp);
/* Configure the RX DMA */
sam_dmarxsetup(priv->rx.dma, paddr, maddr, 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.
*/
cp15_invalidate_dcache((uintptr_t)apb->samp,
(uintptr_t)apb->samp + apb->nmaxbytes);
}
#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, (wdentry_t)ssc_rxdma_timeout,
1, (uint32_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)
{
i2slldbg("ERROR: wd_start failed: %d\n", errno);
}
}
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.
*/
i2svdbg("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_SAMA5_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 = irqsave();
(void)ssc_rxdma_setup(priv);
irqrestore(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
* interupts must be enabled to do this because the rx.done queue is
* also modified from the interrupt level.
*/
flags = irqsave();
bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rx.done);
irqrestore(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)
{
i2slldbg("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 */
(void)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:
* argc - The number of arguments (should be 1)
* arg - The argument (state structure reference cast to uint32_t)
*
* Returned Value:
* None
*
* Assumptions:
* Always called from the interrupt level with interrupts disabled.
*
****************************************************************************/
#ifdef SSC_HAVE_TX
static void ssc_txdma_timeout(int argc, uint32_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 paddr;
uintptr_t maddr;
uint32_t timeout;
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;
DEBUGASSERT(((uintptr_t)apb->samp & 3) == 0);
/* Physical address of the SSC THR register and of the buffer location
* in
* RAM.
*/
paddr = ssc_physregaddr(priv, SAM_SSC_THR_OFFSET);
maddr = sam_physramaddr((uintptr_t)apb->samp);
/* Configure the TX DMA */
sam_dmatxsetup(priv->tx.dma, paddr, maddr, apb->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.
*/
cp15_clean_dcache((uintptr_t)apb->samp,
(uintptr_t)apb->samp + apb->nbytes);
}
#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, (wdentry_t)ssc_txdma_timeout,
1, (uint32_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)
{
i2slldbg("ERROR: wd_start failed: %d\n", errno);
}
}
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.
*/
i2svdbg("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_SAMA5_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 = irqsave();
(void)ssc_txdma_setup(priv);
irqrestore(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
* interupts must be enabled to do this because the tx.done queue is
* also modified from the interrupt level.
*/
flags = irqsave();
bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.done);
irqrestore(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)
{
i2slldbg("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 */
(void)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_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 I2C 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);
/* Save the new data width */
priv->datalen = bits;
/* Upate the DMA flags */
ret = ssc_dma_flags(priv, &dmaflags);
if (ret < 0)
{
i2sdbg("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 recieve 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;
struct sam_buffer_s *bfcontainer;
irqstate_t flags;
int ret;
DEBUGASSERT(priv && apb && ((uintptr_t)apb->samp & 3) == 0);
i2svdbg("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);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the SSC driver data */
ssc_exclsem_take(priv);
/* Has the RX channel been enabled? */
if (!priv->rxenab)
{
i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno);
ret = -EAGAIN;
goto errout_with_exclsem;
}
/* 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 = irqsave();
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
ssc_dump_rxqueues(priv, "Receving");
/* Then start the next transfer. If there is already a transfer in progess,
* then this will do nothing.
*/
ret = ssc_rxdma_setup(priv);
DEBUGASSERT(ret == OK);
irqrestore(flags);
ssc_exclsem_give(priv);
return OK;
errout_with_exclsem:
ssc_exclsem_give(priv);
ssc_buf_free(priv, bfcontainer);
return ret;
#else
i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno);
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 I2C transmitter or if (2) the sample rate is
* driven by the I2C 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);
/* Save the new data width */
priv->datalen = bits;
/* Upate the DMA flags */
ret = ssc_dma_flags(priv, &dmaflags);
if (ret < 0)
{
i2sdbg("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;
struct sam_buffer_s *bfcontainer;
irqstate_t flags;
int ret;
DEBUGASSERT(priv && apb && ((uintptr_t)apb->samp & 3) == 0);
i2svdbg("apb=%p nbytes=%d arg=%p timeout=%d\n",
apb, apb->nbytes, arg, timeout);
ssc_dump_buffer("Sending", apb->samp, apb->nbytes);
#ifdef SSC_HAVE_TX
/* Allocate a buffer container in advance */
bfcontainer = ssc_buf_allocate(priv);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the SSC driver data */
ssc_exclsem_take(priv);
/* Has the TX channel been enabled? */
if (!priv->txenab)
{
i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno);
ret = -EAGAIN;
goto errout_with_exclsem;
}
/* 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 = irqsave();
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 progess,
* then this will do nothing.
*/
ret = ssc_txdma_setup(priv);
DEBUGASSERT(ret == OK);
irqrestore(flags);
ssc_exclsem_give(priv);
return OK;
errout_with_exclsem:
ssc_exclsem_give(priv);
ssc_buf_free(priv, bfcontainer);
return ret;
#else
i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno);
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;
/* 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:
i2sdbg("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:
i2sdbg("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(SSC_STTDLY - 1) | 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' deterimined 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(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(0) FSLEN field extension = 0
*/
regval = (SSC_RFMR_DATLEN(CONFIG_SAMA5_SSC0_DATALEN - 1) | SSC_RFMR_MSBF |
SSC_RFMR_DATNB(SSC_DATNB - 1) | SSC_RFMR_FSLEN(SSC_FSLEN - 1) |
SSC_RFMR_FSOS_NONE | SSC_RFMR_FSLENEXT(0));
/* 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;
/* 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:
i2sdbg("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:
i2sdbg("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 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(SSC_STTDLY - 1) |
SSC_TCMR_PERIOD(SCC_PERIOD / 2 - 1));
}
else
{
regval |= (SSC_TCMR_CKG_CONT | SSC_TCMR_START_EDGE |
SSC_TCMR_STTDLY(SSC_STTDLY - 1) |
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 hardcoded to:
*
* SSC_TFMR_DATLEN(n) 'n' deterimined 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 Frame sync data is enabled
* SSC_TFMR_FSLEN(1) Pulse length = + (FSLEN_EXT * 16) + 1 = 2 TX clocks
* SSC_TFMR_FSLENEXT(0) FSLEN field extension = 0
*
* 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(CONFIG_SAMA5_SSC0_DATALEN - 1) |
SSC_TFMR_MSBF | SSC_TFMR_DATNB(SSC_DATNB - 1) |
SSC_TFMR_FSLEN(SSC_FSLEN - 1) | SSC_TFMR_FSOS_NEGATIVE |
SSC_TFMR_FSDEN | SSC_TFMR_FSLENEXT(0));
}
else
{
regval = (SSC_TFMR_DATLEN(CONFIG_SAMA5_SSC0_DATALEN - 1) |
SSC_TFMR_MSBF | SSC_TFMR_DATNB(SSC_DATNB - 1) |
SSC_TFMR_FSLEN(SSC_FSLEN - 1) | SSC_TFMR_FSOS_NONE |
SSC_TFMR_FSDEN | SSC_TFMR_FSLENEXT(0));
}
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 Parameter:
* 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 Parameter:
* 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;
/* Set the maximum SSC peripheral clock frequency */
regval = PMC_PCR_PID(priv->pid) | PMC_PCR_CMD | SSC_PCR_DIV | 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 */
(void)ssc_mck2divider(priv);
/* Enable peripheral clocking */
sam_enableperiph1(priv->pid);
i2svdbg("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 regval;
switch (priv->datalen)
{
case 8:
regval = DMA8_FLAGS;
break;
case 16:
regval = DMA16_FLAGS;
break;
case 32:
regval = DMA32_FLAGS;
break;
default:
i2sdbg("ERROR: Unsupported data width: %d\n", priv->datalen);
return -ENOSYS;
}
*dmaflags = (regval | DMA_PID(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)
{
i2sdbg("ERROR: ssc_dma_flags failed: %d\n", ret);
return ret;
}
/* Allocate DMA channels. These allocations exploit that fact that
* SSC0 is managed by DMAC0 and SSC1 is managed by DMAC1. Hence,
* the SSC number (sscno) is the same as the DMAC number.
*/
#ifdef SSC_HAVE_RX
if (priv->rxenab)
{
/* Allocate an RX DMA channel */
priv->rx.dma = sam_dmachannel(priv->sscno, dmaflags);
if (!priv->rx.dma)
{
i2sdbg("ERROR: Failed to allocate the RX DMA channel\n");
goto errout;
}
/* Create a watchdog time to catch RX DMA timeouts */
priv->rx.dog = wd_create();
if (!priv->rx.dog)
{
i2sdbg("ERROR: Failed to create the RX DMA watchdog\n");
goto errout;
}
}
#endif
#ifdef SSC_HAVE_TX
if (priv->txenab)
{
/* Allocate a TX DMA channel */
priv->tx.dma = sam_dmachannel(priv->sscno, dmaflags);
if (!priv->tx.dma)
{
i2sdbg("ERROR: Failed to allocate the TX DMA channel\n");
goto errout;
}
/* Create a watchdog time to catch TX DMA timeouts */
priv->tx.dog = wd_create();
if (!priv->tx.dog)
{
i2sdbg("ERROR: Failed to create the TX DMA watchdog\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
if (priv->tx.dog)
{
wd_delete(priv->tx.dog);
}
if (priv->tx.dma)
{
sam_dmafree(priv->tx.dma);
}
#endif
#ifdef SSC_HAVE_RX
if (priv->rx.dog)
{
wd_delete(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_SAMA5_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_SAMA5_SSC0_RX
priv->rxenab = true;
/* Configure the receiver data (RD) and receiver frame synchro (RF) pins */
sam_configpio(PIO_SSC0_RD);
sam_configpio(PIO_SSC0_RF);
#if defined(CONFIG_SAMA5_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_configpio(PIO_SSC0_RK); /* External clock received on the RK I/O pad */
priv->rxclk = SSC_CLKSRC_RKIN;
#elif defined(CONFIG_SAMA5_SSC0_RX_TXCLK)
priv->rxclk = SSC_CLKSRC_TXOUT;
#elif defined(CONFIG_SAMA5_SSC0_RX_MCKDIV)
priv->rxclk = SSC_CLKSRC_MCKDIV;
#else
priv->rxclk = SSC_CLKSRC_NONE;
#endif
/* Remember the configured RX clock output */
#if defined(CONFIG_SAMA5_SSC0_RX_RKOUTPUT_CONT)
priv->rxout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMA5_SSC0_RX_RKOUTPUT_XFR)
priv->rxout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMA5_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_SAMA5_SSC0_RX */
#ifdef CONFIG_SAMA5_SSC0_TX
priv->txenab = true;
/* Configure the transmitter data (TD) and transmitter frame synchro (TF)
* pins
*/
sam_configpio(PIO_SSC0_TD);
sam_configpio(PIO_SSC0_TF);
#if defined(CONFIG_SAMA5_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_configpio(PIO_SSC0_TK); /* External clock received on the TK I/O pad */
priv->txclk = SSC_CLKSRC_TKIN;
#elif defined(CONFIG_SAMA5_SSC0_TX_RXCLK)
priv->txclk = SSC_CLKSRC_RXOUT;
#elif defined(CONFIG_SAMA5_SSC0_TX_MCKDIV)
priv->txclk = SSC_CLKSRC_MCKDIV;
#else
priv->txclk = SSC_CLKSRC_NONE;
#endif
/* Remember the configured TX clock output */
#if defined(CONFIG_SAMA5_SSC0_TX_TKOUTPUT_CONT)
priv->txout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMA5_SSC0_TX_TKOUTPUT_XFR)
priv->txout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMA5_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_SAMA5_SSC0_TX */
/* Set/clear loopback mode */
#if defined(CONFIG_SAMA5_SSC0_RX) && defined(CONFIG_SAMA5_SSC0_TX) && \
defined(CONFIG_SAMA5_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_SAMA5_SSC0_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
priv->samplerate = 0;
#endif
/* Configure driver state specific to this SSC peripheral */
priv->base = SAM_SSC0_VBASE;
priv->datalen = CONFIG_SAMA5_SSC0_DATALEN;
priv->pid = SAM_PID_SSC0;
}
#endif
#ifdef CONFIG_SAMA5_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_SAMA5_SSC1_RX
priv->rxenab = true;
/* Configure the receiver data (RD) and receiver frame synchro (RF) pins */
sam_configpio(PIO_SSC1_RD);
sam_configpio(PIO_SSC1_RF);
#ifdef CONFIG_SAMA5_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_configpio(PIO_SSC1_RK); /* External clock received on the RK I/O pad */
priv->rxclk = SSC_CLKSRC_RKIN;
#elif defined(CONFIG_SAMA5_SSC1_RX_TXCLK)
priv->rxclk = SSC_CLKSRC_TXOUT;
#elif defined(CONFIG_SAMA5_SSC1_RX_MCKDIV)
priv->rxclk = SSC_CLKSRC_MCKDIV;
#else
priv->rxclk = SSC_CLKSRC_NONE;
#endif
/* Remember the configured RX clock output */
#if defined(CONFIG_SAMA5_SSC1_RX_RKOUTPUT_CONT)
priv->rxout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMA5_SSC1_RX_RKOUTPUT_XFR)
priv->rxout = SSC_CLKOUT_XFER; /* Only output clock during transfers */
#else /* if defined(CONFIG_SAMA5_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_SAMA5_SSC1_RX */
#ifdef CONFIG_SAMA5_SSC1_TX
priv->txenab = true;
/* Configure the transmitter data (TD) and transmitter frame synchro (TF)
* pins
*/
sam_configpio(PIO_SSC1_TD);
sam_configpio(PIO_SSC1_TF);
#if defined(CONFIG_SAMA5_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_configpio(PIO_SSC1_TK); /* External clock received on the TK I/O pad */
priv->txclk = SSC_CLKSRC_TKIN;
#elif defined(CONFIG_SAMA5_SSC1_TX_RXCLK)
priv->txclk = SSC_CLKSRC_RXOUT;
#elif defined(CONFIG_SAMA5_SSC1_TX_MCKDIV)
priv->txclk = SSC_CLKSRC_MCKDIV;
#else
priv->txclk = SSC_CLKSRC_NONE;
#endif
/* Remember the configured TX clock output */
#if defined(CONFIG_SAMA5_SSC1_TX_TKOUTPUT_CONT)
priv->txout = SSC_CLKOUT_CONT; /* Continuous */
#elif defined(CONFIG_SAMA5_SSC1_TX_TKOUTPUT_XFR)
priv->txout = SSC_CLKOUT_XFER;/* Only output clock during transfers */
#else /* if defined(CONFIG_SAMA5_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_SAMA5_SSC1_TX */
/* Set/clear loopback mode */
#if defined(CONFIG_SAMA5_SSC1_RX) && defined(CONFIG_SAMA5_SSC1_TX) && \
defined(CONFIG_SAMA5_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_SAMA5_SSC1_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
priv->samplerate = 0;
#endif
/* Configure driver state specific to this SSC peripheral */
priv->base = SAM_SSC1_VBASE;
priv->datalen = CONFIG_SAMA5_SSC1_DATALEN;
priv->pid = SAM_PID_SSC1;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sam_ssc_initialize
*
* Description:
* Initialize the selected SSC port
*
* Input Parameter:
* port - I2S "port" number (identifying the "logical" SSC port)
*
* Returned Value:
* Valid SSC device structure reference on succcess; 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 */
i2svdbg("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 = (struct sam_ssc_s *)zalloc(sizeof(struct sam_ssc_s));
if (!priv)
{
i2sdbg("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 zalloc().
*/
/* Initialize the common parts for the SSC device structure */
sem_init(&priv->exclsem, 0, 1);
priv->dev.ops = &g_sscops;
priv->sscno = port;
/* Initialize buffering */
ssc_buf_initialize(priv);
flags = irqsave();
#ifdef CONFIG_SAMA5_SSC0
if (port == 0)
{
ssc0_configure(priv);
}
else
#endif /* CONFIG_SAMA5_SSC0 */
#ifdef CONFIG_SAMA5_SSC1
if (port == 1)
{
ssc1_configure(priv);
}
else
#endif /* CONFIG_SAMA5_SSC1 */
{
i2sdbg("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)
{
i2sdbg("ERROR: Failed to configure the receiver: %d\n", ret);
goto errout_with_clocking;
}
/* Configure the transmitter */
ret = ssc_tx_configure(priv);
if (ret < 0)
{
i2sdbg("ERROR: Failed to configure the transmitter: %d\n", ret);
goto errout_with_clocking;
}
irqrestore(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:
sem_destroy(&priv->exclsem);
kfree(priv);
return NULL;
}
#endif /* CONFIG_SAMA5_SSC0 || CONFIG_SAMA5_SSC1 */