| /* |
| * 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. |
| */ |
| |
| #define DT_DRV_COMPAT intel_cavs_i2s |
| |
| #include <errno.h> |
| #include <string.h> |
| #include <sys/__assert.h> |
| #include <kernel.h> |
| #include <device.h> |
| #include <init.h> |
| #include <drivers/dma.h> |
| #include <drivers/i2s.h> |
| #include <soc.h> |
| #include "i2s_cavs.h" |
| |
| #define LOG_DOMAIN dev_i2s_cavs |
| #define LOG_LEVEL CONFIG_I2S_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(LOG_DOMAIN); |
| |
| /* length of the buffer queue */ |
| #define I2S_CAVS_BUF_Q_LEN 2 |
| |
| #define CAVS_SSP_WORD_SIZE_BITS_MIN 4 |
| #define CAVS_SSP_WORD_SIZE_BITS_MAX 32 |
| #define CAVS_SSP_WORD_PER_FRAME_MIN 1 |
| #define CAVS_SSP_WORD_PER_FRAME_MAX 8 |
| |
| #define CAVS_I2S_DMA_BURST_SIZE 8 |
| |
| /* |
| * This indicates the Tx/Rx stream. Most members of the stream are |
| * self-explanatory |
| * |
| * in_queue and out_queue are used as follows |
| * transmit stream: |
| * application provided buffer is queued to in_queue until loaded to DMA. |
| * when DMA channel is idle, buffer is retrieved from in_queue and loaded |
| * to DMA and queued to out_queue. |
| * when DMA completes, buffer is retrieved from out_queue and freed. |
| * |
| * receive stream: |
| * driver allocates buffer from slab and loads DMA |
| * buffer is queued to in_queue |
| * when DMA completes, buffer is retrieved from in_queue and queued to |
| * out_queue |
| * when application reads, buffer is read (may optionally block) from |
| * out_queue and presented to application. |
| */ |
| struct stream { |
| int32_t state; |
| uint32_t dma_channel; |
| struct dma_config dma_cfg; |
| struct dma_block_config dma_block; |
| struct k_msgq in_queue; |
| void *in_msgs[I2S_CAVS_BUF_Q_LEN]; |
| struct k_msgq out_queue; |
| void *out_msgs[I2S_CAVS_BUF_Q_LEN]; |
| }; |
| |
| struct i2s_cavs_config { |
| struct i2s_cavs_ssp *regs; |
| struct i2s_cavs_mn_div *mn_regs; |
| uint32_t irq_id; |
| void (*irq_connect)(void); |
| const struct device *dev_dma; |
| }; |
| |
| /* Device run time data */ |
| struct i2s_cavs_dev_data { |
| struct i2s_config cfg; |
| struct stream tx; |
| struct stream rx; |
| }; |
| |
| #define DEV_NAME(dev) ((dev)->name) |
| #define DEV_CFG(dev) \ |
| ((const struct i2s_cavs_config *const)(dev)->config) |
| #define DEV_DATA(dev) \ |
| ((struct i2s_cavs_dev_data *const)(dev)->data) |
| |
| static void i2s_dma_tx_callback(const struct device *, void *, uint32_t, int); |
| static void i2s_tx_stream_disable(struct i2s_cavs_dev_data *, |
| volatile struct i2s_cavs_ssp *const, const struct device *); |
| static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *, |
| volatile struct i2s_cavs_ssp *const, const struct device *); |
| |
| static inline void i2s_purge_stream_buffers(struct stream *strm, |
| struct k_mem_slab *mem_slab) |
| { |
| void *buffer; |
| |
| while (k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT) == 0) { |
| k_mem_slab_free(mem_slab, &buffer); |
| } |
| while (k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT) == 0) { |
| k_mem_slab_free(mem_slab, &buffer); |
| } |
| } |
| |
| /* This function is executed in the interrupt context */ |
| static void i2s_dma_tx_callback(const struct device *dma_dev, void *arg, |
| uint32_t channel, int status) |
| { |
| const struct device *dev = (const struct device *)arg; |
| const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev); |
| struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev); |
| |
| volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs; |
| struct stream *strm = &dev_data->tx; |
| void *buffer; |
| int ret; |
| |
| ret = k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT); |
| if (ret == 0) { |
| /* transmission complete. free the buffer */ |
| k_mem_slab_free(dev_data->cfg.mem_slab, &buffer); |
| } else { |
| LOG_ERR("no buffer in output queue for channel %u", |
| channel); |
| } |
| |
| switch (strm->state) { |
| |
| case I2S_STATE_RUNNING: |
| /* get the next buffer from queue */ |
| ret = k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT); |
| if (ret == 0) { |
| /* reload the DMA */ |
| dma_reload(dev_cfg->dev_dma, strm->dma_channel, |
| (uint32_t)buffer, (uint32_t)&ssp->ssd, |
| dev_data->cfg.block_size); |
| dma_start(dev_cfg->dev_dma, strm->dma_channel); |
| ssp->ssc1 |= SSCR1_TSRE; |
| k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT); |
| } |
| |
| if (ret || status) { |
| /* |
| * DMA encountered an error (status != 0) |
| * or |
| * No bufers in input queue |
| */ |
| LOG_ERR("DMA status %08x channel %u k_msgq_get ret %d", |
| status, channel, ret); |
| strm->state = I2S_STATE_STOPPING; |
| i2s_tx_stream_disable(dev_data, ssp, dev_cfg->dev_dma); |
| } |
| |
| break; |
| |
| case I2S_STATE_STOPPING: |
| i2s_tx_stream_disable(dev_data, ssp, dev_cfg->dev_dma); |
| break; |
| } |
| } |
| |
| static void i2s_dma_rx_callback(const struct device *dma_dev, void *arg, |
| uint32_t channel, int status) |
| { |
| const struct device *dev = (const struct device *)arg; |
| const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev); |
| struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev); |
| volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs; |
| struct stream *strm = &dev_data->rx; |
| void *buffer; |
| int ret; |
| |
| switch (strm->state) { |
| |
| case I2S_STATE_RUNNING: |
| /* retrieve buffer from input queue */ |
| ret = k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("get buffer from in_queue %p failed (%d)", |
| &strm->in_queue, ret); |
| } |
| /* put buffer to output queue */ |
| ret = k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("buffer %p -> out_queue %p err %d", |
| buffer, &strm->out_queue, ret); |
| } |
| /* allocate new buffer for next audio frame */ |
| ret = k_mem_slab_alloc(dev_data->cfg.mem_slab, &buffer, |
| K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("buffer alloc from slab %p err %d", |
| dev_data->cfg.mem_slab, ret); |
| i2s_rx_stream_disable(dev_data, ssp, dev_cfg->dev_dma); |
| strm->state = I2S_STATE_READY; |
| } else { |
| /* put buffer in input queue */ |
| ret = k_msgq_put(&strm->in_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("buffer %p -> in_queue %p err %d", |
| buffer, &strm->in_queue, ret); |
| } |
| |
| SOC_DCACHE_INVALIDATE(buffer, dev_data->cfg.block_size); |
| |
| /* reload the DMA */ |
| dma_reload(dev_cfg->dev_dma, strm->dma_channel, |
| (uint32_t)&ssp->ssd, (uint32_t)buffer, |
| dev_data->cfg.block_size); |
| dma_start(dev_cfg->dev_dma, strm->dma_channel); |
| ssp->ssc1 |= SSCR1_RSRE; |
| } |
| break; |
| case I2S_STATE_STOPPING: |
| i2s_rx_stream_disable(dev_data, ssp, dev_cfg->dev_dma); |
| strm->state = I2S_STATE_READY; |
| break; |
| } |
| } |
| |
| static int i2s_cavs_configure(const struct device *dev, enum i2s_dir dir, |
| const 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); |
| volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs; |
| volatile struct i2s_cavs_mn_div *const mn_div = dev_cfg->mn_regs; |
| struct dma_block_config *dma_block; |
| uint8_t num_words = i2s_cfg->channels; |
| uint8_t word_size_bits = i2s_cfg->word_size; |
| uint8_t word_size_bytes; |
| uint32_t bit_clk_freq, mclk; |
| int ret; |
| |
| uint32_t ssc0; |
| uint32_t ssc1; |
| uint32_t ssc2; |
| uint32_t ssc3; |
| uint32_t sspsp; |
| uint32_t sspsp2; |
| uint32_t sstsa; |
| uint32_t ssrsa; |
| uint32_t ssto; |
| uint32_t ssioc = 0U; |
| uint32_t mdiv; |
| uint32_t i2s_m = 0U; |
| uint32_t i2s_n = 0U; |
| uint32_t frame_len = 0U; |
| bool inverted_frame = false; |
| |
| if ((dev_data->tx.state != I2S_STATE_NOT_READY) && |
| (dev_data->tx.state != I2S_STATE_READY) && |
| (dev_data->rx.state != I2S_STATE_NOT_READY) && |
| (dev_data->rx.state != I2S_STATE_READY)) { |
| LOG_ERR("invalid state tx(%u) rx(%u)", dev_data->tx.state, |
| dev_data->rx.state); |
| return -EINVAL; |
| } |
| |
| if (i2s_cfg->frame_clk_freq == 0U) { |
| LOG_ERR("Invalid frame_clk_freq %u", |
| i2s_cfg->frame_clk_freq); |
| return -EINVAL; |
| } |
| |
| if (word_size_bits < CAVS_SSP_WORD_SIZE_BITS_MIN || |
| word_size_bits > CAVS_SSP_WORD_SIZE_BITS_MAX) { |
| LOG_ERR("Unsupported I2S word size %u", word_size_bits); |
| return -EINVAL; |
| } |
| |
| if (num_words < CAVS_SSP_WORD_PER_FRAME_MIN || |
| num_words > CAVS_SSP_WORD_PER_FRAME_MAX) { |
| LOG_ERR("Unsupported words per frame number %u", num_words); |
| return -EINVAL; |
| } |
| |
| if ((i2s_cfg->options & I2S_OPT_PINGPONG) == I2S_OPT_PINGPONG) { |
| LOG_ERR("Ping-pong mode not supported"); |
| return -ENOTSUP; |
| } |
| |
| memcpy(&dev_data->cfg, i2s_cfg, sizeof(struct i2s_config)); |
| |
| /* reset SSP settings */ |
| /* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */ |
| ssc0 = SSCR0_MOD | SSCR0_PSP | SSCR0_RIM; |
| |
| /* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */ |
| ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL; |
| |
| /* sscr2 dynamic setting is LJDFD */ |
| ssc2 = 0U; |
| |
| /* sscr3 dynamic settings are TFT, RFT */ |
| ssc3 = SSCR3_TX(CAVS_I2S_DMA_BURST_SIZE) | |
| SSCR3_RX(CAVS_I2S_DMA_BURST_SIZE); |
| |
| /* sspsp dynamic settings are SCMODE, SFRMP, DMYSTRT, SFRMWDTH */ |
| sspsp = 0U; |
| |
| /* sspsp2 no dynamic setting */ |
| sspsp2 = 0x0; |
| |
| /* ssto no dynamic setting */ |
| ssto = 0x0; |
| |
| /* sstsa dynamic setting is TTSA, set according to num_words */ |
| sstsa = BIT_MASK(num_words); |
| /* ssrsa dynamic setting is RTSA, set according to num_words */ |
| ssrsa = BIT_MASK(num_words); |
| |
| if (i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) { |
| /* set BCLK mode as slave */ |
| ssc1 |= SSCR1_SCLKDIR; |
| } else { |
| /* enable BCLK output */ |
| ssioc = SSIOC_SCOE; |
| } |
| |
| if (i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE) { |
| /* set WCLK mode as slave */ |
| ssc1 |= SSCR1_SFRMDIR; |
| } |
| |
| 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); |
| break; |
| |
| case I2S_FMT_CLK_IF_NB: |
| inverted_frame = true; /* handled later with format */ |
| break; |
| |
| case I2S_FMT_CLK_IF_IB: |
| sspsp |= SSPSP_SCMODE(2); |
| inverted_frame = true; /* handled later with format */ |
| break; |
| |
| default: |
| LOG_ERR("Unsupported Clock format"); |
| return -EINVAL; |
| } |
| |
| mclk = soc_get_ref_clk_freq(); |
| 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) { |
| LOG_INF("MCLK/BCLK is not an integer, using M/N divider"); |
| |
| /* |
| * Simplification: Instead of calculating lowest values of |
| * M and N, just set M and N as BCLK and MCLK respectively |
| * in 0.1KHz units |
| * In addition, double M so that it can be later divided by 2 |
| * to get an approximately 50% duty cycle clock |
| */ |
| i2s_m = (bit_clk_freq << 1) / 100U; |
| i2s_n = mclk / 100U; |
| |
| /* set divider value of 1 which divides the clock by 2 */ |
| mdiv = 1U; |
| |
| /* Select M/N divider as the clock source */ |
| ssc0 |= SSCR0_ECS; |
| } else { |
| mdiv = (mclk / bit_clk_freq) - 1; |
| } |
| |
| /* divisor must be within SCR range */ |
| if (mdiv > (SSCR0_SCR_MASK >> 8)) { |
| LOG_ERR("Divisor is not within SCR range"); |
| return -EINVAL; |
| } |
| |
| /* set the SCR divisor */ |
| ssc0 |= SSCR0_SCR(mdiv); |
| |
| /* 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) | SSPSP_FSRT; |
| 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: |
| 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; |
| |
| mn_div->mval = I2S_MNVAL(i2s_m); |
| mn_div->nval = I2S_MNVAL(i2s_n); |
| |
| /* Set up DMA channel parameters */ |
| word_size_bytes = (word_size_bits + 7) / 8U; |
| dev_data->tx.dma_cfg.source_data_size = word_size_bytes; |
| dev_data->tx.dma_cfg.dest_data_size = word_size_bytes; |
| dev_data->rx.dma_cfg.source_data_size = word_size_bytes; |
| dev_data->rx.dma_cfg.dest_data_size = word_size_bytes; |
| |
| dma_block = dev_data->tx.dma_cfg.head_block; |
| dma_block->block_size = i2s_cfg->block_size; |
| dma_block->source_address = (uint32_t)NULL; |
| dma_block->dest_address = (uint32_t)&ssp->ssd; |
| |
| ret = dma_config(dev_cfg->dev_dma, dev_data->tx.dma_channel, |
| &dev_data->tx.dma_cfg); |
| if (ret < 0) { |
| LOG_ERR("dma_config failed: %d", ret); |
| return ret; |
| } |
| |
| dma_block = dev_data->rx.dma_cfg.head_block; |
| dma_block->block_size = i2s_cfg->block_size; |
| dma_block->source_address = (uint32_t)&ssp->ssd; |
| dma_block->dest_address = (uint32_t)NULL; |
| |
| ret = dma_config(dev_cfg->dev_dma, dev_data->rx.dma_channel, |
| &dev_data->rx.dma_cfg); |
| if (ret < 0) { |
| LOG_ERR("dma_config failed: %d", ret); |
| return ret; |
| } |
| |
| /* enable port */ |
| ssp->ssc0 |= SSCR0_SSE; |
| |
| /* enable interrupt */ |
| irq_enable(dev_cfg->irq_id); |
| |
| dev_data->tx.state = I2S_STATE_READY; |
| dev_data->rx.state = I2S_STATE_READY; |
| |
| return 0; |
| } |
| |
| static int i2s_tx_stream_start(struct i2s_cavs_dev_data *dev_data, |
| volatile struct i2s_cavs_ssp *const ssp, |
| const struct device *dev_dma) |
| { |
| int ret = 0; |
| void *buffer; |
| unsigned int key; |
| struct stream *strm = &dev_data->tx; |
| |
| /* retrieve buffer from input queue */ |
| ret = k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("No buffer in input queue to start transmission"); |
| return ret; |
| } |
| |
| ret = dma_reload(dev_dma, strm->dma_channel, (uint32_t)buffer, |
| (uint32_t)&ssp->ssd, dev_data->cfg.block_size); |
| if (ret != 0) { |
| LOG_ERR("dma_reload failed (%d)", ret); |
| return ret; |
| } |
| |
| /* put buffer in output queue */ |
| ret = k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("failed to put buffer in output queue"); |
| return ret; |
| } |
| |
| ret = dma_start(dev_dma, strm->dma_channel); |
| |
| if (ret < 0) { |
| LOG_ERR("dma_start failed (%d)", ret); |
| return ret; |
| } |
| |
| /* Enable transmit operation */ |
| key = irq_lock(); |
| ssp->ssc1 |= SSCR1_TSRE; |
| ssp->sstsa |= SSTSA_TXEN; |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int i2s_rx_stream_start(struct i2s_cavs_dev_data *dev_data, |
| volatile struct i2s_cavs_ssp *const ssp, |
| const struct device *dev_dma) |
| { |
| int ret = 0; |
| void *buffer; |
| unsigned int key; |
| struct stream *strm = &dev_data->rx; |
| |
| /* allocate receive buffer from SLAB */ |
| ret = k_mem_slab_alloc(dev_data->cfg.mem_slab, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("buffer alloc from mem_slab failed (%d)", ret); |
| return ret; |
| } |
| |
| SOC_DCACHE_INVALIDATE(buffer, dev_data->cfg.block_size); |
| |
| ret = dma_reload(dev_dma, strm->dma_channel, (uint32_t)&ssp->ssd, |
| (uint32_t)buffer, dev_data->cfg.block_size); |
| if (ret != 0) { |
| LOG_ERR("dma_reload failed (%d)", ret); |
| return ret; |
| } |
| |
| /* put buffer in input queue */ |
| ret = k_msgq_put(&strm->in_queue, &buffer, K_NO_WAIT); |
| if (ret != 0) { |
| LOG_ERR("failed to put buffer in output queue"); |
| return ret; |
| } |
| |
| LOG_INF("Starting DMA Ch%u", strm->dma_channel); |
| ret = dma_start(dev_dma, strm->dma_channel); |
| if (ret < 0) { |
| LOG_ERR("Failed to start DMA Ch%d (%d)", strm->dma_channel, |
| ret); |
| return ret; |
| } |
| |
| /* Enable Receive operation */ |
| key = irq_lock(); |
| ssp->ssc1 |= SSCR1_RSRE; |
| ssp->ssrsa |= SSRSA_RXEN; |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static void i2s_tx_stream_disable(struct i2s_cavs_dev_data *dev_data, |
| volatile struct i2s_cavs_ssp *const ssp, |
| const struct device *dev_dma) |
| { |
| struct stream *strm = &dev_data->tx; |
| unsigned int key; |
| |
| /* |
| * Enable transmit undderrun interrupt to allow notification |
| * upon transmit FIFO being emptied. |
| * Defer disabling of TX to the underrun processing in ISR |
| */ |
| key = irq_lock(); |
| ssp->ssc0 &= ~SSCR0_TIM; |
| irq_unlock(key); |
| |
| LOG_INF("Stopping DMA channel %u for TX stream", strm->dma_channel); |
| dma_stop(dev_dma, strm->dma_channel); |
| |
| /* purge buffers queued in the stream */ |
| i2s_purge_stream_buffers(strm, dev_data->cfg.mem_slab); |
| } |
| |
| static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *dev_data, |
| volatile struct i2s_cavs_ssp *const ssp, |
| const struct device *dev_dma) |
| { |
| struct stream *strm = &dev_data->rx; |
| uint32_t data; |
| |
| /* Disable DMA service request handshake logic. Handshake is |
| * not required now since DMA is not in operation. |
| */ |
| ssp->ssrsa &= ~SSRSA_RXEN; |
| |
| LOG_INF("Stopping RX stream & DMA channel %u", strm->dma_channel); |
| dma_stop(dev_dma, strm->dma_channel); |
| |
| /* Empty the FIFO */ |
| while (ssp->sss & SSSR_RNE) { |
| /* read the RX FIFO */ |
| data = ssp->ssd; |
| } |
| |
| /* purge buffers queued in the stream */ |
| i2s_purge_stream_buffers(strm, dev_data->cfg.mem_slab); |
| } |
| |
| static int i2s_cavs_trigger(const 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); |
| volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs; |
| struct stream *strm; |
| unsigned int key; |
| int ret = 0; |
| |
| if (dir == I2S_DIR_BOTH) { |
| return -ENOSYS; |
| } |
| |
| strm = (dir == I2S_DIR_TX) ? &dev_data->tx : &dev_data->rx; |
| |
| key = irq_lock(); |
| switch (cmd) { |
| case I2S_TRIGGER_START: |
| if (strm->state != I2S_STATE_READY) { |
| LOG_ERR("START trigger: invalid state %u", strm->state); |
| ret = -EIO; |
| break; |
| } |
| |
| if (dir == I2S_DIR_TX) { |
| ret = i2s_tx_stream_start(dev_data, ssp, |
| dev_cfg->dev_dma); |
| } else { |
| ret = i2s_rx_stream_start(dev_data, ssp, |
| dev_cfg->dev_dma); |
| } |
| |
| if (ret < 0) { |
| LOG_DBG("START trigger failed %d", ret); |
| break; |
| } |
| |
| strm->state = I2S_STATE_RUNNING; |
| break; |
| |
| case I2S_TRIGGER_STOP: |
| case I2S_TRIGGER_DRAIN: |
| case I2S_TRIGGER_DROP: |
| if (strm->state != I2S_STATE_RUNNING) { |
| LOG_DBG("STOP/DRAIN/DROP trigger: invalid state"); |
| ret = -EIO; |
| break; |
| } |
| strm->state = I2S_STATE_STOPPING; |
| break; |
| |
| case I2S_TRIGGER_PREPARE: |
| break; |
| |
| default: |
| LOG_ERR("Unsupported trigger command"); |
| ret = -EINVAL; |
| } |
| |
| irq_unlock(key); |
| return ret; |
| } |
| |
| static int i2s_cavs_read(const 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->rx; |
| void *buffer; |
| int ret = 0; |
| |
| if (strm->state == I2S_STATE_NOT_READY) { |
| LOG_ERR("invalid state %d", strm->state); |
| return -EIO; |
| } |
| |
| ret = k_msgq_get(&strm->out_queue, &buffer, |
| SYS_TIMEOUT_MS(dev_data->cfg.timeout)); |
| if (ret != 0) { |
| return -EAGAIN; |
| } |
| |
| *mem_block = buffer; |
| *size = dev_data->cfg.block_size; |
| return 0; |
| } |
| |
| static int i2s_cavs_write(const 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 (strm->state != I2S_STATE_RUNNING && |
| strm->state != I2S_STATE_READY) { |
| LOG_ERR("invalid state (%d)", strm->state); |
| return -EIO; |
| } |
| |
| SOC_DCACHE_FLUSH(mem_block, size); |
| |
| ret = k_msgq_put(&strm->in_queue, &mem_block, |
| SYS_TIMEOUT_MS(dev_data->cfg.timeout)); |
| if (ret) { |
| LOG_ERR("k_msgq_put failed %d", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| /* clear IRQ sources atm */ |
| static void i2s_cavs_isr(const struct device *dev) |
| { |
| const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev); |
| volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs; |
| struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev); |
| uint32_t status; |
| |
| /* clear interrupts */ |
| status = ssp->sss; |
| ssp->sss = status; |
| |
| if (status & SSSR_TUR) { |
| /* |
| * transmit underrun occurred. |
| * 1. disable transmission |
| * 2. disable underrun interrupt |
| */ |
| ssp->sstsa &= ~SSTSA_TXEN; |
| ssp->ssc0 |= SSCR0_TIM; |
| dev_data->tx.state = I2S_STATE_READY; |
| } |
| } |
| |
| static int i2s_cavs_initialize(const 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); |
| |
| if (!device_is_ready(dev_cfg->dev_dma)) { |
| LOG_ERR("%s device not ready", dev_cfg->dev_dma->name); |
| return -ENODEV; |
| } |
| |
| /* Initialize the buffer queues */ |
| k_msgq_init(&dev_data->tx.in_queue, (char *)dev_data->tx.in_msgs, |
| sizeof(void *), I2S_CAVS_BUF_Q_LEN); |
| k_msgq_init(&dev_data->rx.in_queue, (char *)dev_data->rx.in_msgs, |
| sizeof(void *), I2S_CAVS_BUF_Q_LEN); |
| k_msgq_init(&dev_data->tx.out_queue, (char *)dev_data->tx.out_msgs, |
| sizeof(void *), I2S_CAVS_BUF_Q_LEN); |
| k_msgq_init(&dev_data->rx.out_queue, (char *)dev_data->rx.out_msgs, |
| sizeof(void *), I2S_CAVS_BUF_Q_LEN); |
| |
| /* register ISR */ |
| dev_cfg->irq_connect(); |
| |
| dev_data->tx.state = I2S_STATE_NOT_READY; |
| dev_data->rx.state = I2S_STATE_NOT_READY; |
| |
| LOG_INF("Device %s initialized", DEV_NAME(dev)); |
| |
| return 0; |
| } |
| |
| static const struct i2s_driver_api i2s_cavs_driver_api = { |
| .configure = i2s_cavs_configure, |
| .read = i2s_cavs_read, |
| .write = i2s_cavs_write, |
| .trigger = i2s_cavs_trigger, |
| }; |
| |
| #define I2S_CAVS_DEVICE_INIT(n) \ |
| static void i2s_cavs_irq_connect_##n(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), \ |
| i2s_cavs_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } \ |
| \ |
| static const struct i2s_cavs_config i2s_cavs_config_##n = { \ |
| .regs = (struct i2s_cavs_ssp *) \ |
| DT_INST_REG_ADDR_BY_IDX(n, 0), \ |
| .mn_regs = (struct i2s_cavs_mn_div *) \ |
| DT_INST_REG_ADDR_BY_IDX(n, 1), \ |
| .irq_id = DT_INST_IRQN(n), \ |
| .irq_connect = i2s_cavs_irq_connect_##n, \ |
| .dev_dma = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(n, tx)),\ |
| }; \ |
| \ |
| static struct i2s_cavs_dev_data i2s_cavs_data_##n = { \ |
| .tx = { \ |
| .dma_channel = \ |
| DT_INST_DMAS_CELL_BY_NAME(n, tx, channel),\ |
| .dma_cfg = { \ |
| .source_burst_length = \ |
| CAVS_I2S_DMA_BURST_SIZE, \ |
| .dest_burst_length = \ |
| CAVS_I2S_DMA_BURST_SIZE, \ |
| .dma_callback = i2s_dma_tx_callback, \ |
| .user_data = \ |
| (void *)DEVICE_DT_INST_GET(n), \ |
| .complete_callback_en = 1, \ |
| .error_callback_en = 1, \ |
| .block_count = 1, \ |
| .head_block = \ |
| &i2s_cavs_data_##n.tx.dma_block,\ |
| .channel_direction = \ |
| MEMORY_TO_PERIPHERAL,\ |
| .dma_slot = \ |
| DT_INST_DMAS_CELL_BY_NAME(n, tx, channel),\ |
| }, \ |
| }, \ |
| .rx = { \ |
| .dma_channel = \ |
| DT_INST_DMAS_CELL_BY_NAME(n, rx, channel),\ |
| .dma_cfg = { \ |
| .source_burst_length = \ |
| CAVS_I2S_DMA_BURST_SIZE, \ |
| .dest_burst_length = \ |
| CAVS_I2S_DMA_BURST_SIZE, \ |
| .dma_callback = i2s_dma_rx_callback, \ |
| .user_data = \ |
| (void *)DEVICE_DT_INST_GET(n), \ |
| .complete_callback_en = 1, \ |
| .error_callback_en = 1, \ |
| .block_count = 1, \ |
| .head_block = \ |
| &i2s_cavs_data_##n.rx.dma_block,\ |
| .channel_direction = \ |
| PERIPHERAL_TO_MEMORY,\ |
| .dma_slot = \ |
| DT_INST_DMAS_CELL_BY_NAME(n, rx, channel),\ |
| }, \ |
| }, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| i2s_cavs_initialize, NULL, \ |
| &i2s_cavs_data_##n, \ |
| &i2s_cavs_config_##n, \ |
| POST_KERNEL, CONFIG_I2S_INIT_PRIORITY, \ |
| &i2s_cavs_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(I2S_CAVS_DEVICE_INIT) |