|  | /* | 
|  | * 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) |