drivers: i2s: introduce CAVS I2S

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

Change-Id: I7b816f9736dc35e79a81d3664d6405dc0aac15b4
Signed-off-by: Rajavardhan Gundi <rajavardhan.gundi@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
This commit is contained in:
Rajavardhan Gundi 2018-02-28 15:50:20 +05:30 committed by Anas Nashif
commit 5b6f02442b
7 changed files with 1049 additions and 0 deletions

View file

@ -160,3 +160,15 @@ void dcache_writeback_region(void *addr, size_t size)
{
xthal_dcache_region_writeback(addr, size);
}
void dcache_invalidate_region(void *addr, size_t size)
{
xthal_dcache_region_invalidate(addr, size);
}
void setup_ownership_i2s(void)
{
u32_t value = I2S_OWNSEL(0) | I2S_OWNSEL(1) |
I2S_OWNSEL(2) | I2S_OWNSEL(3);
*(volatile u32_t *)SUE_DSPIOPO_REG |= value;
}

View file

@ -100,11 +100,30 @@
#define DMA_HANDSHAKE_SSP3_TX 8
#define DMA_HANDSHAKE_SSP3_RX 9
/* I2S */
#define I2S0_CAVS_IRQ 0x00000010
#define I2S1_CAVS_IRQ 0x00000110
#define I2S2_CAVS_IRQ 0x00000210
#define I2S3_CAVS_IRQ 0x00000310
#define SSP_SIZE 0x0000200
#define SSP_BASE(x) (0x00077000 + (x) * SSP_SIZE)
#define SOC_INTEL_S1000_MCK_XTAL_FREQ_HZ 38400000
/* address of I2S ownership register. We need to properly configure
* this register in order to access the I2S registers.
*/
#define SUE_DSP_RES_ALLOC_REG_BASE 0x00071A60
#define SUE_DSPIOPO_REG (SUE_DSP_RES_ALLOC_REG_BASE + 0x08)
#define I2S_OWNSEL(x) (0x1 << (8 + (x)))
extern void _soc_irq_enable(u32_t irq);
extern void _soc_irq_disable(u32_t irq);
extern void setup_ownership_dma0(void);
extern void setup_ownership_dma1(void);
extern void setup_ownership_dma2(void);
extern void dcache_writeback_region(void *addr, size_t size);
extern void setup_ownership_i2s(void);
#endif /* __INC_SOC_H */

View file

@ -1 +1,2 @@
zephyr_sources_ifdef(CONFIG_I2S_SAM_SSC i2s_sam_ssc.c)
zephyr_sources_ifdef(CONFIG_I2S_CAVS i2s_cavs.c)

59
drivers/i2s/Kconfig.cavs Normal file
View file

@ -0,0 +1,59 @@
# Kconfig - Intel S1000 I2S configuration options
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig I2S_CAVS
bool "Intel I2S (SSP) Bus Driver"
depends on BOARD_INTEL_S1000_CRB
select DMA
default n
help
Enable Inter Sound (I2S) bus driver for Intel_S1000 based on
Synchronous Serial Port (SSP) module.
if I2S_CAVS
config I2S_CAVS_TX_BLOCK_COUNT
int "TX queue length"
default 4
help
The maximum number of blocks that can be accommodated in the Tx queue.
config I2S_CAVS_0_NAME
string "I2S 0 device name"
default "I2S_0"
config I2S_CAVS_0_IRQ_PRI
int "Interrupt priority"
default 0
config I2S_CAVS_DMA_NAME
string "DMA device name"
default "DMA_0"
help
Name of the DMA device this device driver can use.
config I2S_CAVS_0_DMA_TX_CHANNEL
int "DMA TX channel"
default 2
help
DMA channel number to use for TX transfers.
config I2S_CAVS_1_NAME
string "I2S 1 device name"
default "I2S_1"
config I2S_CAVS_1_IRQ_PRI
int "Interrupt priority"
default 0
config I2S_CAVS_1_DMA_TX_CHANNEL
int "DMA TX channel"
default 4
help
DMA channel number to use for TX transfers.
endif # I2S_CAVS

798
drivers/i2s/i2s_cavs.c Normal file
View file

@ -0,0 +1,798 @@
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file
* @brief I2S bus (SSP) driver for Intel CAVS.
*
* Limitations:
* - DMA is used in simple single block transfer mode (with linked list
* enabled) and "interrupt on full transfer completion" mode.
*/
#include <errno.h>
#include <string.h>
#include <misc/__assert.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <dma.h>
#include <i2s.h>
#include <soc.h>
#include "i2s_cavs.h"
#define SYS_LOG_DOMAIN "dev/i2s_cavs"
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2S_LEVEL
#include <logging/sys_log.h>
#ifdef CONFIG_DCACHE_WRITEBACK
#define DCACHE_INVALIDATE(addr, size) \
{ dcache_invalidate_region(addr, size); }
#define DCACHE_CLEAN(addr, size) \
{ dcache_writeback_region(addr, size); }
#else
#define DCACHE_INVALIDATE(addr, size) \
do { } while (0)
#define DCACHE_CLEAN(addr, size) \
do { } while (0)
#endif
#define CAVS_SSP_WORD_SIZE_BITS_MIN 2
#define CAVS_SSP_WORD_SIZE_BITS_MAX 32
#define CAVS_SSP_WORD_PER_FRAME_MIN 1
#define CAVS_SSP_WORD_PER_FRAME_MAX 16
struct queue_item {
void *mem_block;
size_t size;
};
/* Minimal ring buffer implementation:
* buf - holds the pointer to Queue items
* len - Max number of Queue items that can be referenced
* head - current write index number
* tail - current read index number
*/
struct ring_buf {
struct queue_item *buf;
u16_t len;
u16_t head;
u16_t tail;
};
/* This indicates the Tx/Rx stream. Most members of the stream are
* self-explanatory except for sem.
* sem - This is initialized to CONFIG_I2S_CAVS_TX_BLOCK_COUNT. If at all
* mem_block_queue gets filled with the MAX blocks configured, this semaphore
* ensures nothing gets written into the mem_block_queue before a slot gets
* freed (which happens when a block gets read out).
*/
struct stream {
s32_t state;
struct k_sem sem;
u32_t dma_channel;
struct dma_config dma_cfg;
struct i2s_config cfg;
struct ring_buf mem_block_queue;
void *mem_block;
bool last_block;
int (*stream_start)(struct stream *, struct i2s_cavs_ssp *const,
struct device *);
void (*stream_disable)(struct stream *, struct i2s_cavs_ssp *const,
struct device *);
void (*queue_drop)(struct stream *);
};
struct i2s_cavs_config {
struct i2s_cavs_ssp *regs;
u32_t irq_id;
void (*irq_config)(void);
};
/* Device run time data */
struct i2s_cavs_dev_data {
struct device *dev_dma;
struct stream tx;
};
#define DEV_NAME(dev) ((dev)->config->name)
#define DEV_CFG(dev) \
((const struct i2s_cavs_config *const)(dev)->config->config_info)
#define DEV_DATA(dev) \
((struct i2s_cavs_dev_data *const)(dev)->driver_data)
static struct device *get_dev_from_dma_channel(u32_t dma_channel);
static void dma_tx_callback(struct device *, u32_t, int);
static void tx_stream_disable(struct stream *, struct i2s_cavs_ssp *const,
struct device *);
static inline u16_t modulo_inc(u16_t val, u16_t max)
{
val++;
return (val < max) ? val : 0;
}
/*
* Get data from the queue
*/
static int queue_get(struct ring_buf *rb, u8_t mode, void **mem_block,
size_t *size)
{
unsigned int key;
key = irq_lock();
/* In case of ping-pong mode, the buffers are not freed after
* reading. They are fixed in size. Another thread will populate
* the pong buffer while the ping buffer is being read out and
* vice versa. Hence, we just need to keep reading from buffer0
* (ping buffer) followed by buffer1 (pong buffer) and the same
* cycle continues.
*
* In case of non-ping-pong modes, each buffer is freed after it
* is read. The tail pointer will keep progressing depending upon
* the reads. The head pointer will move whenever there's a write.
* If tail equals head, it would mean we have read everything there
* is and the buffer is empty.
*/
if (rb->tail == rb->head) {
if ((mode & I2S_OPT_PINGPONG) == I2S_OPT_PINGPONG) {
/* Point back to the first element */
rb->tail = 0;
} else {
/* Ring buffer is empty */
irq_unlock(key);
return -ENOMEM;
}
}
*mem_block = rb->buf[rb->tail].mem_block;
*size = rb->buf[rb->tail].size;
rb->tail = modulo_inc(rb->tail, rb->len);
irq_unlock(key);
return 0;
}
/*
* Put data in the queue
*/
static int queue_put(struct ring_buf *rb, u8_t mode, void *mem_block,
size_t size)
{
u16_t head_next;
unsigned int key;
key = irq_lock();
head_next = rb->head;
head_next = modulo_inc(head_next, rb->len);
/* In case of ping-pong mode, the below comparison incorrectly
* leads to complications as the buffers are always predefined.
* Hence excluding ping-pong mode from this comparison.
*/
if ((mode & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) {
if (head_next == rb->tail) {
/* Ring buffer is full */
irq_unlock(key);
return -ENOMEM;
}
}
rb->buf[rb->head].mem_block = mem_block;
rb->buf[rb->head].size = size;
rb->head = head_next;
irq_unlock(key);
return 0;
}
static int start_dma(struct device *dev_dma, u32_t channel,
struct dma_config *cfg, void *src, void *dst,
u32_t blk_size)
{
int ret;
struct dma_block_config blk_cfg = {
.block_size = blk_size,
.source_address = (u32_t)src,
.dest_address = (u32_t)dst,
};
cfg->head_block = &blk_cfg;
ret = dma_config(dev_dma, channel, cfg);
if (ret < 0) {
SYS_LOG_ERR("dma_config failed: %d", ret);
return ret;
}
ret = dma_start(dev_dma, channel);
if (ret < 0) {
SYS_LOG_ERR("dma_start failed: %d", ret);
}
return ret;
}
/* This function is executed in the interrupt context */
static void dma_tx_callback(struct device *dev_dma, u32_t channel, int status)
{
struct device *dev = get_dev_from_dma_channel(channel);
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
struct stream *strm = &dev_data->tx;
size_t mem_block_size;
int ret;
__ASSERT_NO_MSG(strm->mem_block != NULL);
if ((strm->cfg.options & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) {
/* All block data sent */
k_mem_slab_free(strm->cfg.mem_slab, &strm->mem_block);
strm->mem_block = NULL;
}
/* Stop transmission if there was an error */
if (strm->state == I2S_STATE_ERROR) {
SYS_LOG_DBG("TX error detected");
goto tx_disable;
}
/* Stop transmission if we were requested */
if (strm->last_block) {
strm->state = I2S_STATE_READY;
goto tx_disable;
}
/* Prepare to send the next data block */
ret = queue_get(&strm->mem_block_queue, strm->cfg.options,
&strm->mem_block, &mem_block_size);
if (ret < 0) {
if (strm->state == I2S_STATE_STOPPING) {
strm->state = I2S_STATE_READY;
} else {
strm->state = I2S_STATE_ERROR;
}
goto tx_disable;
}
k_sem_give(&strm->sem);
/* Assure cache coherency before DMA read operation */
DCACHE_CLEAN(strm->mem_block, mem_block_size);
ret = start_dma(dev_data->dev_dma, strm->dma_channel, &strm->dma_cfg,
strm->mem_block, (void *)&(ssp->ssd),
mem_block_size);
if (ret < 0) {
SYS_LOG_DBG("Failed to start TX DMA transfer: %d", ret);
goto tx_disable;
}
return;
tx_disable:
tx_stream_disable(strm, ssp, dev_data->dev_dma);
}
static int i2s_cavs_configure(struct device *dev, enum i2s_dir dir,
struct i2s_config *i2s_cfg)
{
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
u8_t num_words = i2s_cfg->channels;
u8_t word_size_bits = i2s_cfg->word_size;
u8_t word_size_bytes;
u32_t bit_clk_freq, mclk;
struct stream *strm;
u32_t ssc0;
u32_t ssc1;
u32_t ssc2;
u32_t ssc3;
u32_t sspsp;
u32_t sspsp2;
u32_t sstsa;
u32_t ssrsa;
u32_t ssto;
u32_t ssioc;
u32_t mdiv;
u32_t i2s_m;
u32_t i2s_n;
u32_t frame_len = 0;
bool inverted_frame = false;
if (dir == I2S_DIR_TX) {
strm = &dev_data->tx;
} else {
SYS_LOG_ERR("TX direction must be selected");
return -EINVAL;
}
if (strm->state != I2S_STATE_NOT_READY &&
strm->state != I2S_STATE_READY) {
SYS_LOG_ERR("invalid state");
return -EINVAL;
}
if (i2s_cfg->frame_clk_freq == 0) {
strm->queue_drop(strm);
memset(&strm->cfg, 0, sizeof(struct i2s_config));
strm->state = I2S_STATE_NOT_READY;
return 0;
}
if (word_size_bits < CAVS_SSP_WORD_SIZE_BITS_MIN ||
word_size_bits > CAVS_SSP_WORD_SIZE_BITS_MAX) {
SYS_LOG_ERR("Unsupported I2S word size");
return -EINVAL;
}
if (num_words < CAVS_SSP_WORD_PER_FRAME_MIN ||
num_words > CAVS_SSP_WORD_PER_FRAME_MAX) {
SYS_LOG_ERR("Unsupported words per frame number");
return -EINVAL;
}
memcpy(&strm->cfg, i2s_cfg, sizeof(struct i2s_config));
/* reset SSP settings */
/* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */
/*
* FIXME: MOD, ACS, NCS are not set,
* no support for network mode for now
*/
ssc0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;
/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_RWOT | SSCR1_TRAIL;
/* sscr2 dynamic setting is LJDFD */
ssc2 = SSCR2_SDFD | SSCR2_TURM1;
/* sscr3 dynamic settings are TFT, RFT */
ssc3 = 0;
/* sspsp dynamic settings are SCMODE, SFRMP, DMYSTRT, SFRMWDTH */
sspsp = 0;
/* sspsp2 no dynamic setting */
sspsp2 = 0x0;
/* ssioc dynamic setting is SFCR */
ssioc = SSIOC_SCOE;
/* i2s_m M divider setting, default 1 */
i2s_m = 0x1;
/* i2s_n N divider setting, default 1 */
i2s_n = 0x1;
/* ssto no dynamic setting */
ssto = 0x0;
/* sstsa dynamic setting is TTSA, default 2 slots */
/* TODO: Expand I2s.h to make this configurable */
sstsa = (0x1 << 8) | (0x3 << 0);
/* ssrsa dynamic setting is RTSA, default 2 slots */
/* TODO: Expand I2s.h to make this configurable */
ssrsa = 0;
/* clock masters */
ssc1 &= ~SSCR1_SFRMDIR;
/* codec is clk slave & FRM slave */
/* TODO: Expand I2s.h to make this configurable */
ssc1 |= SSCR1_SCFR;
ssioc |= SSIOC_SFCR;
/* clock signal polarity */
switch (i2s_cfg->format & I2S_FMT_CLK_FORMAT_MASK) {
case I2S_FMT_CLK_NF_NB:
break;
case I2S_FMT_CLK_NF_IB:
sspsp |= SSPSP_SCMODE(2);
inverted_frame = true; /* handled later with format */
break;
case I2S_FMT_CLK_IF_NB:
break;
case I2S_FMT_CLK_IF_IB:
sspsp |= SSPSP_SCMODE(2);
inverted_frame = true; /* handled later with format */
break;
default:
SYS_LOG_ERR("Unsupported Clock format");
return -EINVAL;
}
ssc0 |= SSCR0_MOD | SSCR0_ACS;
mclk = SOC_INTEL_S1000_MCK_XTAL_FREQ_HZ;
bit_clk_freq = i2s_cfg->frame_clk_freq * word_size_bits * num_words;
/* BCLK is generated from MCLK - must be divisible */
if (mclk % bit_clk_freq) {
SYS_LOG_ERR("MCLK is not a factor of BCLK");
return -EINVAL;
}
/* divisor must be within SCR range */
mdiv = (mclk / bit_clk_freq) - 1;
if (mdiv > (SSCR0_SCR_MASK >> 8)) {
SYS_LOG_ERR("Divisor is not within SCR range");
return -EINVAL;
}
/* set the SCR divisor */
ssc0 |= SSCR0_SCR(mdiv);
/* word_size_bits must be <= 38 for SSP */
if (word_size_bits > 38) {
SYS_LOG_ERR("word size bits is more than 38");
return -EINVAL;
}
/* format */
switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) {
case I2S_FMT_DATA_FORMAT_I2S:
ssc0 |= SSCR0_FRDC(i2s_cfg->channels);
/* set asserted frame length */
frame_len = word_size_bits;
/* handle frame polarity, I2S default is falling/active low */
sspsp |= SSPSP_SFRMP(!inverted_frame);
break;
case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
ssc0 |= SSCR0_FRDC(i2s_cfg->channels);
/* LJDFD enable */
ssc2 &= ~SSCR2_LJDFD;
/* set asserted frame length */
frame_len = word_size_bits;
/* LEFT_J default is rising/active high, opposite of I2S */
sspsp |= SSPSP_SFRMP(inverted_frame);
break;
case I2S_FMT_DATA_FORMAT_PCM_SHORT:
case I2S_FMT_DATA_FORMAT_PCM_LONG:
default:
SYS_LOG_ERR("Unsupported I2S data format");
return -EINVAL;
}
sspsp |= SSPSP_SFRMWDTH(frame_len);
if (word_size_bits > 16) {
ssc0 |= (SSCR0_EDSS | SSCR0_DSIZE(word_size_bits - 16));
} else {
ssc0 |= SSCR0_DSIZE(word_size_bits);
}
ssp->ssc0 = ssc0;
ssp->ssc1 = ssc1;
ssp->ssc2 = ssc2;
ssp->ssc3 = ssc3;
ssp->sspsp2 = sspsp2;
ssp->sspsp = sspsp;
ssp->ssioc = ssioc;
ssp->ssto = ssto;
ssp->sstsa = sstsa;
ssp->ssrsa = ssrsa;
/* Set up DMA channel parameters */
word_size_bytes = (word_size_bits + 7) / 8;
strm->dma_cfg.source_data_size = word_size_bytes;
strm->dma_cfg.dest_data_size = word_size_bytes;
strm->state = I2S_STATE_READY;
return 0;
}
static int tx_stream_start(struct stream *strm,
struct i2s_cavs_ssp *const ssp,
struct device *dev_dma)
{
size_t mem_block_size;
int ret;
ret = queue_get(&strm->mem_block_queue, strm->cfg.options,
&strm->mem_block, &mem_block_size);
if (ret < 0) {
return ret;
}
k_sem_give(&strm->sem);
/* Assure cache coherency before DMA read operation */
DCACHE_CLEAN(strm->mem_block, mem_block_size);
ret = start_dma(dev_dma, strm->dma_channel, &strm->dma_cfg,
strm->mem_block, (void *)&(ssp->ssd),
mem_block_size);
if (ret < 0) {
SYS_LOG_ERR("Failed to start TX DMA transfer: %d", ret);
return ret;
}
/* enable port */
ssp->ssc0 |= SSCR0_SSE;
/* Enable DMA service request handshake logic. Though DMA is
* already started, it won't work without the handshake logic.
*/
ssp->ssc1 |= SSCR1_TSRE;
ssp->sstsa |= (0x1 << 8);
return 0;
}
static void tx_stream_disable(struct stream *strm,
struct i2s_cavs_ssp *const ssp,
struct device *dev_dma)
{
/* Disable DMA service request handshake logic. Handshake is
* not required now since DMA is not in operation.
*/
ssp->ssc1 &= ~SSCR1_TSRE;
ssp->sstsa &= ~(0x1 << 8);
dma_stop(dev_dma, strm->dma_channel);
if (((strm->cfg.options & I2S_OPT_PINGPONG) != I2S_OPT_PINGPONG) &&
(strm->mem_block != NULL)) {
k_mem_slab_free(strm->cfg.mem_slab, &strm->mem_block);
strm->mem_block = NULL;
}
strm->mem_block_queue.head = 0;
strm->mem_block_queue.tail = 0;
}
static void tx_queue_drop(struct stream *strm)
{
size_t size;
void *mem_block;
unsigned int n = 0;
while (queue_get(&strm->mem_block_queue, strm->cfg.options,
&mem_block, &size) == 0) {
if ((strm->cfg.options & I2S_OPT_PINGPONG)
!= I2S_OPT_PINGPONG) {
k_mem_slab_free(strm->cfg.mem_slab, &mem_block);
n++;
}
}
strm->mem_block_queue.head = 0;
strm->mem_block_queue.tail = 0;
for (; n > 0; n--) {
k_sem_give(&strm->sem);
}
}
static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
enum i2s_trigger_cmd cmd)
{
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
struct stream *strm;
unsigned int key;
int ret;
if (dir == I2S_DIR_TX) {
strm = &dev_data->tx;
} else {
SYS_LOG_ERR("TX direction must be selected");
return -EINVAL;
}
switch (cmd) {
case I2S_TRIGGER_START:
if (strm->state != I2S_STATE_READY) {
SYS_LOG_DBG("START trigger: invalid state");
return -EIO;
}
__ASSERT_NO_MSG(strm->mem_block == NULL);
ret = strm->stream_start(strm, ssp, dev_data->dev_dma);
if (ret < 0) {
SYS_LOG_DBG("START trigger failed %d", ret);
return ret;
}
strm->state = I2S_STATE_RUNNING;
strm->last_block = false;
break;
case I2S_TRIGGER_STOP:
key = irq_lock();
if (strm->state != I2S_STATE_RUNNING) {
irq_unlock(key);
SYS_LOG_DBG("STOP trigger: invalid state");
return -EIO;
}
strm->state = I2S_STATE_STOPPING;
irq_unlock(key);
strm->last_block = true;
break;
case I2S_TRIGGER_DRAIN:
key = irq_lock();
if (strm->state != I2S_STATE_RUNNING) {
irq_unlock(key);
SYS_LOG_DBG("DRAIN trigger: invalid state");
return -EIO;
}
strm->state = I2S_STATE_STOPPING;
irq_unlock(key);
break;
case I2S_TRIGGER_DROP:
if (strm->state == I2S_STATE_NOT_READY) {
SYS_LOG_DBG("DROP trigger: invalid state");
return -EIO;
}
strm->stream_disable(strm, ssp, dev_data->dev_dma);
strm->queue_drop(strm);
strm->state = I2S_STATE_READY;
break;
case I2S_TRIGGER_PREPARE:
if (strm->state != I2S_STATE_ERROR) {
SYS_LOG_DBG("PREPARE trigger: invalid state");
return -EIO;
}
strm->state = I2S_STATE_READY;
strm->queue_drop(strm);
break;
default:
SYS_LOG_ERR("Unsupported trigger command");
return -EINVAL;
}
return 0;
}
static int i2s_cavs_write(struct device *dev, void *mem_block, size_t size)
{
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
struct stream *strm = &dev_data->tx;
int ret;
if (dev_data->tx.state != I2S_STATE_RUNNING &&
dev_data->tx.state != I2S_STATE_READY) {
SYS_LOG_DBG("invalid state");
return -EIO;
}
ret = k_sem_take(&dev_data->tx.sem, dev_data->tx.cfg.timeout);
if (ret < 0) {
SYS_LOG_ERR("Failure taking sem");
return ret;
}
/* Add data to the end of the TX queue */
queue_put(&dev_data->tx.mem_block_queue, strm->cfg.options,
mem_block, size);
return 0;
}
/* clear IRQ sources atm */
static void i2s_cavs_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
u32_t temp;
/* clear IRQ */
temp = ssp->sss;
ssp->sss = temp;
}
static int i2s1_cavs_initialize(struct device *dev)
{
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
#ifdef CONFIG_SOC_INTEL_S1000
setup_ownership_i2s();
#endif
/* Configure interrupts */
dev_cfg->irq_config();
/* Initialize semaphores */
k_sem_init(&dev_data->tx.sem, CONFIG_I2S_CAVS_TX_BLOCK_COUNT,
CONFIG_I2S_CAVS_TX_BLOCK_COUNT);
dev_data->dev_dma = device_get_binding(CONFIG_I2S_CAVS_DMA_NAME);
if (!dev_data->dev_dma) {
SYS_LOG_ERR("%s device not found", CONFIG_I2S_CAVS_DMA_NAME);
return -ENODEV;
}
/* Enable module's IRQ */
irq_enable(dev_cfg->irq_id);
SYS_LOG_INF("Device %s initialized", DEV_NAME(dev));
return 0;
}
static const struct i2s_driver_api i2s_cavs_driver_api = {
.configure = i2s_cavs_configure,
.write = i2s_cavs_write,
.trigger = i2s_cavs_trigger,
};
/* I2S1 */
static struct device DEVICE_NAME_GET(i2s1_cavs);
static struct device *get_dev_from_dma_channel(u32_t dma_channel)
{
return &DEVICE_NAME_GET(i2s1_cavs);
}
struct queue_item tx_0_ring_buf[CONFIG_I2S_CAVS_TX_BLOCK_COUNT + 1];
static void i2s1_irq_config(void)
{
IRQ_CONNECT(I2S1_CAVS_IRQ, CONFIG_I2S_1_IRQ_PRI, i2s_cavs_isr,
DEVICE_GET(i2s1_cavs), 0);
}
static const struct i2s_cavs_config i2s1_cavs_config = {
.regs = (struct i2s_cavs_ssp *)SSP_BASE(1),
.irq_id = I2S1_CAVS_IRQ,
.irq_config = i2s1_irq_config,
};
static struct i2s_cavs_dev_data i2s1_cavs_data = {
.tx = {
.dma_channel = CONFIG_I2S_CAVS_1_DMA_TX_CHANNEL,
.dma_cfg = {
.source_data_size = 1,
.dest_data_size = 1,
.source_burst_length = 1,
.dest_burst_length = 1,
.dma_callback = dma_tx_callback,
.complete_callback_en = 0,
.error_callback_en = 1,
.block_count = 1,
.channel_direction = MEMORY_TO_PERIPHERAL,
.dma_slot = DMA_HANDSHAKE_SSP1_TX,
},
.mem_block_queue.buf = tx_0_ring_buf,
.mem_block_queue.len = ARRAY_SIZE(tx_0_ring_buf),
.stream_start = tx_stream_start,
.stream_disable = tx_stream_disable,
.queue_drop = tx_queue_drop,
},
};
DEVICE_AND_API_INIT(i2s1_cavs, CONFIG_I2S_CAVS_1_NAME, &i2s1_cavs_initialize,
&i2s1_cavs_data, &i2s1_cavs_config, POST_KERNEL,
CONFIG_I2S_INIT_PRIORITY, &i2s_cavs_driver_api);

137
drivers/i2s/i2s_cavs.h Normal file
View file

@ -0,0 +1,137 @@
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file
* @brief I2S bus (SSP) driver for Intel CAVS.
*
* Limitations:
* - DMA is used in simple single block transfer mode (with linked list
* enabled) and "interrupt on full transfer completion" mode.
*/
#ifndef _I2S_CAVS_H_
#define _I2S_CAVS_H_
#ifdef __cplusplus
extern "C" {
#endif
struct i2s_cavs_ssp {
u32_t ssc0; /* 0x00 - Control0 */
u32_t ssc1; /* 0x04 - Control1 */
u32_t sss; /* 0x08 - Status */
u32_t ssit; /* 0x0C - Interrupt Test */
u32_t ssd; /* 0x10 - Data */
u32_t reserved0[5];
u32_t ssto; /* 0x28 - Time Out */
u32_t sspsp; /* 0x2C - Programmable Serial Protocol */
u32_t sstsa; /* 0x30 - TX Time Slot Active */
u32_t ssrsa; /* 0x34 - RX Time Slot Active */
u32_t sstss; /* 0x38 - Time Slot Status */
u32_t reserved1;
u32_t ssc2; /* 0x40 - Command / Status 2 */
u32_t sspsp2; /* 0x44 - Programmable Serial Protocol 2 */
u32_t ssc3; /* 0x48 - Command / Status 3 */
u32_t ssioc; /* 0x4C - IO Control */
};
/* SSCR0 bits */
#define SSCR0_DSS_MASK (0x0000000f)
#define SSCR0_DSIZE(x) ((x) - 1)
#define SSCR0_FRF (0x00000030)
#define SSCR0_MOT (00 << 4)
#define SSCR0_TI (1 << 4)
#define SSCR0_NAT (2 << 4)
#define SSCR0_PSP (3 << 4)
#define SSCR0_ECS (1 << 6)
#define SSCR0_SSE (1 << 7)
#define SSCR0_SCR_MASK (0x000fff00)
#define SSCR0_SCR(x) ((x) << 8)
#define SSCR0_EDSS (1 << 20)
#define SSCR0_NCS (1 << 21)
#define SSCR0_RIM (1 << 22)
#define SSCR0_TIM (1 << 23)
#define SSCR0_FRDC(x) (((x) - 1) << 24)
#define SSCR0_ACS (1 << 30)
#define SSCR0_MOD (1 << 31)
/* SSCR1 bits */
#define SSCR1_RIE (1 << 0)
#define SSCR1_TIE (1 << 1)
#define SSCR1_LBM (1 << 2)
#define SSCR1_SPO (1 << 3)
#define SSCR1_SPH (1 << 4)
#define SSCR1_MWDS (1 << 5)
#define SSCR1_EFWR (1 << 14)
#define SSCR1_STRF (1 << 15)
#define SSCR1_IFS (1 << 16)
#define SSCR1_PINTE (1 << 18)
#define SSCR1_TINTE (1 << 19)
#define SSCR1_RSRE (1 << 20)
#define SSCR1_TSRE (1 << 21)
#define SSCR1_TRAIL (1 << 22)
#define SSCR1_RWOT (1 << 23)
#define SSCR1_SFRMDIR (1 << 24)
#define SSCR1_SCLKDIR (1 << 25)
#define SSCR1_ECRB (1 << 26)
#define SSCR1_ECRA (1 << 27)
#define SSCR1_SCFR (1 << 28)
#define SSCR1_EBCEI (1 << 29)
#define SSCR1_TTE (1 << 30)
#define SSCR1_TTELP (1 << 31)
/* SSCR2 bits */
#define SSCR2_TURM1 (1 << 1)
#define SSCR2_SDFD (1 << 14)
#define SSCR2_SDPM (1 << 16)
#define SSCR2_LJDFD (1 << 17)
/* SSR bits */
#define SSSR_TNF (1 << 2)
#define SSSR_RNE (1 << 3)
#define SSSR_BSY (1 << 4)
#define SSSR_TFS (1 << 5)
#define SSSR_RFS (1 << 6)
#define SSSR_ROR (1 << 7)
/* SSPSP bits */
#define SSPSP_SCMODE(x) ((x) << 0)
#define SSPSP_SFRMP(x) ((x) << 2)
#define SSPSP_ETDS (1 << 3)
#define SSPSP_STRTDLY(x) ((x) << 4)
#define SSPSP_DMYSTRT(x) ((x) << 7)
#define SSPSP_SFRMDLY(x) ((x) << 9)
#define SSPSP_SFRMWDTH(x) ((x) << 16)
#define SSPSP_DMYSTOP(x) ((x) << 23)
#define SSPSP_FSRT (1 << 25)
#define SSPSP_EDMYSTOP(x) ((x) << 26)
/* SSTSA bits */
#define SSTSA_TTSA(x) (1 << x)
#define SSTSA_TXEN (1 << 8)
/* SSRSA bits */
#define SSRSA_RTSA(x) (1 << x)
#define SSRSA_RXEN (1 << 8)
/* SSCR3 bits */
#define SSCR3_TFL_MASK (0x0000003f)
#define SSCR3_RFL_MASK (0x00003f00)
#define SSCR3_TFT_MASK (0x003f0000)
#define SSCR3_TX(x) (((x) - 1) << 16)
#define SSCR3_RFT_MASK (0x3f000000)
#define SSCR3_RX(x) (((x) - 1) << 24)
/* SSIOC bits */
#define SSIOC_TXDPDEB (1 << 1)
#define SSIOC_SFCR (1 << 4)
#define SSIOC_SCOE (1 << 5)
#ifdef __cplusplus
}
#endif
#endif /* _I2S_CAVS_H_ */

View file

@ -148,11 +148,24 @@ typedef u8_t i2s_fmt_t;
#define I2S_FMT_DATA_ORDER_LSB (1 << 3)
/** Invert bit ordering, send LSB first */
#define I2S_FMT_DATA_ORDER_INV I2S_FMT_DATA_ORDER_LSB
/** Data Format bit field position. */
#define I2S_FMT_CLK_FORMAT_SHIFT 4
/** Data Format bit field mask. */
#define I2S_FMT_CLK_FORMAT_MASK (0x3 << I2S_FMT_CLK_FORMAT_SHIFT)
/** Invert bit clock */
#define I2S_FMT_BIT_CLK_INV (1 << 4)
/** Invert frame clock */
#define I2S_FMT_FRAME_CLK_INV (1 << 5)
/** NF represents "Normal Frame" whereas IF represents "Inverted Frame"
* NB represents "Normal Bit Clk" whereas IB represents "Inverted Bit clk"
*/
#define I2S_FMT_CLK_NF_NB (0 << I2S_FMT_CLK_FORMAT_SHIFT)
#define I2S_FMT_CLK_NF_IB (1 << I2S_FMT_CLK_FORMAT_SHIFT)
#define I2S_FMT_CLK_IF_NB (2 << I2S_FMT_CLK_FORMAT_SHIFT)
#define I2S_FMT_CLK_IF_IB (3 << I2S_FMT_CLK_FORMAT_SHIFT)
typedef u8_t i2s_opt_t;
@ -168,6 +181,7 @@ typedef u8_t i2s_opt_t;
#define I2S_OPT_FRAME_CLK_MASTER (0 << 2)
/** I2S driver is frame clock slave */
#define I2S_OPT_FRAME_CLK_SLAVE (1 << 2)
/** @brief Loop back mode.
*
* In loop back mode RX input will be connected internally to TX output.
@ -175,6 +189,15 @@ typedef u8_t i2s_opt_t;
*/
#define I2S_OPT_LOOPBACK (1 << 7)
/** @brief Ping pong mode
*
* In ping pong mode TX output will keep alternating between a ping buffer
* and a pong buffer. This is normally used in audio streams when one buffer
* is being populated while the other is being played (DMAed) and vice versa.
* So, in this mode, 2 sets of buffers fixed in size are used. Static Arrays
* are used to achieve this and hence they are never freed.
*/
#define I2S_OPT_PINGPONG (1 << 6)
/**
* @brief I2C Direction