blob: d06edda8c9fc93ac90ca29ee0eaf4550d4cc8d9b [file] [log] [blame]
/*
* Copyright (c) 2025 Renesas Electronics Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_ra_i2s_ssie
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/irq.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include "r_ssi.h"
#include "rp_ssi.h"
#ifdef CONFIG_I2S_RENESAS_RA_SSIE_DTC
#include "r_dtc.h"
#endif
#include <soc.h>
#define I2S_OPT_BIT_CLK_FRAME_CLK_MASK 0x6
#define VALID_DIVISOR_COUNT 13
#define SSI_CLOCK_DIV_INVALID (-1)
LOG_MODULE_REGISTER(renesas_ra_i2s_ssie, CONFIG_I2S_LOG_LEVEL);
struct renesas_ra_ssie_config {
void (*irq_config_func)(const struct device *dev);
const struct pinctrl_dev_config *pcfg;
const struct device *clock_dev;
const struct clock_control_ra_subsys_cfg clock_subsys;
const struct device *audio_clock_dev;
};
struct renesas_ra_ssie_stream {
struct i2s_config cfg;
void *mem_block;
size_t mem_block_len;
};
struct i2s_buf {
void *mem_block;
size_t mem_block_len;
};
struct renesas_ra_ssie_data {
ssi_instance_ctrl_t fsp_ctrl;
i2s_cfg_t fsp_cfg;
ssi_extended_cfg_t fsp_ext_cfg;
volatile enum i2s_state state;
enum i2s_dir active_dir;
struct k_msgq rx_queue;
struct k_msgq tx_queue;
struct renesas_ra_ssie_stream tx_stream;
struct renesas_ra_ssie_stream rx_stream;
struct i2s_buf tx_msgs[CONFIG_I2S_RENESAS_RA_SSIE_TX_BLOCK_COUNT];
struct i2s_buf rx_msgs[CONFIG_I2S_RENESAS_RA_SSIE_RX_BLOCK_COUNT];
bool tx_configured;
bool rx_configured;
bool stop_with_draining;
bool full_duplex;
bool trigger_drop;
#if defined(CONFIG_I2S_RENESAS_RA_SSIE_DTC)
struct st_transfer_instance rx_transfer;
struct st_transfer_cfg rx_transfer_cfg;
struct st_dtc_instance_ctrl rx_transfer_ctrl;
struct st_dtc_extended_cfg rx_transfer_cfg_extend;
struct st_transfer_info DTC_TRANSFER_INFO_ALIGNMENT rx_transfer_info;
struct st_transfer_instance tx_transfer;
struct st_transfer_cfg tx_transfer_cfg;
struct st_dtc_instance_ctrl tx_transfer_ctrl;
struct st_dtc_extended_cfg tx_transfer_cfg_extend;
struct st_transfer_info DTC_TRANSFER_INFO_ALIGNMENT tx_transfer_info;
#endif /* CONFIG_I2S_RENESAS_RA_SSIE_DTC */
};
/* FSP interruption handlers. */
void ssi_txi_isr(void);
void ssi_rxi_isr(void);
void ssi_int_isr(void);
__maybe_unused static void ssi_rt_isr(void *p_args)
{
const struct device *dev = (struct device *)p_args;
struct renesas_ra_ssie_data *dev_data = (struct renesas_ra_ssie_data *)dev->data;
R_SSI0_Type *p_ssi_reg = dev_data->fsp_ctrl.p_reg;
if (p_ssi_reg->SSIFSR_b.TDE && dev_data->active_dir == I2S_DIR_TX) {
ssi_txi_isr();
}
if (p_ssi_reg->SSIFSR_b.RDF && dev_data->active_dir == I2S_DIR_RX) {
ssi_rxi_isr();
}
}
static int audio_clock_enable(const struct renesas_ra_ssie_config *config,
ssi_extended_cfg_t *fsp_ext_cfg)
{
int ret;
uint32_t rate;
const struct device *audio_clk_dev = config->audio_clock_dev;
if (!device_is_ready(audio_clk_dev)) {
LOG_ERR("Invalid audio_clock device");
return -ENODEV;
}
ret = clock_control_on(audio_clk_dev, (clock_control_subsys_t)0);
if (ret < 0) {
LOG_ERR("Failed to enable Audio clock, error %d", ret);
return ret;
}
ret = clock_control_get_rate(config->audio_clock_dev, (clock_control_subsys_t)0, &rate);
if (ret < 0) {
LOG_ERR("Failed to get audio clock rate, error: (%d)", ret);
return ret;
}
return 0;
}
static ssi_clock_div_t get_ssi_clock_div_enum(uint32_t divisor)
{
switch (divisor) {
case 1:
return SSI_CLOCK_DIV_1;
case 2:
return SSI_CLOCK_DIV_2;
case 4:
return SSI_CLOCK_DIV_4;
case 6:
return SSI_CLOCK_DIV_6;
case 8:
return SSI_CLOCK_DIV_8;
case 12:
return SSI_CLOCK_DIV_12;
case 16:
return SSI_CLOCK_DIV_16;
case 24:
return SSI_CLOCK_DIV_24;
case 32:
return SSI_CLOCK_DIV_32;
case 48:
return SSI_CLOCK_DIV_48;
case 64:
return SSI_CLOCK_DIV_64;
case 96:
return SSI_CLOCK_DIV_96;
case 128:
return SSI_CLOCK_DIV_128;
default:
return SSI_CLOCK_DIV_INVALID;
}
}
static int renesas_ra_ssie_set_clock_divider(const struct device *dev,
const struct i2s_config *i2s_cfg,
ssi_extended_cfg_t *fsp_ext_cfg)
{
static const uint32_t valid_divisors[] = {1, 2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128};
const struct renesas_ra_ssie_config *config = dev->config;
int ret;
double err;
uint32_t rate;
uint32_t divider;
uint32_t target_bclk;
uint32_t actual_bclk = 0;
uint32_t selected_div = 0;
double error_percent = DBL_MAX;
ssi_clock_div_t bit_clock_div = SSI_CLOCK_DIV_INVALID;
ret = clock_control_get_rate(config->audio_clock_dev, (clock_control_subsys_t)0, &rate);
if (ret < 0) {
LOG_ERR("Failed to get audio clock rate, error: (%d)", ret);
return ret;
}
if (rate <= 0) {
return -EIO;
}
target_bclk = i2s_cfg->word_size * i2s_cfg->channels * i2s_cfg->frame_clk_freq;
for (size_t i = 0; i < VALID_DIVISOR_COUNT; ++i) {
divider = valid_divisors[i];
actual_bclk = rate / divider;
if (actual_bclk >= target_bclk) {
err = 100.0 * ((double)actual_bclk - target_bclk) / target_bclk;
if (selected_div == 0 || err < error_percent) {
selected_div = divider;
error_percent = err;
}
}
}
if (selected_div == 0 || error_percent > 10.0) {
return -EIO;
}
bit_clock_div = get_ssi_clock_div_enum(selected_div);
if (bit_clock_div == SSI_CLOCK_DIV_INVALID) {
return -EINVAL;
}
fsp_ext_cfg->bit_clock_div = bit_clock_div;
return 0;
}
static void free_buffer_when_stop(struct renesas_ra_ssie_stream *stream)
{
if (stream->mem_block != NULL) {
k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block);
stream->mem_block = NULL;
stream->mem_block_len = 0;
}
}
static void free_tx_buffer(struct renesas_ra_ssie_data *dev_data, const void *buffer)
{
k_mem_slab_free(dev_data->tx_stream.cfg.mem_slab, (void *)buffer);
LOG_DBG("Freed TX %p", buffer);
}
static void free_rx_buffer(struct renesas_ra_ssie_data *dev_data, void *buffer)
{
k_mem_slab_free(dev_data->rx_stream.cfg.mem_slab, buffer);
LOG_DBG("Freed RX %p", buffer);
}
static void drop_queue(const struct device *dev, enum i2s_dir dir)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
struct i2s_buf msg_item;
if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) {
while (k_msgq_get(&dev_data->tx_queue, &msg_item, K_NO_WAIT) == 0) {
free_tx_buffer(dev_data, msg_item.mem_block);
}
}
if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) {
while (k_msgq_get(&dev_data->rx_queue, &msg_item, K_NO_WAIT) == 0) {
free_rx_buffer(dev_data, msg_item.mem_block);
}
}
}
static int renesas_ra_ssie_rx_start_transfer(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream = &dev_data->rx_stream;
int ret;
fsp_err_t fsp_err;
ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block, K_NO_WAIT);
if (ret < 0) {
return -ENOMEM;
}
stream->mem_block_len = stream->cfg.block_size;
fsp_err = R_SSI_Read(&dev_data->fsp_ctrl, stream->mem_block, stream->mem_block_len);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to start read data");
dev_data->state = I2S_STATE_ERROR;
free_buffer_when_stop(&dev_data->rx_stream);
return -EIO;
}
return 0;
}
static int renesas_ra_ssie_tx_start_transfer(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream = &dev_data->tx_stream;
struct i2s_buf msg_item;
fsp_err_t fsp_err;
int ret;
ret = k_msgq_get(&dev_data->tx_queue, &msg_item, K_NO_WAIT);
if (ret < 0) {
dev_data->state = I2S_STATE_ERROR;
return -ENOMEM;
}
stream->mem_block = msg_item.mem_block;
stream->mem_block_len = msg_item.mem_block_len;
fsp_err = R_SSI_Write(&dev_data->fsp_ctrl, stream->mem_block, stream->mem_block_len);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to start write data");
dev_data->state = I2S_STATE_ERROR;
free_buffer_when_stop(stream);
return -EIO;
}
return 0;
}
static int renesas_ra_ssie_tx_rx_start_transfer(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream_tx = &dev_data->tx_stream;
struct renesas_ra_ssie_stream *stream_rx = &dev_data->rx_stream;
struct i2s_buf msg_item_tx;
fsp_err_t fsp_err;
int ret;
ret = k_msgq_get(&dev_data->tx_queue, &msg_item_tx, K_NO_WAIT);
if (ret < 0) {
dev_data->state = I2S_STATE_ERROR;
return -ENOMEM;
}
stream_tx->mem_block = msg_item_tx.mem_block;
stream_tx->mem_block_len = msg_item_tx.mem_block_len;
ret = k_mem_slab_alloc(stream_rx->cfg.mem_slab, &stream_rx->mem_block, K_NO_WAIT);
if (ret < 0) {
dev_data->state = I2S_STATE_ERROR;
return -ENOMEM;
}
stream_rx->mem_block_len = stream_rx->cfg.block_size;
fsp_err = R_SSI_WriteRead(&dev_data->fsp_ctrl, stream_tx->mem_block, stream_rx->mem_block,
stream_rx->mem_block_len);
if (fsp_err != FSP_SUCCESS) {
dev_data->state = I2S_STATE_ERROR;
free_buffer_when_stop(stream_tx);
free_buffer_when_stop(stream_rx);
LOG_ERR("Failed to start write and read data");
return -EIO;
}
return 0;
}
static void renesas_ra_ssie_idle_dir_both_handle(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream_rx = &dev_data->rx_stream;
struct renesas_ra_ssie_stream *stream_tx = &dev_data->tx_stream;
int ret;
if (dev_data->state == I2S_STATE_STOPPING) {
/* If there is no msg in tx queue, set deivice the state to ready. */
if (k_msgq_num_used_get(&dev_data->tx_queue) == 0) {
dev_data->state = I2S_STATE_READY;
goto dir_both_idle_end;
} else if (dev_data->stop_with_draining == false) {
dev_data->state = I2S_STATE_READY;
goto dir_both_idle_end;
}
}
k_mem_slab_free(stream_tx->cfg.mem_slab, stream_tx->mem_block);
stream_tx->mem_block = NULL;
stream_tx->mem_block_len = 0;
ret = renesas_ra_ssie_tx_rx_start_transfer(dev);
if (ret < 0) {
goto dir_both_idle_end;
}
return;
dir_both_idle_end:
free_buffer_when_stop(stream_tx);
free_buffer_when_stop(stream_rx);
}
static void renesas_ra_ssie_rx_callback(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream = &dev_data->rx_stream;
struct i2s_buf msg_item_rx;
fsp_err_t fsp_err;
int ret;
if (stream->mem_block == NULL) {
return;
}
if (dev_data->trigger_drop) {
free_buffer_when_stop(stream);
return;
}
msg_item_rx.mem_block = stream->mem_block;
msg_item_rx.mem_block_len = stream->mem_block_len;
ret = k_msgq_put(&dev_data->rx_queue, &msg_item_rx, K_NO_WAIT);
if (ret < 0) {
dev_data->state = I2S_STATE_ERROR;
goto rx_disable;
}
stream->mem_block = NULL;
stream->mem_block_len = 0;
if (dev_data->active_dir == I2S_DIR_RX) {
if (dev_data->state == I2S_STATE_STOPPING) {
goto rx_disable;
}
ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block, K_NO_WAIT);
if (ret < 0) {
dev_data->state = I2S_STATE_ERROR;
goto rx_disable;
}
stream->mem_block_len = stream->cfg.block_size;
fsp_err = R_SSI_Read(&dev_data->fsp_ctrl, stream->mem_block, stream->mem_block_len);
if (fsp_err != FSP_SUCCESS) {
dev_data->state = I2S_STATE_ERROR;
LOG_ERR("Failed to restart RX transfer");
goto rx_disable;
}
}
return;
rx_disable:
free_buffer_when_stop(stream);
R_SSI_Stop(&dev_data->fsp_ctrl);
}
static void renesas_ra_ssie_tx_callback(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
struct renesas_ra_ssie_stream *stream = &dev_data->tx_stream;
struct i2s_buf msg_item;
fsp_err_t fsp_err;
int ret;
if (dev_data->trigger_drop) {
goto tx_disable;
}
if (dev_data->active_dir == I2S_DIR_TX) {
if (dev_data->state == I2S_STATE_STOPPING) {
/* If there is no msg in tx queue, set decive state to ready*/
if (k_msgq_num_used_get(&dev_data->tx_queue) == 0) {
goto tx_disable;
} else if (dev_data->stop_with_draining == false) {
goto tx_disable;
}
}
ret = k_msgq_get(&dev_data->tx_queue, &msg_item, K_NO_WAIT);
if (ret < 0) {
goto tx_disable;
}
k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block);
stream->mem_block = msg_item.mem_block;
stream->mem_block_len = msg_item.mem_block_len;
fsp_err =
R_SSI_Write(&dev_data->fsp_ctrl, stream->mem_block, stream->mem_block_len);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to restart write data");
}
}
return;
tx_disable:
free_buffer_when_stop(stream);
}
static void renesas_ra_ssie_idle_callback(const struct device *dev)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
if (dev_data->trigger_drop) {
dev_data->state = I2S_STATE_READY;
return;
}
switch (dev_data->active_dir) {
case I2S_DIR_BOTH:
renesas_ra_ssie_idle_dir_both_handle(dev);
break;
case I2S_DIR_TX:
if (dev_data->state == I2S_STATE_STOPPING) {
dev_data->state = I2S_STATE_READY;
free_buffer_when_stop(&dev_data->tx_stream);
}
if (dev_data->state == I2S_STATE_RUNNING) {
renesas_ra_ssie_tx_start_transfer(dev);
}
break;
case I2S_DIR_RX:
if (dev_data->state == I2S_STATE_STOPPING) {
dev_data->state = I2S_STATE_READY;
}
break;
default:
LOG_ERR("Invalid direction: %d", dev_data->active_dir);
return;
}
}
static void renesas_ra_ssie_callback(i2s_callback_args_t *p_args)
{
const struct device *dev = p_args->p_context;
switch (p_args->event) {
case I2S_EVENT_IDLE:
renesas_ra_ssie_idle_callback(dev);
break;
case I2S_EVENT_TX_EMPTY:
renesas_ra_ssie_tx_callback(dev);
break;
case I2S_EVENT_RX_FULL:
renesas_ra_ssie_rx_callback(dev);
break;
default:
LOG_ERR("Invalid triger envent: %d", p_args->event);
}
}
static int renesas_ra_ssie_start_transfer(const struct device *dev)
{
struct renesas_ra_ssie_data *const dev_data = dev->data;
int ret;
/* Start transfer following the current state */
switch (dev_data->active_dir) {
case I2S_DIR_BOTH:
dev_data->state = I2S_STATE_RUNNING;
ret = renesas_ra_ssie_tx_rx_start_transfer(dev);
break;
case I2S_DIR_TX:
dev_data->state = I2S_STATE_RUNNING;
ret = renesas_ra_ssie_tx_start_transfer(dev);
break;
case I2S_DIR_RX:
dev_data->state = I2S_STATE_RUNNING;
ret = renesas_ra_ssie_rx_start_transfer(dev);
break;
default:
LOG_ERR("Invalid direction: %d", dev_data->active_dir);
return -EIO;
}
if (ret < 0) {
LOG_ERR("START - Starting transfer failed");
return ret;
}
return 0;
}
static int i2s_renesas_ra_ssie_configure(const struct device *dev, enum i2s_dir dir,
const struct i2s_config *i2s_cfg)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
i2s_cfg_t *fsp_cfg = &dev_data->fsp_cfg;
fsp_err_t fsp_err;
int ret;
uint32_t frame_size_bytes;
ssi_extended_cfg_t new_fsp_extend_cfg;
i2s_cfg_t new_fsp_cfg;
/* If the node do not only full duplex, return ERROR when configure I2S_DIR_BOTH */
if (dev_data->full_duplex == false) {
if (dir == I2S_DIR_BOTH) {
LOG_ERR("Cannot configure I2S_DIR_BOTH direction for half-duplex device");
return -ENOSYS;
}
}
/* If the state is not in ready or not ready state, return ERROR */
if (dev_data->state != I2S_STATE_READY && dev_data->state != I2S_STATE_NOT_READY) {
LOG_ERR("Cannot configure in state: %d", dev_data->state);
return -EINVAL;
}
memcpy(&new_fsp_extend_cfg, &dev_data->fsp_ext_cfg, sizeof(ssi_extended_cfg_t));
memcpy(&new_fsp_cfg, &dev_data->fsp_cfg, sizeof(i2s_cfg_t));
/* If frame_clk_freq = 0, set the state to not ready
* Then drop all data in tx and rx queue
*/
if (i2s_cfg->frame_clk_freq == 0) {
drop_queue(dev, dir);
if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) {
dev_data->tx_configured = false;
memset(&dev_data->tx_stream, 0, sizeof(struct renesas_ra_ssie_stream));
}
if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) {
dev_data->rx_configured = false;
memset(&dev_data->rx_stream, 0, sizeof(struct renesas_ra_ssie_stream));
}
dev_data->state = I2S_STATE_NOT_READY;
return 0;
}
if (i2s_cfg->mem_slab == NULL) {
LOG_ERR("No memory block to store data");
return -EINVAL;
}
if (i2s_cfg->block_size == 0) {
LOG_ERR("Block size must be greater than 0");
return -EINVAL;
}
if (i2s_cfg->channels != 2) {
LOG_ERR("Unsupported number of channels: %u", i2s_cfg->channels);
return -EINVAL;
}
switch (i2s_cfg->word_size) {
case 8:
new_fsp_cfg.pcm_width = I2S_PCM_WIDTH_8_BITS;
new_fsp_cfg.word_length = I2S_WORD_LENGTH_8_BITS;
frame_size_bytes = 2;
break;
case 16:
new_fsp_cfg.pcm_width = I2S_PCM_WIDTH_16_BITS;
new_fsp_cfg.word_length = I2S_WORD_LENGTH_16_BITS;
frame_size_bytes = 4;
break;
case 24:
new_fsp_cfg.pcm_width = I2S_PCM_WIDTH_24_BITS;
new_fsp_cfg.word_length = I2S_WORD_LENGTH_24_BITS;
frame_size_bytes = 8;
break;
case 32:
new_fsp_cfg.pcm_width = I2S_PCM_WIDTH_32_BITS;
new_fsp_cfg.word_length = I2S_WORD_LENGTH_32_BITS;
frame_size_bytes = 8;
break;
default:
LOG_ERR("Unsupported word size: %u", i2s_cfg->word_size);
return -EINVAL;
}
if ((i2s_cfg->block_size % frame_size_bytes) != 0) {
LOG_ERR("Block size must be multiple of frame size");
return -EINVAL;
}
switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) {
case I2S_FMT_DATA_FORMAT_I2S:
break;
case I2S_FMT_DATA_FORMAT_PCM_SHORT:
__fallthrough;
case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
__fallthrough;
case I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED:
__fallthrough;
default:
LOG_ERR("Unsupported data format: 0x%02x", i2s_cfg->format);
return -EINVAL;
}
if (i2s_cfg->format & I2S_FMT_DATA_ORDER_LSB) {
LOG_ERR("Unsupported stream format: 0x%02x", i2s_cfg->format);
return -EINVAL;
}
if (i2s_cfg->format & I2S_FMT_BIT_CLK_INV) {
LOG_ERR("Unsupported stream format: 0x%02x", i2s_cfg->format);
return -EINVAL;
}
if (i2s_cfg->format & I2S_FMT_FRAME_CLK_INV) {
LOG_ERR("Unsupported stream format: 0x%02x", i2s_cfg->format);
return -EINVAL;
}
/* Module always generate bit clock although there is no data transferring */
switch (i2s_cfg->options & BIT(0)) {
case I2S_OPT_BIT_CLK_CONT:
break;
case I2S_OPT_BIT_CLK_GATED:
__fallthrough;
default:
LOG_ERR("Unsupported operation mode");
return -EINVAL;
}
/*
* For master mode, bit clock and frame clock is generated from internal
* For slave mode, bit clock and frame clock is get from external
*/
switch (i2s_cfg->options & (I2S_OPT_BIT_CLK_FRAME_CLK_MASK)) {
case I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER:
new_fsp_cfg.operating_mode = I2S_MODE_MASTER;
ret = renesas_ra_ssie_set_clock_divider(dev, i2s_cfg, &new_fsp_extend_cfg);
if (ret < 0) {
return ret;
}
break;
case I2S_OPT_BIT_CLK_SLAVE | I2S_OPT_FRAME_CLK_SLAVE:
new_fsp_cfg.operating_mode = I2S_MODE_SLAVE;
break;
default:
LOG_ERR("Unsupported operation mode");
return -EINVAL;
}
if ((i2s_cfg->options & I2S_OPT_LOOPBACK) || (i2s_cfg->options & I2S_OPT_PINGPONG)) {
LOG_ERR("Unsupported options: 0x%02x", i2s_cfg->options);
return -EINVAL;
}
#ifdef CONFIG_I2S_RENESAS_RA_SSIE_DTC
new_fsp_cfg.p_transfer_tx = NULL;
new_fsp_cfg.p_transfer_rx = NULL;
#endif
if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) {
memcpy(&dev_data->tx_stream.cfg, i2s_cfg, sizeof(struct i2s_config));
dev_data->tx_configured = true;
if (dev_data->full_duplex == false) {
dev_data->rx_configured = false;
}
}
if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) {
memcpy(&dev_data->rx_stream.cfg, i2s_cfg, sizeof(struct i2s_config));
dev_data->rx_configured = true;
if (dev_data->full_duplex == false) {
dev_data->tx_configured = false;
}
}
#ifdef CONFIG_I2S_RENESAS_RA_SSIE_DTC
if (dev_data->tx_configured == true) {
new_fsp_cfg.p_transfer_tx = &dev_data->tx_transfer;
}
if (dev_data->rx_configured == true) {
new_fsp_cfg.p_transfer_rx = &dev_data->rx_transfer;
}
#endif
fsp_err = R_SSI_Close(&dev_data->fsp_ctrl);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to configure the device");
return -EIO;
}
memcpy(&dev_data->fsp_ext_cfg, &new_fsp_extend_cfg, sizeof(ssi_extended_cfg_t));
memcpy(&dev_data->fsp_cfg, &new_fsp_cfg, sizeof(i2s_cfg_t));
fsp_cfg->p_extend = &dev_data->fsp_ext_cfg;
fsp_err = R_SSI_Open(&dev_data->fsp_ctrl, fsp_cfg);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to configure the device");
return -EIO;
}
dev_data->state = I2S_STATE_READY;
return 0;
}
static const struct i2s_config *i2s_renesas_ra_ssie_get_config(const struct device *dev,
enum i2s_dir dir)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
if (dir == I2S_DIR_TX && dev_data->tx_configured) {
return &dev_data->tx_stream.cfg;
}
if (dir == I2S_DIR_RX && dev_data->rx_configured) {
return &dev_data->rx_stream.cfg;
}
return NULL;
}
static int i2s_renesas_ra_ssie_write(const struct device *dev, void *mem_block, size_t size)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
struct i2s_buf msg_item = {.mem_block = mem_block, .mem_block_len = size};
int ret;
if (!dev_data->tx_configured) {
LOG_ERR("Device is not configured");
return -EIO;
}
if (dev_data->state != I2S_STATE_RUNNING && dev_data->state != I2S_STATE_READY) {
LOG_ERR("Cannot write in state: %d", dev_data->state);
return -EIO;
}
if (size > dev_data->tx_stream.cfg.block_size) {
LOG_ERR("This device can only write blocks up to %u bytes",
dev_data->tx_stream.cfg.block_size);
return -EIO;
}
ret = k_msgq_put(&dev_data->tx_queue, &msg_item, K_MSEC(dev_data->tx_stream.cfg.timeout));
if (ret < 0) {
return ret;
}
return 0;
}
static int i2s_renesas_ra_ssie_read(const struct device *dev, void **mem_block, size_t *size)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
struct i2s_buf msg_item;
int ret;
if (!dev_data->rx_configured) {
LOG_ERR("Device is not configured");
return -EIO;
}
/* If read in not ready state, return ERROR */
if (dev_data->state == I2S_STATE_NOT_READY) {
LOG_ERR("RX invalid state: %d", (int)dev_data->state);
return -EIO;
}
ret = k_msgq_get(&dev_data->rx_queue, &msg_item,
(dev_data->state == I2S_STATE_ERROR)
? K_NO_WAIT
: K_MSEC(dev_data->rx_stream.cfg.timeout));
if (ret == -ENOMSG) {
return -EIO;
}
if (ret == 0) {
*mem_block = msg_item.mem_block;
*size = msg_item.mem_block_len;
}
return ret;
}
static int i2s_renesas_ra_ssie_trigger(const struct device *dev, enum i2s_dir dir,
enum i2s_trigger_cmd cmd)
{
struct renesas_ra_ssie_data *dev_data = dev->data;
bool configured = false;
if (dir == I2S_DIR_BOTH) {
if (dev_data->full_duplex == false) {
LOG_ERR("I2S_DIR_BOTH is not supported for half-duplex device");
return -ENOSYS;
}
configured = dev_data->tx_configured && dev_data->rx_configured;
} else if (dir == I2S_DIR_TX) {
configured = dev_data->tx_configured;
} else if (dir == I2S_DIR_RX) {
configured = dev_data->rx_configured;
}
if (!configured) {
LOG_ERR("Device is not configured");
return -EIO;
}
if (dev_data->state == I2S_STATE_RUNNING && dev_data->active_dir != dir) {
LOG_ERR("Inappropriate trigger (%d/%d), active stream(s): %d", cmd, dir,
dev_data->active_dir);
return -EINVAL;
}
switch (cmd) {
case I2S_TRIGGER_START:
if (dev_data->state != I2S_STATE_READY) {
return -EIO;
}
dev_data->active_dir = dir;
dev_data->stop_with_draining = false;
dev_data->trigger_drop = false;
return renesas_ra_ssie_start_transfer(dev);
case I2S_TRIGGER_STOP:
if (dev_data->state != I2S_STATE_RUNNING) {
return -EIO;
}
dev_data->stop_with_draining = false;
dev_data->trigger_drop = false;
dev_data->state = I2S_STATE_STOPPING;
return 0;
case I2S_TRIGGER_DRAIN:
if (dev_data->state != I2S_STATE_RUNNING) {
return -EIO;
}
dev_data->trigger_drop = false;
if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) {
if (k_msgq_num_used_get(&dev_data->tx_queue) > 0) {
dev_data->stop_with_draining = true;
}
dev_data->state = I2S_STATE_STOPPING;
} else if (dir == I2S_DIR_RX) {
dev_data->stop_with_draining = false;
dev_data->state = I2S_STATE_STOPPING;
}
return 0;
case I2S_TRIGGER_DROP:
if (dev_data->state == I2S_STATE_NOT_READY) {
return -EIO;
}
if (dev_data->state != I2S_STATE_READY) {
R_SSI_Stop(&dev_data->fsp_ctrl);
}
dev_data->trigger_drop = true;
drop_queue(dev, dir);
return 0;
case I2S_TRIGGER_PREPARE:
if (dev_data->state != I2S_STATE_ERROR) {
return -EIO;
}
drop_queue(dev, dir);
dev_data->state = I2S_STATE_READY;
return 0;
default:
LOG_ERR("Invalid trigger: %d", cmd);
return -EINVAL;
}
}
static int i2s_renesas_ra_ssie_init(const struct device *dev)
{
const struct renesas_ra_ssie_config *config = dev->config;
struct renesas_ra_ssie_data *dev_data = dev->data;
fsp_err_t fsp_err;
int ret;
if (!device_is_ready(config->clock_dev)) {
return -ENODEV;
}
ret = clock_control_on(config->clock_dev, (clock_control_subsys_t)&config->clock_subsys);
if (ret < 0) {
LOG_ERR("Failed to start ssie bus clock, err=%d", ret);
return ret;
}
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (ret < 0) {
LOG_ERR("%s: pinctrl config failed.", __func__);
return ret;
}
k_msgq_init(&dev_data->tx_queue, (char *)dev_data->tx_msgs, sizeof(struct i2s_buf),
CONFIG_I2S_RENESAS_RA_SSIE_TX_BLOCK_COUNT);
k_msgq_init(&dev_data->rx_queue, (char *)dev_data->rx_msgs, sizeof(struct i2s_buf),
CONFIG_I2S_RENESAS_RA_SSIE_RX_BLOCK_COUNT);
fsp_err = R_SSI_Open(&dev_data->fsp_ctrl, &dev_data->fsp_cfg);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to initialize the device");
return -EIO;
}
config->irq_config_func(dev);
ret = audio_clock_enable(config, &dev_data->fsp_ext_cfg);
if (ret < 0) {
return ret;
}
return 0;
}
static DEVICE_API(i2s, i2s_renesas_ra_drv_api) = {
.configure = i2s_renesas_ra_ssie_configure,
.config_get = i2s_renesas_ra_ssie_get_config,
.read = i2s_renesas_ra_ssie_read,
.write = i2s_renesas_ra_ssie_write,
.trigger = i2s_renesas_ra_ssie_trigger,
};
#define EVENT_SSI_INT(channel) BSP_PRV_IELS_ENUM(CONCAT(EVENT_SSI, channel, _INT))
#define EVENT_SSI_TXI(channel) BSP_PRV_IELS_ENUM(CONCAT(EVENT_SSI, channel, _TXI))
#define EVENT_SSI_RXI(channel) BSP_PRV_IELS_ENUM(CONCAT(EVENT_SSI, channel, _RXI))
#define SSIE_RA_HALF_DUPLEX_INIT(index) \
R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, ssi_rt, irq)] = \
EVENT_SSI_TXI(DT_INST_PROP(index, channel)); \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, ssi_rt, irq), \
DT_INST_IRQ_BY_NAME(index, ssi_rt, priority), ssi_rt_isr, \
DEVICE_DT_INST_GET(index), 0); \
irq_enable(DT_INST_IRQ_BY_NAME(index, ssi_rt, irq));
#define SSIE_RA_FULL_DUPLEX_INIT(index) \
R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, ssi_txi, irq)] = \
EVENT_SSI_TXI(DT_INST_PROP(index, channel)); \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, ssi_txi, irq), \
DT_INST_IRQ_BY_NAME(index, ssi_txi, priority), ssi_txi_isr, (NULL), 0); \
irq_enable(DT_INST_IRQ_BY_NAME(index, ssi_txi, irq)); \
\
R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, ssi_rxi, irq)] = \
EVENT_SSI_RXI(DT_INST_PROP(index, channel)); \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, ssi_rxi, irq), \
DT_INST_IRQ_BY_NAME(index, ssi_rxi, priority), ssi_rxi_isr, (NULL), 0); \
irq_enable(DT_INST_IRQ_BY_NAME(index, ssi_rxi, irq));
#define RA_SSIE_RX_IRQ_BY_NAME(index, prop) \
COND_CODE_1(DT_INST_PROP(index, full_duplex), (DT_INST_IRQ_BY_NAME(index, ssi_rxi, prop)), \
(DT_INST_IRQ_BY_NAME(index, ssi_rt, prop)))
#define RA_SSIE_TX_IRQ_BY_NAME(index, prop) \
COND_CODE_1(DT_INST_PROP(index, full_duplex), (DT_INST_IRQ_BY_NAME(index, ssi_txi, prop)), \
(DT_INST_IRQ_BY_NAME(index, ssi_rt, prop)))
#ifndef CONFIG_I2S_RENESAS_RA_SSIE_DTC
#define SSIE_DTC_INIT(index)
#else
#define SSIE_DTC_TX_SOURCE(index) \
COND_CODE_1(DT_INST_PROP(index, full_duplex), (DT_INST_IRQ_BY_NAME(index, ssi_txi, irq)), \
(DT_INST_IRQ_BY_NAME(index, ssi_rt, irq)))
#define SSIE_DTC_RX_SOURCE(index) \
COND_CODE_1(DT_INST_PROP(index, full_duplex), (DT_INST_IRQ_BY_NAME(index, ssi_rxi, irq)), \
(DT_INST_IRQ_BY_NAME(index, ssi_rt, irq)))
#define SSIE_DTC_INIT(index) \
.tx_transfer_info = \
{ \
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED, \
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, \
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, \
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, \
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, \
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, \
.transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, \
.p_dest = (void *)NULL, \
.p_src = (void const *)NULL, \
.num_blocks = 0, \
.length = 0, \
}, \
.tx_transfer_cfg_extend = {.activation_source = SSIE_DTC_TX_SOURCE(index)}, \
.tx_transfer_cfg = \
{ \
.p_info = &renesas_ra_ssie_data_##index.tx_transfer_info, \
.p_extend = &renesas_ra_ssie_data_##index.tx_transfer_cfg_extend, \
}, \
.tx_transfer = \
{ \
.p_ctrl = &renesas_ra_ssie_data_##index.tx_transfer_ctrl, \
.p_cfg = &renesas_ra_ssie_data_##index.tx_transfer_cfg, \
.p_api = &g_transfer_on_dtc, \
}, \
.rx_transfer_info = \
{ \
.transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, \
.transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_DESTINATION, \
.transfer_settings_word_b.irq = TRANSFER_IRQ_END, \
.transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, \
.transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_FIXED, \
.transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, \
.transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, \
.p_dest = (void *)NULL, \
.p_src = (void const *)NULL, \
.num_blocks = 0, \
.length = 0, \
}, \
.rx_transfer_cfg_extend = \
{ \
.activation_source = SSIE_DTC_RX_SOURCE(index), \
}, \
.rx_transfer_cfg = \
{ \
.p_info = &renesas_ra_ssie_data_##index.rx_transfer_info, \
.p_extend = &renesas_ra_ssie_data_##index.rx_transfer_cfg_extend, \
}, \
.rx_transfer = { \
.p_ctrl = &renesas_ra_ssie_data_##index.rx_transfer_ctrl, \
.p_cfg = &renesas_ra_ssie_data_##index.rx_transfer_cfg, \
.p_api = &g_transfer_on_dtc, \
},
#endif
#define RENESAS_RA_SSIE_CLOCK_SOURCE(index) \
COND_CODE_1( \
DT_NODE_HAS_COMPAT(DT_INST_CLOCKS_CTLR_BY_NAME(index, audio_clock), pwm_clock), \
(SSI_AUDIO_CLOCK_INTERNAL), (SSI_AUDIO_CLOCK_EXTERNAL))
#define SSIE_RA_IRQ_INIT(index) \
COND_CODE_1(DT_INST_PROP(index, full_duplex), (SSIE_RA_FULL_DUPLEX_INIT(index)), \
(SSIE_RA_HALF_DUPLEX_INIT(index)))
#define SSIE_RA_INIT(index) \
static void renesas_ra_i2s_ssie_irq_config_func##index(const struct device *dev) \
{ \
SSIE_RA_IRQ_INIT(index) \
\
/* ssi_if */ \
R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, ssi_if, irq)] = \
EVENT_SSI_INT(DT_INST_PROP(index, channel)); \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, ssi_if, irq), \
DT_INST_IRQ_BY_NAME(index, ssi_if, priority), ssi_int_isr, (NULL), 0); \
irq_enable(DT_INST_IRQ_BY_NAME(index, ssi_if, irq)); \
} \
PINCTRL_DT_INST_DEFINE(index); \
\
static struct renesas_ra_ssie_config renesas_ra_ssie_config_##index = { \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \
.irq_config_func = renesas_ra_i2s_ssie_irq_config_func##index, \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(index)), \
.clock_subsys = \
{ \
.mstp = (uint32_t)DT_INST_CLOCKS_CELL_BY_NAME(index, pclk, mstp), \
.stop_bit = DT_INST_CLOCKS_CELL_BY_NAME(index, pclk, stop_bit), \
}, \
.audio_clock_dev = \
DEVICE_DT_GET_OR_NULL(DT_INST_CLOCKS_CTLR_BY_NAME(index, audio_clock)), \
}; \
static const ssi_extended_cfg_t ssi_extended_cfg_t_##index = { \
.audio_clock = RENESAS_RA_SSIE_CLOCK_SOURCE(index), \
.bit_clock_div = SSI_CLOCK_DIV_1, \
}; \
static struct renesas_ra_ssie_data renesas_ra_ssie_data_##index = { \
.state = I2S_STATE_NOT_READY, \
.stop_with_draining = false, \
.trigger_drop = false, \
.full_duplex = DT_INST_PROP(index, full_duplex) ? true : false, \
.fsp_ext_cfg = ssi_extended_cfg_t_##index, \
.fsp_cfg = {.channel = DT_INST_PROP(index, channel), \
.operating_mode = I2S_MODE_MASTER, \
.pcm_width = I2S_PCM_WIDTH_16_BITS, \
.word_length = I2S_WORD_LENGTH_16_BITS, \
.ws_continue = I2S_WS_CONTINUE_OFF, \
.p_callback = renesas_ra_ssie_callback, \
.p_context = DEVICE_DT_INST_GET(index), \
.p_extend = &ssi_extended_cfg_t_##index, \
.txi_irq = RA_SSIE_TX_IRQ_BY_NAME(index, irq), \
.rxi_irq = RA_SSIE_RX_IRQ_BY_NAME(index, irq), \
.int_irq = DT_INST_IRQ_BY_NAME(index, ssi_if, irq), \
.txi_ipl = RA_SSIE_TX_IRQ_BY_NAME(index, priority), \
.rxi_ipl = RA_SSIE_RX_IRQ_BY_NAME(index, priority), \
.idle_err_ipl = DT_INST_IRQ_BY_NAME(index, ssi_if, priority), \
.p_transfer_tx = NULL, \
.p_transfer_rx = NULL}, \
SSIE_DTC_INIT(index)}; \
\
DEVICE_DT_INST_DEFINE(index, &i2s_renesas_ra_ssie_init, NULL, \
&renesas_ra_ssie_data_##index, &renesas_ra_ssie_config_##index, \
POST_KERNEL, CONFIG_I2S_INIT_PRIORITY, &i2s_renesas_ra_drv_api);
DT_INST_FOREACH_STATUS_OKAY(SSIE_RA_INIT)