/*
 * Copyright (c) 2017 comsuisse AG
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT atmel_sam_ssc

/** @file
 * @brief I2S bus (SSC) driver for Atmel SAM MCU family.
 *
 * Limitations:
 * - TX and RX path share a common bit clock divider and as a result they cannot
 *   be configured independently. If RX and TX path are set to different bit
 *   clock frequencies the latter setting will quietly override the former.
 *   We should return an error in such a case.
 * - DMA is used in simple single block transfer mode and as such is not able
 *   to handle high speed data. To support higher transfer speeds the DMA
 *   linked list mode should be used.
 */

#include <errno.h>
#include <string.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control/atmel_sam_pmc.h>
#include <soc.h>

#define LOG_DOMAIN dev_i2s_sam_ssc
#define LOG_LEVEL CONFIG_I2S_LOG_LEVEL
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(LOG_DOMAIN);

#if __DCACHE_PRESENT == 1
#define DCACHE_INVALIDATE(addr, size) \
	SCB_InvalidateDCache_by_Addr((uint32_t *)addr, size)
#define DCACHE_CLEAN(addr, size) \
	SCB_CleanDCache_by_Addr((uint32_t *)addr, size)
#else
#define DCACHE_INVALIDATE(addr, size) {; }
#define DCACHE_CLEAN(addr, size) {; }
#endif

#define SAM_SSC_WORD_SIZE_BITS_MIN    2
#define SAM_SSC_WORD_SIZE_BITS_MAX   32
#define SAM_SSC_WORD_PER_FRAME_MIN    1
#define SAM_SSC_WORD_PER_FRAME_MAX   16

struct queue_item {
	void *mem_block;
	size_t size;
};

/* Minimal ring buffer implementation */
struct ring_buffer {
	struct queue_item *buf;
	uint16_t len;
	uint16_t head;
	uint16_t tail;
};

/* Device constant configuration parameters */
struct i2s_sam_dev_cfg {
	const struct device *dev_dma;
	Ssc *regs;
	void (*irq_config)(void);
	const struct atmel_sam_pmc_config clock_cfg;
	const struct pinctrl_dev_config *pcfg;
	uint8_t irq_id;
};

struct stream {
	int32_t state;
	struct k_sem sem;
	uint32_t dma_channel;
	uint8_t dma_perid;
	uint8_t word_size_bytes;
	bool last_block;
	struct i2s_config cfg;
	struct ring_buffer mem_block_queue;
	void *mem_block;
	int (*stream_start)(struct stream *, Ssc *const,
			    const struct device *);
	void (*stream_disable)(struct stream *, Ssc *const,
			       const struct device *);
	void (*queue_drop)(struct stream *);
	int (*set_data_format)(const struct i2s_sam_dev_cfg *const,
			       const struct i2s_config *);
};

/* Device run time data */
struct i2s_sam_dev_data {
	struct stream rx;
	struct stream tx;
};

#define MODULO_INC(val, max) { val = (++val < max) ? val : 0; }

static const struct device *get_dev_from_dma_channel(uint32_t dma_channel);
static void dma_rx_callback(const struct device *, void *, uint32_t, int);
static void dma_tx_callback(const struct device *, void *, uint32_t, int);
static void rx_stream_disable(struct stream *, Ssc *const,
			      const struct device *);
static void tx_stream_disable(struct stream *, Ssc *const,
			      const struct device *);

/*
 * Get data from the queue
 */
static int queue_get(struct ring_buffer *rb, void **mem_block, size_t *size)
{
	unsigned int key;

	key = irq_lock();

	if (rb->tail == rb->head) {
		/* Ring buffer is empty */
		irq_unlock(key);
		return -ENOMEM;
	}

	*mem_block = rb->buf[rb->tail].mem_block;
	*size = rb->buf[rb->tail].size;
	MODULO_INC(rb->tail, rb->len);

	irq_unlock(key);

	return 0;
}

/*
 * Put data in the queue
 */
static int queue_put(struct ring_buffer *rb, void *mem_block, size_t size)
{
	uint16_t head_next;
	unsigned int key;

	key = irq_lock();

	head_next = rb->head;
	MODULO_INC(head_next, rb->len);

	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 reload_dma(const struct device *dev_dma, uint32_t channel,
		      void *src, void *dst, size_t size)
{
	int ret;

	ret = dma_reload(dev_dma, channel, (uint32_t)src, (uint32_t)dst, size);
	if (ret < 0) {
		return ret;
	}

	ret = dma_start(dev_dma, channel);

	return ret;
}

static int start_dma(const struct device *dev_dma, uint32_t channel,
		     struct dma_config *cfg, void *src, void *dst,
		     uint32_t blk_size)
{
	struct dma_block_config blk_cfg;
	int ret;

	(void)memset(&blk_cfg, 0, sizeof(blk_cfg));
	blk_cfg.block_size = blk_size;
	blk_cfg.source_address = (uint32_t)src;
	blk_cfg.dest_address = (uint32_t)dst;

	cfg->head_block = &blk_cfg;

	ret = dma_config(dev_dma, channel, cfg);
	if (ret < 0) {
		return ret;
	}

	ret = dma_start(dev_dma, channel);

	return ret;
}

/* This function is executed in the interrupt context */
static void dma_rx_callback(const struct device *dma_dev, void *user_data,
			    uint32_t channel, int status)
{
	const struct device *dev = get_dev_from_dma_channel(channel);
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	struct stream *stream = &dev_data->rx;
	int ret;

	ARG_UNUSED(user_data);
	__ASSERT_NO_MSG(stream->mem_block != NULL);

	/* Stop reception if there was an error */
	if (stream->state == I2S_STATE_ERROR) {
		goto rx_disable;
	}

	/* All block data received */
	ret = queue_put(&stream->mem_block_queue, stream->mem_block,
			stream->cfg.block_size);
	if (ret < 0) {
		stream->state = I2S_STATE_ERROR;
		goto rx_disable;
	}
	stream->mem_block = NULL;
	k_sem_give(&stream->sem);

	/* Stop reception if we were requested */
	if (stream->state == I2S_STATE_STOPPING) {
		stream->state = I2S_STATE_READY;
		goto rx_disable;
	}

	/* Prepare to receive the next data block */
	ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block,
			       K_NO_WAIT);
	if (ret < 0) {
		stream->state = I2S_STATE_ERROR;
		goto rx_disable;
	}

	/* Assure cache coherency before DMA write operation */
	DCACHE_INVALIDATE(stream->mem_block, stream->cfg.block_size);

	ret = reload_dma(dev_cfg->dev_dma, stream->dma_channel,
			 (void *)&(ssc->SSC_RHR), stream->mem_block,
			 stream->cfg.block_size);
	if (ret < 0) {
		LOG_DBG("Failed to reload RX DMA transfer: %d", ret);
		goto rx_disable;
	}

	return;

rx_disable:
	rx_stream_disable(stream, ssc, dev_cfg->dev_dma);
}

/* This function is executed in the interrupt context */
static void dma_tx_callback(const struct device *dma_dev, void *user_data,
			    uint32_t channel, int status)
{
	const struct device *dev = get_dev_from_dma_channel(channel);
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	struct stream *stream = &dev_data->tx;
	size_t mem_block_size;
	int ret;

	ARG_UNUSED(user_data);
	__ASSERT_NO_MSG(stream->mem_block != NULL);

	/* All block data sent */
	k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block);
	stream->mem_block = NULL;

	/* Stop transmission if there was an error */
	if (stream->state == I2S_STATE_ERROR) {
		LOG_DBG("TX error detected");
		goto tx_disable;
	}

	/* Stop transmission if we were requested */
	if (stream->last_block) {
		stream->state = I2S_STATE_READY;
		goto tx_disable;
	}

	/* Prepare to send the next data block */
	ret = queue_get(&stream->mem_block_queue, &stream->mem_block,
			&mem_block_size);
	if (ret < 0) {
		if (stream->state == I2S_STATE_STOPPING) {
			stream->state = I2S_STATE_READY;
		} else {
			stream->state = I2S_STATE_ERROR;
		}
		goto tx_disable;
	}
	k_sem_give(&stream->sem);

	/* Assure cache coherency before DMA read operation */
	DCACHE_CLEAN(stream->mem_block, mem_block_size);

	ret = reload_dma(dev_cfg->dev_dma, stream->dma_channel,
			 stream->mem_block, (void *)&(ssc->SSC_THR),
			 mem_block_size);
	if (ret < 0) {
		LOG_DBG("Failed to reload TX DMA transfer: %d", ret);
		goto tx_disable;
	}

	return;

tx_disable:
	tx_stream_disable(stream, ssc, dev_cfg->dev_dma);
}

static int set_rx_data_format(const struct i2s_sam_dev_cfg *const dev_cfg,
			      const struct i2s_config *i2s_cfg)
{
	Ssc *const ssc = dev_cfg->regs;
	const bool pin_rk_en = IS_ENABLED(CONFIG_I2S_SAM_SSC_0_PIN_RK_EN);
	const bool pin_rf_en = IS_ENABLED(CONFIG_I2S_SAM_SSC_0_PIN_RF_EN);
	uint8_t word_size_bits = i2s_cfg->word_size;
	uint8_t num_words = i2s_cfg->channels;
	uint8_t fslen = 0U;
	uint32_t ssc_rcmr = 0U;
	uint32_t ssc_rfmr = 0U;
	bool frame_clk_master = !(i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE);

	switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) {

	case I2S_FMT_DATA_FORMAT_I2S:
		num_words = 2U;
		fslen = word_size_bits - 1;

		ssc_rcmr = SSC_RCMR_CKI
			   | (pin_rf_en ? SSC_RCMR_START_RF_FALLING : 0)
			   | SSC_RCMR_STTDLY(1);

		ssc_rfmr = (pin_rf_en && frame_clk_master
			    ? SSC_RFMR_FSOS_NEGATIVE : SSC_RFMR_FSOS_NONE);
		break;

	case I2S_FMT_DATA_FORMAT_PCM_SHORT:
		ssc_rcmr = (pin_rf_en ? SSC_RCMR_START_RF_FALLING : 0)
			   | SSC_RCMR_STTDLY(0);

		ssc_rfmr = (pin_rf_en && frame_clk_master
			    ? SSC_RFMR_FSOS_POSITIVE : SSC_RFMR_FSOS_NONE);
		break;

	case I2S_FMT_DATA_FORMAT_PCM_LONG:
		fslen = num_words * word_size_bits / 2U - 1;

		ssc_rcmr = (pin_rf_en ? SSC_RCMR_START_RF_RISING : 0)
			   | SSC_RCMR_STTDLY(0);

		ssc_rfmr = (pin_rf_en && frame_clk_master
			    ? SSC_RFMR_FSOS_POSITIVE : SSC_RFMR_FSOS_NONE);
		break;

	case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
		fslen = num_words * word_size_bits / 2U - 1;

		ssc_rcmr = SSC_RCMR_CKI
			   | (pin_rf_en ? SSC_RCMR_START_RF_RISING : 0)
			   | SSC_RCMR_STTDLY(0);

		ssc_rfmr = (pin_rf_en && frame_clk_master
			    ? SSC_RFMR_FSOS_POSITIVE : SSC_RFMR_FSOS_NONE);
		break;

	default:
		LOG_ERR("Unsupported I2S data format");
		return -EINVAL;
	}

	if (pin_rk_en) {
		ssc_rcmr |= ((i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE)
			     ? SSC_RCMR_CKS_RK : SSC_RCMR_CKS_MCK)
			    | ((i2s_cfg->options & I2S_OPT_BIT_CLK_GATED)
			       ? SSC_RCMR_CKO_TRANSFER : SSC_RCMR_CKO_CONTINUOUS);
	} else {
		ssc_rcmr |= SSC_RCMR_CKS_TK
			    | SSC_RCMR_CKO_NONE;
	}
	/* SSC_RCMR.PERIOD bit filed does not support setting the
	 * frame period with one bit resolution. In case the required
	 * frame period is an odd number set it to be one bit longer.
	 */
	ssc_rcmr |= (pin_rf_en ? 0 : SSC_RCMR_START_TRANSMIT)
		    | SSC_RCMR_PERIOD((num_words * word_size_bits + 1) / 2U - 1);

	/* Receive Clock Mode Register */
	ssc->SSC_RCMR = ssc_rcmr;

	ssc_rfmr |= SSC_RFMR_DATLEN(word_size_bits - 1)
		    | ((i2s_cfg->format & I2S_FMT_DATA_ORDER_LSB)
		       ? 0 : SSC_RFMR_MSBF)
		    | SSC_RFMR_DATNB(num_words - 1)
		    | SSC_RFMR_FSLEN(fslen)
		    | SSC_RFMR_FSLEN_EXT(fslen >> 4);

	/* Receive Frame Mode Register */
	ssc->SSC_RFMR = ssc_rfmr;

	return 0;
}

static int set_tx_data_format(const struct i2s_sam_dev_cfg *const dev_cfg,
			      const struct i2s_config *i2s_cfg)
{
	Ssc *const ssc = dev_cfg->regs;
	uint8_t word_size_bits = i2s_cfg->word_size;
	uint8_t num_words = i2s_cfg->channels;
	uint8_t fslen = 0U;
	uint32_t ssc_tcmr = 0U;
	uint32_t ssc_tfmr = 0U;

	switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) {

	case I2S_FMT_DATA_FORMAT_I2S:
		num_words = 2U;
		fslen = word_size_bits - 1;

		ssc_tcmr = SSC_TCMR_START_TF_FALLING
			   | SSC_TCMR_STTDLY(1);

		ssc_tfmr = SSC_TFMR_FSOS_NEGATIVE;
		break;

	case I2S_FMT_DATA_FORMAT_PCM_SHORT:
		ssc_tcmr = SSC_TCMR_CKI
			   | SSC_TCMR_START_TF_FALLING
			   | SSC_TCMR_STTDLY(0);

		ssc_tfmr = SSC_TFMR_FSOS_POSITIVE;
		break;

	case I2S_FMT_DATA_FORMAT_PCM_LONG:
		fslen = num_words * word_size_bits / 2U - 1;

		ssc_tcmr = SSC_TCMR_CKI
			   | SSC_TCMR_START_TF_RISING
			   | SSC_TCMR_STTDLY(0);

		ssc_tfmr = SSC_TFMR_FSOS_POSITIVE;
		break;

	case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
		fslen = num_words * word_size_bits / 2U - 1;

		ssc_tcmr = SSC_TCMR_START_TF_RISING
			   | SSC_TCMR_STTDLY(0);

		ssc_tfmr = SSC_TFMR_FSOS_POSITIVE;
		break;

	default:
		LOG_ERR("Unsupported I2S data format");
		return -EINVAL;
	}

	/* SSC_TCMR.PERIOD bit filed does not support setting the
	 * frame period with one bit resolution. In case the required
	 * frame period is an odd number set it to be one bit longer.
	 */
	ssc_tcmr |= ((i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE)
		     ? SSC_TCMR_CKS_TK : SSC_TCMR_CKS_MCK)
		    | ((i2s_cfg->options & I2S_OPT_BIT_CLK_GATED)
		       ? SSC_TCMR_CKO_TRANSFER : SSC_TCMR_CKO_CONTINUOUS)
		    | SSC_TCMR_PERIOD((num_words * word_size_bits + 1) / 2U - 1);

	/* Transmit Clock Mode Register */
	ssc->SSC_TCMR = ssc_tcmr;

	if (i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE) {
		ssc_tfmr &= ~SSC_TFMR_FSOS_Msk;
		ssc_tfmr |= SSC_TFMR_FSOS_NONE;
	}

	ssc_tfmr |= SSC_TFMR_DATLEN(word_size_bits - 1)
		    | ((i2s_cfg->format & I2S_FMT_DATA_ORDER_LSB)
		       ? 0 : SSC_TFMR_MSBF)
		    | SSC_TFMR_DATNB(num_words - 1)
		    | SSC_TFMR_FSLEN(fslen)
		    | SSC_TFMR_FSLEN_EXT(fslen >> 4);

	/* Transmit Frame Mode Register */
	ssc->SSC_TFMR = ssc_tfmr;

	return 0;
}

/* Calculate number of bytes required to store a word of bit_size length */
static uint8_t get_word_size_bytes(uint8_t bit_size)
{
	uint8_t byte_size_min = (bit_size + 7) / 8U;
	uint8_t byte_size;

	byte_size = (byte_size_min == 3U) ? 4 : byte_size_min;

	return byte_size;
}

static int bit_clock_set(Ssc *const ssc, uint32_t bit_clk_freq)
{
	uint32_t clk_div = SOC_ATMEL_SAM_MCK_FREQ_HZ / bit_clk_freq / 2U;

	if (clk_div == 0U || clk_div >= (1 << 12)) {
		LOG_ERR("Invalid bit clock frequency");
		return -EINVAL;
	}

	ssc->SSC_CMR = clk_div;

	LOG_DBG("freq = %d", bit_clk_freq);

	return 0;
}

static const struct i2s_config *i2s_sam_config_get(const struct device *dev,
						   enum i2s_dir dir)
{
	struct i2s_sam_dev_data *const dev_data = dev->data;
	struct stream *stream;

	if (dir == I2S_DIR_RX) {
		stream = &dev_data->rx;
	} else {
		stream = &dev_data->tx;
	}

	if (stream->state == I2S_STATE_NOT_READY) {
		return NULL;
	}

	return &stream->cfg;
}

static int i2s_sam_configure(const struct device *dev, enum i2s_dir dir,
			     const struct i2s_config *i2s_cfg)
{
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	uint8_t num_words = i2s_cfg->channels;
	uint8_t word_size_bits = i2s_cfg->word_size;
	uint32_t bit_clk_freq;
	struct stream *stream;
	int ret;

	if (dir == I2S_DIR_RX) {
		stream = &dev_data->rx;
	} else if (dir == I2S_DIR_TX) {
		stream = &dev_data->tx;
	} else if (dir == I2S_DIR_BOTH) {
		return -ENOSYS;
	} else {
		LOG_ERR("Either RX or TX direction must be selected");
		return -EINVAL;
	}

	if (stream->state != I2S_STATE_NOT_READY &&
	    stream->state != I2S_STATE_READY) {
		LOG_ERR("invalid state");
		return -EINVAL;
	}

	if (i2s_cfg->frame_clk_freq == 0U) {
		stream->queue_drop(stream);
		(void)memset(&stream->cfg, 0, sizeof(struct i2s_config));
		stream->state = I2S_STATE_NOT_READY;
		return 0;
	}

	if (i2s_cfg->format & I2S_FMT_FRAME_CLK_INV) {
		LOG_ERR("Frame clock inversion is not implemented");
		LOG_ERR("Please submit a patch");
		return -EINVAL;
	}

	if (i2s_cfg->format & I2S_FMT_BIT_CLK_INV) {
		LOG_ERR("Bit clock inversion is not implemented");
		LOG_ERR("Please submit a patch");
		return -EINVAL;
	}

	if (word_size_bits < SAM_SSC_WORD_SIZE_BITS_MIN ||
	    word_size_bits > SAM_SSC_WORD_SIZE_BITS_MAX) {
		LOG_ERR("Unsupported I2S word size");
		return -EINVAL;
	}

	if (num_words < SAM_SSC_WORD_PER_FRAME_MIN ||
	    num_words > SAM_SSC_WORD_PER_FRAME_MAX) {
		LOG_ERR("Unsupported words per frame number");
		return -EINVAL;
	}

	memcpy(&stream->cfg, i2s_cfg, sizeof(struct i2s_config));

	bit_clk_freq = i2s_cfg->frame_clk_freq * word_size_bits * num_words;
	ret = bit_clock_set(ssc, bit_clk_freq);
	if (ret < 0) {
		return ret;
	}

	ret = stream->set_data_format(dev_cfg, i2s_cfg);
	if (ret < 0) {
		return ret;
	}

	/* Set up DMA channel parameters */
	stream->word_size_bytes = get_word_size_bytes(word_size_bits);

	if (i2s_cfg->options & I2S_OPT_LOOPBACK) {
		ssc->SSC_RFMR |= SSC_RFMR_LOOP;
	}

	stream->state = I2S_STATE_READY;

	return 0;
}

static int rx_stream_start(struct stream *stream, Ssc *const ssc,
			   const struct device *dev_dma)
{
	int ret;

	ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block,
			       K_NO_WAIT);
	if (ret < 0) {
		return ret;
	}

	/* Workaround for a hardware bug: DMA engine will read first data
	 * item even if SSC_SR.RXEN (Receive Enable) is not set. An extra read
	 * before enabling DMA engine sets hardware FSM in the correct state.
	 */
	(void)ssc->SSC_RHR;

	struct dma_config dma_cfg = {
		.source_data_size = stream->word_size_bytes,
		.dest_data_size = stream->word_size_bytes,
		.block_count = 1,
		.dma_slot = stream->dma_perid,
		.channel_direction = PERIPHERAL_TO_MEMORY,
		.source_burst_length = 1,
		.dest_burst_length = 1,
		.dma_callback = dma_rx_callback,
	};

	ret = start_dma(dev_dma, stream->dma_channel, &dma_cfg,
			(void *)&(ssc->SSC_RHR), stream->mem_block,
			stream->cfg.block_size);
	if (ret < 0) {
		LOG_ERR("Failed to start RX DMA transfer: %d", ret);
		return ret;
	}

	/* Clear status register */
	(void)ssc->SSC_SR;

	ssc->SSC_IER = SSC_IER_OVRUN;

	ssc->SSC_CR = SSC_CR_RXEN;

	return 0;
}

static int tx_stream_start(struct stream *stream, Ssc *const ssc,
			   const struct device *dev_dma)
{
	size_t mem_block_size;
	int ret;

	ret = queue_get(&stream->mem_block_queue, &stream->mem_block,
			&mem_block_size);
	if (ret < 0) {
		return ret;
	}
	k_sem_give(&stream->sem);

	/* Workaround for a hardware bug: DMA engine will transfer first data
	 * item even if SSC_SR.TXEN (Transmit Enable) is not set. An extra write
	 * before enabling DMA engine sets hardware FSM in the correct state.
	 * This data item will not be output on I2S interface.
	 */
	ssc->SSC_THR = 0;

	struct dma_config dma_cfg = {
		.source_data_size = stream->word_size_bytes,
		.dest_data_size = stream->word_size_bytes,
		.block_count = 1,
		.dma_slot = stream->dma_perid,
		.channel_direction = MEMORY_TO_PERIPHERAL,
		.source_burst_length = 1,
		.dest_burst_length = 1,
		.dma_callback = dma_tx_callback,
	};

	/* Assure cache coherency before DMA read operation */
	DCACHE_CLEAN(stream->mem_block, mem_block_size);

	ret = start_dma(dev_dma, stream->dma_channel, &dma_cfg,
			stream->mem_block, (void *)&(ssc->SSC_THR),
			mem_block_size);
	if (ret < 0) {
		LOG_ERR("Failed to start TX DMA transfer: %d", ret);
		return ret;
	}

	/* Clear status register */
	(void)ssc->SSC_SR;

	ssc->SSC_IER = SSC_IER_TXEMPTY;

	ssc->SSC_CR = SSC_CR_TXEN;

	return 0;
}

static void rx_stream_disable(struct stream *stream, Ssc *const ssc,
			      const struct device *dev_dma)
{
	ssc->SSC_CR = SSC_CR_RXDIS;
	ssc->SSC_IDR = SSC_IDR_OVRUN;
	dma_stop(dev_dma, stream->dma_channel);
	if (stream->mem_block != NULL) {
		k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block);
		stream->mem_block = NULL;
	}
}

static void tx_stream_disable(struct stream *stream, Ssc *const ssc,
			      const struct device *dev_dma)
{
	ssc->SSC_CR = SSC_CR_TXDIS;
	ssc->SSC_IDR = SSC_IDR_TXEMPTY;
	dma_stop(dev_dma, stream->dma_channel);
	if (stream->mem_block != NULL) {
		k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block);
		stream->mem_block = NULL;
	}
}

static void rx_queue_drop(struct stream *stream)
{
	size_t size;
	void *mem_block;

	while (queue_get(&stream->mem_block_queue, &mem_block, &size) == 0) {
		k_mem_slab_free(stream->cfg.mem_slab, mem_block);
	}

	k_sem_reset(&stream->sem);
}

static void tx_queue_drop(struct stream *stream)
{
	size_t size;
	void *mem_block;
	unsigned int n = 0U;

	while (queue_get(&stream->mem_block_queue, &mem_block, &size) == 0) {
		k_mem_slab_free(stream->cfg.mem_slab, mem_block);
		n++;
	}

	for (; n > 0; n--) {
		k_sem_give(&stream->sem);
	}
}

static int i2s_sam_trigger(const struct device *dev, enum i2s_dir dir,
			   enum i2s_trigger_cmd cmd)
{
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	struct stream *stream;
	unsigned int key;
	int ret;

	if (dir == I2S_DIR_RX) {
		stream = &dev_data->rx;
	} else if (dir == I2S_DIR_TX) {
		stream = &dev_data->tx;
	} else if (dir == I2S_DIR_BOTH) {
		return -ENOSYS;
	} else {
		LOG_ERR("Either RX or TX direction must be selected");
		return -EINVAL;
	}

	switch (cmd) {
	case I2S_TRIGGER_START:
		if (stream->state != I2S_STATE_READY) {
			LOG_DBG("START trigger: invalid state");
			return -EIO;
		}

		__ASSERT_NO_MSG(stream->mem_block == NULL);

		ret = stream->stream_start(stream, ssc, dev_cfg->dev_dma);
		if (ret < 0) {
			LOG_DBG("START trigger failed %d", ret);
			return ret;
		}

		stream->state = I2S_STATE_RUNNING;
		stream->last_block = false;
		break;

	case I2S_TRIGGER_STOP:
		key = irq_lock();
		if (stream->state != I2S_STATE_RUNNING) {
			irq_unlock(key);
			LOG_DBG("STOP trigger: invalid state");
			return -EIO;
		}
		stream->state = I2S_STATE_STOPPING;
		irq_unlock(key);
		stream->last_block = true;
		break;

	case I2S_TRIGGER_DRAIN:
		key = irq_lock();
		if (stream->state != I2S_STATE_RUNNING) {
			irq_unlock(key);
			LOG_DBG("DRAIN trigger: invalid state");
			return -EIO;
		}
		stream->state = I2S_STATE_STOPPING;
		irq_unlock(key);
		break;

	case I2S_TRIGGER_DROP:
		if (stream->state == I2S_STATE_NOT_READY) {
			LOG_DBG("DROP trigger: invalid state");
			return -EIO;
		}
		stream->stream_disable(stream, ssc, dev_cfg->dev_dma);
		stream->queue_drop(stream);
		stream->state = I2S_STATE_READY;
		break;

	case I2S_TRIGGER_PREPARE:
		if (stream->state != I2S_STATE_ERROR) {
			LOG_DBG("PREPARE trigger: invalid state");
			return -EIO;
		}
		stream->state = I2S_STATE_READY;
		stream->queue_drop(stream);
		break;

	default:
		LOG_ERR("Unsupported trigger command");
		return -EINVAL;
	}

	return 0;
}

static int i2s_sam_read(const struct device *dev, void **mem_block,
			size_t *size)
{
	struct i2s_sam_dev_data *const dev_data = dev->data;
	int ret;

	if (dev_data->rx.state == I2S_STATE_NOT_READY) {
		LOG_DBG("invalid state");
		return -EIO;
	}

	if (dev_data->rx.state != I2S_STATE_ERROR) {
		ret = k_sem_take(&dev_data->rx.sem,
				 SYS_TIMEOUT_MS(dev_data->rx.cfg.timeout));
		if (ret < 0) {
			return ret;
		}
	}

	/* Get data from the beginning of RX queue */
	ret = queue_get(&dev_data->rx.mem_block_queue, mem_block, size);
	if (ret < 0) {
		return -EIO;
	}

	return 0;
}

static int i2s_sam_write(const struct device *dev, void *mem_block,
			 size_t size)
{
	struct i2s_sam_dev_data *const dev_data = dev->data;
	int ret;

	if (dev_data->tx.state != I2S_STATE_RUNNING &&
	    dev_data->tx.state != I2S_STATE_READY) {
		LOG_DBG("invalid state");
		return -EIO;
	}

	ret = k_sem_take(&dev_data->tx.sem,
			 SYS_TIMEOUT_MS(dev_data->tx.cfg.timeout));
	if (ret < 0) {
		return ret;
	}

	/* Add data to the end of the TX queue */
	queue_put(&dev_data->tx.mem_block_queue, mem_block, size);

	return 0;
}

static void i2s_sam_isr(const struct device *dev)
{
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	uint32_t isr_status;

	/* Retrieve interrupt status */
	isr_status = ssc->SSC_SR & ssc->SSC_IMR;

	/* Check for RX buffer overrun */
	if (isr_status & SSC_SR_OVRUN) {
		dev_data->rx.state = I2S_STATE_ERROR;
		/* Disable interrupt */
		ssc->SSC_IDR = SSC_IDR_OVRUN;
		LOG_DBG("RX buffer overrun error");
	}
	/* Check for TX buffer underrun */
	if (isr_status & SSC_SR_TXEMPTY) {
		dev_data->tx.state = I2S_STATE_ERROR;
		/* Disable interrupt */
		ssc->SSC_IDR = SSC_IDR_TXEMPTY;
		LOG_DBG("TX buffer underrun error");
	}
}

static int i2s_sam_initialize(const struct device *dev)
{
	const struct i2s_sam_dev_cfg *const dev_cfg = dev->config;
	struct i2s_sam_dev_data *const dev_data = dev->data;
	Ssc *const ssc = dev_cfg->regs;
	int ret;

	/* Configure interrupts */
	dev_cfg->irq_config();

	/* Initialize semaphores */
	k_sem_init(&dev_data->rx.sem, 0, CONFIG_I2S_SAM_SSC_RX_BLOCK_COUNT);
	k_sem_init(&dev_data->tx.sem, CONFIG_I2S_SAM_SSC_TX_BLOCK_COUNT,
		   CONFIG_I2S_SAM_SSC_TX_BLOCK_COUNT);

	if (!device_is_ready(dev_cfg->dev_dma)) {
		LOG_ERR("%s device not ready", dev_cfg->dev_dma->name);
		return -ENODEV;
	}

	/* Connect pins to the peripheral */
	ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret < 0) {
		return ret;
	}

	/* Enable SSC clock in PMC */
	(void)clock_control_on(SAM_DT_PMC_CONTROLLER,
			       (clock_control_subsys_t)&dev_cfg->clock_cfg);

	/* Reset the module, disable receiver & transmitter */
	ssc->SSC_CR = SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST;

	/* Enable module's IRQ */
	irq_enable(dev_cfg->irq_id);

	LOG_INF("Device %s initialized", dev->name);

	return 0;
}

static DEVICE_API(i2s, i2s_sam_driver_api) = {
	.configure = i2s_sam_configure,
	.config_get = i2s_sam_config_get,
	.read = i2s_sam_read,
	.write = i2s_sam_write,
	.trigger = i2s_sam_trigger,
};

/* I2S0 */

static const struct device *get_dev_from_dma_channel(uint32_t dma_channel)
{
	return &DEVICE_DT_NAME_GET(DT_DRV_INST(0));
}

static void i2s0_sam_irq_config(void)
{
	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), i2s_sam_isr,
		    DEVICE_DT_INST_GET(0), 0);
}

PINCTRL_DT_INST_DEFINE(0);

static const struct i2s_sam_dev_cfg i2s0_sam_config = {
	.dev_dma = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(0, tx)),
	.regs = (Ssc *)DT_INST_REG_ADDR(0),
	.irq_config = i2s0_sam_irq_config,
	.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(0),
	.irq_id = DT_INST_IRQN(0),
	.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
};

struct queue_item rx_0_ring_buf[CONFIG_I2S_SAM_SSC_RX_BLOCK_COUNT + 1];
struct queue_item tx_0_ring_buf[CONFIG_I2S_SAM_SSC_TX_BLOCK_COUNT + 1];

static struct i2s_sam_dev_data i2s0_sam_data = {
	.rx = {
		.dma_channel = DT_INST_DMAS_CELL_BY_NAME(0, rx, channel),
		.dma_perid = DT_INST_DMAS_CELL_BY_NAME(0, rx, perid),
		.mem_block_queue.buf = rx_0_ring_buf,
		.mem_block_queue.len = ARRAY_SIZE(rx_0_ring_buf),
		.stream_start = rx_stream_start,
		.stream_disable = rx_stream_disable,
		.queue_drop = rx_queue_drop,
		.set_data_format = set_rx_data_format,
	},
	.tx = {
		.dma_channel = DT_INST_DMAS_CELL_BY_NAME(0, tx, channel),
		.dma_perid = DT_INST_DMAS_CELL_BY_NAME(0, tx, perid),
		.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,
		.set_data_format = set_tx_data_format,
	},
};

DEVICE_DT_INST_DEFINE(0, &i2s_sam_initialize, NULL,
		    &i2s0_sam_data, &i2s0_sam_config, POST_KERNEL,
		    CONFIG_I2S_INIT_PRIORITY, &i2s_sam_driver_api);
