blob: d76e445b5029fb0e2aa60eafbe570c66b55420db [file] [log] [blame]
/*
* Copyright (c) 2025 Renesas Electronics Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_ra_ipc_mbox
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/mbox.h>
#include <zephyr/irq.h>
#include <soc.h>
#include <r_ipc.h>
#define IPC_MBOX_FIFO_SIZE 4
#define IPC_MAX_CHANNELS 8
LOG_MODULE_REGISTER(mbox_renesas_ra_ipc, CONFIG_MBOX_LOG_LEVEL);
extern void ipc_isr(void);
struct renesas_ra_ipc_data {
ipc_instance_ctrl_t ipc_ctrl;
ipc_cfg_t fsp_config;
mbox_callback_t user_cb[IPC_MAX_CHANNELS];
void *user_cb_data[IPC_MAX_CHANNELS];
uint32_t enabled_channel_mask;
uint32_t received_data;
};
struct renesas_ra_ipc_config {
const uint32_t channel_mask;
const uint8_t message_channel;
};
static void mbox_renesas_ra_callback(ipc_callback_args_t *fsp_args)
{
const struct device *dev = fsp_args->p_context;
const struct renesas_ra_ipc_config *config = dev->config;
struct renesas_ra_ipc_data *data = dev->data;
if (fsp_args->event == IPC_EVENT_MESSAGE_RECEIVED) {
if (!(data->enabled_channel_mask & BIT(config->message_channel))) {
return;
}
data->received_data = fsp_args->message;
struct mbox_msg msg = {
.data = (const void *)(&data->received_data),
.size = IPC_MBOX_FIFO_SIZE,
};
if (data->user_cb[config->message_channel] != NULL) {
data->user_cb[config->message_channel](
dev, config->message_channel,
data->user_cb_data[config->message_channel], &msg);
}
}
if (fsp_args->event >= IPC_EVENT_IRQ0 && fsp_args->event <= IPC_EVENT_IRQ7) {
/* FSP guarantees IPC_EVENT_IRQn are one-hot (1u<<n). */
__ASSERT((fsp_args->event & (fsp_args->event - 1U)) == 0U,
"IRQ event must be one-hot");
uint8_t channel_id = __builtin_ctz(fsp_args->event);
if (!(data->enabled_channel_mask & (uint32_t)fsp_args->event)) {
return;
}
if (data->user_cb[channel_id] != NULL) {
data->user_cb[channel_id](dev, channel_id, data->user_cb_data[channel_id],
NULL);
}
}
}
static int renesas_ra_ipc_send(const struct device *dev, mbox_channel_id_t channel_id,
const struct mbox_msg *msg)
{
const struct renesas_ra_ipc_config *config = dev->config;
struct renesas_ra_ipc_data *data = dev->data;
fsp_err_t fsp_err;
if (channel_id >= IPC_MAX_CHANNELS) {
return -EINVAL;
}
if (!(BIT(channel_id) & config->channel_mask)) {
LOG_ERR("Channel %d is not available", channel_id);
return -EINVAL;
}
/* Signalling mode. */
if (msg == NULL) {
fsp_err = R_IPC_EventGenerate((ipc_ctrl_t *)(&data->ipc_ctrl), BIT(channel_id));
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to send signal on channel %u", channel_id);
return -EIO;
}
return 0;
}
if (channel_id == config->message_channel) {
uint32_t msg_tmp = 0;
if (msg->size > IPC_MBOX_FIFO_SIZE) {
LOG_ERR("Invalid message size: %zu, expected <= %zu", msg->size,
IPC_MBOX_FIFO_SIZE);
return -EMSGSIZE;
}
if (msg->data == NULL && msg->size > 0) {
LOG_ERR("Message data is NULL");
return -EINVAL;
}
memcpy(&msg_tmp, msg->data, msg->size);
fsp_err = R_IPC_MessageSend((ipc_ctrl_t *const)(&data->ipc_ctrl), msg_tmp);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("Failed to send message on channel %u", channel_id);
if (fsp_err == FSP_ERR_OVERFLOW) {
return -EBUSY;
}
return -EIO;
}
} else {
LOG_ERR("Channel %u is valid but unsupported for message transfer", channel_id);
return -ENOTSUP;
}
return 0;
}
static int renesas_ra_ipc_reg_callback(const struct device *dev, mbox_channel_id_t channel_id,
mbox_callback_t cb, void *user_data)
{
const struct renesas_ra_ipc_config *config = dev->config;
struct renesas_ra_ipc_data *data = dev->data;
if (channel_id >= IPC_MAX_CHANNELS) {
return -EINVAL;
}
if (!(BIT(channel_id) & config->channel_mask)) {
LOG_ERR("Channel %d is not available", channel_id);
return -EINVAL;
}
uint32_t lock = irq_lock();
data->user_cb[channel_id] = cb;
data->user_cb_data[channel_id] = user_data;
irq_unlock(lock);
return 0;
}
static int renesas_ra_ipc_mtu_get(const struct device *dev)
{
ARG_UNUSED(dev);
return IPC_MBOX_FIFO_SIZE;
}
static uint32_t renesas_ra_ipc_max_channels_get(const struct device *dev)
{
const struct renesas_ra_ipc_config *config = dev->config;
return POPCOUNT(config->channel_mask);
}
static int renesas_ra_ipc_set_enabled(const struct device *dev, mbox_channel_id_t channel_id,
bool enabled)
{
const struct renesas_ra_ipc_config *config = dev->config;
struct renesas_ra_ipc_data *data = dev->data;
if (channel_id >= IPC_MAX_CHANNELS) {
return -EINVAL;
}
if (!(BIT(channel_id) & config->channel_mask)) {
LOG_ERR("Channel %d is not available", channel_id);
return -EINVAL;
}
if (enabled) {
data->enabled_channel_mask |= BIT(channel_id);
} else {
data->enabled_channel_mask &= ~BIT(channel_id);
}
return 0;
}
static int renesas_ra_ipc_init(const struct device *dev)
{
struct renesas_ra_ipc_data *data = dev->data;
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err = R_IPC_Open(&data->ipc_ctrl, &data->fsp_config);
if (fsp_err != FSP_SUCCESS) {
LOG_ERR("MBOX initialization failed");
return -EIO;
}
return 0;
}
static DEVICE_API(mbox, renesas_ra_ipc_driver_api) = {
.send = renesas_ra_ipc_send,
.register_callback = renesas_ra_ipc_reg_callback,
.mtu_get = renesas_ra_ipc_mtu_get,
.max_channels_get = renesas_ra_ipc_max_channels_get,
.set_enabled = renesas_ra_ipc_set_enabled,
};
#define IPC_RENESAS_RA_IRQ_INIT(idx) \
{ \
R_ICU->IELSR_b[DT_INST_IRQ(idx, irq)].IELS = \
BSP_PRV_IELS_ENUM(CONCAT(EVENT_IPC_IRQ, DT_INST_PROP(idx, unit))); \
IRQ_CONNECT(DT_INST_IRQ(idx, irq), DT_INST_IRQ(idx, priority), ipc_isr, NULL, 0); \
irq_enable(DT_INST_IRQ(idx, irq)); \
}
#define IPC_RENESAS_RA_INIT(idx) \
static struct renesas_ra_ipc_data ipc_renesas_ra_data_##idx = { \
.fsp_config = \
{ \
.channel = DT_INST_PROP(idx, unit), \
.irq = DT_INST_IRQ(idx, irq), \
.ipl = DT_INST_IRQ(idx, priority), \
.p_callback = mbox_renesas_ra_callback, \
.p_context = (void *)DEVICE_DT_INST_GET(idx), \
}, \
}; \
static const struct renesas_ra_ipc_config ipc_renesas_ra_config_##idx = { \
.channel_mask = DT_INST_PROP(idx, channel_mask), \
.message_channel = DT_INST_PROP(idx, message_channel_select), \
}; \
static int ipc_renesas_ra_init##idx(const struct device *dev) \
{ \
int err = renesas_ra_ipc_init(dev); \
if (err != 0) { \
return err; \
} \
IPC_RENESAS_RA_IRQ_INIT(idx); \
return 0; \
} \
DEVICE_DT_INST_DEFINE(idx, ipc_renesas_ra_init##idx, NULL, &ipc_renesas_ra_data_##idx, \
&ipc_renesas_ra_config_##idx, POST_KERNEL, \
CONFIG_MBOX_INIT_PRIORITY, &renesas_ra_ipc_driver_api)
DT_INST_FOREACH_STATUS_OKAY(IPC_RENESAS_RA_INIT);