drivers: i2s: Add LiteX I2S controller driver

This introduces LiteX I2S driver supporting the following features:
    - 8,16,24,32 bit sample width,
    - mono/stereo sound,
    - different sample frequencies
    - big/little-endian data format,
    - concatenated channels mode (for selected sample widths only),
    - slave/master mode operation.

Signed-off-by: Pawel Sagan <psagan@internships.antmicro.com>
Signed-off-by: Mateusz Holenko <mholenko@antmicro.com>
This commit is contained in:
Pawel Sagan 2020-03-12 17:54:08 +01:00 committed by Anas Nashif
parent fd2370be88
commit cc30fb871b
10 changed files with 854 additions and 0 deletions

View file

@ -190,6 +190,7 @@
/drivers/i2s/i2s_ll_stm32* @avisconti
/drivers/i2c/i2c_common.c @sjg20
/drivers/i2c/i2c_shell.c @nashif
/drivers/i2s/*litex* @mateusz-holenko @kgugala @pgielda
/drivers/ieee802154/ @jukkar @tbursztyka
/drivers/ieee802154/ieee802154_rf2xx* @jukkar @tbursztyka @nandojve
/drivers/interrupt_controller/ @andrewboie

View file

@ -64,3 +64,11 @@
&gpio_in {
status = "okay";
};
&i2s_rx {
status = "okay";
};
&i2s_tx {
status = "okay";
};

View file

@ -28,3 +28,5 @@ CONFIG_I2C_LITEX=y
CONFIG_PWM=y
CONFIG_PWM_LITEX=y
CONFIG_XIP=n
CONFIG_I2S=y
CONFIG_I2S_LITEX=y

View file

@ -7,3 +7,4 @@ zephyr_library_sources_ifdef(CONFIG_I2S_SAM_SSC i2s_sam_ssc.c)
zephyr_library_sources_ifdef(CONFIG_I2S_CAVS i2s_cavs.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE i2s_handlers.c)
zephyr_library_sources_ifdef(CONFIG_I2S_STM32 i2s_ll_stm32.c)
zephyr_library_sources_ifdef(CONFIG_I2S_LITEX i2s_litex.c)

31
drivers/i2s/Kconfig.litex Normal file
View file

@ -0,0 +1,31 @@
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig I2S_LITEX
bool "Litex I2S driver"
depends on SOC_RISCV32_LITEX_VEXRISCV
help
Enable Litex Inter Sound (I2S) bus driver.
if I2S_LITEX
config I2S_LITEX_RX_BLOCK_COUNT
int "RX queue length"
default 500
config I2S_LITEX_TX_BLOCK_COUNT
int "TX queue length"
default 500
config I2S_LITEX_CHANNELS_CONCATENATED
bool "Channels placed without padding in fifo"
default n
config I2S_LITEX_DATA_BIG_ENDIAN
bool "Received data will be stored as big endian"
default n
endif

646
drivers/i2s/i2s_litex.c Normal file
View file

@ -0,0 +1,646 @@
/*
* Copyright (c) 2020 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <drivers/i2s.h>
#include <sys/byteorder.h>
#include <soc.h>
#include <sys/util.h>
#include <assert.h>
#include "i2s_litex.h"
#include <logging/log.h>
LOG_MODULE_REGISTER(i2s_litex);
#define DEV_CFG(dev) ((struct i2s_litex_cfg *const)(dev)->config)
#define DEV_DATA(dev) ((struct i2s_litex_data *const)(dev)->data)
#define MODULO_INC(val, max) \
{ \
val = (val == max - 1) ? 0 : val + 1; \
}
/**
* @brief Enable i2s device
*
* @param reg base register of device
*
* @return N/A
*/
static void i2s_enable(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_REG_OFFSET);
litex_write8(reg_data | I2S_ENABLE, reg + I2S_CONTROL_REG_OFFSET);
}
/**
* @brief Disable i2s device
*
* @param reg base register of device
*
* @return N/A
*/
static void i2s_disable(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_REG_OFFSET);
litex_write8(reg_data & ~(I2S_ENABLE), reg + I2S_CONTROL_REG_OFFSET);
}
/**
* @brief Reset i2s fifo
*
* @param reg base register of device
*
* @return N/A
*/
static void i2s_reset_fifo(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_REG_OFFSET);
litex_write8(reg_data | I2S_FIFO_RESET, reg + I2S_CONTROL_REG_OFFSET);
}
/**
* @brief Get i2s format handled by device
*
* @param reg base register of device
*
* @return currently supported format or error
* when format can't be handled
*/
static i2s_fmt_t i2s_get_foramt(uintptr_t reg)
{
uint8_t reg_data = litex_read32(reg + I2S_CONFIG_REG_OFFSET);
reg_data &= I2S_CONF_FORMAT_MASK;
if (reg_data == LITEX_I2S_STANDARD) {
return I2S_FMT_DATA_FORMAT_I2S;
} else if (reg_data == LITEX_I2S_LEFT_JUSTIFIED) {
return I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED;
}
return -EINVAL;
}
/**
* @brief Get i2s sample width handled by device
*
* @param reg base register of device
*
* @return i2s sample width in bits
*/
static uint32_t i2s_get_sample_width(uintptr_t reg)
{
uint32_t reg_data = litex_read32(reg + I2S_CONFIG_REG_OFFSET);
reg_data &= I2S_CONF_SAMPLE_WIDTH_MASK;
return reg_data >> I2S_CONF_SAMPLE_WIDTH_OFFSET;
}
/**
* @brief Get i2s audio sampling rate handled by device
*
* @param reg base register of device
*
* @return audio sampling rate in Hz
*/
static uint32_t i2s_get_audio_freq(uintptr_t reg)
{
uint32_t reg_data = litex_read32(reg + I2S_CONFIG_REG_OFFSET);
reg_data &= I2S_CONF_LRCK_MASK;
return reg_data >> I2S_CONF_LRCK_FREQ_OFFSET;
}
/**
* @brief Enable i2s interrupt in event register
*
* @param reg base register of device
* @param irq_type irq type to be enabled one of I2S_EV_READY or I2S_EV_ERROR
*
* @return N/A
*/
static void i2s_irq_enable(uintptr_t reg, int irq_type)
{
assert(irq_type == I2S_EV_READY || irq_type == I2S_EV_ERROR);
uint8_t reg_data = litex_read8(reg + I2S_EV_ENABLE_REG_OFFSET);
litex_write8(reg_data | irq_type, reg + I2S_EV_ENABLE_REG_OFFSET);
}
/**
* @brief Disable i2s interrupt in event register
*
* @param reg base register of device
* @param irq_type irq type to be disabled one of I2S_EV_READY or I2S_EV_ERROR
*
* @return N/A
*/
static void i2s_irq_disable(uintptr_t reg, int irq_type)
{
assert(irq_type == I2S_EV_READY || irq_type == I2S_EV_ERROR);
uint8_t reg_data = litex_read8(reg + I2S_EV_ENABLE_REG_OFFSET);
litex_write8(reg_data & ~(irq_type), reg + I2S_EV_ENABLE_REG_OFFSET);
}
/**
* @brief Clear all pending irqs
*
* @param reg base register of device
*
* @return N/A
*/
static void i2s_clear_pending_irq(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_EV_PENDING_REG_OFFSET);
litex_write8(reg_data, reg + I2S_EV_PENDING_REG_OFFSET);
}
/**
* @brief fast data copy function,
* each operation copies 32 bit data chunks
* This function copies data from fifo into user buffer
*
* @param dst memory destination where fifo data will be copied to
* @param size amount of data to be copied
* @param sample_width width of signle sample in bits
* @param channels number of received channels
*
* @return N/A
*/
static void i2s_copy_from_fifo(uint8_t *dst, size_t size, int sample_width,
int channels)
{
uint32_t data;
int chan_size = sample_width / 8;
#if CONFIG_I2S_LITEX_CHANNELS_CONCATENATED
if (channels == 2) {
for (size_t i = 0; i < size / chan_size; i += 4) {
/* using sys_read function, as fifo is not a csr,
* but a contignous memory space
*/
*(dst + i) = sys_read32(I2S_RX_FIFO_ADDR);
}
} else {
for (size_t i = 0; i < size / chan_size; i += 2) {
data = sys_read32(I2S_RX_FIFO_ADDR);
*((uint16_t *)(dst + i)) = data & 0xffff;
}
}
#else
int max_off = chan_size - 1;
for (size_t i = 0; i < size / chan_size; ++i) {
data = sys_read32(I2S_RX_FIFO_ADDR);
for (int off = max_off; off >= 0; off--) {
#if CONFIG_I2S_LITEX_DATA_BIG_ENDIAN
*(dst + i * chan_size + (max_off - off)) =
data >> 8 * off;
#else
*(dst + i * chan_size + off) = data >> 8 * off;
#endif
}
/* if mono, copy every left channel
* right channel is discarded
*/
if (channels == 1) {
sys_read32(I2S_RX_FIFO_ADDR);
}
}
#endif
}
/**
* @brief fast data copy function,
* each operation copies 32 bit data chunks
* This function copies data from user buffer into fifo
*
* @param src memory from which data will be copied to fifo
* @param size amount of data to be copied in bytes
* @param sample_width width of signle sample in bits
* @param channels number of received channels
*
* @return N/A
*/
static void i2s_copy_to_fifo(uint8_t *src, size_t size, int sample_width,
int channels)
{
int chan_size = sample_width / 8;
#if CONFIG_I2S_LITEX_CHANNELS_CONCATENATED
if (channels == 2) {
for (size_t i = 0; i < size / chan_size; i += 4) {
/* using sys_write function, as fifo is not a csr,
* but a contignous memory space
*/
sys_write32(*(src + i), I2S_TX_FIFO_ADDR);
}
} else {
for (size_t i = 0; i < size / chan_size; i += 2) {
sys_write32(*((uint16_t *)(src + i)), I2S_TX_FIFO_ADDR);
}
}
#else
int max_off = chan_size - 1;
uint32_t data;
uint8_t *d_ptr = (uint8_t *)&data;
for (size_t i = 0; i < size / chan_size; ++i) {
for (int off = max_off; off >= 0; off--) {
#if CONFIG_I2S_LITEX_DATA_BIG_ENDIAN
*(d_ptr + off) =
*(src + i * chan_size + (max_off - off));
#else
*(d_ptr + off) = *(src + i * chan_size + off);
#endif
}
sys_write32(data, I2S_TX_FIFO_ADDR);
/* if mono send every left channel
* right channel will be same as left
*/
if (channels == 1) {
sys_write32(data, I2S_TX_FIFO_ADDR);
}
}
#endif
}
/*
* 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 i2s_litex_initialize(struct device *dev)
{
struct i2s_litex_cfg *cfg = DEV_CFG(dev);
struct i2s_litex_data *const dev_data = DEV_DATA(dev);
k_sem_init(&dev_data->rx.sem, 0, CONFIG_I2S_LITEX_RX_BLOCK_COUNT);
k_sem_init(&dev_data->tx.sem, CONFIG_I2S_LITEX_TX_BLOCK_COUNT - 1,
CONFIG_I2S_LITEX_TX_BLOCK_COUNT);
cfg->irq_config(dev);
return 0;
}
static int i2s_litex_configure(struct device *dev, enum i2s_dir dir,
struct i2s_config *i2s_cfg)
{
struct i2s_litex_data *const dev_data = DEV_DATA(dev);
const struct i2s_litex_cfg *const cfg = DEV_CFG(dev);
struct stream *stream;
int channels_concatenated;
int dev_audio_freq = i2s_get_audio_freq(cfg->base);
int channel_div;
if (dir == I2S_DIR_RX) {
stream = &dev_data->rx;
channels_concatenated = litex_read8(I2S_RX_STATUS_REG) &
I2S_RX_STAT_CHANNEL_CONCATENATED_MASK;
} else if (dir == I2S_DIR_TX) {
stream = &dev_data->tx;
channels_concatenated = litex_read8(I2S_TX_STATUS_REG) &
I2S_TX_STAT_CHANNEL_CONCATENATED_MASK;
} 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->options & I2S_OPT_BIT_CLK_GATED) {
LOG_ERR("invalid operating mode");
return -EINVAL;
}
if (i2s_cfg->frame_clk_freq != dev_audio_freq) {
LOG_WRN("invalid audio frequency sampling rate");
}
if (i2s_cfg->channels == 1) {
channel_div = 2;
} else if (i2s_cfg->channels == 2) {
channel_div = 1;
} else {
LOG_ERR("invalid channels number");
return -EINVAL;
}
int req_buf_s =
(cfg->fifo_depth * (i2s_cfg->word_size / 8)) / channel_div;
if (i2s_cfg->block_size < req_buf_s) {
LOG_ERR("not enough space to allocate signle buffer");
LOG_ERR("fifo requires at least %i bytes", req_buf_s);
return -EINVAL;
} else if (i2s_cfg->block_size != req_buf_s) {
LOG_WRN("the buffer is greater than required,"
"only %"
"i bytes of data are valid ",
req_buf_s);
i2s_cfg->block_size = req_buf_s;
}
int dev_sample_width = i2s_get_sample_width(cfg->base);
if (i2s_cfg->word_size != 8U && i2s_cfg->word_size != 16U &&
i2s_cfg->word_size != 24U && i2s_cfg->word_size != 32U &&
i2s_cfg->word_size != dev_sample_width) {
LOG_ERR("invalid word size");
return -EINVAL;
}
int dev_format = i2s_get_foramt(cfg->base);
if (dev_format != i2s_cfg->format) {
LOG_ERR("unsupported I2S data format");
return -EINVAL;
}
#if CONFIG_I2S_LITEX_CHANNELS_CONCATENATED
#if CONFIG_I2S_LITEX_DATA_BIG_ENDIAN
LOG_ERR("Big endian is not uspported "
"when channels are conncatenated");
return -EINVAL;
#endif
if (channels_concatenated == 0) {
LOG_ERR("invalid state. "
"Your device is configured to send "
"channels with padding. "
"Please reconfigure driver");
return -EINVAL;
}
if (i2s_cfg->word_size != 16) {
LOG_ERR("invalid word size");
return -EINVAL;
}
#endif
memcpy(&stream->cfg, i2s_cfg, sizeof(struct i2s_config));
stream->state = I2S_STATE_READY;
return 0;
}
static int i2s_litex_read(struct device *dev, void **mem_block, size_t *size)
{
struct i2s_litex_data *const dev_data = DEV_DATA(dev);
int ret;
if (dev_data->rx.state == I2S_STATE_NOT_READY) {
LOG_DBG("invalid state");
return -ENOMEM;
}
/* just to implement timeout*/
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 */
return queue_get(&dev_data->rx.mem_block_queue, mem_block, size);
}
static int i2s_litex_write(struct device *dev, void *mem_block, size_t size)
{
struct i2s_litex_data *const dev_data = DEV_DATA(dev);
const struct i2s_litex_cfg *cfg = DEV_CFG(dev);
int ret;
if (dev_data->tx.state != I2S_STATE_RUNNING &&
dev_data->tx.state != I2S_STATE_READY) {
LOG_DBG("invalid state");
return -EIO;
}
/* just to implement timeout */
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 */
ret = queue_put(&dev_data->tx.mem_block_queue, mem_block, size);
if (ret < 0) {
return ret;
}
if (dev_data->tx.state == I2S_STATE_READY) {
i2s_irq_enable(cfg->base, I2S_EV_READY);
dev_data->tx.state = I2S_STATE_RUNNING;
}
return ret;
}
static int i2s_litex_trigger(struct device *dev, enum i2s_dir dir,
enum i2s_trigger_cmd cmd)
{
struct i2s_litex_data *const dev_data = DEV_DATA(dev);
const struct i2s_litex_cfg *const cfg = DEV_CFG(dev);
struct stream *stream;
if (dir == I2S_DIR_RX) {
stream = &dev_data->rx;
} else if (dir == I2S_DIR_TX) {
stream = &dev_data->tx;
} 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_ERR("START trigger: invalid state %d",
stream->state);
return -EIO;
}
__ASSERT_NO_MSG(stream->mem_block == NULL);
i2s_reset_fifo(cfg->base);
i2s_enable(cfg->base);
i2s_irq_enable(cfg->base, I2S_EV_READY);
stream->state = I2S_STATE_RUNNING;
break;
case I2S_TRIGGER_STOP:
if (stream->state != I2S_STATE_RUNNING &&
stream->state != I2S_STATE_READY) {
LOG_ERR("STOP trigger: invalid state");
return -EIO;
}
i2s_disable(cfg->base);
i2s_irq_disable(cfg->base, I2S_EV_READY);
stream->state = I2S_STATE_READY;
break;
default:
LOG_ERR("unsupported trigger command");
return -EINVAL;
}
return 0;
}
static inline void clear_rx_fifo(const struct i2s_litex_cfg *cfg)
{
for (int i = 0; i < I2S_RX_FIFO_DEPTH; i++) {
sys_read32(I2S_RX_FIFO_ADDR);
}
i2s_clear_pending_irq(cfg->base);
}
static void i2s_litex_isr_rx(void *arg)
{
struct device *const dev = (struct device *)arg;
const struct i2s_litex_cfg *cfg = DEV_CFG(dev);
struct stream *stream = &DEV_DATA(dev)->rx;
int ret;
/* 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) {
clear_rx_fifo(cfg);
return;
}
i2s_copy_from_fifo((uint8_t *)stream->mem_block, stream->cfg.block_size,
stream->cfg.word_size, stream->cfg.channels);
i2s_clear_pending_irq(cfg->base);
ret = queue_put(&stream->mem_block_queue, stream->mem_block,
stream->cfg.block_size);
if (ret < 0) {
LOG_WRN("Couldn't copy data "
"from RX fifo to the ring "
"buffer (no space left) - "
"dropping a frame");
return;
}
k_sem_give(&stream->sem);
}
static void i2s_litex_isr_tx(void *arg)
{
struct device *const dev = (struct device *)arg;
const struct i2s_litex_cfg *cfg = DEV_CFG(dev);
size_t mem_block_size;
struct stream *stream = &DEV_DATA(dev)->tx;
int ret;
ret = queue_get(&stream->mem_block_queue, &stream->mem_block,
&mem_block_size);
if (ret < 0) {
i2s_irq_disable(cfg->base, I2S_EV_READY);
stream->state = I2S_STATE_READY;
return;
}
k_sem_give(&stream->sem);
i2s_copy_to_fifo((uint8_t *)stream->mem_block, mem_block_size,
stream->cfg.word_size, stream->cfg.channels);
i2s_clear_pending_irq(cfg->base);
k_mem_slab_free(stream->cfg.mem_slab, &stream->mem_block);
}
static const struct i2s_driver_api i2s_litex_driver_api = {
.configure = i2s_litex_configure,
.read = i2s_litex_read,
.write = i2s_litex_write,
.trigger = i2s_litex_trigger,
};
#define I2S_INIT(dir) \
\
static struct queue_item rx_ring_buf[CONFIG_I2S_LITEX_RX_BLOCK_COUNT]; \
static struct queue_item tx_ring_buf[CONFIG_I2S_LITEX_TX_BLOCK_COUNT]; \
\
static struct i2s_litex_data i2s_litex_data_##dir = { \
.dir.mem_block_queue.buf = dir##_ring_buf, \
.dir.mem_block_queue.len = \
sizeof(dir##_ring_buf) / sizeof(struct queue_item), \
}; \
\
static void i2s_litex_irq_config_func_##dir(struct device *dev); \
\
static struct i2s_litex_cfg i2s_litex_cfg_##dir = { \
.base = DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_##dir), control), \
.fifo_base = \
DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_##dir), fifo), \
.fifo_depth = DT_PROP(DT_NODELABEL(i2s_##dir), fifo_depth), \
.irq_config = i2s_litex_irq_config_func_##dir \
}; \
DEVICE_AND_API_INIT(i2s_##dir, DT_LABEL(DT_NODELABEL(i2s_##dir)), \
i2s_litex_initialize, &i2s_litex_data_##dir, \
&i2s_litex_cfg_##dir, POST_KERNEL, \
CONFIG_I2S_INIT_PRIORITY, \
&i2s_litex_driver_api); \
\
static void i2s_litex_irq_config_func_##dir(struct device *dev) \
{ \
IRQ_CONNECT(DT_IRQN(DT_NODELABEL(i2s_##dir)), \
DT_IRQ(DT_NODELABEL(i2s_##dir), \
priority), \
i2s_litex_isr_##dir, \
DEVICE_GET(i2s_##dir), 0); \
irq_enable(DT_IRQN(DT_NODELABEL(i2s_##dir))); \
}
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2s_rx), okay)
I2S_INIT(rx);
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2s_tx), okay)
I2S_INIT(tx);
#endif

110
drivers/i2s/i2s_litex.h Normal file
View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2020 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _I2S_LITEI2S__H
#define _I2S_LITEI2S__H
#include <device.h>
#include <drivers/i2s.h>
#include <devicetree.h>
/* i2s register offsets*/
#define I2S_EV_STATUS_REG_OFFSET 0x0
#define I2S_EV_PENDING_REG_OFFSET 0x4
#define I2S_EV_ENABLE_REG_OFFSET 0x8
#define I2S_CONTROL_REG_OFFSET 0xc
#define I2S_STATUS_REG_OFFSET 0x10
#define I2S_CONFIG_REG_OFFSET 0x20
/* i2s configuration mask*/
#define I2S_CONF_FORMAT_OFFSET 0
#define I2S_CONF_SAMPLE_WIDTH_OFFSET 2
#define I2S_CONF_LRCK_FREQ_OFFSET 8
#define I2S_CONF_FORMAT_MASK (0x3 << I2S_CONF_FORMAT_OFFSET)
#define I2S_CONF_SAMPLE_WIDTH_MASK (0x3f << I2S_CONF_SAMPLE_WIDTH_OFFSET)
#define I2S_CONF_LRCK_MASK (0xffffff << I2S_CONF_LRCK_FREQ_OFFSET)
/* i2s control register options*/
#define I2S_ENABLE (1 << 0)
#define I2S_FIFO_RESET (1 << 1)
/* i2s event*/
#define I2S_EV_ENABLE (1 << 0)
/* i2s event types*/
#define I2S_EV_READY (1 << 0)
#define I2S_EV_ERROR (1 << 1)
/* i2s rx*/
#define I2S_RX_BASE_ADDR DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_rx), control)
#define I2S_RX_EV_STATUS_REG (I2S_RX_BASE_ADDR + I2S_EV_STATUS_REG_OFFSET)
#define I2S_RX_EV_PENDING_REG (I2S_RX_BASE_ADDR + I2S_EV_PENDING_REG_OFFSET)
#define I2S_RX_EV_ENABLE_REG (I2S_RX_BASE_ADDR + I2S_EV_ENABLE_REG_OFFSET)
#define I2S_RX_CONTROL_REG (I2S_RX_BASE_ADDR + I2S_CONTROL_REG_OFFSET)
#define I2S_RX_STATUS_REG (I2S_RX_BASE_ADDR + I2S_STATUS_REG_OFFSET)
#define I2S_RX_CONFIG_REG (I2S_RX_BASE_ADDR + I2S_CONFIG_REG_OFFSET)
#define I2S_RX_STAT_CHANNEL_CONCATENATED_OFFSET 31
#define I2S_RX_STAT_CHANNEL_CONCATENATED_MASK \
(0x1 << I2S_RX_STAT_CHANNEL_CONCATENATED_OFFSET)
#define I2S_RX_FIFO_ADDR DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_rx), fifo)
#define I2S_RX_FIFO_DEPTH DT_PROP(DT_NODELABEL(i2s_rx), fifo_depth)
/* i2s tx*/
#define I2S_TX_BASE_ADDR DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_tx), control)
#define I2S_TX_EV_STATUS_REG (I2S_RX_BASE_ADDR + I2S_EV_STATUS_REG_OFFSET)
#define I2S_TX_EV_PENDING_REG (I2S_TX_BASE_ADDR + I2S_EV_PENDING_REG_OFFSET)
#define I2S_TX_EV_ENABLE_REG (I2S_TX_BASE_ADDR + I2S_EV_ENABLE_REG_OFFSET)
#define I2S_TX_CONTROL_REG (I2S_TX_BASE_ADDR + I2S_CONTROL_REG_OFFSET)
#define I2S_TX_STATUS_REG (I2S_TX_BASE_ADDR + I2S_STATUS_REG_OFFSET)
#define I2S_TX_CONFIG_REG (I2S_TX_BASE_ADDR + I2S_CONFIG_REG_OFFSET)
#define I2S_TX_STAT_CHANNEL_CONCATENATED_OFFSET 24
#define I2S_TX_STAT_CHANNEL_CONCATENATED_MASK \
(0x1 << I2S_TX_STAT_CHANNEL_CONCATENATED_OFFSET)
#define I2S_TX_FIFO_ADDR DT_REG_ADDR_BY_NAME(DT_NODELABEL(i2s_tx), fifo)
#define I2S_TX_FIFO_DEPTH DT_PROP(DT_NODELABEL(i2s_tx), fifo_depth)
enum litex_i2s_fmt {
LITEX_I2S_STANDARD = 1,
LITEX_I2S_LEFT_JUSTIFIED = 2,
};
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;
};
struct stream {
int32_t state;
struct k_sem sem;
struct i2s_config cfg;
struct ring_buf mem_block_queue;
void *mem_block;
};
/* Device run time data */
struct i2s_litex_data {
struct stream rx;
struct stream tx;
};
/* Device const configuration */
struct i2s_litex_cfg {
uint32_t base;
uint32_t fifo_base;
uint16_t fifo_depth;
void (*irq_config)(struct device *dev);
};
#endif /* _I2S_LITEI2S__H */

View file

@ -22,6 +22,8 @@
#define ETH0_IRQ DT_IRQN(DT_INST(0, litex_eth0))
#define I2S_RX_IRQ DT_IRQN(DT_NODELABEL(i2s_rx))
#define I2S_TX_IRQ DT_IRQN(DT_NODELABEL(i2s_tx))
static inline void vexriscv_litex_irq_setmask(uint32_t mask)
{
__asm__ volatile ("csrw %0, %1" :: "i"(IRQ_MASK), "r"(mask));
@ -83,6 +85,17 @@ static void vexriscv_litex_irq_handler(void *device)
ite->isr(ite->arg);
}
#endif
#ifdef CONFIG_I2S
if (irqs & (1 << I2S_RX_IRQ)) {
ite = &_sw_isr_table[I2S_RX_IRQ];
ite->isr(ite->arg);
}
if (irqs & (1 << I2S_TX_IRQ)) {
ite = &_sw_isr_table[I2S_TX_IRQ];
ite->isr(ite->arg);
}
#endif
}
void arch_irq_enable(unsigned int irq)

View file

@ -0,0 +1,18 @@
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
#
# SPDX-License-Identifier: Apache-2.0
#
description: LiteX I2S controller
compatible: "litex,i2s"
include: i2s-controller.yaml
properties:
reg:
required: true
fifo_depth:
type: int
required: true

View file

@ -137,5 +137,29 @@
status = "disabled";
#pwm-cells = <2>;
};
i2s_rx: i2s_rx@e000a800 {
compatible = "litex,i2s";
reg = <0xe000a800 0x20 0xb1000000 0x40000>;
interrupt-parent = <&intc0>;
interrupts = <6 2>;
#address-cells = <1>;
#size-cells = <0>;
reg-names = "control", "fifo";
fifo_depth = <256>;
label = "i2s_rx";
status = "disabled";
};
i2s_tx: i2s_tx@e000b000 {
compatible = "litex,i2s";
reg = <0xe000b000 0x20 0xb2000000 0x40000>;
interrupt-parent = <&intc0>;
interrupts = <7 2>;
#address-cells = <1>;
#size-cells = <0>;
reg-names = "control", "fifo";
fifo_depth = <256>;
label = "i2s_tx";
status = "disabled";
};
};
};