drivers: i2s: introduce CAVS I2S

Implements the driver for Intel CAVS I2S. Only Playback
is currently supported.

Change-Id: I7b816f9736dc35e79a81d3664d6405dc0aac15b4
Signed-off-by: Rajavardhan Gundi <rajavardhan.gundi@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
diff --git a/arch/xtensa/soc/intel_s1000/soc.c b/arch/xtensa/soc/intel_s1000/soc.c
index 4d54a2d..f4e84a9 100644
--- a/arch/xtensa/soc/intel_s1000/soc.c
+++ b/arch/xtensa/soc/intel_s1000/soc.c
@@ -160,3 +160,15 @@
 {
 	xthal_dcache_region_writeback(addr, size);
 }
+
+void dcache_invalidate_region(void *addr, size_t size)
+{
+	xthal_dcache_region_invalidate(addr, size);
+}
+
+void setup_ownership_i2s(void)
+{
+	u32_t value = I2S_OWNSEL(0) | I2S_OWNSEL(1) |
+			 I2S_OWNSEL(2) | I2S_OWNSEL(3);
+	*(volatile u32_t *)SUE_DSPIOPO_REG |= value;
+}
diff --git a/arch/xtensa/soc/intel_s1000/soc.h b/arch/xtensa/soc/intel_s1000/soc.h
index 2c11137..42084df 100644
--- a/arch/xtensa/soc/intel_s1000/soc.h
+++ b/arch/xtensa/soc/intel_s1000/soc.h
@@ -100,11 +100,30 @@
 #define DMA_HANDSHAKE_SSP3_TX			8
 #define DMA_HANDSHAKE_SSP3_RX			9
 
+/* I2S */
+#define I2S0_CAVS_IRQ				0x00000010
+#define I2S1_CAVS_IRQ				0x00000110
+#define I2S2_CAVS_IRQ				0x00000210
+#define I2S3_CAVS_IRQ				0x00000310
+
+#define SSP_SIZE				0x0000200
+#define SSP_BASE(x)				(0x00077000 + (x) * SSP_SIZE)
+
+#define SOC_INTEL_S1000_MCK_XTAL_FREQ_HZ	38400000
+
+/* address of I2S ownership register. We need to properly configure
+ * this register in order to access the I2S registers.
+ */
+#define SUE_DSP_RES_ALLOC_REG_BASE		0x00071A60
+#define SUE_DSPIOPO_REG			(SUE_DSP_RES_ALLOC_REG_BASE + 0x08)
+#define I2S_OWNSEL(x)				(0x1 << (8 + (x)))
+
 extern void _soc_irq_enable(u32_t irq);
 extern void _soc_irq_disable(u32_t irq);
 extern void setup_ownership_dma0(void);
 extern void setup_ownership_dma1(void);
 extern void setup_ownership_dma2(void);
 extern void dcache_writeback_region(void *addr, size_t size);
+extern void setup_ownership_i2s(void);
 
 #endif /* __INC_SOC_H */
diff --git a/drivers/i2s/CMakeLists.txt b/drivers/i2s/CMakeLists.txt
index 8d4f120..d6ceb18 100644
--- a/drivers/i2s/CMakeLists.txt
+++ b/drivers/i2s/CMakeLists.txt
@@ -1 +1,2 @@
 zephyr_sources_ifdef(CONFIG_I2S_SAM_SSC		i2s_sam_ssc.c)
+zephyr_sources_ifdef(CONFIG_I2S_CAVS		i2s_cavs.c)
diff --git a/drivers/i2s/Kconfig.cavs b/drivers/i2s/Kconfig.cavs
new file mode 100644
index 0000000..623ad7d
--- /dev/null
+++ b/drivers/i2s/Kconfig.cavs
@@ -0,0 +1,59 @@
+# Kconfig - Intel S1000 I2S configuration options
+#
+# Copyright (c) 2017 Intel Corporation
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+menuconfig I2S_CAVS
+	bool "Intel I2S (SSP) Bus Driver"
+	depends on BOARD_INTEL_S1000_CRB
+	select DMA
+	default n
+	help
+	  Enable Inter Sound (I2S) bus driver for Intel_S1000 based on
+	  Synchronous Serial Port (SSP) module.
+
+if I2S_CAVS
+
+config I2S_CAVS_TX_BLOCK_COUNT
+	int "TX queue length"
+	default 4
+	help
+	  The maximum number of blocks that can be accommodated in the Tx queue.
+
+config I2S_CAVS_0_NAME
+	string "I2S 0 device name"
+	default "I2S_0"
+
+config I2S_CAVS_0_IRQ_PRI
+	int "Interrupt priority"
+	default 0
+
+config I2S_CAVS_DMA_NAME
+	string "DMA device name"
+	default "DMA_0"
+	help
+	  Name of the DMA device this device driver can use.
+
+config I2S_CAVS_0_DMA_TX_CHANNEL
+	int "DMA TX channel"
+	default 2
+	help
+	  DMA channel number to use for TX transfers.
+
+config I2S_CAVS_1_NAME
+	string "I2S 1 device name"
+	default "I2S_1"
+
+config I2S_CAVS_1_IRQ_PRI
+	int "Interrupt priority"
+	default 0
+
+config I2S_CAVS_1_DMA_TX_CHANNEL
+	int "DMA TX channel"
+	default 4
+	help
+	  DMA channel number to use for TX transfers.
+
+endif # I2S_CAVS
diff --git a/drivers/i2s/i2s_cavs.c b/drivers/i2s/i2s_cavs.c
new file mode 100644
index 0000000..a9ef875
--- /dev/null
+++ b/drivers/i2s/i2s_cavs.c
@@ -0,0 +1,798 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** @file
+ * @brief I2S bus (SSP) driver for Intel CAVS.
+ *
+ * Limitations:
+ * - DMA is used in simple single block transfer mode (with linked list
+ *   enabled) and "interrupt on full transfer completion" mode.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <misc/__assert.h>
+#include <kernel.h>
+#include <device.h>
+#include <init.h>
+#include <dma.h>
+#include <i2s.h>
+#include <soc.h>
+#include "i2s_cavs.h"
+
+#define SYS_LOG_DOMAIN "dev/i2s_cavs"
+#define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2S_LEVEL
+#include <logging/sys_log.h>
+
+#ifdef CONFIG_DCACHE_WRITEBACK
+#define DCACHE_INVALIDATE(addr, size) \
+		{ dcache_invalidate_region(addr, size); }
+#define DCACHE_CLEAN(addr, size) \
+		{ dcache_writeback_region(addr, size); }
+#else
+#define DCACHE_INVALIDATE(addr, size) \
+		do { } while (0)
+
+#define DCACHE_CLEAN(addr, size) \
+		do { } while (0)
+#endif
+
+#define CAVS_SSP_WORD_SIZE_BITS_MIN    2
+#define CAVS_SSP_WORD_SIZE_BITS_MAX   32
+#define CAVS_SSP_WORD_PER_FRAME_MIN    1
+#define CAVS_SSP_WORD_PER_FRAME_MAX   16
+
+struct queue_item {
+	void *mem_block;
+	size_t size;
+};
+
+/* Minimal ring buffer implementation:
+ * buf - holds the pointer to Queue items
+ * len - Max number of Queue items that can be referenced
+ * head - current write index number
+ * tail - current read index number
+ */
+struct ring_buf {
+	struct queue_item *buf;
+	u16_t len;
+	u16_t head;
+	u16_t tail;
+};
+
+/* This indicates the Tx/Rx stream. Most members of the stream are
+ * self-explanatory except for sem.
+ * sem - This is initialized to CONFIG_I2S_CAVS_TX_BLOCK_COUNT. If at all
+ * mem_block_queue gets filled with the MAX blocks configured, this semaphore
+ * ensures nothing gets written into the mem_block_queue before a slot gets
+ * freed (which happens when a block gets read out).
+ */
+struct stream {
+	s32_t state;
+	struct k_sem sem;
+	u32_t dma_channel;
+	struct dma_config dma_cfg;
+	struct i2s_config cfg;
+	struct ring_buf mem_block_queue;
+	void *mem_block;
+	bool last_block;
+	int (*stream_start)(struct stream *, struct i2s_cavs_ssp *const,
+				struct device *);
+	void (*stream_disable)(struct stream *, struct i2s_cavs_ssp *const,
+				struct device *);
+	void (*queue_drop)(struct stream *);
+};
+
+struct i2s_cavs_config {
+	struct i2s_cavs_ssp *regs;
+	u32_t irq_id;
+	void (*irq_config)(void);
+};
+
+/* Device run time data */
+struct i2s_cavs_dev_data {
+	struct device *dev_dma;
+	struct stream tx;
+};
+
+#define DEV_NAME(dev) ((dev)->config->name)
+#define DEV_CFG(dev) \
+	((const struct i2s_cavs_config *const)(dev)->config->config_info)
+#define DEV_DATA(dev) \
+	((struct i2s_cavs_dev_data *const)(dev)->driver_data)
+
+static struct device *get_dev_from_dma_channel(u32_t dma_channel);
+static void dma_tx_callback(struct device *, u32_t, int);
+static void tx_stream_disable(struct stream *, struct i2s_cavs_ssp *const,
+				struct device *);
+
+static inline u16_t modulo_inc(u16_t val, u16_t max)
+{
+	val++;
+	return (val < max) ? val : 0;
+}
+
+/*
+ * Get data from the queue
+ */
+static int queue_get(struct ring_buf *rb, u8_t mode, void **mem_block,
+			size_t *size)
+{
+	unsigned int key;
+
+	key = irq_lock();
+
+	/* In case of ping-pong mode, the buffers are not freed after
+	 * reading. They are fixed in size. Another thread will populate
+	 * the pong buffer while the ping buffer is being read out and
+	 * vice versa. Hence, we just need to keep reading from buffer0
+	 * (ping buffer) followed by buffer1 (pong buffer) and the same
+	 * cycle continues.
+	 *
+	 * In case of non-ping-pong modes, each buffer is freed after it
+	 * is read. The tail pointer will keep progressing depending upon
+	 * the reads. The head pointer will move whenever there's a write.
+	 * If tail equals head, it would mean we have read everything there
+	 * is and the buffer is empty.
+	 */
+	if (rb->tail == rb->head) {
+		if ((mode & I2S_OPT_PINGPONG) == I2S_OPT_PINGPONG) {
+			/* Point back to the first element */
+			rb->tail = 0;
+		} else {
+			/* Ring buffer is empty */
+			irq_unlock(key);
+			return -ENOMEM;
+		}
+	}
+
+	*mem_block = rb->buf[rb->tail].mem_block;
+	*size = rb->buf[rb->tail].size;
+	rb->tail = modulo_inc(rb->tail, rb->len);
+
+	irq_unlock(key);
+
+	return 0;
+}
+
+/*
+ * Put data in the queue
+ */
+static int queue_put(struct ring_buf *rb, u8_t mode, void *mem_block,
+			size_t size)
+{
+	u16_t head_next;
+	unsigned int key;
+
+	key = irq_lock();
+
+	head_next = rb->head;
+	head_next = modulo_inc(head_next, rb->len);
+
+	/* In case of ping-pong mode, the below comparison incorrectly
+	 * leads to complications as the buffers are always predefined.
+	 * Hence excluding ping-pong mode from this comparison.
+	 */
+	if ((mode & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) {
+		if (head_next == rb->tail) {
+			/* Ring buffer is full */
+			irq_unlock(key);
+			return -ENOMEM;
+		}
+	}
+
+	rb->buf[rb->head].mem_block = mem_block;
+	rb->buf[rb->head].size = size;
+	rb->head = head_next;
+
+	irq_unlock(key);
+
+	return 0;
+}
+
+static int start_dma(struct device *dev_dma, u32_t channel,
+		     struct dma_config *cfg, void *src, void *dst,
+		     u32_t blk_size)
+{
+	int ret;
+
+	struct dma_block_config blk_cfg = {
+		.block_size = blk_size,
+		.source_address = (u32_t)src,
+		.dest_address = (u32_t)dst,
+	};
+
+	cfg->head_block = &blk_cfg;
+
+	ret = dma_config(dev_dma, channel, cfg);
+	if (ret < 0) {
+		SYS_LOG_ERR("dma_config failed: %d", ret);
+		return ret;
+	}
+
+	ret = dma_start(dev_dma, channel);
+	if (ret < 0) {
+		SYS_LOG_ERR("dma_start failed: %d", ret);
+	}
+
+	return ret;
+}
+
+/* This function is executed in the interrupt context */
+static void dma_tx_callback(struct device *dev_dma, u32_t channel, int status)
+{
+	struct device *dev = get_dev_from_dma_channel(channel);
+	const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
+	struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
+	struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
+	struct stream *strm = &dev_data->tx;
+	size_t mem_block_size;
+	int ret;
+
+	__ASSERT_NO_MSG(strm->mem_block != NULL);
+
+	if ((strm->cfg.options & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) {
+		/* All block data sent */
+		k_mem_slab_free(strm->cfg.mem_slab, &strm->mem_block);
+		strm->mem_block = NULL;
+	}
+
+	/* Stop transmission if there was an error */
+	if (strm->state == I2S_STATE_ERROR) {
+		SYS_LOG_DBG("TX error detected");
+		goto tx_disable;
+	}
+
+	/* Stop transmission if we were requested */
+	if (strm->last_block) {
+		strm->state = I2S_STATE_READY;
+		goto tx_disable;
+	}
+
+	/* Prepare to send the next data block */
+	ret = queue_get(&strm->mem_block_queue, strm->cfg.options,
+			&strm->mem_block, &mem_block_size);
+	if (ret < 0) {
+		if (strm->state == I2S_STATE_STOPPING) {
+			strm->state = I2S_STATE_READY;
+		} else {
+			strm->state = I2S_STATE_ERROR;
+		}
+		goto tx_disable;
+	}
+
+	k_sem_give(&strm->sem);
+
+	/* Assure cache coherency before DMA read operation */
+	DCACHE_CLEAN(strm->mem_block, mem_block_size);
+
+	ret = start_dma(dev_data->dev_dma, strm->dma_channel, &strm->dma_cfg,
+			strm->mem_block, (void *)&(ssp->ssd),
+			mem_block_size);
+	if (ret < 0) {
+		SYS_LOG_DBG("Failed to start TX DMA transfer: %d", ret);
+		goto tx_disable;
+	}
+	return;
+
+tx_disable:
+	tx_stream_disable(strm, ssp, dev_data->dev_dma);
+}
+
+static int i2s_cavs_configure(struct device *dev, enum i2s_dir dir,
+			     struct i2s_config *i2s_cfg)
+{
+	const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
+	struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
+	struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
+	u8_t num_words = i2s_cfg->channels;
+	u8_t word_size_bits = i2s_cfg->word_size;
+	u8_t word_size_bytes;
+	u32_t bit_clk_freq, mclk;
+	struct stream *strm;
+
+	u32_t ssc0;
+	u32_t ssc1;
+	u32_t ssc2;
+	u32_t ssc3;
+	u32_t sspsp;
+	u32_t sspsp2;
+	u32_t sstsa;
+	u32_t ssrsa;
+	u32_t ssto;
+	u32_t ssioc;
+	u32_t mdiv;
+	u32_t i2s_m;
+	u32_t i2s_n;
+	u32_t frame_len = 0;
+	bool inverted_frame = false;
+
+	if (dir == I2S_DIR_TX) {
+		strm = &dev_data->tx;
+	} else {
+		SYS_LOG_ERR("TX direction must be selected");
+		return -EINVAL;
+	}
+
+	if (strm->state != I2S_STATE_NOT_READY &&
+	    strm->state != I2S_STATE_READY) {
+		SYS_LOG_ERR("invalid state");
+		return -EINVAL;
+	}
+
+	if (i2s_cfg->frame_clk_freq == 0) {
+		strm->queue_drop(strm);
+		memset(&strm->cfg, 0, sizeof(struct i2s_config));
+		strm->state = I2S_STATE_NOT_READY;
+		return 0;
+	}
+
+	if (word_size_bits < CAVS_SSP_WORD_SIZE_BITS_MIN ||
+	    word_size_bits > CAVS_SSP_WORD_SIZE_BITS_MAX) {
+		SYS_LOG_ERR("Unsupported I2S word size");
+		return -EINVAL;
+	}
+
+	if (num_words < CAVS_SSP_WORD_PER_FRAME_MIN ||
+	    num_words > CAVS_SSP_WORD_PER_FRAME_MAX) {
+		SYS_LOG_ERR("Unsupported words per frame number");
+		return -EINVAL;
+	}
+
+	memcpy(&strm->cfg, i2s_cfg, sizeof(struct i2s_config));
+
+	/* reset SSP settings */
+	/* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */
+	/*
+	 * FIXME: MOD, ACS, NCS are not set,
+	 * no support for network mode for now
+	 */
+	ssc0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;
+
+	/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
+	ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_RWOT | SSCR1_TRAIL;
+
+	/* sscr2 dynamic setting is LJDFD */
+	ssc2 = SSCR2_SDFD | SSCR2_TURM1;
+
+	/* sscr3 dynamic settings are TFT, RFT */
+	ssc3 = 0;
+
+	/* sspsp dynamic settings are SCMODE, SFRMP, DMYSTRT, SFRMWDTH */
+	sspsp = 0;
+
+	/* sspsp2 no dynamic setting */
+	sspsp2 = 0x0;
+
+	/* ssioc dynamic setting is SFCR */
+	ssioc = SSIOC_SCOE;
+
+	/* i2s_m M divider setting, default 1 */
+	i2s_m = 0x1;
+
+	/* i2s_n N divider setting, default 1 */
+	i2s_n = 0x1;
+
+	/* ssto no dynamic setting */
+	ssto = 0x0;
+
+	/* sstsa dynamic setting is TTSA, default 2 slots */
+	/* TODO: Expand I2s.h to make this configurable */
+	sstsa = (0x1 << 8) | (0x3 << 0);
+
+	/* ssrsa dynamic setting is RTSA, default 2 slots */
+	/* TODO: Expand I2s.h to make this configurable */
+	ssrsa = 0;
+
+	/* clock masters */
+	ssc1 &= ~SSCR1_SFRMDIR;
+
+	/* codec is clk slave & FRM slave */
+	/* TODO: Expand I2s.h to make this configurable */
+	ssc1 |= SSCR1_SCFR;
+	ssioc |= SSIOC_SFCR;
+
+	/* clock signal polarity */
+	switch (i2s_cfg->format & I2S_FMT_CLK_FORMAT_MASK) {
+	case I2S_FMT_CLK_NF_NB:
+		break;
+
+	case I2S_FMT_CLK_NF_IB:
+		sspsp |= SSPSP_SCMODE(2);
+		inverted_frame = true; /* handled later with format */
+		break;
+
+	case I2S_FMT_CLK_IF_NB:
+		break;
+
+	case I2S_FMT_CLK_IF_IB:
+		sspsp |= SSPSP_SCMODE(2);
+		inverted_frame = true; /* handled later with format */
+		break;
+
+	default:
+		SYS_LOG_ERR("Unsupported Clock format");
+		return -EINVAL;
+	}
+
+	ssc0 |= SSCR0_MOD | SSCR0_ACS;
+
+	mclk = SOC_INTEL_S1000_MCK_XTAL_FREQ_HZ;
+	bit_clk_freq = i2s_cfg->frame_clk_freq * word_size_bits * num_words;
+
+	/* BCLK is generated from MCLK - must be divisible */
+	if (mclk % bit_clk_freq) {
+		SYS_LOG_ERR("MCLK is not a factor of BCLK");
+		return -EINVAL;
+	}
+
+	/* divisor must be within SCR range */
+	mdiv = (mclk / bit_clk_freq) - 1;
+	if (mdiv > (SSCR0_SCR_MASK >> 8)) {
+		SYS_LOG_ERR("Divisor is not within SCR range");
+		return -EINVAL;
+	}
+
+	/* set the SCR divisor */
+	ssc0 |= SSCR0_SCR(mdiv);
+
+	/* word_size_bits must be <= 38 for SSP */
+	if (word_size_bits > 38) {
+		SYS_LOG_ERR("word size bits is more than 38");
+		return -EINVAL;
+	}
+
+	/* format */
+	switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) {
+
+	case I2S_FMT_DATA_FORMAT_I2S:
+		ssc0 |= SSCR0_FRDC(i2s_cfg->channels);
+
+		/* set asserted frame length */
+		frame_len = word_size_bits;
+
+		/* handle frame polarity, I2S default is falling/active low */
+		sspsp |= SSPSP_SFRMP(!inverted_frame);
+		break;
+
+	case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
+		ssc0 |= SSCR0_FRDC(i2s_cfg->channels);
+
+		/* LJDFD enable */
+		ssc2 &= ~SSCR2_LJDFD;
+
+		/* set asserted frame length */
+		frame_len = word_size_bits;
+
+		/* LEFT_J default is rising/active high, opposite of I2S */
+		sspsp |= SSPSP_SFRMP(inverted_frame);
+		break;
+
+	case I2S_FMT_DATA_FORMAT_PCM_SHORT:
+	case I2S_FMT_DATA_FORMAT_PCM_LONG:
+	default:
+		SYS_LOG_ERR("Unsupported I2S data format");
+		return -EINVAL;
+	}
+
+	sspsp |= SSPSP_SFRMWDTH(frame_len);
+
+	if (word_size_bits > 16) {
+		ssc0 |= (SSCR0_EDSS | SSCR0_DSIZE(word_size_bits - 16));
+	} else {
+		ssc0 |= SSCR0_DSIZE(word_size_bits);
+	}
+
+	ssp->ssc0 = ssc0;
+	ssp->ssc1 = ssc1;
+	ssp->ssc2 = ssc2;
+	ssp->ssc3 = ssc3;
+	ssp->sspsp2 = sspsp2;
+	ssp->sspsp = sspsp;
+	ssp->ssioc = ssioc;
+	ssp->ssto = ssto;
+	ssp->sstsa = sstsa;
+	ssp->ssrsa = ssrsa;
+
+	/* Set up DMA channel parameters */
+	word_size_bytes = (word_size_bits + 7) / 8;
+	strm->dma_cfg.source_data_size = word_size_bytes;
+	strm->dma_cfg.dest_data_size = word_size_bytes;
+
+	strm->state = I2S_STATE_READY;
+	return 0;
+}
+
+static int tx_stream_start(struct stream *strm,
+			   struct i2s_cavs_ssp *const ssp,
+			   struct device *dev_dma)
+{
+	size_t mem_block_size;
+	int ret;
+
+	ret = queue_get(&strm->mem_block_queue, strm->cfg.options,
+			&strm->mem_block, &mem_block_size);
+	if (ret < 0) {
+		return ret;
+	}
+	k_sem_give(&strm->sem);
+
+	/* Assure cache coherency before DMA read operation */
+	DCACHE_CLEAN(strm->mem_block, mem_block_size);
+
+	ret = start_dma(dev_dma, strm->dma_channel, &strm->dma_cfg,
+			strm->mem_block, (void *)&(ssp->ssd),
+			mem_block_size);
+	if (ret < 0) {
+		SYS_LOG_ERR("Failed to start TX DMA transfer: %d", ret);
+		return ret;
+	}
+
+	/* enable port */
+	ssp->ssc0 |= SSCR0_SSE;
+
+	/* Enable DMA service request handshake logic. Though DMA is
+	 * already started, it won't work without the handshake logic.
+	 */
+	ssp->ssc1 |= SSCR1_TSRE;
+	ssp->sstsa |= (0x1 << 8);
+
+	return 0;
+}
+
+static void tx_stream_disable(struct stream *strm,
+			      struct i2s_cavs_ssp *const ssp,
+			      struct device *dev_dma)
+{
+	/* Disable DMA service request handshake logic. Handshake is
+	 * not required now since DMA is not in operation.
+	 */
+	ssp->ssc1 &= ~SSCR1_TSRE;
+	ssp->sstsa &= ~(0x1 << 8);
+
+	dma_stop(dev_dma, strm->dma_channel);
+
+	if (((strm->cfg.options & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) &&
+	    (strm->mem_block != NULL)) {
+		k_mem_slab_free(strm->cfg.mem_slab, &strm->mem_block);
+		strm->mem_block = NULL;
+	}
+	strm->mem_block_queue.head = 0;
+	strm->mem_block_queue.tail = 0;
+}
+
+static void tx_queue_drop(struct stream *strm)
+{
+	size_t size;
+	void *mem_block;
+	unsigned int n = 0;
+
+	while (queue_get(&strm->mem_block_queue, strm->cfg.options,
+			 &mem_block, &size) == 0) {
+		if ((strm->cfg.options & I2S_OPT_PINGPONG)
+				!= I2S_OPT_PINGPONG) {
+			k_mem_slab_free(strm->cfg.mem_slab, &mem_block);
+			n++;
+		}
+	}
+
+	strm->mem_block_queue.head = 0;
+	strm->mem_block_queue.tail = 0;
+
+	for (; n > 0; n--) {
+		k_sem_give(&strm->sem);
+	}
+}
+
+static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
+			   enum i2s_trigger_cmd cmd)
+{
+	const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
+	struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
+	struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
+	struct stream *strm;
+	unsigned int key;
+	int ret;
+
+	if (dir == I2S_DIR_TX) {
+		strm = &dev_data->tx;
+	} else {
+		SYS_LOG_ERR("TX direction must be selected");
+		return -EINVAL;
+	}
+
+	switch (cmd) {
+	case I2S_TRIGGER_START:
+		if (strm->state != I2S_STATE_READY) {
+			SYS_LOG_DBG("START trigger: invalid state");
+			return -EIO;
+		}
+
+		__ASSERT_NO_MSG(strm->mem_block == NULL);
+
+		ret = strm->stream_start(strm, ssp, dev_data->dev_dma);
+		if (ret < 0) {
+			SYS_LOG_DBG("START trigger failed %d", ret);
+			return ret;
+		}
+
+		strm->state = I2S_STATE_RUNNING;
+		strm->last_block = false;
+		break;
+
+	case I2S_TRIGGER_STOP:
+		key = irq_lock();
+		if (strm->state != I2S_STATE_RUNNING) {
+			irq_unlock(key);
+			SYS_LOG_DBG("STOP trigger: invalid state");
+			return -EIO;
+		}
+		strm->state = I2S_STATE_STOPPING;
+		irq_unlock(key);
+		strm->last_block = true;
+		break;
+
+	case I2S_TRIGGER_DRAIN:
+		key = irq_lock();
+		if (strm->state != I2S_STATE_RUNNING) {
+			irq_unlock(key);
+			SYS_LOG_DBG("DRAIN trigger: invalid state");
+			return -EIO;
+		}
+		strm->state = I2S_STATE_STOPPING;
+		irq_unlock(key);
+		break;
+
+	case I2S_TRIGGER_DROP:
+		if (strm->state == I2S_STATE_NOT_READY) {
+			SYS_LOG_DBG("DROP trigger: invalid state");
+			return -EIO;
+		}
+		strm->stream_disable(strm, ssp, dev_data->dev_dma);
+		strm->queue_drop(strm);
+		strm->state = I2S_STATE_READY;
+		break;
+
+	case I2S_TRIGGER_PREPARE:
+		if (strm->state != I2S_STATE_ERROR) {
+			SYS_LOG_DBG("PREPARE trigger: invalid state");
+			return -EIO;
+		}
+		strm->state = I2S_STATE_READY;
+		strm->queue_drop(strm);
+		break;
+
+	default:
+		SYS_LOG_ERR("Unsupported trigger command");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int i2s_cavs_write(struct device *dev, void *mem_block, size_t size)
+{
+	struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
+	struct stream *strm = &dev_data->tx;
+	int ret;
+
+	if (dev_data->tx.state != I2S_STATE_RUNNING &&
+	    dev_data->tx.state != I2S_STATE_READY) {
+		SYS_LOG_DBG("invalid state");
+		return -EIO;
+	}
+
+	ret = k_sem_take(&dev_data->tx.sem, dev_data->tx.cfg.timeout);
+	if (ret < 0) {
+		SYS_LOG_ERR("Failure taking sem");
+		return ret;
+	}
+
+	/* Add data to the end of the TX queue */
+	queue_put(&dev_data->tx.mem_block_queue, strm->cfg.options,
+			 mem_block, size);
+
+	return 0;
+}
+
+/* clear IRQ sources atm */
+static void i2s_cavs_isr(void *arg)
+{
+	struct device *dev = (struct device *)arg;
+	const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
+	struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
+	u32_t temp;
+
+	/* clear IRQ */
+	temp = ssp->sss;
+	ssp->sss = temp;
+}
+
+static int i2s1_cavs_initialize(struct device *dev)
+{
+	const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
+	struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
+
+#ifdef CONFIG_SOC_INTEL_S1000
+	setup_ownership_i2s();
+#endif
+
+	/* Configure interrupts */
+	dev_cfg->irq_config();
+
+	/* Initialize semaphores */
+	k_sem_init(&dev_data->tx.sem, CONFIG_I2S_CAVS_TX_BLOCK_COUNT,
+		   CONFIG_I2S_CAVS_TX_BLOCK_COUNT);
+
+	dev_data->dev_dma = device_get_binding(CONFIG_I2S_CAVS_DMA_NAME);
+	if (!dev_data->dev_dma) {
+		SYS_LOG_ERR("%s device not found", CONFIG_I2S_CAVS_DMA_NAME);
+		return -ENODEV;
+	}
+
+	/* Enable module's IRQ */
+	irq_enable(dev_cfg->irq_id);
+
+	SYS_LOG_INF("Device %s initialized", DEV_NAME(dev));
+
+	return 0;
+}
+
+static const struct i2s_driver_api i2s_cavs_driver_api = {
+	.configure = i2s_cavs_configure,
+	.write = i2s_cavs_write,
+	.trigger = i2s_cavs_trigger,
+};
+
+/* I2S1 */
+
+static struct device DEVICE_NAME_GET(i2s1_cavs);
+
+static struct device *get_dev_from_dma_channel(u32_t dma_channel)
+{
+	return &DEVICE_NAME_GET(i2s1_cavs);
+}
+
+struct queue_item tx_0_ring_buf[CONFIG_I2S_CAVS_TX_BLOCK_COUNT + 1];
+
+static void i2s1_irq_config(void)
+{
+	IRQ_CONNECT(I2S1_CAVS_IRQ, CONFIG_I2S_1_IRQ_PRI, i2s_cavs_isr,
+		    DEVICE_GET(i2s1_cavs), 0);
+}
+
+static const struct i2s_cavs_config i2s1_cavs_config = {
+	.regs = (struct i2s_cavs_ssp *)SSP_BASE(1),
+	.irq_id = I2S1_CAVS_IRQ,
+	.irq_config = i2s1_irq_config,
+};
+
+static struct i2s_cavs_dev_data i2s1_cavs_data = {
+	.tx = {
+		.dma_channel = CONFIG_I2S_CAVS_1_DMA_TX_CHANNEL,
+		.dma_cfg = {
+			.source_data_size = 1,
+			.dest_data_size = 1,
+			.source_burst_length = 1,
+			.dest_burst_length = 1,
+			.dma_callback = dma_tx_callback,
+			.complete_callback_en = 0,
+			.error_callback_en = 1,
+			.block_count = 1,
+			.channel_direction = MEMORY_TO_PERIPHERAL,
+			.dma_slot = DMA_HANDSHAKE_SSP1_TX,
+		},
+		.mem_block_queue.buf = tx_0_ring_buf,
+		.mem_block_queue.len = ARRAY_SIZE(tx_0_ring_buf),
+		.stream_start = tx_stream_start,
+		.stream_disable = tx_stream_disable,
+		.queue_drop = tx_queue_drop,
+	},
+};
+
+DEVICE_AND_API_INIT(i2s1_cavs, CONFIG_I2S_CAVS_1_NAME, &i2s1_cavs_initialize,
+		    &i2s1_cavs_data, &i2s1_cavs_config, POST_KERNEL,
+		    CONFIG_I2S_INIT_PRIORITY, &i2s_cavs_driver_api);
diff --git a/drivers/i2s/i2s_cavs.h b/drivers/i2s/i2s_cavs.h
new file mode 100644
index 0000000..ef72323
--- /dev/null
+++ b/drivers/i2s/i2s_cavs.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018 Intel Corporation.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** @file
+ * @brief I2S bus (SSP) driver for Intel CAVS.
+ *
+ * Limitations:
+ * - DMA is used in simple single block transfer mode (with linked list
+ *   enabled) and "interrupt on full transfer completion" mode.
+ */
+
+#ifndef _I2S_CAVS_H_
+#define _I2S_CAVS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct i2s_cavs_ssp {
+	u32_t ssc0;		/* 0x00 - Control0 */
+	u32_t ssc1;		/* 0x04 - Control1 */
+	u32_t sss;		/* 0x08 - Status */
+	u32_t ssit;		/* 0x0C - Interrupt Test */
+	u32_t ssd;		/* 0x10 - Data */
+	u32_t reserved0[5];
+	u32_t ssto;		/* 0x28 - Time Out */
+	u32_t sspsp;		/* 0x2C - Programmable Serial Protocol */
+	u32_t sstsa;		/* 0x30 - TX Time Slot Active */
+	u32_t ssrsa;		/* 0x34 - RX Time Slot Active */
+	u32_t sstss;		/* 0x38 - Time Slot Status */
+	u32_t reserved1;
+	u32_t ssc2;		/* 0x40 - Command / Status 2 */
+	u32_t sspsp2;		/* 0x44 - Programmable Serial Protocol 2 */
+	u32_t ssc3;		/* 0x48 - Command / Status 3 */
+	u32_t ssioc;		/* 0x4C - IO Control */
+};
+
+/* SSCR0 bits */
+#define SSCR0_DSS_MASK		(0x0000000f)
+#define SSCR0_DSIZE(x)		((x) - 1)
+#define SSCR0_FRF		(0x00000030)
+#define SSCR0_MOT		(00 << 4)
+#define SSCR0_TI		(1 << 4)
+#define SSCR0_NAT		(2 << 4)
+#define SSCR0_PSP		(3 << 4)
+#define SSCR0_ECS		(1 << 6)
+#define SSCR0_SSE		(1 << 7)
+#define SSCR0_SCR_MASK		(0x000fff00)
+#define SSCR0_SCR(x)		((x) << 8)
+#define SSCR0_EDSS		(1 << 20)
+#define SSCR0_NCS		(1 << 21)
+#define SSCR0_RIM		(1 << 22)
+#define SSCR0_TIM		(1 << 23)
+#define SSCR0_FRDC(x)		(((x) - 1) << 24)
+#define SSCR0_ACS		(1 << 30)
+#define SSCR0_MOD		(1 << 31)
+
+/* SSCR1 bits */
+#define SSCR1_RIE		(1 << 0)
+#define SSCR1_TIE		(1 << 1)
+#define SSCR1_LBM		(1 << 2)
+#define SSCR1_SPO		(1 << 3)
+#define SSCR1_SPH		(1 << 4)
+#define SSCR1_MWDS		(1 << 5)
+#define SSCR1_EFWR		(1 << 14)
+#define SSCR1_STRF		(1 << 15)
+#define SSCR1_IFS		(1 << 16)
+#define SSCR1_PINTE		(1 << 18)
+#define SSCR1_TINTE		(1 << 19)
+#define SSCR1_RSRE		(1 << 20)
+#define SSCR1_TSRE		(1 << 21)
+#define SSCR1_TRAIL		(1 << 22)
+#define SSCR1_RWOT		(1 << 23)
+#define SSCR1_SFRMDIR		(1 << 24)
+#define SSCR1_SCLKDIR		(1 << 25)
+#define SSCR1_ECRB		(1 << 26)
+#define SSCR1_ECRA		(1 << 27)
+#define SSCR1_SCFR		(1 << 28)
+#define SSCR1_EBCEI		(1 << 29)
+#define SSCR1_TTE		(1 << 30)
+#define SSCR1_TTELP		(1 << 31)
+
+/* SSCR2 bits */
+#define SSCR2_TURM1		(1 << 1)
+#define SSCR2_SDFD		(1 << 14)
+#define SSCR2_SDPM		(1 << 16)
+#define SSCR2_LJDFD		(1 << 17)
+
+/* SSR bits */
+#define SSSR_TNF		(1 << 2)
+#define SSSR_RNE		(1 << 3)
+#define SSSR_BSY		(1 << 4)
+#define SSSR_TFS		(1 << 5)
+#define SSSR_RFS		(1 << 6)
+#define SSSR_ROR		(1 << 7)
+
+/* SSPSP bits */
+#define SSPSP_SCMODE(x)		((x) << 0)
+#define SSPSP_SFRMP(x)		((x) << 2)
+#define SSPSP_ETDS		(1 << 3)
+#define SSPSP_STRTDLY(x)	((x) << 4)
+#define SSPSP_DMYSTRT(x)	((x) << 7)
+#define SSPSP_SFRMDLY(x)	((x) << 9)
+#define SSPSP_SFRMWDTH(x)	((x) << 16)
+#define SSPSP_DMYSTOP(x)	((x) << 23)
+#define SSPSP_FSRT		(1 << 25)
+#define SSPSP_EDMYSTOP(x)	((x) << 26)
+
+/* SSTSA bits */
+#define SSTSA_TTSA(x)		(1 << x)
+#define SSTSA_TXEN		(1 << 8)
+
+/* SSRSA bits */
+#define SSRSA_RTSA(x)		(1 << x)
+#define SSRSA_RXEN		(1 << 8)
+
+/* SSCR3 bits */
+#define SSCR3_TFL_MASK		(0x0000003f)
+#define SSCR3_RFL_MASK		(0x00003f00)
+#define SSCR3_TFT_MASK		(0x003f0000)
+#define SSCR3_TX(x)		(((x) - 1) << 16)
+#define SSCR3_RFT_MASK		(0x3f000000)
+#define SSCR3_RX(x)		(((x) - 1) << 24)
+
+/* SSIOC bits */
+#define SSIOC_TXDPDEB		(1 << 1)
+#define SSIOC_SFCR		(1 << 4)
+#define SSIOC_SCOE		(1 << 5)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _I2S_CAVS_H_ */
diff --git a/include/i2s.h b/include/i2s.h
index 4e37be4..cdc2d40 100644
--- a/include/i2s.h
+++ b/include/i2s.h
@@ -148,11 +148,24 @@
 #define I2S_FMT_DATA_ORDER_LSB              (1 << 3)
 /** Invert bit ordering, send LSB first */
 #define I2S_FMT_DATA_ORDER_INV              I2S_FMT_DATA_ORDER_LSB
+
+/** Data Format bit field position. */
+#define I2S_FMT_CLK_FORMAT_SHIFT           4
+/** Data Format bit field mask. */
+#define I2S_FMT_CLK_FORMAT_MASK            (0x3 << I2S_FMT_CLK_FORMAT_SHIFT)
+
 /** Invert bit clock */
 #define I2S_FMT_BIT_CLK_INV                 (1 << 4)
 /** Invert frame clock */
 #define I2S_FMT_FRAME_CLK_INV               (1 << 5)
 
+/** NF represents "Normal Frame" whereas IF represents "Inverted Frame"
+ *  NB represents "Normal Bit Clk" whereas IB represents "Inverted Bit clk"
+ */
+#define I2S_FMT_CLK_NF_NB		(0 << I2S_FMT_CLK_FORMAT_SHIFT)
+#define I2S_FMT_CLK_NF_IB		(1 << I2S_FMT_CLK_FORMAT_SHIFT)
+#define I2S_FMT_CLK_IF_NB		(2 << I2S_FMT_CLK_FORMAT_SHIFT)
+#define I2S_FMT_CLK_IF_IB		(3 << I2S_FMT_CLK_FORMAT_SHIFT)
 
 typedef u8_t i2s_opt_t;
 
@@ -168,6 +181,7 @@
 #define I2S_OPT_FRAME_CLK_MASTER            (0 << 2)
 /** I2S driver is frame clock slave */
 #define I2S_OPT_FRAME_CLK_SLAVE             (1 << 2)
+
 /** @brief Loop back mode.
  *
  * In loop back mode RX input will be connected internally to TX output.
@@ -175,6 +189,15 @@
  */
 #define I2S_OPT_LOOPBACK                    (1 << 7)
 
+/** @brief Ping pong mode
+ *
+ * In ping pong mode TX output will keep alternating between a ping buffer
+ * and a pong buffer. This is normally used in audio streams when one buffer
+ * is being populated while the other is being played (DMAed) and vice versa.
+ * So, in this mode, 2 sets of buffers fixed in size are used. Static Arrays
+ * are used to achieve this and hence they are never freed.
+ */
+#define I2S_OPT_PINGPONG                    (1 << 6)
 
 /**
  * @brief I2C Direction