blob: 1b313f8ae68c3fa74aa3358817177f6627294b62 [file] [log] [blame] [edit]
/*
* Copyright (c) 2020 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <soc.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/__assert.h>
#include "i2s_litex.h"
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(i2s_litex);
#define MODULO_INC(val, max) \
{ \
val = (val == max - 1) ? 0 : val + 1; \
}
/**
* @brief Enable i2s device
*
* @param reg base register of device
*/
static void i2s_enable(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_OFFSET);
litex_write8(reg_data | I2S_ENABLE, reg + I2S_CONTROL_OFFSET);
}
/**
* @brief Disable i2s device
*
* @param reg base register of device
*/
static void i2s_disable(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_OFFSET);
litex_write8(reg_data & ~(I2S_ENABLE), reg + I2S_CONTROL_OFFSET);
}
/**
* @brief Reset i2s fifo
*
* @param reg base register of device
*/
static void i2s_reset_fifo(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_CONTROL_OFFSET);
litex_write8(reg_data | I2S_FIFO_RESET, reg + I2S_CONTROL_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_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_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_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
*/
static void i2s_irq_enable(uintptr_t reg, int irq_type)
{
__ASSERT_NO_MSG(irq_type == I2S_EV_READY || irq_type == I2S_EV_ERROR);
uint8_t reg_data = litex_read8(reg + I2S_EV_ENABLE_OFFSET);
litex_write8(reg_data | irq_type, reg + I2S_EV_ENABLE_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
*/
static void i2s_irq_disable(uintptr_t reg, int irq_type)
{
__ASSERT_NO_MSG(irq_type == I2S_EV_READY || irq_type == I2S_EV_ERROR);
uint8_t reg_data = litex_read8(reg + I2S_EV_ENABLE_OFFSET);
litex_write8(reg_data & ~(irq_type), reg + I2S_EV_ENABLE_OFFSET);
}
/**
* @brief Clear all pending irqs
*
* @param reg base register of device
*/
static void i2s_clear_pending_irq(uintptr_t reg)
{
uint8_t reg_data = litex_read8(reg + I2S_EV_PENDING_OFFSET);
litex_write8(reg_data, reg + I2S_EV_PENDING_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 single sample in bits
* @param channels number of received channels
*/
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 contiguous 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 single sample in bits
* @param channels number of received channels
*/
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(const struct device *dev)
{
const struct i2s_litex_cfg *cfg = dev->config;
struct i2s_litex_data *const dev_data = dev->data;
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(const struct device *dev, enum i2s_dir dir,
const struct i2s_config *i2s_cfg)
{
struct i2s_litex_data *const dev_data = dev->data;
const struct i2s_litex_cfg *const cfg = dev->config;
struct stream *stream;
int channels_concatenated = litex_read8(cfg->base + I2S_STATUS_OFFSET);
int dev_audio_freq = i2s_get_audio_freq(cfg->base);
int channel_div;
if (dir == I2S_DIR_RX) {
stream = &dev_data->rx;
channels_concatenated &= I2S_RX_STAT_CHANNEL_CONCATENATED_MASK;
} else if (dir == I2S_DIR_TX) {
stream = &dev_data->tx;
channels_concatenated &= I2S_TX_STAT_CHANNEL_CONCATENATED_MASK;
} else if (dir == I2S_DIR_BOTH) {
return -ENOSYS;
} 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 single 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);
/* The block_size field will be corrected to req_buf_s in the
* structure copied as stream configuration (see below).
*/
}
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->cfg.block_size = req_buf_s;
stream->state = I2S_STATE_READY;
return 0;
}
static int i2s_litex_read(const struct device *dev, void **mem_block,
size_t *size)
{
struct i2s_litex_data *const dev_data = dev->data;
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(const struct device *dev, void *mem_block,
size_t size)
{
struct i2s_litex_data *const dev_data = dev->data;
const struct i2s_litex_cfg *cfg = dev->config;
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(const struct device *dev, enum i2s_dir dir,
enum i2s_trigger_cmd cmd)
{
struct i2s_litex_data *const dev_data = dev->data;
const struct i2s_litex_cfg *const cfg = dev->config;
struct stream *stream;
if (dir == I2S_DIR_RX) {
stream = &dev_data->rx;
} else if (dir == I2S_DIR_TX) {
stream = &dev_data->tx;
} else if (dir == I2S_DIR_BOTH) {
return -ENOSYS;
} 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)
{
const struct device *dev = (const struct device *)arg;
const struct i2s_litex_cfg *cfg = dev->config;
struct i2s_litex_data *data = dev->data;
struct stream *stream = &data->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)
{
const struct device *dev = (const struct device *)arg;
const struct i2s_litex_cfg *cfg = dev->config;
struct i2s_litex_data *data = dev->data;
size_t mem_block_size;
struct stream *stream = &data->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(const struct device *dev); \
\
static struct i2s_litex_cfg i2s_litex_cfg_##dir = { \
.base = DT_REG_ADDR(DT_NODELABEL(i2s_##dir)), \
.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_DT_DEFINE(DT_NODELABEL(i2s_##dir), i2s_litex_initialize, \
NULL, &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(const struct device *dev) \
{ \
IRQ_CONNECT(DT_IRQN(DT_NODELABEL(i2s_##dir)), \
DT_IRQ(DT_NODELABEL(i2s_##dir), \
priority), \
i2s_litex_isr_##dir, \
DEVICE_DT_GET(DT_NODELABEL(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