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:
parent
fd2370be88
commit
cc30fb871b
|
@ -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
|
||||
|
|
|
@ -64,3 +64,11 @@
|
|||
&gpio_in {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&i2s_rx {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&i2s_tx {
|
||||
status = "okay";
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
31
drivers/i2s/Kconfig.litex
Normal 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
646
drivers/i2s/i2s_litex.c
Normal 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
110
drivers/i2s/i2s_litex.h
Normal 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 */
|
|
@ -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)
|
||||
|
|
18
dts/bindings/i2s/litex,i2s.yaml
Normal file
18
dts/bindings/i2s/litex,i2s.yaml
Normal 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
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue