|  | /* | 
|  | * Copyright (c) 2025 Renesas Electronics Corporation | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT renesas_rz_mhu_mbox | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <string.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/mbox.h> | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | #include "r_mhu_ns.h" | 
|  |  | 
|  | LOG_MODULE_REGISTER(mbox_renesas_rz_mhu, CONFIG_MBOX_LOG_LEVEL); | 
|  |  | 
|  | /* Global dummy value required for FSP driver implementation */ | 
|  | #define MHU_SHM_START_ADDR 0 | 
|  | const uint32_t *const __mhu_shmem_start = (uint32_t *)MHU_SHM_START_ADDR; | 
|  |  | 
|  | /* FSP interrupt handlers. */ | 
|  | void mhu_ns_int_isr(void); | 
|  |  | 
|  | static volatile uint32_t callback_msg; | 
|  | static void mhu_ns_callback(mhu_callback_args_t *p_args) | 
|  | { | 
|  | callback_msg = p_args->msg; | 
|  | } | 
|  |  | 
|  | struct mbox_rz_mhu_config { | 
|  | const mhu_api_t *fsp_api; | 
|  | uint16_t mhu_ch_size; | 
|  | /* Number of supported channels */ | 
|  | uint32_t num_channels; | 
|  | /* TX channels mask */ | 
|  | uint32_t tx_mask; | 
|  | /* RX channels mask */ | 
|  | uint32_t rx_mask; | 
|  | }; | 
|  |  | 
|  | struct mbox_rz_mhu_data { | 
|  | const struct device *dev; | 
|  | mhu_ns_instance_ctrl_t *fsp_ctrl; | 
|  | mhu_cfg_t *fsp_cfg; | 
|  | mbox_callback_t cb; | 
|  | void *user_data; | 
|  | uint32_t channel_id; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Return true if the channel of the MBOX device is an inbound channel. | 
|  | */ | 
|  | static inline bool is_rx_channel_valid(const struct device *dev, uint32_t ch) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  |  | 
|  | return ((ch < config->num_channels) && (config->rx_mask & BIT(ch))); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Return true if the channel of the MBOX device is an outbound channel. | 
|  | */ | 
|  | static inline bool is_tx_channel_valid(const struct device *dev, uint32_t ch) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  |  | 
|  | return ((ch < config->num_channels) && (config->tx_mask & BIT(ch))); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Interrupt handler | 
|  | */ | 
|  | static void mbox_rz_mhu_isr(const struct device *dev) | 
|  | { | 
|  | struct mbox_rz_mhu_data *data = dev->data; | 
|  | struct mbox_msg msg; | 
|  |  | 
|  | mhu_ns_int_isr(); | 
|  | if (data->cb && data->fsp_cfg->p_shared_memory) { | 
|  | uint32_t local_msg = callback_msg; | 
|  |  | 
|  | msg.data = &local_msg; | 
|  |  | 
|  | /* On the receiving end, the size of the message is always 4 bytes since the FSP MHU | 
|  | * driver requires the message to be of type uint32_t | 
|  | */ | 
|  | msg.size = sizeof(local_msg); | 
|  |  | 
|  | data->cb(dev, data->channel_id, data->user_data, &msg); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Try to send a message over the MBOX device. | 
|  | */ | 
|  | static int mbox_rz_mhu_send(const struct device *dev, mbox_channel_id_t channel_id, | 
|  | const struct mbox_msg *msg) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  | struct mbox_rz_mhu_data *data = dev->data; | 
|  | fsp_err_t fsp_err = FSP_SUCCESS; | 
|  |  | 
|  | /* FSP driver implementation requires the message to be of type uint32_t */ | 
|  | uint32_t message = 0; | 
|  |  | 
|  | if (!is_tx_channel_valid(dev, channel_id)) { | 
|  | if (!is_rx_channel_valid(dev, channel_id)) { | 
|  | /* Channel is neither RX nor TX */ | 
|  | LOG_ERR("Invalid MBOX channel number: %d", channel_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Channel is a RX channel, but this function only accepts TX */ | 
|  | LOG_ERR("Channel ID %d is a RX channel, but only TX channels are allowed", | 
|  | channel_id); | 
|  | return -ENOSYS; | 
|  | } | 
|  |  | 
|  | if (msg != NULL) { | 
|  | /* Maximum size allowed is 4 bytes */ | 
|  | if (msg->size > config->mhu_ch_size) { | 
|  | LOG_ERR("Size %d is not valid. Maximum size is 4 bytes", msg->size); | 
|  | return -EMSGSIZE; | 
|  | } | 
|  |  | 
|  | if (msg->data && msg->size) { | 
|  | /* Copy message */ | 
|  | memcpy(&message, msg->data, msg->size); | 
|  | } else { | 
|  | /* Clear Message */ | 
|  | message = 0; | 
|  | } | 
|  | } else { | 
|  | message = 0; | 
|  | } | 
|  |  | 
|  | if (data->fsp_cfg->p_shared_memory) { | 
|  |  | 
|  | #if CONFIG_MBOX_BUSY_WAIT_TIMEOUT_US > 0 | 
|  | /* The FSP MHU "msgSend" API continuously polls until the | 
|  | * previous message is consumed before sending a new one. To avoid | 
|  | * blocking indefinitely, we need to check if the remote clears the message | 
|  | * within the allowed time before sending a new one | 
|  | */ | 
|  | if (MHU_SEND_TYPE_MSG == data->fsp_ctrl->send_type) { | 
|  | if (data->fsp_ctrl->p_regs->MSG_INT_STSn != 0) { | 
|  | k_busy_wait(CONFIG_MBOX_BUSY_WAIT_TIMEOUT_US); | 
|  | if (data->fsp_ctrl->p_regs->MSG_INT_STSn != 0) { | 
|  | LOG_ERR("Remote is busy"); | 
|  | return -EBUSY; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | if (data->fsp_ctrl->p_regs->RSP_INT_STSn != 0) { | 
|  | k_busy_wait(CONFIG_MBOX_BUSY_WAIT_TIMEOUT_US); | 
|  | if (data->fsp_ctrl->p_regs->RSP_INT_STSn != 0) { | 
|  | LOG_ERR("Remote is busy"); | 
|  | return -EBUSY; | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Send message to shared memory, this will also invoke interrupt on the receiving | 
|  | * core | 
|  | */ | 
|  | fsp_err = config->fsp_api->msgSend(data->fsp_ctrl, message); | 
|  | } | 
|  |  | 
|  | if (fsp_err) { | 
|  | LOG_ERR("Message send failed"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Register a callback function on a channel for incoming messages. | 
|  | */ | 
|  | static int mbox_rz_mhu_reg_callback(const struct device *dev, mbox_channel_id_t channel_id, | 
|  | mbox_callback_t cb, void *user_data) | 
|  | { | 
|  | struct mbox_rz_mhu_data *data = dev->data; | 
|  |  | 
|  | if (!is_rx_channel_valid(dev, channel_id)) { | 
|  | if (!is_tx_channel_valid(dev, channel_id)) { | 
|  | /* Channel is neither RX nor TX */ | 
|  | LOG_ERR("Invalid MBOX channel number: %d", channel_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Channel is a TX channel, but this function only accepts RX */ | 
|  | LOG_ERR("Channel ID %d is a TX channel, but only RX channels are allowed", | 
|  | channel_id); | 
|  | return -ENOSYS; | 
|  | } | 
|  |  | 
|  | if (!cb) { | 
|  | LOG_ERR("Must provide callback"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | data->cb = cb; | 
|  | data->user_data = user_data; | 
|  | data->channel_id = channel_id; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Initialize the module. | 
|  | */ | 
|  | static int mbox_rz_mhu_init(const struct device *dev) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  | struct mbox_rz_mhu_data *data = dev->data; | 
|  | fsp_err_t fsp_err = FSP_SUCCESS; | 
|  |  | 
|  | fsp_err = config->fsp_api->open(data->fsp_ctrl, data->fsp_cfg); | 
|  |  | 
|  | if (fsp_err) { | 
|  | LOG_ERR("MBOX initialization failed"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Enable (disable) interrupts and callbacks for inbound channels. | 
|  | */ | 
|  | static int mbox_rz_mhu_set_enabled(const struct device *dev, mbox_channel_id_t channel_id, | 
|  | bool enabled) | 
|  | { | 
|  | if (!is_rx_channel_valid(dev, channel_id)) { | 
|  | if (!is_tx_channel_valid(dev, channel_id)) { | 
|  | /* Channel is neither RX nor TX */ | 
|  | LOG_ERR("Invalid MBOX channel number: %d", channel_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Channel is a TX channel, but this function only accepts RX */ | 
|  | LOG_ERR("Channel ID %d is a TX channel, but only RX channels are allowed", | 
|  | channel_id); | 
|  | return -ENOSYS; | 
|  | } | 
|  |  | 
|  | ARG_UNUSED(enabled); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Return the maximum number of bytes possible in an outbound message. | 
|  | */ | 
|  | static int mbox_rz_mhu_mtu_get(const struct device *dev) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  |  | 
|  | return config->mhu_ch_size; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Return the maximum number of channels. | 
|  | */ | 
|  | static uint32_t mbox_rz_mhu_max_channels_get(const struct device *dev) | 
|  | { | 
|  | const struct mbox_rz_mhu_config *config = dev->config; | 
|  |  | 
|  | return config->num_channels; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(mbox, mbox_rz_mhu_driver_api) = { | 
|  | .send = mbox_rz_mhu_send, | 
|  | .register_callback = mbox_rz_mhu_reg_callback, | 
|  | .mtu_get = mbox_rz_mhu_mtu_get, | 
|  | .max_channels_get = mbox_rz_mhu_max_channels_get, | 
|  | .set_enabled = mbox_rz_mhu_set_enabled, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * ************************* DRIVER REGISTER SECTION *************************** | 
|  | */ | 
|  |  | 
|  | #define MHU_RZG_IRQ_CONNECT(idx, irq_name, isr)                                                    \ | 
|  | do {                                                                                       \ | 
|  | IRQ_CONNECT(DT_INST_IRQ_BY_NAME(idx, irq_name, irq),                               \ | 
|  | DT_INST_IRQ_BY_NAME(idx, irq_name, priority), isr,                     \ | 
|  | DEVICE_DT_INST_GET(idx), 0);                                           \ | 
|  | irq_enable(DT_INST_IRQ_BY_NAME(idx, irq_name, irq));                               \ | 
|  | } while (0) | 
|  |  | 
|  | #define MHU_RZG_CONFIG_FUNC(idx) MHU_RZG_IRQ_CONNECT(idx, mhuns, mbox_rz_mhu_isr); | 
|  |  | 
|  | #define MHU_RZG_INIT(idx)                                                                          \ | 
|  | static mhu_ns_instance_ctrl_t g_mhu_ns##idx##_ctrl;                                        \ | 
|  | static mhu_cfg_t g_mhu_ns##idx##_cfg = {                                                   \ | 
|  | .channel = DT_INST_PROP(idx, channel),                                             \ | 
|  | .rx_ipl = DT_INST_IRQ_BY_NAME(idx, mhuns, priority),                               \ | 
|  | .rx_irq = DT_INST_IRQ_BY_NAME(idx, mhuns, irq),                                    \ | 
|  | .p_callback = mhu_ns_callback,                                                     \ | 
|  | .p_context = NULL,                                                                 \ | 
|  | .p_shared_memory = (void *)COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, shared_memory),  \ | 
|  | (DT_REG_ADDR(DT_INST_PHANDLE(idx, shared_memory))), (NULL)),                 \ | 
|  | };                                                                                         \ | 
|  | static const struct mbox_rz_mhu_config mbox_rz_mhu_config_##idx = {                        \ | 
|  | .fsp_api = &g_mhu_ns_on_mhu_ns,                                                    \ | 
|  | .mhu_ch_size = 4,                                                                  \ | 
|  | .num_channels = DT_INST_PROP(idx, channels_count),                                 \ | 
|  | .tx_mask = DT_INST_PROP(idx, tx_mask),                                             \ | 
|  | .rx_mask = DT_INST_PROP(idx, rx_mask),                                             \ | 
|  | };                                                                                         \ | 
|  | static struct mbox_rz_mhu_data mbox_rz_mhu_data_##idx = {                                  \ | 
|  | .dev = DEVICE_DT_INST_GET(idx),                                                    \ | 
|  | .fsp_ctrl = &g_mhu_ns##idx##_ctrl,                                                 \ | 
|  | .fsp_cfg = &g_mhu_ns##idx##_cfg,                                                   \ | 
|  | };                                                                                         \ | 
|  | static int mbox_rz_mhu_init_##idx(const struct device *dev)                                \ | 
|  | {                                                                                          \ | 
|  | MHU_RZG_CONFIG_FUNC(idx)                                                           \ | 
|  | return mbox_rz_mhu_init(dev);                                                      \ | 
|  | }                                                                                          \ | 
|  | DEVICE_DT_INST_DEFINE(idx, mbox_rz_mhu_init_##idx, NULL, &mbox_rz_mhu_data_##idx,          \ | 
|  | &mbox_rz_mhu_config_##idx, PRE_KERNEL_1,                             \ | 
|  | CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &mbox_rz_mhu_driver_api) | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(MHU_RZG_INIT); |