s5j/i2s: Add device driver for I2S
authorIvan <ivan.galkin@samsung.com>
Mon, 24 Apr 2017 18:44:42 +0000 (03:44 +0900)
committerIvan Galkin <ivan.galkin@samsung.com>
Sun, 27 Aug 2017 05:02:16 +0000 (14:02 +0900)
I2S supports only 48KHz Stereo playback.
I2S supports both read and write channels.

Change-Id: Iba94830758e5b90a56fe33e33333f3709d6e2823
Signed-off-by: Ivan <ivan.galkin@samsung.com>
Signed-off-by: Bongryul Lee <bongryul.lee@samsung.com>
Signed-off-by: Junhwan Park <junhwan.park@samsung.com>
os/arch/arm/src/s5j/Kconfig
os/arch/arm/src/s5j/Make.defs
os/arch/arm/src/s5j/chip/s5jt200_i2s.h [new file with mode: 0755]
os/arch/arm/src/s5j/s5j_i2s.c [new file with mode: 0644]
os/arch/arm/src/s5j/s5j_i2s.h [new file with mode: 0755]

index 979a95d..374d667 100644 (file)
@@ -26,6 +26,7 @@ config S5J_S5JT200
        default n
        select S5J_HAVE_ADC
        select S5J_HAVE_I2C
+       select S5J_HAVE_I2S
        select S5J_HAVE_MCT
        select S5J_HAVE_PWM0
        select S5J_HAVE_PWM1
@@ -58,6 +59,10 @@ config S5J_HAVE_I2C
        bool
        default n
 
+config S5J_HAVE_I2S
+       bool
+       default n
+
 config S5J_HAVE_MCT
        bool
        default n
@@ -147,6 +152,39 @@ config S5J_I2C
        default n
        depends on S5J_HAVE_I2C
 
+config S5J_I2S
+       bool "I2S"
+       default n
+       depends on S5J_HAVE_I2S
+       select I2S
+       select AUDIO
+       select S5J_DMA
+
+config S5J_I2S_RX
+       bool "S5J I2S RX support"
+       default n
+       depends on S5J_I2S
+
+config S5J_I2S_TX_P
+       bool "S5J I2S TX Prime support"
+       default n
+       depends on S5J_I2S
+
+config S5J_I2S_TX_S
+       bool "S5J I2S TX Secondary support"
+       default n
+       depends on S5J_I2S
+
+config S5J_I2S_MAXINFLIGHT
+       int "I2S queue size"
+       default 16
+       depends on S5J_I2S
+       ---help---
+               This is the total number of transfers, both RX and TX, that can be
+               enqueue before the caller is required to wait.  This setting
+               determines the number certain queue data structures that will be
+               pre-allocated.
+
 config S5J_MCT
        bool
        default n
index eb1af77..c023f53 100644 (file)
@@ -169,6 +169,10 @@ CHIP_CSRCS += s5j_rtc_lowerhalf.c
 endif
 endif
 
+ifeq ($(CONFIG_S5J_I2S),y)
+CHIP_CSRCS += s5j_i2s.c
+endif
+
 ifeq ($(CONFIG_S5J_SSS),y)
 VPATH += chip/sss
 CHIP_CSRCS += sss_driver_io.c
diff --git a/os/arch/arm/src/s5j/chip/s5jt200_i2s.h b/os/arch/arm/src/s5j/chip/s5jt200_i2s.h
new file mode 100755 (executable)
index 0000000..6ec8e92
--- /dev/null
@@ -0,0 +1,166 @@
+/****************************************************************************
+ *
+ * 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/s5j//chip/s5jt200_i2s.h
+ *
+ *   Copyright (C) 2013-2014 Gregory Nutt. All rights reserved.
+ *   Author: 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.
+ *
+ ************************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_S5JT200_CHIP_S5J_I2S_H
+#define __ARCH_ARM_SRC_S5JT200_CHIP_S5J_I2S_H
+
+/************************************************************************************
+ * Included Files
+ ************************************************************************************/
+
+#include <tinyara/config.h>
+#include "chip/s5j_memorymap.h"
+
+/************************************************************************************
+ * Pre-processor Definitions
+ ************************************************************************************/
+
+#define S5J_I2S_MAXPERCLK         26000000     /* Maximum peripheral clock frequency */
+
+/* I2S Register Offsets *************************************************************/
+
+#define S5J_I2S_CON    0x0000  /* I2S interface control */
+#define S5J_I2S_MOD    0x0004  /* I2S interface mode */
+#define S5J_I2S_FIC    0x0008  /* I2S interface TxFIFO and Rx FIFO control */
+#define S5J_I2S_PSR    0x000C  /* I2S interface clock divider control */
+#define S5J_I2S_TXD    0x0010  /* I2S interface transmit sound data */
+#define S5J_I2S_RXD    0x0014  /* I2S interface receive data */
+#define S5J_I2S_FICS   0x0018  /* I2S interface secondary TxFIFO_S Control */
+#define S5J_I2S_TXDS   0x001C  /* I2S interface transmit secondary data */
+#define S5J_I2S_VER    0x0044  /* I2S version */
+
+/* I2S Register Bit Definitions *****************************************************/
+
+#define I2S_CR_SW_RST_SHIFT    (31)
+#define I2S_CR_SW_RST_MASK     (1 << I2S_CR_SW_RST_SHIFT)
+#define I2S_CR_SW_RST_RELEASE  (1 << I2S_CR_SW_RST_SHIFT)
+
+#define I2S_CR_FRXOFSTATUS     (1 << 26)
+#define I2S_CR_FRXOFINTEN      (1 << 25)
+#define I2S_CR_FTXSUR STATUS   (1 << 24)
+#define I2S_CR_FTXSURINTEN     (1 << 23)
+#define I2S_CR_FTXSEMPT        (1 << 22)
+#define I2S_CR_FTXSFULL        (1 << 21)
+#define I2S_CR_TXSDMAPAUSE     (1 << 20)
+#define I2S_CR_TXSDMACTIVE     (1 << 18)
+#define I2S_CR_FTXURSTATUS     (1 << 17)
+#define I2S_CR_FTXURINTEN      (1 << 16)
+#define I2S_CR_LRI             (1 << 11)
+#define I2S_CR_FTX0EMPT        (1 << 10)
+#define I2S_CR_FRXEMPT                 (1 << 9)
+#define I2S_CR_FTX0FULL        (1 << 8)
+#define I2S_CR_FRXFULL                 (1 << 7)
+#define I2S_CR_TXDMAPAUSE      (1 << 6)
+#define I2S_CR_RXDMAPAUSE      (1 << 5)
+#define I2S_CR_TXCHPAUSE       (1 << 4)
+#define I2S_CR_RXCHPAUSE       (1 << 3)
+#define I2S_CR_TXDMACTIVE      (1 << 2)
+#define I2S_CR_RXDMACTIVE      (1 << 1)
+#define I2S_CR_I2SACTIVE       (1 << 0)
+
+/* MOD register */
+#define I2S_MOD_OP_CLK_MASK    (3 << 30)
+#define I2S_MOD_OP_CLK_PCLK    (3 << 30)
+
+#define BLC_8BIT       (1)
+#define BLC_16BIT      (0)
+#define BLC_24BIT      (2)
+
+#define I2S_MOD_BLC_S_MASK     (3 << 26)
+#define I2S_MOD_BLC_S(x)       ((x & 3) << 26)
+#define I2S_MOD_BLC_P_MASK     (3 << 24)
+#define I2S_MOD_BLC_P(x)       ((x & 3) << 24)
+#define I2S_MOD_LRP            (1 << 15)
+#define I2S_MOD_BLC_MASK       (3 << 13)
+#define I2S_MOD_BLC(x)         ((x & 3) << 13)
+#define I2S_MOD_CDCLKCON_IN    (1 << 12)
+#define I2S_MOD_MSS_SLAVE      (1 << 11)
+#define I2S_MOD_RCLKSRC        (1 << 10)
+
+#define TXR_TX                         0
+#define TXR_RX                         1
+#define TXR_TXRX               2
+#define I2S_MOD_TXR_MASK       (3 << 8)
+#define I2S_MOD_TXR(x)         ((x & 3) << 8)
+
+#define I2S_MOD_SDF_MASK       (3 << 6)
+#define I2S_MOD_SDF_I2S                (0 << 6)
+#define I2S_MOD_SDF_MSB_Jstf   (1 << 6)
+#define I2S_MOD_SDF_LSB_Jstf   (2 << 6)
+
+#define RFS_256        (0x0)
+#define RFS_192        (0x7)
+
+#define BFS_64                 (0x4)
+
+#define I2S_MOD_RFS_MASK       (7 << 3)
+#define I2S_MOD_RFS(x)         ((x & 7) << 3)
+#define I2S_MOD_BFS_MASK       (7 << 0)
+#define I2S_MOD_BFS(x)         ((x & 7) << 0)
+
+/* FIC register */
+#define I2S_FIC_TFLUSH                 (1 << 15)
+#define I2S_FIC_FTX0CNT        (0x7F << 8)
+#define I2S_FIC_RFLUSH                 (1 << 7)
+#define I2S_FIC_FRXCNT                 (0x7F << 0)
+
+#define I2S_PSR_PSREN          (1 << 15)
+#define I2S_PSR_PSVAL          (0x3F << 8)
+
+#define I2S_FICS_TXDMA_TH_EN   (1 << 31)
+#define I2S_FICS_TXDMA_TH      (0x3F << 24)
+#define I2S_FICS_RXDMA_TH_EN   (1 << 23)
+#define I2S_FICS_RXDMA_TH      (0x3F << 16)
+#define I2S_FICS_TFLUSHS       (1 << 15)
+#define I2S_FICS_FTXSCNT       (0x7F << 8)
+
+#endif                                                 /* __ARCH_ARM_SRC_S5JT200_CHIP_S5J_I2S_H */
diff --git a/os/arch/arm/src/s5j/s5j_i2s.c b/os/arch/arm/src/s5j/s5j_i2s.c
new file mode 100644 (file)
index 0000000..3ee950f
--- /dev/null
@@ -0,0 +1,2063 @@
+/****************************************************************************
+ *
+ * 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 */
diff --git a/os/arch/arm/src/s5j/s5j_i2s.h b/os/arch/arm/src/s5j/s5j_i2s.h
new file mode 100755 (executable)
index 0000000..2057e35
--- /dev/null
@@ -0,0 +1,125 @@
+/****************************************************************************
+ *
+ * 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/sam_ssc.h
+ *
+ *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
+ *   Author: 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.
+ *
+ ************************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_S5JT200_S5J_I2S_H
+#define __ARCH_ARM_SRC_S5JT200_S5J_I2S_H
+
+/************************************************************************************
+ * Included Files
+ ************************************************************************************/
+
+#include <tinyara/config.h>
+#include <tinyara/audio/i2s.h>
+
+#include <arch/chip/irq.h>
+#include <chip.h>
+
+#include "s5j_gpio.h"
+#include "chip.h"
+#include "chip/s5j_i2s.h"
+
+/************************************************************************************
+ * Pre-processor Definitions
+ ************************************************************************************/
+
+/************************************************************************************
+ * Public Types
+ ************************************************************************************/
+
+/************************************************************************************
+ * Inline Functions
+ ************************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+/************************************************************************************
+ * Public Data
+ ************************************************************************************/
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C" {
+#else
+#define EXTERN extern
+#endif
+
+/************************************************************************************
+ * Public Function Prototypes
+ ************************************************************************************/
+
+#define enter_critical_section()       0
+#define leave_critical_section(x)
+
+/****************************************************************************
+ * Name: s5j_i2s_initialize
+ *
+ * Description:
+ *   Initialize the selected I2S port.
+ *
+ * Input Parameter:
+ *   Port number (for hardware that has mutiple I2S interfaces)
+ *
+ * Returned Value:
+ *   Valid I2S device structure reference on succcess; a NULL on failure
+ *
+ ****************************************************************************/
+
+FAR struct i2s_dev_s *s5j_i2s_initialize(void);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif                                                 /* __ASSEMBLY__ */
+#endif                                                 /* __ARCH_ARM_SRC_S5JT200_S5J_I2S_H */