* 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
****************************************************************************/
#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
#undef SSC_HAVE_RX
#undef SSC_HAVE_TX
#if defined(CONFIG_SAMV7_SSC0)
# ifndef CONFIG_SAMV7_XDMAC
# error CONFIG_SAMV7_XDMAC required by SSC0
# endif
* 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
# 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
# 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
#if defined(CONFIG_SAMV7_SSC1)
# ifndef CONFIG_SAMV7_XDMAC
# error CONFIG_SAMV7_XDMAC required by SSC1
# endif
* 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
# 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
# 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
#if defined(SSC_HAVE_RX) || defined(SSC_HAVE_TX)
#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
*
* |<---------------- 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)
#define SCC_PERIOD(s,d) ((s) + (d) * SSC_DATNB)
#define SSC_CLKSRC_NONE 0
#define SSC_CLKSRC_MCKDIV 1
#define SSC_CLKSRC_RXOUT 2
#define SSC_CLKSRC_TXOUT 2
#define SSC_CLKSRC_TKIN 3
#define SSC_CLKSRC_RKIN 3
#define SSC_CLKOUT_NONE 0
#define SSC_CLKOUT_CONT 1
#define SSC_CLKOUT_XFER 2
* 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
#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)
* 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)
#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
****************************************************************************/
struct sam_buffer_s
{
struct sam_buffer_s *flink;
i2s_callback_t callback;
uint32_t timeout;
void *arg;
struct ap_buffer_s *apb;
int result;
};
* transport
*/
struct sam_transport_s
{
DMA_HANDLE dma;
struct wdog_s dog;
sq_queue_t pend;
sq_queue_t act;
sq_queue_t done;
struct work_s work;
#ifdef CONFIG_SAMV7_SSC_DMADEBUG
struct sam_dmaregs_s dmaregs[DMA_NSAMPLES];
#endif
};
struct sam_ssc_s
{
struct i2s_dev_s dev;
uintptr_t base;
mutex_t lock;
uint8_t datalen;
uint8_t align;
uint8_t pid;
uint8_t rxfslen;
uint8_t txfslen;
uint8_t rxsttdly;
uint8_t txsttdly;
uint8_t rxenab:1;
uint8_t txenab:1;
uint8_t loopback:1;
uint8_t sscno:1;
uint8_t rxclk:2;
uint8_t txclk:2;
uint8_t rxout:2;
uint8_t txout:2;
uint32_t frequency;
#ifdef SSC_HAVE_MCK2
uint32_t samplerate;
#endif
#ifdef SSC_HAVE_RX
struct sam_transport_s rx;
#endif
#ifdef SSC_HAVE_TX
struct sam_transport_s tx;
#endif
sem_t bufsem;
struct sam_buffer_s *freelist;
struct sam_buffer_s containers[CONFIG_SAMV7_SSC_MAXINFLIGHT];
#ifdef CONFIG_SAMV7_SSC_REGDEBUG
bool wr;
uint32_t regaddr;
uint32_t regval;
int count;
#endif
};
* Private Function Prototypes
****************************************************************************/
#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
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);
#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
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);
#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
****************************************************************************/
static const struct i2s_ops_s g_sscops =
{
.i2s_rxsamplerate = ssc_rxsamplerate,
.i2s_rxdatawidth = ssc_rxdatawidth,
.i2s_receive = ssc_receive,
.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 &&
regval == priv->regval &&
regaddr == priv->regaddr)
{
priv->count++;
return false;
}
else
{
if (priv->count > 0)
{
i2sinfo("...[Repeats %d times]...\n", priv->count);
}
priv->wr = wr;
priv->regval = regval;
priv->regaddr = regaddr;
priv->count = 0;
}
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;
* have at least one free buffer container.
*/
ret = nxsem_wait_uninterruptible(&priv->bufsem);
if (ret < 0)
{
return NULL;
}
flags = enter_critical_section();
bfcontainer = priv->freelist;
DEBUGASSERT(bfcontainer);
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;
flags = enter_critical_section();
bfcontainer->flink = priv->freelist;
priv->freelist = bfcontainer;
leave_critical_section(flags);
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)
{
memset(xpt->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct sam_dmaregs_s));
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);
sam_dmasample(priv->rx.dma, &priv->rx.dmaregs[DMA_END_TRANSFER]);
sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_INITIAL],
"RX: Initial Registers");
sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_SETUP],
"RX: After DMA Setup");
sam_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_START],
"RX: After DMA Start");
* -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);
sam_dmasample(priv->tx.dma, &priv->tx.dmaregs[DMA_END_TRANSFER]);
sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_INITIAL],
"TX: Initial Registers");
sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_SETUP],
"TX: After DMA Setup");
sam_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_START],
"TX: After DMA Start");
* -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);
ssc_rxdma_sample(priv, DMA_TIMEOUT);
sam_dmastop(priv->rx.dma);
* 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;
* returning success.
*/
if (!sq_empty(&priv->rx.act))
{
return OK;
}
if (sq_empty(&priv->rx.pend))
{
return OK;
}
ssc_rxdma_sampleinit(priv);
timeout = 0;
notimeout = false;
do
{
* 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);
apb->nbytes = 0;
apb->curbyte = 0;
* in RAM.
*/
regaddr = ssc_regaddr(priv, SAM_SSC_RHR_OFFSET);
memaddr = (uintptr_t)apb->samp;
sam_dmarxsetup(priv->rx.dma, regaddr, memaddr, apb->nmaxbytes);
if (bfcontainer->timeout > 0)
{
timeout += bfcontainer->timeout;
}
else
{
notimeout = true;
}
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
* 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
while (0);
#else
while (!sq_empty(&priv->rx.pend));
#endif
ssc_rxdma_sample(priv, DMA_AFTER_SETUP);
sam_dmastart(priv->rx.dma, ssc_rxdma_callback, priv);
ssc_rxdma_sample(priv, DMA_AFTER_START);
ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXEN);
if (!notimeout)
{
ret = wd_start(&priv->rx.dog, timeout,
ssc_rxdma_timeout, (wdparm_t)priv);
* 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);
* 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");
if (sq_empty(&priv->rx.act))
{
#ifdef CONFIG_SAMV7_SSC_DMADEBUG
bfcontainer = (struct sam_buffer_s *)sq_peek(&priv->rx.done);
if (bfcontainer)
{
ssc_rxdma_sampledone(priv, bfcontainer->result);
}
#endif
* disabled.
*/
flags = enter_critical_section();
ssc_rxdma_setup(priv);
leave_critical_section(flags);
}
while (sq_peek(&priv->rx.done) != NULL)
{
* 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;
* the audio buffer.
*/
if (bfcontainer->result == OK)
{
apb->nbytes = apb->nmaxbytes;
}
ssc_dump_buffer("Received", apb->samp, apb->nbytes);
bfcontainer->callback(&priv->dev, apb, bfcontainer->arg,
bfcontainer->result);
* cause the audio buffer to be freed.
*/
apb_free(apb);
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;
* 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");
while (!sq_empty(&priv->rx.act))
{
bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rx.act);
bfcontainer->result = result;
* queue
*/
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done);
}
* REVISIT: There may be a race condition here. So we do nothing is the
* worker is not available.
*/
if (work_available(&priv->rx.work))
{
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);
wd_cancel(&priv->rx.dog);
ssc_rxdma_sample(priv, DMA_CALLBACK);
* get audio glitches at the boundaries between DMA transfers.
* Unfortunately, we cannot call sam_dmasetup() from an interrupt handler!
*/
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);
ssc_txdma_sample(priv, DMA_TIMEOUT);
sam_dmastop(priv->tx.dma);
* 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;
* returning success.
*/
if (!sq_empty(&priv->tx.act))
{
return OK;
}
if (sq_empty(&priv->tx.pend))
{
return OK;
}
ssc_txdma_sampleinit(priv);
timeout = 0;
notimeout = false;
do
{
* queue.
*/
bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.pend);
DEBUGASSERT(bfcontainer && bfcontainer->apb);
apb = bfcontainer->apb;
samp = (uintptr_t)&apb->samp[apb->curbyte];
nbytes = apb->nbytes - apb->curbyte;
DEBUGASSERT((samp & priv->align) == 0 && (nbytes & priv->align) == 0);
* in RAM.
*/
regaddr = ssc_regaddr(priv, SAM_SSC_THR_OFFSET);
memaddr = (uintptr_t)samp;
sam_dmatxsetup(priv->tx.dma, regaddr, memaddr, nbytes);
if (bfcontainer->timeout > 0)
{
timeout += bfcontainer->timeout;
}
else
{
notimeout = true;
}
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
* before starting the DMA.
*/
#warning REVISIT
#if 1
up_invalidate_dcache_all();
#else
up_clean_dcache(samp, samp + nbytes);
#endif
}
#if 1
while (0);
#else
while (!sq_empty(&priv->tx.pend));
#endif
ssc_txdma_sample(priv, DMA_AFTER_SETUP);
sam_dmastart(priv->tx.dma, ssc_txdma_callback, priv);
ssc_txdma_sample(priv, DMA_AFTER_START);
ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_TXEN);
if (!notimeout)
{
ret = wd_start(&priv->tx.dog, timeout,
ssc_txdma_timeout, (wdparm_t)priv);
* 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);
* 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");
if (sq_empty(&priv->tx.act))
{
#ifdef CONFIG_SAMV7_SSC_DMADEBUG
bfcontainer = (struct sam_buffer_s *)sq_peek(&priv->tx.done);
if (bfcontainer)
{
ssc_txdma_sampledone(priv, bfcontainer->result);
}
#endif
* disabled.
*/
flags = enter_critical_section();
ssc_txdma_setup(priv);
leave_critical_section(flags);
}
while (sq_peek(&priv->tx.done) != NULL)
{
* 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);
DEBUGASSERT(bfcontainer && bfcontainer->callback);
bfcontainer->callback(&priv->dev, bfcontainer->apb,
bfcontainer->arg, bfcontainer->result);
* cause the audio buffer to be freed.
*/
apb_free(bfcontainer->apb);
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;
* 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");
while (!sq_empty(&priv->tx.act))
{
bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->tx.act);
bfcontainer->result = result;
* queue
*/
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
}
* REVISIT: There may be a race condition here. So we do nothing is the
* worker is not available.
*/
if (work_available(&priv->tx.work))
{
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);
wd_cancel(&priv->tx.dog);
ssc_txdma_sample(priv, DMA_CALLBACK);
* get audio glitches at the boundaries between DMA transfers.
* Unfortunately, we cannot call sam_dmasetup() from an interrupt handler!
*/
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)
{
* 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;
}
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);
if (priv->rxclk == SSC_CLKSRC_MCKDIV)
{
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);
ret = ssc_checkwidth(priv, bits);
if (ret < 0)
{
i2serr("ERROR: ssc_checkwidth failed: %d\n", ret);
return 0;
}
ret = ssc_dma_flags(priv, &dmaflags);
if (ret < 0)
{
i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
return 0;
}
sam_dmaconfig(priv->rx.dma, dmaflags);
#ifdef SSC_HAVE_RX
if (priv->txenab)
{
sam_dmaconfig(priv->tx.dma, dmaflags);
}
#endif
#ifdef SSC_HAVE_MCK2
if (priv->rxclk == SSC_CLKSRC_MCKDIV)
{
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
bfcontainer = ssc_buf_allocate(priv);
if (bfcontainer == NULL)
{
return -ENOMEM;
}
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
goto errout_with_buf;
}
if (!priv->rxenab)
{
i2serr("ERROR: SSC%d has no receiver\n", priv->sscno);
ret = -EAGAIN;
goto errout_with_lock;
}
apb_reference(apb);
bfcontainer->callback = (void *)callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->result = -EBUSY;
flags = enter_critical_section();
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
ssc_dump_rxqueues(priv, "Receiving");
* 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);
if (priv->txclk == SSC_CLKSRC_MCKDIV)
{
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);
ret = ssc_checkwidth(priv, bits);
if (ret < 0)
{
i2serr("ERROR: ssc_checkwidth failed: %d\n", ret);
return 0;
}
ret = ssc_dma_flags(priv, &dmaflags);
if (ret < 0)
{
i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
return 0;
}
sam_dmaconfig(priv->tx.dma, dmaflags);
#ifdef SSC_HAVE_RX
if (priv->rxenab)
{
sam_dmaconfig(priv->rx.dma, dmaflags);
}
#endif
#ifdef SSC_HAVE_MCK2
if (priv->txclk == SSC_CLKSRC_MCKDIV)
{
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
* 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
bfcontainer = ssc_buf_allocate(priv);
if (bfcontainer == NULL)
{
return -ENOMEM;
}
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
goto errout_with_buf;
}
if (!priv->txenab)
{
i2serr("ERROR: SSC%d has no transmitter\n", priv->sscno);
ret = -EAGAIN;
goto errout_with_lock;
}
apb_reference(apb);
bfcontainer->callback = (void *)callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->result = -EBUSY;
flags = enter_critical_section();
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
ssc_dump_txqueues(priv, "Transmitting");
* 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;
DEBUGASSERT(priv->rxfslen > 0);
fslen = priv->rxfslen - 1;
regval = 0;
switch (priv->rxclk)
{
case SSC_CLKSRC_RKIN:
regval = SSC_RCMR_CKS_RK;
break;
case SSC_CLKSRC_TXOUT:
regval = SSC_RCMR_CKS_TK;
break;
case SSC_CLKSRC_MCKDIV:
#ifdef SSC_HAVE_MCK2
DEBUGASSERT(priv->samplerate > 0);
regval = SSC_RCMR_CKS_MCK;
break;
#endif
case SSC_CLKSRC_NONE:
default:
i2serr("ERROR: No receiver clock\n");
return -EINVAL;
}
switch (priv->rxout)
{
case SSC_CLKOUT_CONT:
regval |= SSC_RCMR_CKO_CONT;
break;
case SSC_CLKOUT_XFER:
regval |= SSC_RCMR_CKO_TRANSFER;
break;
case SSC_CLKOUT_NONE:
regval |= SSC_RCMR_CKO_NONE;
break;
default:
i2serr("ERROR: Invalid clock output selection\n");
return -EINVAL;
}
* 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);
* 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);
regval |= (SSC_RFMR_FSLEN(fslen & 0x0f) |
SSC_RFMR_FSLENEXT((fslen >> 4) & 0x0f));
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
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;
fslen = priv->txfslen > 0 ? priv->txfslen - 1 : 0;
* period of the waveform.
*/
period = SCC_PERIOD(priv->txsttdly, priv->datalen);
regval = 0;
switch (priv->txclk)
{
case SSC_CLKSRC_TKIN:
regval = SSC_TCMR_CKS_TK;
break;
case SSC_CLKSRC_RXOUT:
regval = SSC_TCMR_CKS_RK;
break;
case SSC_CLKSRC_MCKDIV:
#ifdef SSC_HAVE_MCK2
DEBUGASSERT(priv->samplerate > 0);
regval = SSC_TCMR_CKS_MCK;
break;
#endif
case SSC_CLKSRC_NONE:
default:
i2serr("ERROR: No transmitter clock\n");
return -EINVAL;
}
switch (priv->txout)
{
case SSC_CLKOUT_CONT:
regval |= SSC_TCMR_CKO_CONT;
break;
case SSC_CLKOUT_XFER:
regval |= SSC_TCMR_CKO_TRANSFER;
break;
case SSC_CLKOUT_NONE:
regval |= SSC_TCMR_CKO_NONE;
break;
default:
i2serr("ERROR: Invalid clock output selection\n");
return -EINVAL;
}
* 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);
* 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);
}
if (priv->txfslen > 0)
{
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
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);
if (priv->samplerate == 0)
{
bitrate = 0;
regval = 0;
}
else
{
bitrate = priv->samplerate * priv->datalen;
* 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);
}
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;
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
{
priv->frequency = (mck >> 3);
regval = PMC_PCR_DIV8;
}
#else
priv->frequency = mck;
regval = 0;
#endif
regval |= PMC_PCR_PID(priv->pid) | PMC_PCR_CMD | PMC_PCR_EN;
putreg32(regval, SAM_PMC_PCR);
ssc_putreg(priv, SAM_SSC_CR_OFFSET,
SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST);
ssc_mck2divider(priv);
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;
ret = ssc_dma_flags(priv, &dmaflags);
if (ret < 0)
{
i2serr("ERROR: ssc_dma_flags failed: %d\n", ret);
return ret;
}
#ifdef SSC_HAVE_RX
if (priv->rxenab)
{
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)
{
priv->tx.dma = sam_dmachannel(0, dmaflags);
if (!priv->tx.dma)
{
i2serr("ERROR: Failed to allocate the TX DMA channel\n");
goto errout;
}
}
#endif
return OK;
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)
{
* select pins must be selected by board-specific logic.
*/
#ifdef CONFIG_SAMV7_SSC0_RX
priv->rxenab = true;
sam_configgpio(GPIO_SSC0_RD);
sam_configgpio(GPIO_SSC0_RF);
#if defined(CONFIG_SAMV7_SSC0_RX_RKINPUT)
* the receiver clock.
*
* REVISIT: The SSC is also capable of generated the receiver clock
* output on the RK pin.
*/
sam_configgpio(GPIO_SSC0_RK);
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
priv->rxfslen = CONFIG_SSC0_RX_FSLEN;
priv->rxsttdly = CONFIG_SSC0_RX_STTDLY;
#if defined(CONFIG_SAMV7_SSC0_RX_RKOUTPUT_CONT)
priv->rxout = SSC_CLKOUT_CONT;
#elif defined(CONFIG_SAMV7_SSC0_RX_RKOUTPUT_XFR)
priv->rxout = SSC_CLKOUT_XFER;
#else
priv->rxout = SSC_CLKOUT_NONE;
#endif
#else
priv->rxenab = false;
priv->rxclk = SSC_CLKSRC_NONE;
priv->rxout = SSC_CLKOUT_NONE;
#endif
#ifdef CONFIG_SAMV7_SSC0_TX
priv->txenab = true;
* pins
*/
sam_configgpio(GPIO_SSC0_TD);
sam_configgpio(GPIO_SSC0_TF);
#if defined(CONFIG_SAMV7_SSC0_TX_TKINPUT)
* the transmitter clock.
*
* REVISIT: The SSC is also capable of generated the transmitter clock
* output on the TK pin.
*/
sam_configgpio(GPIO_SSC0_TK);
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
#if defined(CONFIG_SAMV7_SSC0_TX_TKOUTPUT_CONT)
priv->txout = SSC_CLKOUT_CONT;
#elif defined(CONFIG_SAMV7_SSC0_TX_TKOUTPUT_XFR)
priv->txout = SSC_CLKOUT_XFER;
#else
priv->txout = SSC_CLKOUT_NONE;
#endif
#else
priv->txenab = false;
priv->txclk = SSC_CLKSRC_NONE;
priv->txout = SSC_CLKOUT_NONE;
#endif
priv->txfslen = CONFIG_SSC0_TX_FSLEN;
priv->txsttdly = CONFIG_SSC0_TX_STTDLY;
#if defined(CONFIG_SAMV7_SSC0_RX) && defined(CONFIG_SAMV7_SSC0_TX) && \
defined(CONFIG_SAMV7_SSC0_LOOPBACK)
priv->loopback = true;
#else
priv->loopback = false;
#endif
#if defined(SSC0_HAVE_MCK2)
priv->samplerate = CONFIG_SAMV7_SSC0_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
priv->samplerate = 0;
#endif
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)
{
* select pins must be selected by board-specific logic.
*/
#ifdef CONFIG_SAMV7_SSC1_RX
priv->rxenab = true;
sam_configgpio(GPIO_SSC1_RD);
sam_configgpio(GPIO_SSC1_RF);
#ifdef CONFIG_SAMV7_SSC1_RX_RKINPUT
* the receiver clock.
*
* REVISIT: The SSC is also capable of generated the receiver clock
* output on the RK pin.
*/
sam_configgpio(GPIO_SSC1_RK);
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
priv->rxfslen = CONFIG_SSC1_RX_FSLEN;
priv->rxsttdly = CONFIG_SSC1_RX_STTDLY;
#if defined(CONFIG_SAMV7_SSC1_RX_RKOUTPUT_CONT)
priv->rxout = SSC_CLKOUT_CONT;
#elif defined(CONFIG_SAMV7_SSC1_RX_RKOUTPUT_XFR)
priv->rxout = SSC_CLKOUT_XFER;
#else
priv->rxout = SSC_CLKOUT_NONE;
#endif
#else
priv->rxenab = false;
priv->rxclk = SSC_CLKSRC_NONE;
priv->rxout = SSC_CLKOUT_NONE;
#endif
#ifdef CONFIG_SAMV7_SSC1_TX
priv->txenab = true;
* pins
*/
sam_configgpio(GPIO_SSC1_TD);
sam_configgpio(GPIO_SSC1_TF);
#if defined(CONFIG_SAMV7_SSC1_TX_TKINPUT)
* the transmitter clock.
*
* REVISIT: The SSC is also capable of generated the transmitter clock
* output on the TK pin.
*/
sam_configgpio(GPIO_SSC1_TK);
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
#if defined(CONFIG_SAMV7_SSC1_TX_TKOUTPUT_CONT)
priv->txout = SSC_CLKOUT_CONT;
#elif defined(CONFIG_SAMV7_SSC1_TX_TKOUTPUT_XFR)
priv->txout = SSC_CLKOUT_XFER;
#else
priv->txout = SSC_CLKOUT_NONE;
#endif
#else
priv->txenab = false;
priv->txclk = SSC_CLKSRC_NONE;
priv->txout = SSC_CLKOUT_NONE;
#endif
priv->txfslen = CONFIG_SSC1_TX_FSLEN;
priv->txsttdly = CONFIG_SSC1_TX_STTDLY;
#if defined(CONFIG_SAMV7_SSC1_RX) && defined(CONFIG_SAMV7_SSC1_TX) && \
defined(CONFIG_SAMV7_SSC1_LOOPBACK)
priv->loopback = true;
#else
priv->loopback = false;
#endif
#if defined(SSC1_HAVE_MCK2)
priv->samplerate = CONFIG_SAMV7_SSC1_MCKDIV_SAMPLERATE;
#elif defined(SSC_HAVE_MCK2)
priv->samplerate = 0;
#endif
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;
i2sinfo("port: %d\n", port);
* 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;
}
* were zeroed by kmm_zalloc().
*/
nxmutex_init(&priv->lock);
priv->dev.ops = &g_sscops;
priv->sscno = port;
ssc_buf_initialize(priv);
flags = enter_critical_section();
#ifdef CONFIG_SAMV7_SSC0
if (port == 0)
{
ssc0_configure(priv);
}
else
#endif
#ifdef CONFIG_SAMV7_SSC1
if (port == 1)
{
ssc1_configure(priv);
}
else
#endif
{
i2serr("ERROR: Unsupported I2S port: %d\n", port);
goto errout_with_alloc;
}
ret = ssc_dma_allocate(priv);
if (ret < 0)
{
goto errout_with_alloc;
}
ssc_clocking(priv);
ret = ssc_rx_configure(priv);
if (ret < 0)
{
i2serr("ERROR: Failed to configure the receiver: %d\n", ret);
goto errout_with_clocking;
}
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");
return &priv->dev;
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
#endif