--- /dev/null
+/****************************************************************************
+ *
+ * Copyright 2017 Samsung Electronics All Rights Reserved.
+ *
+ * Licensed 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.
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * arch/arm/src/sama5/s5j_i2s.c
+ *
+ * Copyright (C) 2013-2014, 2016 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 <tinyara/config.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <semaphore.h>
+#include <errno.h>
+#include <assert.h>
+#include <queue.h>
+#include <debug.h>
+
+#include <arch/board/board.h>
+
+#include <tinyara/irq.h>
+#include <tinyara/arch.h>
+#include <tinyara/kmalloc.h>
+#include <tinyara/wdog.h>
+#include <tinyara/wqueue.h>
+#include <tinyara/audio/audio.h>
+#include <tinyara/audio/i2s.h>
+
+#include "up_internal.h"
+#include "up_arch.h"
+#include "cache.h"
+
+#include "chip.h"
+#include "chip/s5jt200_i2s.h"
+#include "s5j_dma.h"
+#include "s5j_vclk.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Configuration ************************************************************/
+
+#ifndef CONFIG_SCHED_WORKQUEUE
+#error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
+#endif
+
+#ifndef CONFIG_AUDIO
+#error CONFIG_AUDIO required by this driver
+#endif
+
+#ifndef CONFIG_S5J_I2S_MAXINFLIGHT
+#define CONFIG_S5J_I2S_MAXINFLIGHT 16
+#endif
+
+/* Assume no RX/TX support until we learn better */
+
+#undef I2S_HAVE_RX
+#undef I2S_HAVE_TX_P
+#undef I2S_HAVE_TX_S
+
+#if defined(CONFIG_S5J_I2S)
+
+/* The I2S can handle most any bit width from 2 to 32. However, the DMA
+ * logic here is constrained to byte, half-word, and word sizes.
+ */
+
+/* Check for I2S RX support */
+
+#if defined(CONFIG_S5J_I2S_RX)
+#define I2S_HAVE_RX 1
+#endif
+
+/* Check for I2S TX support */
+
+#if defined(CONFIG_S5J_I2S_TX_P)
+#define I2S_HAVE_TX_P 1
+#endif
+
+#if defined(CONFIG_S5J_I2S_TX_S)
+#define I2S_HAVE_TX_S 1
+#endif
+
+#ifndef CONFIG_S5J_I2S_DATALEN
+#define CONFIG_S5J_I2S_DATALEN 16
+#endif
+
+#ifndef CONFIG_S5J_I2S_SAMPLERATE
+#define CONFIG_S5J_I2S_SAMPLERATE 1
+#endif
+
+#endif
+
+#ifdef CONFIG_DEBUG
+#define CONFIG_DEBUG_FEATURES
+#endif
+
+/* DMA configuration */
+
+#if !defined(CONFIG_I2S_TXP_DMACH) || !defined(CONFIG_I2S_TXS_DMACH) || !defined(CONFIG_I2S_RX_DMACH)
+#undef CONFIG_I2S_TXP_DMACH
+#undef CONFIG_I2S_TXS_DMACH
+#undef CONFIG_I2S_RX_DMACH
+
+#define CONFIG_I2S_TXP_DMACH 0
+#define CONFIG_I2S_TXS_DMACH 1
+#define CONFIG_I2S_RX_DMACH 2
+#endif
+
+#if defined(I2S_HAVE_RX) || defined(I2S_HAVE_TX_P) || defined(I2S_HAVE_TX_S)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+/* I2S buffer container */
+
+struct s5j_buffer_s {
+ struct s5j_buffer_s *flink; /* Supports a singly linked list */
+ i2s_callback_t callback; /* Function to call when the transfer completes */
+ dma_task *dmatask; /* DMA transfer task structure */
+ 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 s5j_transport_s {
+ DMA_HANDLE dma; /* I2S 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 */
+};
+
+/* The state of the one I2S peripheral */
+
+struct s5j_i2s_s {
+ struct i2s_dev_s dev; /* Externally visible I2S interface */
+ uintptr_t base; /* I2S controller register base address */
+ sem_t exclsem; /* Assures mutually exclusive acess to I2S */
+ uint8_t datalen; /* Data width (8, 16, or 32) */
+ uint8_t rx_datalen; /* Data width (8, 16, or 32) */
+ uint8_t txp_datalen; /* Data width (8, 16, or 32) */
+ uint8_t txs_datalen; /* Data width (8, 16, or 32) */
+
+ uint8_t rxenab: 1; /* True: RX transfers enabled */
+ uint8_t txpenab: 1; /* True: TX primary transfers enabled */
+ uint8_t txsenab: 1; /* True: TX secondary transfers enabled */
+
+ uint32_t samplerate; /* Not actually needed in slave mode */
+
+#ifdef I2S_HAVE_RX
+ struct s5j_transport_s rx; /* RX transport state */
+#endif
+#ifdef I2S_HAVE_TX_P
+ struct s5j_transport_s txp; /* TX primary transport state */
+#endif
+#ifdef I2S_HAVE_TX_S
+ struct s5j_transport_s txs; /* TX secodary transport state */
+#endif
+
+ /* Pre-allocated pool of buffer containers */
+
+ sem_t bufsem_tx; /* Buffer wait semaphore */
+ struct s5j_buffer_s *freelist_tx; /* A list a free buffer containers */
+ struct s5j_buffer_s containers_tx[CONFIG_S5J_I2S_MAXINFLIGHT];
+
+ sem_t bufsem_rx; /* Buffer wait semaphore */
+ struct s5j_buffer_s *freelist_rx; /* A list a free buffer containers */
+ struct s5j_buffer_s containers_rx[CONFIG_S5J_I2S_MAXINFLIGHT];
+ /* Debug stuff */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Register helpers */
+
+#ifdef CONFIG_S5J_I2S_DUMPBUFFERS
+#define i2s_init_buffer(b,s) memset(b, 0x55, s);
+#define i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
+#else
+#define i2s_init_buffer(b,s)
+#define i2s_dump_buffer(m,b,s)
+#endif
+
+/* Semaphore helpers */
+
+static void i2s_exclsem_take(struct s5j_i2s_s *priv);
+#define i2s_exclsem_give(priv) sem_post(&priv->exclsem)
+
+static void i2s_bufsem_rx_take(struct s5j_i2s_s *priv);
+#define i2s_bufsem_rx_give(priv) sem_post(&priv->bufsem_rx)
+
+static void i2s_bufsem_tx_take(struct s5j_i2s_s *priv);
+#define i2s_bufsem_tx_give(priv) sem_post(&priv->bufsem_tx)
+
+/* Buffer container helpers */
+
+static struct s5j_buffer_s *i2s_buf_rx_allocate(struct s5j_i2s_s *priv);
+static void i2s_buf_rx_free(struct s5j_i2s_s *priv, struct s5j_buffer_s *bfcontainer);
+static void i2s_buf_rx_initialize(struct s5j_i2s_s *priv);
+
+static struct s5j_buffer_s *i2s_buf_tx_allocate(struct s5j_i2s_s *priv);
+static void i2s_buf_tx_free(struct s5j_i2s_s *priv, struct s5j_buffer_s *bfcontainer);
+static void i2s_buf_tx_initialize(struct s5j_i2s_s *priv);
+
+#ifdef I2S_HAVE_RX
+static void i2s_rxdma_timeout(int argc, uint32_t arg);
+static int i2s_rxdma_setup(struct s5j_i2s_s *priv);
+static void i2s_rx_worker(void *arg);
+static void i2s_rx_schedule(struct s5j_i2s_s *priv, int result);
+static void i2s_rxdma_callback(DMA_HANDLE handle, void *arg, int result);
+#endif
+#ifdef I2S_HAVE_TX_P
+static void i2s_txpdma_timeout(int argc, uint32_t arg);
+static int i2s_txpdma_setup(struct s5j_i2s_s *priv);
+static void i2s_txp_worker(void *arg);
+static void i2s_txp_schedule(struct s5j_i2s_s *priv, int result);
+static void i2s_txpdma_callback(DMA_HANDLE handle, void *arg, int result);
+#endif
+
+/* I2S methods (and close friends) */
+
+static int i2s_checkwidth(struct s5j_i2s_s *priv, int bits);
+
+static uint32_t i2s_rxdatawidth(struct i2s_dev_s *dev, int bits);
+static int i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
+ i2s_callback_t callback, void *arg, uint32_t timeout);
+
+static uint32_t i2s_samplerate(struct i2s_dev_s *dev, uint32_t rate);
+
+static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits);
+static int i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
+ i2s_callback_t callback, void *arg, uint32_t timeout);
+
+/* Initialization */
+
+#ifdef I2S_HAVE_RX
+static int i2s_rx_configure(struct s5j_i2s_s *priv);
+#endif
+#ifdef I2S_HAVE_TX_P
+static int i2s_txp_configure(struct s5j_i2s_s *priv);
+#endif
+static uint32_t i2s_bitrate(struct s5j_i2s_s *priv);
+static int i2s_dma_allocate(struct s5j_i2s_s *priv);
+static void i2s_dma_free(struct s5j_i2s_s *priv);
+
+static int i2s_configure(struct s5j_i2s_s *priv);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+/* I2S device operations */
+
+static const struct i2s_ops_s g_i2sops = {
+ /* Receiver methods */
+
+ .i2s_rxsamplerate = i2s_samplerate,
+ .i2s_rxdatawidth = i2s_rxdatawidth,
+ .i2s_receive = i2s_receive,
+
+ /* Transmitter methods */
+
+ .i2s_txsamplerate = i2s_samplerate,
+ .i2s_txdatawidth = i2s_txdatawidth,
+ .i2s_send = i2s_send,
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: i2s_exclsem_take
+ *
+ * Description:
+ * Take the exclusive access semaphore handling any exceptional conditions
+ *
+ * Input Parameters:
+ * priv - A reference to the I2S peripheral state
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void i2s_exclsem_take(struct s5j_i2s_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: i2s_bufsem_take
+ *
+ * Description:
+ * Take the buffer semaphore handling any exceptional conditions
+ *
+ * Input Parameters:
+ * priv - A reference to the I2S peripheral state
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void i2s_bufsem_rx_take(struct s5j_i2s_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_rx);
+ DEBUGASSERT(ret == 0 || errno == EINTR);
+ } while (ret < 0);
+}
+
+static void i2s_bufsem_tx_take(struct s5j_i2s_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_tx);
+ DEBUGASSERT(ret == 0 || errno == EINTR);
+ } while (ret < 0);
+}
+
+/****************************************************************************
+ * Name: i2s_buf_allocate
+ *
+ * Description:
+ * Allocate a buffer container by removing the one at the head of the
+ * free list
+ *
+ * Input Parameters:
+ * priv - i2s 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 I2S state structure.
+ * That would result in a deadlock!
+ *
+ ****************************************************************************/
+
+static struct s5j_buffer_s *i2s_buf_rx_allocate(struct s5j_i2s_s *priv)
+{
+ struct s5j_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.
+ */
+
+ i2s_bufsem_rx_take(priv);
+
+ /* Get the buffer from the head of the free list */
+
+ flags = irqsave();
+ bfcontainer = priv->freelist_rx;
+ ASSERT(bfcontainer);
+
+ /* Unlink the buffer from the freelist */
+
+ priv->freelist_rx = bfcontainer->flink;
+ irqrestore(flags);
+
+ return bfcontainer;
+}
+
+static struct s5j_buffer_s *i2s_buf_tx_allocate(struct s5j_i2s_s *priv)
+{
+ struct s5j_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.
+ */
+
+ i2s_bufsem_tx_take(priv);
+
+ /* Get the buffer from the head of the free list */
+
+ flags = irqsave();
+ bfcontainer = priv->freelist_tx;
+ ASSERT(bfcontainer);
+
+ /* Unlink the buffer from the freelist */
+
+ priv->freelist_tx = bfcontainer->flink;
+ irqrestore(flags);
+
+ return bfcontainer;
+}
+
+/****************************************************************************
+ * Name: i2s_buf_free
+ *
+ * Description:
+ * Free buffer container by adding it to the head of the free list
+ *
+ * Input Parameters:
+ * priv - I2S state instance
+ * bfcontainer - The buffer container to be freed
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * The caller has exclusive access to the I2S state structure
+ *
+ ****************************************************************************/
+
+static void i2s_buf_rx_free(struct s5j_i2s_s *priv, struct s5j_buffer_s *bfcontainer)
+{
+ irqstate_t flags;
+
+ /* Put the buffer container back on the free list */
+
+ flags = irqsave();
+ bfcontainer->flink = priv->freelist_rx;
+ priv->freelist_rx = bfcontainer;
+ irqrestore(flags);
+
+ /* Wake up any threads waiting for a buffer container */
+
+ i2s_bufsem_rx_give(priv);
+}
+
+static void i2s_buf_tx_free(struct s5j_i2s_s *priv, struct s5j_buffer_s *bfcontainer)
+{
+ irqstate_t flags;
+
+ /* Put the buffer container back on the free list */
+
+ flags = irqsave();
+ bfcontainer->flink = priv->freelist_tx;
+ priv->freelist_tx = bfcontainer;
+ irqrestore(flags);
+
+ /* Wake up any threads waiting for a buffer container */
+
+ i2s_bufsem_tx_give(priv);
+}
+
+/****************************************************************************
+ * Name: i2s_buf_initialize
+ *
+ * Description:
+ * Initialize the buffer container allocator by adding all of the
+ * pre-allocated buffer containers to the free list
+ *
+ * Input Parameters:
+ * priv - I2S state instance
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Called early in I2S initialization so that there are no issues with
+ * concurrency.
+ *
+ ****************************************************************************/
+
+static void i2s_buf_rx_initialize(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ priv->freelist_rx = NULL;
+ sem_init(&priv->bufsem_rx, 0, CONFIG_S5J_I2S_MAXINFLIGHT);
+
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ i2s_buf_rx_free(priv, &priv->containers_rx[i]);
+ }
+}
+
+static void i2s_buf_tx_initialize(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ priv->freelist_tx = NULL;
+ sem_init(&priv->bufsem_tx, 0, CONFIG_S5J_I2S_MAXINFLIGHT);
+
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ i2s_buf_tx_free(priv, &priv->containers_tx[i]);
+ }
+}
+
+/****************************************************************************
+ * Name: i2s_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 I2S_HAVE_RX
+static void i2s_rxdma_timeout(int argc, uint32_t arg)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ DEBUGASSERT(priv != NULL);
+
+ /* Cancel the DMA */
+
+ s5j_dmastop(priv->rx.dma);
+
+ /* Then schedule completion of the transfer to occur on the worker thread.
+ * NOTE: s5j_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.
+ */
+
+ i2s_rx_schedule(priv, -ETIMEDOUT);
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_rxdma_setup
+ *
+ * Description:
+ * Setup and initiate the next RX DMA transfer
+ *
+ * Input Parameters:
+ * priv - I2S state instance
+ *
+ * Returned Value:
+ * OK on success; a negated errno value on failure
+ *
+ * Assumptions:
+ * Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static int i2s_rxdma_setup(struct s5j_i2s_s *priv)
+{
+ struct s5j_buffer_s *bfcontainer;
+ struct ap_buffer_s *apb;
+ uint32_t timeout;
+ bool notimeout;
+ int ret;
+ dma_task *dmatask = 0;
+
+ /* 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;
+ }
+
+ /* 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 s5j_buffer_s *)sq_remfirst(&priv->rx.pend);
+ DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+ apb = bfcontainer->apb;
+ dmatask = bfcontainer->dmatask;
+ /* No data received yet */
+
+ apb->nbytes = 0;
+ apb->curbyte = 0;
+
+ dmatask->dst = (void *)apb->samp;
+ dmatask->src = (void *)(priv->base + S5J_I2S_RXD);
+ dmatask->size = apb->nmaxbytes;
+ dmatask->callback = i2s_rxdma_callback;
+ dmatask->arg = priv;
+
+ /* Configure the RX DMA task */
+
+ s5j_dmasetup(priv->rx.dma, dmatask);
+
+ /* 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.
+ */
+
+ arch_invalidate_dcache((uintptr_t) dmatask->dst, (uintptr_t)(dmatask->dst + apb->nmaxbytes));
+
+ }
+
+ /* FLUSH RX FIFO */
+ putreg32(I2S_FIC_RFLUSH, priv->base + S5J_I2S_FIC);
+ putreg32(0, priv->base + S5J_I2S_FIC);
+
+ /* Enable Receiver */
+ modifyreg32(priv->base + S5J_I2S_CON, 0, I2S_CR_RXDMACTIVE);
+ modifyreg32(priv->base + S5J_I2S_CON, 0, I2S_CR_I2SACTIVE);
+
+ /* Start the DMA, saving the container as the current active transfer */
+
+ s5j_dmastart(priv->rx.dma, dmatask);
+
+ /* Start a watchdog to catch DMA timeouts */
+
+ if (!notimeout) {
+ ret = wd_start(priv->rx.dog, timeout, (wdentry_t) i2s_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) {
+ lldbg("ERROR: wd_start failed: %d\n", errno);
+ }
+ }
+
+ return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_rx_worker
+ *
+ * Description:
+ * RX transfer done worker
+ *
+ * Input Parameters:
+ * arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static void i2s_rx_worker(void *arg)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ struct s5j_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_setup() from the interrupt level.
+ * So we have to start the next DMA here.
+ */
+
+ llvdbg("rx.act.head=%p rx.done.head=%p\n", priv->rx.act.head, priv->rx.done.head);
+
+ /* Check if the DMA is IDLE */
+
+ if (sq_empty(&priv->rx.act)) {
+
+ /* Then start the next DMA. This must be done with interrupts
+ * disabled.
+ */
+
+ flags = irqsave();
+ (void)i2s_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
+ * interrupts must be enabled to do this because the rx.done queue is
+ * also modified from the interrupt level.
+ */
+
+ flags = irqsave();
+ bfcontainer = (struct s5j_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;
+ }
+
+ i2s_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 */
+
+ i2s_buf_rx_free(priv, bfcontainer);
+ }
+
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_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 I2S_HAVE_RX
+static void i2s_rx_schedule(struct s5j_i2s_s *priv, int result)
+{
+ struct s5j_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 s5j_dmastop() logic and directly via the
+ * i2s_rxdma_timeout() logic.
+ */
+
+ /* 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 s5j_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, i2s_rx_worker, priv, 0);
+ if (ret != 0) {
+ lldbg("ERROR: Failed to queue RX work: %d\n", ret);
+ }
+ }
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_rxdma_callback
+ *
+ * Description:
+ * This callback function is invoked at the completion of the I2S 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 I2S_HAVE_RX
+static void i2s_rxdma_callback(DMA_HANDLE handle, void *arg, int result)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ DEBUGASSERT(priv != NULL);
+
+ /* Cancel the watchdog timeout */
+
+ (void)wd_cancel(priv->rx.dog);
+
+ /* 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 s5j_dmasetup() from an interrupt handler!
+ */
+
+ /* Then schedule completion of the transfer to occur on the worker thread */
+
+ i2s_rx_schedule(priv, result);
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_txpdma_timeout
+ *
+ * Description:
+ * The Primary TX watchdog timeout without completion of the TX 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 I2S_HAVE_TX_P
+static void i2s_txpdma_timeout(int argc, uint32_t arg)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ DEBUGASSERT(priv != NULL);
+
+ /* Cancel the DMA */
+
+ s5j_dmastop(priv->txp.dma);
+
+ /* Then schedule completion of the transfer to occur on the worker thread.
+ * NOTE: s5j_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.
+ */
+
+ i2s_txp_schedule(priv, -ETIMEDOUT);
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_txpdma_setup
+ *
+ * Description:
+ * Setup and initiate the next TX DMA transfer
+ *
+ * Input Parameters:
+ * priv - I2S state instance
+ *
+ * Returned Value:
+ * OK on success; a negated errno value on failure
+ *
+ * Assumptions:
+ * Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX_P
+static int i2s_txpdma_setup(struct s5j_i2s_s *priv)
+{
+ struct s5j_buffer_s *bfcontainer;
+ struct ap_buffer_s *apb;
+ uint32_t timeout;
+ bool notimeout;
+ int ret;
+ dma_task *dmatask = 0;
+ dmavdbg("Entry\n");
+
+ /* If there is already an active transmission in progress, then bail
+ * returning success.
+ */
+
+ if (!sq_empty(&priv->txp.act)) {
+ return OK;
+ }
+
+ /* If there are no pending transfer, then bail returning success */
+
+ if (sq_empty(&priv->txp.pend)) {
+ return OK;
+ }
+
+ /* 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 s5j_buffer_s *)sq_remfirst(&priv->txp.pend);
+ DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+ apb = bfcontainer->apb;
+ dmatask = bfcontainer->dmatask;
+ /* Get the transfer information, accounting for any data offset */
+
+ //dmatask->dst = (void *)((((int)apb->samp) & ~3) + 4);
+ dmatask->dst = (void *)(priv->base + S5J_I2S_TXD);
+ dmatask->src = (void *)&apb->samp[apb->curbyte];
+ dmatask->size = apb->nbytes - apb->curbyte;
+ dmatask->callback = i2s_txpdma_callback;
+ dmatask->arg = priv;
+
+ /* Configure the TX DMA task */
+
+ s5j_dmasetup(priv->txp.dma, dmatask);
+
+ /* 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->txp.act);
+
+ /* Flush the data cache so that everything is in the physical memory
+ * before starting the DMA.
+ */
+
+ arch_clean_dcache((uintptr_t) dmatask->src, (uintptr_t)(dmatask->src + dmatask->size));
+ }
+
+ /* Start the DMA, saving the container as the current active transfer */
+ s5j_dmastart(priv->txp.dma, dmatask);
+
+ /* Enable transmitter */
+ modifyreg32(priv->base + S5J_I2S_CON, 0, I2S_CR_TXDMACTIVE);
+ modifyreg32(priv->base + S5J_I2S_CON, 0, I2S_CR_I2SACTIVE);
+
+ /* Start a watchdog to catch DMA timeouts */
+
+ if (!notimeout) {
+ ret = wd_start(priv->txp.dog, timeout, (wdentry_t) i2s_txpdma_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) {
+ lldbg("ERROR: wd_start failed: %d\n", errno);
+ }
+ }
+
+ return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_txp_worker
+ *
+ * Description:
+ * Primary TX transfer done worker
+ *
+ * Input Parameters:
+ * arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX_P
+static void i2s_txp_worker(void *arg)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ struct s5j_buffer_s *bfcontainer;
+ irqstate_t flags;
+
+ DEBUGASSERT(priv);
+
+ /* When the transfer was started, the active buffer containers were removed
+ * from the txp.pend queue and saved in the txp.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 txp.act will be moved to the end
+ * of the txp.done queue and txp.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_setup() from the interrupt level.
+ * So we have to start the next DMA here.
+ */
+
+ llvdbg("txp.act.head=%p txp.done.head=%p\n", priv->txp.act.head, priv->txp.done.head);
+
+ /* Check if the DMA is IDLE */
+
+ if (sq_empty(&priv->txp.act)) {
+
+ /* Then start the next DMA. This must be done with interrupts
+ * disabled.
+ */
+
+ flags = irqsave();
+ (void)i2s_txpdma_setup(priv);
+ irqrestore(flags);
+ }
+
+ /* Process each buffer in the tx.done queue */
+
+ while (sq_peek(&priv->txp.done) != NULL) {
+ /* Remove the buffer container from the tx.done queue. NOTE that
+ * interrupts must be enabled to do this because the tx.done queue is
+ * also modified from the interrupt level.
+ */
+
+ flags = irqsave();
+ bfcontainer = (struct s5j_buffer_s *)sq_remfirst(&priv->txp.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 */
+
+ i2s_buf_tx_free(priv, bfcontainer);
+ }
+
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_txp_schedule
+ *
+ * Description:
+ * A 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 I2S_HAVE_TX_P
+static void i2s_txp_schedule(struct s5j_i2s_s *priv, int result)
+{
+ struct s5j_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 s5j_dmastop() logic and directly via the
+ * i2s_txdma_timeout() logic.
+ */
+
+ /* Move all entries from the tx.act queue to the tx.done queue */
+
+ while (!sq_empty(&priv->txp.act)) {
+ /* Remove the next buffer container from the tx.act list */
+
+ bfcontainer = (struct s5j_buffer_s *)sq_remfirst(&priv->txp.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->txp.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->txp.work)) {
+ /* Schedule the TX DMA done processing to occur on the worker thread. */
+
+ ret = work_queue(HPWORK, &priv->txp.work, i2s_txp_worker, priv, 0);
+ if (ret != 0) {
+ lldbg("ERROR: Failed to queue TX primary work: %d\n", ret);
+ }
+ }
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_txpdma_callback
+ *
+ * Description:
+ * This callback function is invoked at the completion of the I2S 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 I2S_HAVE_TX_P
+static void i2s_txpdma_callback(DMA_HANDLE handle, void *arg, int result)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)arg;
+ DEBUGASSERT(priv != NULL);
+
+ /* Cancel the watchdog timeout */
+
+ (void)wd_cancel(priv->txp.dog);
+
+ /* 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 s5j_dmasetup() from an interrupt handler!
+ */
+
+ /* Then schedule completion of the transfer to occur on the worker thread */
+
+ i2s_txp_schedule(priv, result);
+}
+#endif
+
+/****************************************************************************
+ * Name: i2s_checkwidth
+ *
+ * Description:
+ * Check for a valid bit width. The I2S 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 i2s_checkwidth(struct s5j_i2s_s *priv, int bits)
+{
+ /* The I2S can handle most any bit width from 2 to 32. However, the DMA
+ * logic here is constrained to byte, half-word, and word sizes.
+ */
+
+ switch (bits) {
+ case 8:
+ break;
+
+ case 16:
+ break;
+
+ case 32:
+ break;
+
+ default:
+ lldbg("ERROR: Unsupported or invalid data width: %d\n", bits);
+ return (bits < 2 || bits > 32) ? -EINVAL : -ENOSYS;
+ }
+
+ /* Save the new data width */
+ priv->datalen = bits;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_bitrate
+ *
+ * Description:
+ * In master mode supposed to set bit rate. But now, in slave mode,
+ * we just calculate it. Still we set all bit related regs.
+ *
+ * Input Parameter:
+ * priv - I2S device structure (only the sample rate and data length is
+ * needed at this point).
+ *
+ * Returned Value:
+ * The current bitrate
+ *
+ ****************************************************************************/
+
+static uint32_t i2s_bitrate(struct s5j_i2s_s *priv)
+{
+ uint32_t bitrate;
+ uint32_t regval;
+ DEBUGASSERT(priv && priv->samplerate > 0 && priv->datalen > 0);
+
+ switch (priv->datalen) {
+ case 8:
+ regval = BLC_8BIT;
+ break;
+
+ case 16:
+ regval = BLC_16BIT;
+ break;
+
+ case 32:
+ regval = BLC_24BIT;
+ break;
+
+ default:
+ lldbg("ERROR: Unsupported or invalid datalen: %d\n", priv->datalen);
+ return -EINVAL;
+ }
+
+ modifyreg32(priv->base + S5J_I2S_MOD, I2S_MOD_BLC_S_MASK, I2S_MOD_BLC_S(regval));
+ modifyreg32(priv->base + S5J_I2S_MOD, I2S_MOD_BLC_P_MASK, I2S_MOD_BLC_P(regval));
+ modifyreg32(priv->base + S5J_I2S_MOD, I2S_MOD_BLC_MASK, I2S_MOD_BLC(regval));
+
+ /* Even in slave mode it recommended to set. Set common values */
+ modifyreg32(priv->base + S5J_I2S_MOD, I2S_MOD_RFS_MASK, I2S_MOD_RFS(RFS_192));
+ modifyreg32(priv->base + S5J_I2S_MOD, I2S_MOD_BFS_MASK, I2S_MOD_BFS(BFS_64));
+
+ bitrate = priv->samplerate * priv->datalen;
+ return bitrate;
+}
+
+/****************************************************************************
+ * Name: i2s_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 i2s_rxdatawidth(struct i2s_dev_s *dev, int bits)
+{
+
+#ifdef I2S_HAVE_RX
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)dev;
+ int ret;
+
+ DEBUGASSERT(priv && bits > 1);
+
+ /* Check if this is a bit width that we are configured to handle */
+
+ ret = i2s_checkwidth(priv, bits);
+ if (ret < 0) {
+ lldbg("ERROR: i2s_checkwidth failed: %d\n", ret);
+ return 0;
+ }
+
+ /* Reconfigure the RX DMA (and TX DMA if applicable) */
+ return i2s_bitrate(priv);
+#endif
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: i2s_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 i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2s_callback_t callback, void *arg, uint32_t timeout)
+{
+
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)dev;
+#ifdef I2S_HAVE_RX
+ struct s5j_buffer_s *bfcontainer;
+ irqstate_t flags;
+ int ret;
+#endif
+
+ llvdbg("apb=%p nmaxbytes=%d arg=%p timeout=%d\n", apb, apb->nmaxbytes, arg, timeout);
+
+ i2s_init_buffer(apb->samp, apb->nmaxbytes);
+
+#ifdef I2S_HAVE_RX
+ /* Allocate a buffer container in advance */
+
+ bfcontainer = i2s_buf_rx_allocate(priv);
+ DEBUGASSERT(bfcontainer);
+
+ /* Get exclusive access to the I2S driver data */
+
+ i2s_exclsem_take(priv);
+
+ /* Has the RX channel been enabled? */
+
+ if (!priv->rxenab) {
+ lldbg("ERROR: I2S has no receiver\n");
+ 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);
+
+ /* Then start the next transfer. If there is already a transfer in progess,
+ * then this will do nothing.
+ */
+
+ ret = i2s_rxdma_setup(priv);
+ DEBUGASSERT(ret == OK);
+ irqrestore(flags);
+ i2s_exclsem_give(priv);
+ return OK;
+
+errout_with_exclsem:
+ i2s_exclsem_give(priv);
+ i2s_buf_rx_free(priv, bfcontainer);
+ return ret;
+
+#else
+ lldbg("ERROR: I2S has no receiver\n");
+ UNUSED(priv);
+ return -ENOSYS;
+#endif
+
+}
+
+/****************************************************************************
+ * Name: i2s_samplerate
+ *
+ * Description:
+ * Set the I2S 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 i2s_samplerate(struct i2s_dev_s *dev, uint32_t rate)
+{
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)dev;
+ DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0);
+
+ /* Check if the receiver is driven by the MCK/2 */
+
+ priv->samplerate = rate;
+ return i2s_bitrate(priv);
+}
+
+/****************************************************************************
+ * Name: i2s_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 i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
+{
+#ifdef I2S_HAVE_TX_P
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)dev;
+ int ret;
+
+ DEBUGASSERT(priv && bits > 1);
+
+ /* Check if this is a bit width that we are configured to handle */
+
+ ret = i2s_checkwidth(priv, bits);
+ if (ret < 0) {
+ lldbg("ERROR: i2s_checkwidth failed: %d\n", ret);
+ return 0;
+ }
+
+ /* Reconfigure the RX DMA (and TX DMA if applicable) */
+
+ return i2s_bitrate(priv);
+#endif
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: i2s_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 i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2s_callback_t callback, void *arg, uint32_t timeout)
+{
+
+ struct s5j_i2s_s *priv = (struct s5j_i2s_s *)dev;
+#ifdef I2S_HAVE_TX_P
+ struct s5j_buffer_s *bfcontainer;
+ irqstate_t flags;
+ int ret;
+#endif
+
+ DEBUGASSERT(priv && apb);
+
+ llvdbg("apb=%p nbytes=%d arg=%p timeout=%d\n", apb, apb->nbytes - apb->curbyte, arg, timeout);
+
+ i2s_dump_buffer("Sending", &apb->samp[apb->curbyte], apb->nbytes - apb->curbyte);
+
+#ifdef I2S_HAVE_TX_P
+ /* Allocate a buffer container in advance */
+
+ bfcontainer = i2s_buf_tx_allocate(priv);
+ DEBUGASSERT(bfcontainer);
+
+ /* Get exclusive access to the I2S driver data */
+
+ i2s_exclsem_take(priv);
+
+ /* Has the TX channel been enabled? */
+
+ if (!priv->txpenab) {
+ lldbg("ERROR: I2S has no transmitter\n");
+ 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->txp.pend);
+
+ /* Then start the next transfer. If there is already a transfer in progess,
+ * then this will do nothing.
+ */
+
+ ret = i2s_txpdma_setup(priv);
+ DEBUGASSERT(ret == OK);
+ irqrestore(flags);
+ i2s_exclsem_give(priv);
+ return OK;
+
+errout_with_exclsem:
+ i2s_exclsem_give(priv);
+ i2s_buf_tx_free(priv, bfcontainer);
+ return ret;
+
+#else
+ lldbg("ERROR: I2S has no transmitter\n");
+ UNUSED(priv);
+ return -ENOSYS;
+#endif
+}
+
+/****************************************************************************
+ * Name: i2s_rx/tx_configure
+ *
+ * Description:
+ * Configure the i2s receiver and transmitter.
+ *
+ * Input Parameters:
+ * priv - Fully initialized i2s device structure.
+ *
+ * Returned Value:
+ * OK is returned on failure. A negated errno value is returned on failure.
+ *
+ ****************************************************************************/
+
+static int i2s_rx_cfg_clr(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ if (priv->containers_rx[i].dmatask != NULL) {
+ s5j_dmatask_free(priv->containers_rx[i].dmatask);
+ }
+ priv->containers_rx[i].dmatask = NULL;
+ }
+
+ priv->rxenab = 0;
+ return 0;
+}
+
+static int i2s_rx_configure(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ /* Here I initialie dma task structures for RX channel */
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ priv->containers_rx[i].dmatask = dma_task_p2m_sb_4B_x256_alloc(DMA_I2S_RX);
+ if (priv->containers_rx[i].dmatask == NULL) {
+ goto err;
+ }
+ }
+
+ priv->rxenab = 1;
+ return OK;
+
+err:
+ i2s_rx_cfg_clr(priv);
+
+ return -ENOMEM;
+}
+
+static int i2s_txp_cfg_clr(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ if (priv->containers_tx[i].dmatask != NULL) {
+ s5j_dmatask_free(priv->containers_tx[i].dmatask);
+ }
+ priv->containers_tx[i].dmatask = NULL;
+ }
+
+ priv->txpenab = 0;
+ return 0;
+}
+
+static int i2s_txp_configure(struct s5j_i2s_s *priv)
+{
+ int i;
+
+ /* Here I initialize dma task structures for TXP channel */
+ for (i = 0; i < CONFIG_S5J_I2S_MAXINFLIGHT; i++) {
+ priv->containers_tx[i].dmatask = dma_task_m2p_sb_4B_x256_alloc(DMA_I2S_TX);
+
+ if (priv->containers_tx[i].dmatask == NULL) {
+ goto err;
+ }
+ }
+
+ priv->txpenab = 1;
+ return OK;
+
+err:
+ i2s_txp_cfg_clr(priv);
+ return -ENOMEM;
+}
+
+static int i2s_txs_configure(struct s5j_i2s_s *priv)
+{
+ priv->txsenab = 0;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_dma_allocate
+ *
+ * Description:
+ * Allocate I2S DMA channels
+ *
+ * Input Parameters:
+ * priv - Partially initialized I2S 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 i2s_dma_allocate(struct s5j_i2s_s *priv)
+{
+#ifdef I2S_HAVE_RX
+ if (priv->rxenab) {
+ /* Allocate an RX DMA channel */
+
+ priv->rx.dma = s5j_dmachannel(CONFIG_I2S_RX_DMACH, "pdma");
+ if (!priv->rx.dma) {
+ lldbg("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) {
+ lldbg("ERROR: Failed to create the RX DMA watchdog\n");
+ goto errout;
+ }
+ }
+#endif
+
+#ifdef I2S_HAVE_TX_P
+ if (priv->txpenab) {
+ /* Allocate a TX DMA channel */
+
+ priv->txp.dma = s5j_dmachannel(CONFIG_I2S_TXP_DMACH, "pdma");
+ if (!priv->txp.dma) {
+ lldbg("ERROR: Failed to allocate the TXP DMA channel\n");
+ goto errout;
+ }
+
+ /* Create a watchdog time to catch TX DMA timeouts */
+
+ priv->txp.dog = wd_create();
+ if (!priv->txp.dog) {
+ lldbg("ERROR: Failed to create the TXP DMA watchdog\n");
+ goto errout;
+ }
+ }
+#endif
+
+#ifdef I2S_HAVE_TX_S
+ if (priv->txsenab) {
+ /* Allocate a TX DMA channel */
+
+ priv->txs.dma = s5j_dmachannel(CONFIG_I2S_TXS_DMACH, "pdma");
+ if (!priv->txs.dma) {
+ lldbg("ERROR: Failed to allocate the TX DMA channel\n");
+ goto errout;
+ }
+
+ /* Create a watchdog time to catch TX DMA timeouts */
+
+ priv->txs.dog = wd_create();
+ if (!priv->txs.dog) {
+ lldbg("ERROR: Failed to create the TX DMA watchdog\n");
+ goto errout;
+ }
+ }
+#endif
+
+ /* Success exit */
+
+ return OK;
+
+ /* Error exit */
+
+errout:
+ i2s_dma_free(priv);
+ return -ENOMEM;
+}
+
+/****************************************************************************
+ * Name: i2s_dma_free
+ *
+ * Description:
+ * Release DMA-related resources allocated by i2s_dma_allocate()
+ *
+ * Input Parameters:
+ * priv - Partially initialized I2S device structure.
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void i2s_dma_free(struct s5j_i2s_s *priv)
+{
+#ifdef I2S_HAVE_TX_P
+ if (priv->txp.dog) {
+ wd_delete(priv->txp.dog);
+ priv->txp.dog = NULL;
+ }
+
+ if (priv->txp.dma) {
+ s5j_dmafree(priv->txp.dma);
+ priv->txp.dma = NULL;
+ }
+#endif
+
+#ifdef I2S_HAVE_TX_S
+ if (priv->txs.dog) {
+ wd_delete(priv->txs.dog);
+ priv->txs.dog = NULL;
+ }
+
+ if (priv->txs.dma) {
+ s5j_dmafree(priv->txs.dma);
+ priv->txs.dma = NULL;
+ }
+#endif
+
+#ifdef i2s_HAVE_RX
+ if (priv->rx.dog) {
+ wd_delete(priv->rx.dog);
+ priv->rx.dog = NULL;
+ }
+
+ if (priv->rx.dma) {
+ s5j_dmafree(priv->rx.dma);
+ priv->rx.dma = NULL;
+ }
+#endif
+}
+
+/****************************************************************************
+ * Name: i2s_configure
+ *
+ * Description:
+ * Configure i2s
+ *
+ * Input Parameters:
+ * priv - Partially initialized I2S device structure. These functions
+ * will complete the I2S specific portions of the initialization
+ *
+ * Returned Value:
+ * configure status
+ *
+ ****************************************************************************/
+
+static int i2s_configure(struct s5j_i2s_s *priv)
+{
+ unsigned int reg;
+ int ret;
+
+ /* GPIO initialisation */
+
+ priv->base = S5J_I2S_BASE;
+
+ /* Reset I2S */
+ putreg32(0, priv->base + S5J_I2S_CON);
+ modifyreg32(priv->base + S5J_I2S_CON, I2S_CR_SW_RST_MASK,
+ I2S_CR_SW_RST_RELEASE);
+
+ /* Set Mode */
+ reg = I2S_MOD_OP_CLK_PCLK | I2S_MOD_CDCLKCON_IN | I2S_MOD_MSS_SLAVE |
+ I2S_MOD_RCLKSRC | I2S_MOD_TXR(TXR_TXRX) | I2S_MOD_RFS(RFS_256) |
+ I2S_MOD_BFS(BFS_64);
+ putreg32(reg, priv->base + S5J_I2S_MOD);
+
+ ret = s5j_configgpio(GPIO_I2S0_BCLK);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = s5j_configgpio(GPIO_I2S0_LRCK);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = s5j_configgpio(GPIO_I2S0_SDO);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = s5j_configgpio(GPIO_I2S0_SDI);
+ if (ret < 0) {
+ return ret;
+ }
+
+ cal_clk_mux(i2s_mux, i2s_bclk);
+
+ /* We are always slave here ... Below code should set priv structure */
+ /* Set priv structure here */
+ priv->datalen = CONFIG_S5J_I2S_DATALEN;
+ priv->samplerate = CONFIG_S5J_I2S_SAMPLERATE;
+ return 0;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name:
+ *
+ * Description:
+ * Initialize the selected i2s port
+ *
+ * Input Parameter:
+ * none
+ *
+ * Returned Value:
+ * Valid i2s device structure reference on succcess; a NULL on failure
+ *
+ ****************************************************************************/
+
+struct i2s_dev_s *s5j_i2s_initialize(void)
+{
+ struct s5j_i2s_s *priv;
+ int ret;
+
+ /* 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 s5j_i2s_s *)zalloc(sizeof(struct s5j_i2s_s));
+ if (!priv) {
+ lldbg("ERROR: Failed to allocate a chip select structure\n");
+ return NULL;
+ }
+
+ /* Initialize the I2S priv device structure */
+ sem_init(&priv->exclsem, 0, 1);
+ priv->dev.ops = &g_i2sops;
+
+ /* Initialize buffering */
+ i2s_buf_rx_initialize(priv);
+ i2s_buf_tx_initialize(priv);
+
+ /* configure i2s port */
+ ret = i2s_configure(priv);
+
+#ifdef I2S_HAVE_RX
+ /* Configure the receiver */
+
+ ret = i2s_rx_configure(priv);
+ if (ret < 0) {
+ lldbg("ERROR: Failed to configure the receiver: %d\n", ret);
+ goto errout_with_clocking;
+ }
+#endif
+
+#ifdef I2S_HAVE_TX_P
+ /* Configure the transmitter */
+
+ ret = i2s_txp_configure(priv);
+ if (ret < 0) {
+ lldbg("ERROR: Failed to configure the primary transmitter: %d\n", ret);
+ goto errout_with_clocking;
+ }
+#endif
+
+#ifdef I2S_HAVE_TX_S
+ ret = i2s_txs_configure(priv);
+ if (ret < 0) {
+ lldbg("ERROR: Failed to configure the secondary transmitter: %d\n", ret);
+ goto errout_with_clocking;
+ }
+#endif
+
+ /* Allocate DMA channels */
+ ret = i2s_dma_allocate(priv);
+ if (ret < 0) {
+ goto errout_with_alloc;
+ }
+
+ /* Success exit */
+ return &priv->dev;
+
+ /* Failure exits */
+
+errout_with_alloc:
+ i2s_rx_cfg_clr(priv);
+ i2s_txp_cfg_clr(priv);
+
+errout_with_clocking:
+
+ sem_destroy(&priv->exclsem);
+ kmm_free(priv);
+ return NULL;
+}
+
+#endif /* I2S_HAVE_RX || I2S_HAVE_TX_P I2S_HAVE_TX_S */