/* * 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 #include #include #include #include #include #include #include #include #include #include #define LOG_DOMAIN dev_i2s_sam_ssc #define LOG_LEVEL CONFIG_I2S_LOG_LEVEL #include #include 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_buf { 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_buf 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_buf *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_buf *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 const struct i2s_driver_api 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);