mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-06 06:41:55 +00:00
This driver implements a simple MBOX device which supports a single instance, two channels (one for each direction), and only signalling mode with no data transfer. Signalling to another core is achieved by taking and giving two hardware semaphores, similar to the STM32 HSEM IPM driver. Signed-off-by: Celina Sophie Kalus <hello@celinakalus.de>
260 lines
5.9 KiB
C
260 lines
5.9 KiB
C
/*
|
|
* Copyright (c) 2024 Celina Sophie Kalus <hello@celinakalus.de>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
|
|
#include <zephyr/drivers/mbox.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#include "stm32_hsem.h"
|
|
|
|
LOG_MODULE_REGISTER(mbox_stm32_hsem_ipc, CONFIG_MBOX_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT st_mbox_stm32_hsem
|
|
|
|
#define HSEM_CPU1 1
|
|
#define HSEM_CPU2 2
|
|
|
|
#if DT_NODE_EXISTS(DT_NODELABEL(cpu0))
|
|
#define HSEM_CPU_ID HSEM_CPU1
|
|
#elif DT_NODE_EXISTS(DT_NODELABEL(cpu1))
|
|
#define HSEM_CPU_ID HSEM_CPU2
|
|
#else
|
|
#error "Neither cpu0 nor cpu1 defined!"
|
|
#endif
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
#define MBOX_TX_HSEM_ID CFG_HW_IPM_CPU2_SEMID
|
|
#define MBOX_RX_HSEM_ID CFG_HW_IPM_CPU1_SEMID
|
|
#else /* HSEM_CPU2 */
|
|
#define MBOX_TX_HSEM_ID CFG_HW_IPM_CPU1_SEMID
|
|
#define MBOX_RX_HSEM_ID CFG_HW_IPM_CPU2_SEMID
|
|
#endif /* HSEM_CPU_ID */
|
|
|
|
#define MAX_CHANNELS 2
|
|
|
|
struct mbox_stm32_hsem_data {
|
|
const struct device *dev;
|
|
mbox_callback_t cb;
|
|
void *user_data;
|
|
};
|
|
|
|
static struct mbox_stm32_hsem_data stm32_hsem_mbox_data;
|
|
|
|
static struct mbox_stm32_hsem_conf {
|
|
struct stm32_pclken pclken;
|
|
} stm32_hsem_mbox_conf = {
|
|
.pclken = {
|
|
.bus = DT_INST_CLOCKS_CELL(0, bus),
|
|
.enr = DT_INST_CLOCKS_CELL(0, bits)
|
|
},
|
|
};
|
|
|
|
static inline void stm32_hsem_enable_rx_interrupt(void)
|
|
{
|
|
const uint32_t mask_hsem_id = BIT(MBOX_RX_HSEM_ID);
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
LL_HSEM_EnableIT_C1IER(HSEM, mask_hsem_id);
|
|
#else /* HSEM_CPU2 */
|
|
LL_HSEM_EnableIT_C2IER(HSEM, mask_hsem_id);
|
|
#endif /* HSEM_CPU_ID */
|
|
}
|
|
|
|
static inline void stm32_hsem_disable_rx_interrupt(void)
|
|
{
|
|
const uint32_t mask_hsem_id = BIT(MBOX_RX_HSEM_ID);
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
LL_HSEM_DisableIT_C1IER(HSEM, mask_hsem_id);
|
|
#else /* HSEM_CPU2 */
|
|
LL_HSEM_DisableIT_C2IER(HSEM, mask_hsem_id);
|
|
#endif /* HSEM_CPU_ID */
|
|
}
|
|
|
|
static inline void stm32_hsem_clear_rx_interrupt(void)
|
|
{
|
|
const uint32_t mask_hsem_id = BIT(MBOX_RX_HSEM_ID);
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
LL_HSEM_ClearFlag_C1ICR(HSEM, mask_hsem_id);
|
|
#else /* HSEM_CPU2 */
|
|
LL_HSEM_ClearFlag_C2ICR(HSEM, mask_hsem_id);
|
|
#endif /* HSEM_CPU_ID */
|
|
}
|
|
|
|
static inline uint32_t stm32_hsem_is_rx_interrupt_active(void)
|
|
{
|
|
const uint32_t mask_hsem_id = BIT(MBOX_RX_HSEM_ID);
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
return LL_HSEM_IsActiveFlag_C1ISR(HSEM, mask_hsem_id);
|
|
#else /* HSEM_CPU2 */
|
|
return LL_HSEM_IsActiveFlag_C2ISR(HSEM, mask_hsem_id);
|
|
#endif /* HSEM_CPU_ID */
|
|
}
|
|
|
|
static inline bool is_rx_channel_valid(const struct device *dev, uint32_t ch)
|
|
{
|
|
/* Only support one RX channel */
|
|
return (ch == MBOX_RX_HSEM_ID);
|
|
}
|
|
|
|
static inline bool is_tx_channel_valid(const struct device *dev, uint32_t ch)
|
|
{
|
|
/* Only support one TX channel */
|
|
return (ch == MBOX_TX_HSEM_ID);
|
|
}
|
|
|
|
static void mbox_dispatcher(const struct device *dev)
|
|
{
|
|
struct mbox_stm32_hsem_data *data = dev->data;
|
|
|
|
/* Check semaphore rx_semid interrupt status */
|
|
if (!stm32_hsem_is_rx_interrupt_active())
|
|
return;
|
|
|
|
if (data->cb != NULL) {
|
|
data->cb(dev, MBOX_RX_HSEM_ID, data->user_data, NULL);
|
|
}
|
|
|
|
/* Clear semaphore rx_semid interrupt status and masked status */
|
|
stm32_hsem_clear_rx_interrupt();
|
|
}
|
|
|
|
static int mbox_stm32_hsem_send(const struct device *dev, uint32_t channel,
|
|
const struct mbox_msg *msg)
|
|
{
|
|
if (msg) {
|
|
LOG_ERR("Sending data not supported.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!is_tx_channel_valid(dev, channel)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Locking and unlocking the hardware semaphore
|
|
* causes an interrupt on the receiving side.
|
|
*/
|
|
z_stm32_hsem_lock(MBOX_TX_HSEM_ID, HSEM_LOCK_DEFAULT_RETRY);
|
|
z_stm32_hsem_unlock(MBOX_TX_HSEM_ID);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mbox_stm32_hsem_register_callback(const struct device *dev, uint32_t channel,
|
|
mbox_callback_t cb, void *user_data)
|
|
{
|
|
struct mbox_stm32_hsem_data *data = dev->data;
|
|
|
|
if (!(is_rx_channel_valid(dev, channel))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->cb = cb;
|
|
data->user_data = user_data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mbox_stm32_hsem_mtu_get(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
/* We only support signalling */
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t mbox_stm32_hsem_max_channels_get(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
/* Only two channels supported, one RX and one TX */
|
|
return MAX_CHANNELS;
|
|
}
|
|
|
|
static int mbox_stm32_hsem_set_enabled(const struct device *dev, uint32_t channel, bool enable)
|
|
{
|
|
if (!is_rx_channel_valid(dev, channel)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enable) {
|
|
stm32_hsem_clear_rx_interrupt();
|
|
stm32_hsem_enable_rx_interrupt();
|
|
} else {
|
|
stm32_hsem_disable_rx_interrupt();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
static int mbox_stm32_clock_init(const struct device *dev)
|
|
{
|
|
const struct mbox_stm32_hsem_conf *cfg = dev->config;
|
|
const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
|
|
|
|
if (!device_is_ready(clk)) {
|
|
LOG_ERR("Clock control device not ready.");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (clock_control_on(clk, (clock_control_subsys_t *)&cfg->pclken) != 0) {
|
|
LOG_WRN("Failed to enable clock.");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* HSEM_CPU_ID */
|
|
|
|
static int mbox_stm32_hsem_init(const struct device *dev)
|
|
{
|
|
struct mbox_stm32_hsem_data *data = dev->data;
|
|
int ret = 0;
|
|
|
|
data->dev = dev;
|
|
|
|
#if HSEM_CPU_ID == HSEM_CPU1
|
|
ret = mbox_stm32_clock_init(dev);
|
|
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
#endif /* HSEM_CPU_ID */
|
|
|
|
/* Configure interrupt service routine */
|
|
IRQ_CONNECT(DT_INST_IRQN(0),
|
|
DT_INST_IRQ(0, priority),
|
|
mbox_dispatcher, DEVICE_DT_INST_GET(0), 0);
|
|
|
|
irq_enable(DT_INST_IRQN(0));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct mbox_driver_api mbox_stm32_hsem_driver_api = {
|
|
.send = mbox_stm32_hsem_send,
|
|
.register_callback = mbox_stm32_hsem_register_callback,
|
|
.mtu_get = mbox_stm32_hsem_mtu_get,
|
|
.max_channels_get = mbox_stm32_hsem_max_channels_get,
|
|
.set_enabled = mbox_stm32_hsem_set_enabled,
|
|
};
|
|
|
|
DEVICE_DT_INST_DEFINE(
|
|
0,
|
|
mbox_stm32_hsem_init,
|
|
NULL,
|
|
&stm32_hsem_mbox_data,
|
|
&stm32_hsem_mbox_conf,
|
|
POST_KERNEL,
|
|
CONFIG_MBOX_INIT_PRIORITY,
|
|
&mbox_stm32_hsem_driver_api);
|