diff --git a/drivers/mbox/CMakeLists.txt b/drivers/mbox/CMakeLists.txt index e339c971ec8..5e4f61b9d03 100644 --- a/drivers/mbox/CMakeLists.txt +++ b/drivers/mbox/CMakeLists.txt @@ -21,3 +21,4 @@ zephyr_library_sources_ifdef(CONFIG_MBOX_STM32_HSEM mbox_stm32_hsem.c) zephyr_library_sources_ifdef(CONFIG_MBOX_IVSHMEM mbox_ivshmem.c) zephyr_library_sources_ifdef(CONFIG_MBOX_TI_OMAP_MAILBOX mbox_ti_omap.c) zephyr_library_sources_ifdef(CONFIG_MBOX_RENESAS_RZ_MHU mbox_renesas_rz_mhu.c) +zephyr_library_sources_ifdef(CONFIG_MBOX_MHUV3 mbox_mhuv3.c) diff --git a/drivers/mbox/Kconfig b/drivers/mbox/Kconfig index df2d8f0794f..fe456b0b89e 100644 --- a/drivers/mbox/Kconfig +++ b/drivers/mbox/Kconfig @@ -25,6 +25,7 @@ source "drivers/mbox/Kconfig.esp32" source "drivers/mbox/Kconfig.ivshmem" source "drivers/mbox/Kconfig.ti_omap" source "drivers/mbox/Kconfig.renesas_rz" +source "drivers/mbox/Kconfig.mhuv3" config MBOX_INIT_PRIORITY diff --git a/drivers/mbox/Kconfig.mhuv3 b/drivers/mbox/Kconfig.mhuv3 new file mode 100644 index 00000000000..6e28dae1f2f --- /dev/null +++ b/drivers/mbox/Kconfig.mhuv3 @@ -0,0 +1,27 @@ +# +# SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its +# affiliates +# +# SPDX-License-Identifier: Apache-2.0 + +config MBOX_MHUV3 + bool "ARM MHUv3 mailbox driver" + depends on DT_HAS_ARM_MHUV3_ENABLED + default y + help + Driver for Arm MHUv3 (Message Handling Unit v3) + +if MBOX_MHUV3 + +config MBOX_MHUV3_NUM_DBCH + int "ARM MHUv3 mailbox doorbell number of channels" + default 0 + help + Maximum number of doorbell channels allowed in the Postbox/Mailbox. + This number has to be less than or equal to (NUM_DBCH + 1) in PBX_DBCH_CFG0/MBX_DBCH_CFG0 registers. + If the doorbell extension is supported then this number has to be bigger than zero. + MHUv3 allows up to 128 doorbell channels. + This configuration parameter is needed to avoid the dynamic memory allocation of doorbell channels + data structures. + +endif diff --git a/drivers/mbox/mbox_mhuv3.c b/drivers/mbox/mbox_mhuv3.c new file mode 100644 index 00000000000..b32a4129398 --- /dev/null +++ b/drivers/mbox/mbox_mhuv3.c @@ -0,0 +1,1563 @@ +/* + * SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its + * affiliates + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT arm_mhuv3 + +#include + +#include +#include + +#define LOG_LEVEL CONFIG_MBOX_LOG_LEVEL +#include +LOG_MODULE_REGISTER(mbox_mhuv3); + +/* ====== MHUv3 Registers ====== */ + +/* Maximum number of Doorbell channel windows */ +#define MHUV3_DBCW_MAX 128 +/* Number of DBCH combined interrupt status registers */ +#define MHUV3_DBCH_CMB_INT_ST_REG_CNT 4 + +/* Number of FFCH combined interrupt status registers */ +#define MHUV3_FFCH_CMB_INT_ST_REG_CNT 2 + +#define MHUV3_FLAG_BITS 32 + +#define MHUV3_MAJOR_VERSION 2 + +/* Postbox/Mailbox Block Identifier */ +#define BLK_ID_BLK_ID GENMASK(3, 0) +/* Postbox/Mailbox Feature Support 0 */ +#define FEAT_SPT0_DBE_SPT GENMASK(3, 0) +#define FEAT_SPT0_FE_SPT GENMASK(7, 4) +#define FEAT_SPT0_FCE_SPT GENMASK(11, 8) +/* Postbox/Mailbox Feature Support 1 */ +#define FEAT_SPT1_AUTO_OP_SPT GENMASK(3, 0) +/* Postbox/Mailbox Doorbell Channel Configuration 0 */ +#define DBCH_CFG0_NUM_DBCH GENMASK(7, 0) +/* Postbox/Mailbox FIFO Channel Configuration 0 */ +#define FFCH_CFG0_NUM_FFCH GENMASK(7, 0) +#define FFCH_CFG0_P8BA_SPT BIT(8) +#define FFCH_CFG0_P16BA_SPT BIT(9) +#define FFCH_CFG0_P32BA_SPT BIT(10) +#define FFCH_CFG0_P64BA_SPT BIT(11) +#define FFCH_CFG0_FFCH_DEPTH GENMASK(25, 16) +/* Postbox/Mailbox Fast Channel Configuration 0 */ +#define FCH_CFG0_NUM_FCH GENMASK(9, 0) +#define FCH_CFG0_FCGI_SPT BIT(10) /* MBX-only */ +#define FCH_CFG0_NUM_FCG GENMASK(15, 11) +#define FCH_CFG0_NUM_FCH_PER_FCG GENMASK(20, 16) +#define FCH_CFG0_FCH_WS GENMASK(28, 21) +/* Postbox/Mailbox Control */ +#define CTRL_OP_REQ BIT(0) +#define CTRL_CH_OP_MSK BIT(1) +/* Mailbox Fast Channel Control */ +#define FCH_CTRL_INT_EN BIT(2) +/* Postbox/Mailbox Implementer Identification Register */ +#define IIDR_IMPLEMENTER GENMASK(11, 0) +#define IIDR_REVISION GENMASK(15, 12) +#define IIDR_VARIANT GENMASK(19, 16) +#define IIDR_PRODUCT_ID GENMASK(31, 20) +/* Postbox/Mailbox Architecture Identification Register */ +#define AIDR_ARCH_MINOR_REV GENMASK(3, 0) +#define AIDR_ARCH_MAJOR_REV GENMASK(7, 4) +/* Postbox/Mailbox Doorbell/FIFO/Fast Channel Control */ +#define XBCW_CTRL_COMB_EN BIT(0) +/* Postbox Doorbell Interrupt Status/Clear/Enable */ +#define PDBCW_INT_TFR_ACK BIT(0) + +/* Padding bitfields/fields represents hole in the regs MMIO */ + +/* CTRL_Page */ +struct ctrl_page { + uint32_t blk_id; + uint8_t reserved0[12]; + uint32_t feat_spt0; + uint32_t feat_spt1; + uint8_t reserved1[8]; + uint32_t dbch_cfg0; + uint8_t reserved2[12]; + uint32_t ffch_cfg0; + uint8_t reserved3[12]; + uint32_t fch_cfg0; + uint8_t reserved4[188]; + uint32_t x_ctrl; + /*-- MBX-only registers --*/ + uint8_t reserved5[60]; + uint32_t fch_ctrl; + uint32_t fcg_int_en; + uint8_t reserved6[696]; + /*-- End of MBX-only ---- */ + uint32_t dbch_int_st[MHUV3_DBCH_CMB_INT_ST_REG_CNT]; + uint32_t ffch_int_st[MHUV3_FFCH_CMB_INT_ST_REG_CNT]; + /*-- MBX-only registers --*/ + uint8_t reserved7[88]; + uint32_t fcg_int_st; + uint8_t reserved8[12]; + uint32_t fcg_grp_int_st[32]; + uint8_t reserved9[2760]; + /*-- End of MBX-only ---- */ + uint32_t iidr; + uint32_t aidr; + uint32_t imp_def_id[12]; +} __packed; + +/* DBCW_Page */ +struct pdbcw_page { + uint32_t st; + uint8_t reserved0[8]; + uint32_t set; + uint32_t int_st; + uint32_t int_clr; + uint32_t int_en; + uint32_t ctrl; +} __packed; + +struct mdbcw_page { + uint32_t st; + uint32_t st_msk; + uint32_t clr; + uint8_t reserved0[4]; + uint32_t msk_st; + uint32_t msk_set; + uint32_t msk_clr; + uint32_t ctrl; +} __packed; + +struct dummy_page { + uint8_t reserved0[KB(4)]; +} __packed; + +struct mhuv3_pbx_frame_reg { + struct ctrl_page ctrl; + struct pdbcw_page dbcw[MHUV3_DBCW_MAX]; + struct dummy_page ffcw; + struct dummy_page fcw; + uint8_t reserved0[KB(4) * 11]; + struct dummy_page impdef; +} __packed; + +struct mhuv3_mbx_frame_reg { + struct ctrl_page ctrl; + struct mdbcw_page dbcw[MHUV3_DBCW_MAX]; + struct dummy_page ffcw; + struct dummy_page fcw; + uint8_t reserved0[KB(4) * 11]; + struct dummy_page impdef; +} __packed; + +/* ====== MHUv3 data structures ====== */ + +enum mbox_mhuv3_frame { + PBX_FRAME, + MBX_FRAME, +}; + +static char *mbox_mhuv3_str[] = { + "PBX", + "MBX" +}; + +enum mbox_mhuv3_extension_type { + DBE_EXT, + FCE_EXT, + FE_EXT, + NUM_EXT +}; + +static char *mbox_mhuv3_ext_str[] = { + "DBE", + "FCE", + "FE" +}; + +/** @brief MHUv3 channel information. */ +struct mbox_mhuv3_channel { + /** Channel window index associated to this mailbox channel. */ + uint32_t ch_idx; + /** + * Doorbell bit number within the @ch_idx window. + * Only relevant to Doorbell transport. + */ + uint32_t doorbell; + /** Transport protocol specific operations for this channel. */ + const struct mbox_mhuv3_protocol_ops *ops; + /** Callback function to execute on incoming message interrupts. */ + mbox_callback_t cb; + /** Pointer to some private data provided at registration time */ + void *user_data; +}; + +/** @brief MHUv3 operations */ +struct mbox_mhuv3_protocol_ops { + /** Receiver enable callback. */ + int (*rx_enable)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Receiver disable callback. */ + int (*rx_disable)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Read available Sender in-band LE data (if any). */ + int *(*read_data)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** + * Acknowledge data reception to the Sender. Any out-of-band data + * has to have been already retrieved before calling this. + */ + int (*rx_complete)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Sender enable callback. */ + int (*tx_enable)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Sender disable callback. */ + int (*tx_disable)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Report back to the Sender if the last transfer has completed. */ + int (*last_tx_done)(const struct device *dev, struct mbox_mhuv3_channel *chan); + /** Send data to the receiver. */ + int (*send_data)(const struct device *dev, struct mbox_mhuv3_channel *chan, + const struct mbox_msg *msg); +}; + +/** + * @brief MHUv3 extension descriptor + */ +struct mbox_mhuv3_extension { + /** Type of extension. */ + enum mbox_mhuv3_extension_type type; + /** Max number of channels found for this extension. */ + uint32_t num_chans; + /** + * First channel number assigned to this extension, picked from + * the set of all mailbox channels descriptors created. + */ + uint32_t base_ch_idx; + /** Extension specific helper to parse DT and lookup associated + * channel from the related 'mboxes' property. + */ + struct mbox_mhuv3_channel *(*get_channel)(const struct device *dev, + uint32_t channel, + uint32_t param); + /** Extension specific helper to setup the combined irq. */ + void (*combined_irq_setup)(const struct device *dev); + /** Extension specific helper to initialize channels. */ + int (*channels_init)(const struct device *dev, uint32_t *base_ch_idx); + /** + * Extension specific helper to lookup which channel + * triggered the combined irq. + */ + struct mbox_mhuv3_channel *(*chan_from_comb_irq_get)(const struct device *dev); + /** Extension specifier helper to get maximum data size. */ + int (*mtu_get)(void); + /** Array of per-channel pending doorbells. */ + uint32_t pending_db[MHUV3_DBCW_MAX]; + /** Protect access to pending_db */ + struct k_spinlock pending_lock; +}; + +/** + * @brief MHUv3 mailbox configuration data + */ +struct mbox_mhuv3_config { + /** A reference to the MHUv3 control page for this block. */ + struct ctrl_page *ctrl; + union { + /** Base address of the PBX register mapping region. */ + struct mhuv3_pbx_frame_reg *pbx; + /** Base address of the MBX register mapping region. */ + struct mhuv3_mbx_frame_reg *mbx; + }; + /** Interrupt configuration function pointer. */ + void (*cmb_irq_config)(const struct device *dev); +}; + +/** + * @brief MHUv3 mailbox controller data + */ +struct mbox_mhuv3_data { + /** Frame type: MBX_FRAME or PBX_FRAME. */ + enum mbox_mhuv3_frame frame; + /** Flag to indicate if the MHU supports AutoOp full mode. */ + bool auto_op_full; + /** MHUv3 controller architectural major version. */ + uint32_t major; + /** MHUv3 controller architectural minor version. */ + uint32_t minor; + /** MHUv3 controller IIDR implementer. */ + uint32_t implem; + /** MHUv3 controller IIDR revision. */ + uint32_t rev; + /** MHUv3 controller IIDR variant. */ + uint32_t var; + /** MHUv3 controller IIDR product_id. */ + uint32_t prod_id; + /** The total number of channels discovered across all extensions. */ + uint32_t num_chans; + /** Array holding descriptors for any found implemented extension. */ + struct mbox_mhuv3_extension ext[NUM_EXT]; + /** Array of channels */ + /** + * TODO: The total number of channels should be increased when the rest of the + * extensions get supported. + */ + struct mbox_mhuv3_channel channels[CONFIG_MBOX_MHUV3_NUM_DBCH][MHUV3_FLAG_BITS]; +}; + +typedef int (*mhuv3_extension_initializer)(const struct device *dev); + +/* =========================== Utility Functions =========================== */ + +/** + * @brief Reads a bitmask from a 32-bit memory-mapped register. + * + * Extracts and returns the value of bits specified by a bitmask + * from the register at the given memory address. + * + * @param addr Address of the 32-bit register. + * @param bitmask Bitmask specifying the bits to extract. + * + * @return Extracted bitmask value. + */ +static ALWAYS_INLINE uint32_t read_bitmask32(mem_addr_t addr, uint32_t bitmask) +{ + uint32_t reg = sys_read32(addr); + + return FIELD_GET(bitmask, reg); +} + +/** + * @brief Writes a bitmask to a 32-bit memory-mapped register. + * + * Updates bits specified by a bitmask in the register at the given + * memory address with the provided data value. + * + * @param data Value to write to the specified bits. + * @param addr Address of the 32-bit register. + * @param bitmask Bitmask specifying the bits to update. + */ +static ALWAYS_INLINE void write_bitmask32(uint32_t data, mem_addr_t addr, uint32_t bitmask) +{ + uint32_t reg = sys_read32(addr); + + reg &= ~bitmask; + reg |= FIELD_PREP(bitmask, data); + sys_write32(reg, addr); +} + +/* =================== Doorbell transport protocol operations =============== */ + +/** + * @brief Enables transfer acknowledgment events for a channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_tx_enable(const struct device *dev, struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + /* Enable Transfer Acknowledgment events */ + write_bitmask32(0x1, + (mem_addr_t)&cfg->pbx->dbcw[chan->ch_idx].int_en, + PDBCW_INT_TFR_ACK); + + return 0; +} + +/** + * @brief Disables transfer acknowledgment events for a channel. + * + * Disables Transfer Acknowledgment events and clears the corresponding + * interrupt and pending doorbell for the specified channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_tx_disable(const struct device *dev, struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + struct pdbcw_page *dbcw = cfg->pbx->dbcw; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + /* Disable Channel Transfer Acknowledgment events */ + write_bitmask32(0x0, (mem_addr_t)&dbcw[chan->ch_idx].int_en, PDBCW_INT_TFR_ACK); + + /* Clear Channel Transfer Acknowledgment and pending doorbell */ + write_bitmask32(0x1, (mem_addr_t)&dbcw[chan->ch_idx].int_clr, PDBCW_INT_TFR_ACK); + K_SPINLOCK(&ext->pending_lock) { + ext->pending_db[chan->ch_idx] = 0; + } + + return 0; +} + +/** + * @brief Enables transfer events for a channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_rx_enable(const struct device *dev, struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + /* Unmask Channel Transfer events */ + sys_set_bit((mem_addr_t)&cfg->mbx->dbcw[chan->ch_idx].msk_clr, chan->doorbell); + + return 0; +} + +/** + * @brief Disables transfer events for a channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_rx_disable(const struct device *dev, struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + /* Mask Channel Transfer events */ + sys_set_bit((mem_addr_t)&cfg->mbx->dbcw[chan->ch_idx].msk_set, chan->doorbell); + + return 0; +} + +/** + * @brief Completes a transfer event for a channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_rx_complete(const struct device *dev, + struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + /* Clearing the pending transfer generates the Channel Transfer Acknowledge */ + sys_set_bit((mem_addr_t)&cfg->mbx->dbcw[chan->ch_idx].clr, chan->doorbell); + + return 0; +} + +/** + * @brief Checks if the last transfer for a channel is complete. + * + * Determines whether the last transfer for the specified channel in the + * Doorbell Channel Window (DBCW) register is complete. If the transfer is + * complete, it also clears the pending doorbell for the channel. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * + * @return 0 if the last transfer is complete, or a negative error code otherwise. + */ +static int mbox_mhuv3_doorbell_last_tx_done(const struct device *dev, + struct mbox_mhuv3_channel *chan) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + bool done = !(sys_test_bit((mem_addr_t)&cfg->pbx->dbcw[chan->ch_idx].st, chan->doorbell)); + + if (done) { + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + + /* Take care to clear the pending doorbell also when polling */ + K_SPINLOCK(&ext->pending_lock) { + sys_clear_bit((mem_addr_t)&ext->pending_db[chan->ch_idx], chan->doorbell); + } + + return 0; + } + + return -EBUSY; +} + +/** + * @brief Sends data via the specified channel. + * + * Attempts to send data using the specified channel. If the channel is + * busy (i.e., a transfer is already in progress), it returns an error. + * It also sets the appropriate flags to indicate pending data. + * + * @param dev Pointer to the device structure. + * @param chan Pointer to the MHUv3 channel structure. + * @param msg Pointer to the message structure containing data to be sent. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_doorbell_send_data(const struct device *dev, + struct mbox_mhuv3_channel *chan, + const struct mbox_msg *msg) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + int ret = 0; + + if (chan->ch_idx >= MHUV3_DBCW_MAX) { + return -EINVAL; + } + + if (msg) { + LOG_WRN("Doorbell extension does not support sending data"); + } + + K_SPINLOCK(&ext->pending_lock) { + if (sys_test_bit((mem_addr_t)&ext->pending_db[chan->ch_idx], chan->doorbell)) { + ret = -EBUSY; + } else { + sys_set_bit((mem_addr_t)&ext->pending_db[chan->ch_idx], chan->doorbell); + } + } + + if (ret == 0) { + sys_set_bit((mem_addr_t)&cfg->pbx->dbcw[chan->ch_idx].set, chan->doorbell); + } + + return ret; +} + +static const struct mbox_mhuv3_protocol_ops mhuv3_doorbell_ops = { + .tx_enable = mbox_mhuv3_doorbell_tx_enable, + .tx_disable = mbox_mhuv3_doorbell_tx_disable, + .rx_enable = mbox_mhuv3_doorbell_rx_enable, + .rx_disable = mbox_mhuv3_doorbell_rx_disable, + .rx_complete = mbox_mhuv3_doorbell_rx_complete, + .last_tx_done = mbox_mhuv3_doorbell_last_tx_done, + .send_data = mbox_mhuv3_doorbell_send_data, +}; + +/** + * @brief Retrieves the MHUv3 channel based on the provided channel ID. + * + * This function extracts the channel and doorbell numbers from the + * given Flattened Channel ID, and retrieves the corresponding channel + * from the Doorbell extension. The Flattened Channel ID is calculated + * using the formula: Channel ID * MHUV3_FLAG_BITS + Doorbell. + * + * @param dev Pointer to the device structure. + * @param channel_id The Flattened Channel ID that contains both the + * channel and doorbell information. + * + * @return Pointer to the corresponding MHUv3 channel structure. + */ +static struct mbox_mhuv3_channel *mbox_mhuv3_get_channel(const struct device *dev, + mbox_channel_id_t channel_id) +{ + struct mbox_mhuv3_data *data = dev->data; + uint32_t channel, doorbell; + + /** + * TODO: The type of extension, channel and doorbell should be + * extracted from a struct passed to this function from device tree, + * but mbox_channel_id_t is only a Flattened Channel ID, + * where Flattened Channel ID = Channel ID * MHUV3_FLAG_BITS + Doorbell + * Until this is changed, Pin the type to Doorbell extension and use + * the next formula to get the actual channel and doorbell numbers. + */ + enum mbox_mhuv3_extension_type type = DBE_EXT; + + channel = channel_id / MHUV3_FLAG_BITS; + doorbell = channel_id % MHUV3_FLAG_BITS; + + return data->ext[type].get_channel(dev, channel, doorbell); +} + +/** + * @brief Sends data using the specified MHUv3 sender channel. + * + * This function checks if the last transmission is complete for the + * specified channel. If not, it returns an error. If the channel is + * available, it proceeds to send the data using the channel's send_data + * operation. + * + * @param dev Pointer to the device structure. + * @param channel_id The ID of the channel to use for sending data. + * @param msg Pointer to the message structure containing the data to be sent. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_sender_send_data(const struct device *dev, + mbox_channel_id_t channel_id, + const struct mbox_msg *msg) +{ + struct mbox_mhuv3_channel *chan = mbox_mhuv3_get_channel(dev, channel_id); + int ret; + + ret = chan->ops->last_tx_done(dev, chan); + if (ret) { + return ret; + } + + return chan->ops->send_data(dev, chan, msg); +} + +/** + * @brief Attempts to send data using the MHUv3 receiver channel (not supported). + * + * This function logs an error as transmitting data on an MHUv3 receiver channel + * is not supported and returns an I/O error. + * + * @param dev Pointer to the device structure. + * @param channel_id The ID of the channel (unused). + * @param msg Pointer to the message structure (unused). + * + * @return -ENOTSUP to indicate an I/O error as transmission is not supported. + */ +static int mbox_mhuv3_receiver_send_data(const struct device *dev, + mbox_channel_id_t channel_id, + const struct mbox_msg *msg) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel_id); + ARG_UNUSED(msg); + + LOG_ERR("Trying to transmit on a MBX MHUv3 frame"); + + return -ENOTSUP; +} + +/** + * @brief Sends data based on the frame type. + * + * This function determines whether to send data using the sender or receiver + * functions based on the current frame type. + * + * @param dev Pointer to the device structure. + * @param channel_id The ID of the channel to use for sending data. + * @param msg Pointer to the message structure containing the data to be sent. + * + * @return 0 on success, an error code on failure. + */ +static int mbox_mhuv3_send_data(const struct device *dev, + mbox_channel_id_t channel_id, + const struct mbox_msg *msg) +{ + struct mbox_mhuv3_data *data = dev->data; + + if (data->frame == PBX_FRAME) { + return mbox_mhuv3_sender_send_data(dev, channel_id, msg); + } + + return mbox_mhuv3_receiver_send_data(dev, channel_id, msg); +} + +/** + * @brief Retrieves the MHUv3 channel based on the specified channel and doorbell. + * + * This function returns the channel structure for the given channel and doorbell + * numbers. + * + * @param dev Pointer to the device structure. + * @param channel The channel number to retrieve. + * @param doorbell The doorbell number to retrieve. + * + * @return Pointer to the corresponding MHUv3 channel structure, or NULL if invalid. + */ +static struct mbox_mhuv3_channel *mbox_mhuv3_dbe_get_channel(const struct device *dev, + uint32_t channel, + uint32_t doorbell) +{ + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + + if (channel >= ext->num_chans || doorbell >= MHUV3_FLAG_BITS) { + LOG_ERR("Couldn't find a valid channel (%d: %d)", channel, doorbell); + return NULL; + } + + return &data->channels[ext->base_ch_idx + channel][doorbell]; +} + +/** + * @brief Configures the combined IRQ for the MHUv3 device. + * + * This function sets up the combined interrupt handling for the specified device, + * configuring the interrupt enable, clear, and control registers for each channel. + * It handles both PBX and non-PBX frame types differently. + * + * @param dev Pointer to the device structure. + */ +static void mbox_mhuv3_dbe_combined_irq_setup(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + uint32_t i; + + if (data->frame == PBX_FRAME) { + struct pdbcw_page *dbcw = cfg->pbx->dbcw; + + for (i = 0; i < ext->num_chans; i++) { + write_bitmask32(0x1, (mem_addr_t)&dbcw[i].int_clr, PDBCW_INT_TFR_ACK); + write_bitmask32(0x0, (mem_addr_t)&dbcw[i].int_en, PDBCW_INT_TFR_ACK); + write_bitmask32(0x1, (mem_addr_t)&dbcw[i].ctrl, XBCW_CTRL_COMB_EN); + } + } else { + struct mdbcw_page *dbcw = cfg->mbx->dbcw; + + for (i = 0; i < ext->num_chans; i++) { + sys_write32(0xFFFFFFFFU, (mem_addr_t)&dbcw[i].clr); + sys_write32(0xFFFFFFFFU, (mem_addr_t)&dbcw[i].msk_set); + write_bitmask32(0x1, (mem_addr_t)&dbcw[i].ctrl, XBCW_CTRL_COMB_EN); + } + } +} + +/** + * @brief Initializes the MHUv3 DBE channels. + * + * This function initializes the MHUv3 DBE channels by assigning the base index, + * configuring each channel with its corresponding index and doorbell, and + * setting the appropriate channel operations. It updates the base channel index + * after initialization. + * + * @param dev Pointer to the device structure. + * @param base_ch_idx Pointer to the base channel index. + * + * @return 0 on success. + */ +static int mbox_mhuv3_dbe_channels_init(const struct device *dev, uint32_t *base_ch_idx) +{ + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + struct mbox_mhuv3_channel *chan; + + __ASSERT(((*base_ch_idx + ext->num_chans) * MHUV3_FLAG_BITS) < data->num_chans, + "The number of allocated channels is less than required by the MHUv3 extension"); + + ext->base_ch_idx = *base_ch_idx; + chan = &data->channels[*base_ch_idx]; + + for (uint32_t i = 0; i < ext->num_chans; i++) { + for (uint32_t k = 0; k < MHUV3_FLAG_BITS; k++) { + chan->ch_idx = i; + chan->doorbell = k; + chan->ops = &mhuv3_doorbell_ops; + chan++; + } + } + + *base_ch_idx += ext->num_chans; + + return 0; +} + +/** + * @brief Searches for the active doorbell for the given MHUv3 DBE channel. + * + * This function checks the interrupt status and identifies which doorbell has been + * triggered for the specified channel. It returns the doorbell index if found or + * logs a warning if an unexpected interrupt occurs. + * + * @param dev Pointer to the device structure. + * @param channel The channel to check for the doorbell. + * @param doorbell Pointer to store the found doorbell index. + * + * @return true if a doorbell is found, false if an unexpected IRQ is encountered. + */ +static bool mbox_mhuv3_dbe_doorbell_search(const struct device *dev, + uint32_t channel, + uint32_t *doorbell) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + uint32_t st; + + __ASSERT(channel < MHUV3_DBCW_MAX, + "Number of channels exceeds the maximum number of doorbell channel windows."); + + if (data->frame == PBX_FRAME) { + uint32_t active_doorbells, fired_doorbells; + + st = read_bitmask32((mem_addr_t)&cfg->pbx->dbcw[channel].int_st, PDBCW_INT_TFR_ACK); + if (!st) { + goto unexpected_irq; + } + active_doorbells = sys_read32((mem_addr_t)&cfg->pbx->dbcw[channel].st); + K_SPINLOCK(&ext->pending_lock) { + fired_doorbells = ext->pending_db[channel] & ~active_doorbells; + if (!fired_doorbells) { + K_SPINLOCK_BREAK; + } + + *doorbell = find_lsb_set(fired_doorbells) - 1; + ext->pending_db[channel] &= ~BIT(*doorbell); + } + if (!fired_doorbells) { + goto unexpected_irq; + } + /* If no other pending doorbells, clear the transfer acknowledge */ + if (!(fired_doorbells & ~BIT(*doorbell))) { + write_bitmask32(0x1, (mem_addr_t)&cfg->pbx->dbcw[channel].int_clr, + PDBCW_INT_TFR_ACK); + } + } else { + st = sys_read32((mem_addr_t)&cfg->mbx->dbcw[channel].st_msk); + if (!st) { + goto unexpected_irq; + } + + *doorbell = find_lsb_set(st) - 1; + } + + return true; + +unexpected_irq: + LOG_WRN("Unexpected IRQ on %s channel:%d", mbox_mhuv3_str[data->frame], channel); + + return false; +} + +/** + * @brief Retrieves the channel corresponding to a combined interrupt. + * + * This function checks the combined interrupt status for the MHUv3 DBE device and + * identifies the channel and doorbell associated with the interrupt. It returns the + * matching channel if found, or NULL if no valid channel is identified. + * + * @param dev Pointer to the device structure. + * + * @return Pointer to the mbox_mhuv3_channel if a valid channel is found, NULL otherwise. + */ +static struct mbox_mhuv3_channel *mbox_mhuv3_dbe_chan_from_comb_irq_get(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + + for (uint32_t i = 0; i < MHUV3_DBCH_CMB_INT_ST_REG_CNT; i++) { + uint32_t channel, doorbell, int_st; + + int_st = sys_read32((mem_addr_t)&cfg->ctrl->dbch_int_st[i]); + if (!int_st) { + continue; + } + + channel = i * MHUV3_FLAG_BITS + (find_lsb_set(int_st) - 1); + if (channel >= ext->num_chans) { + LOG_ERR("Invalid %s channel: %d", mbox_mhuv3_str[data->frame], channel); + return NULL; + } + + if (!mbox_mhuv3_dbe_doorbell_search(dev, channel, &doorbell)) { + continue; + } + + LOG_DBG("Found %s channel [%d], doorbell[%d]", + mbox_mhuv3_str[data->frame], channel, doorbell); + return &data->channels[channel][doorbell]; + } + + return NULL; +} + +/** + * @brief Retrieves the maximum transmit units for doorbell extension. + * + * @return 0 as doorbell extension does not support transmitting data. + */ +static int mbox_mhuv3_dbe_mtu_get(void) +{ + return 0; +} + +/** + * @brief Initializes the Doorbell Extension (DBE) for the MHUv3 device. + * + * This function initializes the Doorbell Extension (DBE) by reading the number + * of channels from the device's control registers, and setting up necessary handlers. + * If the feature is not supported, it returns early. It also updates the device's + * data with the number of channels available and the extension's function pointers. + * + * @param dev Pointer to the device structure. + * + * @return 0 if initialization is successful, negative error code if there is a failure. + */ +static int mbox_mhuv3_dbe_init(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[DBE_EXT]; + uint32_t num_chans; + + if (!read_bitmask32((mem_addr_t)&cfg->ctrl->feat_spt0, FEAT_SPT0_DBE_SPT)) { + return 0; + } + + LOG_DBG("%s: Initializing Doorbell Extension.", mbox_mhuv3_str[data->frame]); + + ext->type = DBE_EXT; + /* Note that, by the spec, the number of channels is (num_dbch + 1) */ + num_chans = read_bitmask32((mem_addr_t)&cfg->ctrl->dbch_cfg0, DBCH_CFG0_NUM_DBCH) + 1; + __ASSERT(CONFIG_MBOX_MHUV3_NUM_DBCH >= num_chans, + "The number of configured doorbell channels is less than required by the MHUv3 extension"); + ext->num_chans = num_chans; + ext->get_channel = mbox_mhuv3_dbe_get_channel; + ext->combined_irq_setup = mbox_mhuv3_dbe_combined_irq_setup; + ext->channels_init = mbox_mhuv3_dbe_channels_init; + ext->chan_from_comb_irq_get = mbox_mhuv3_dbe_chan_from_comb_irq_get; + ext->mtu_get = mbox_mhuv3_dbe_mtu_get; + + data->num_chans += ext->num_chans * MHUV3_FLAG_BITS; + + LOG_DBG("%s: found %d DBE channels.", mbox_mhuv3_str[data->frame], ext->num_chans); + + return 0; +} + +/** + * @brief Initializes the Fast Channel Extension (FCE) for the MHUv3 device. + * + * This function initializes the Fast Channel Extension (FCE). Currently, + * FCE is not supported. + * + * @param dev Pointer to the device structure. + * + * @return 0, indicating that the function completes successfully. + */ +static int mbox_mhuv3_fce_init(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + + if (!read_bitmask32((mem_addr_t)&cfg->ctrl->feat_spt0, FEAT_SPT0_FCE_SPT)) { + return 0; + } + + LOG_DBG("%s: Fast Channel Extension is not supported by driver.", + mbox_mhuv3_str[data->frame]); + + return 0; +} + +/** + * @brief Initializes the FIFO Extension (FE) for the MHUv3 device. + * + * This function initializes the FIFO Extension (FE). Currently, + * FE is not supported. + * + * @param dev Pointer to the device structure. + * + * @return 0, indicating that the function completes successfully. + */ +static int mbox_mhuv3_fe_init(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + + if (!read_bitmask32((mem_addr_t)&cfg->ctrl->feat_spt0, FEAT_SPT0_FE_SPT)) { + return 0; + } + + LOG_DBG("%s: FIFO Extension is not supported by driver.", mbox_mhuv3_str[data->frame]); + + return 0; +} + +static mhuv3_extension_initializer mhuv3_extension_init[NUM_EXT] = { + mbox_mhuv3_dbe_init, + mbox_mhuv3_fce_init, + mbox_mhuv3_fe_init, +}; + +/** + * @brief Initializes the channels for the MHUv3 device. + * + * This function allocates memory for the channels array and initializes + * each channel based on the device's extensions. It calls the `channels_init` + * function for each extension and updates the base channel index accordingly. + * + * @param dev Pointer to the device structure. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_initialize_channels(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + uint32_t base_ch_idx = 0; + int ret = 0; + + for (uint32_t i = 0; i < NUM_EXT && !ret; i++) { + struct mbox_mhuv3_extension *ext = &data->ext[i]; + + if (ext->num_chans > 0) { + ret = ext->channels_init(dev, &base_ch_idx); + } + } + + return ret; +} + +/** + * @brief Initializes the MHUv3 frame for the device. + * + * This function reads the block ID to determine the frame type (PBX or MBX), + * retrieves the version and implementer information, and checks if the frame + * is supported. It also handles the initialization of extensions for the MHUv3 device. + * + * @param dev Pointer to the device structure. + * + * @return 0 on success, or a negative error code on failure (e.g., -EIO, -ENOMEM). + */ +static int mbox_mhuv3_frame_init(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + struct mbox_mhuv3_data *data = dev->data; + + data->frame = read_bitmask32((mem_addr_t)&cfg->ctrl->blk_id, BLK_ID_BLK_ID); + if (data->frame > MBX_FRAME) { + LOG_ERR("Invalid Frame type- %d", data->frame); + return -EIO; + } + + data->major = read_bitmask32((mem_addr_t)&cfg->ctrl->aidr, AIDR_ARCH_MAJOR_REV); + data->minor = read_bitmask32((mem_addr_t)&cfg->ctrl->aidr, AIDR_ARCH_MINOR_REV); + data->implem = read_bitmask32((mem_addr_t)&cfg->ctrl->iidr, IIDR_IMPLEMENTER); + data->rev = read_bitmask32((mem_addr_t)&cfg->ctrl->iidr, IIDR_REVISION); + data->var = read_bitmask32((mem_addr_t)&cfg->ctrl->iidr, IIDR_VARIANT); + data->prod_id = read_bitmask32((mem_addr_t)&cfg->ctrl->iidr, IIDR_PRODUCT_ID); + if (data->major != MHUV3_MAJOR_VERSION) { + LOG_ERR("Unsupported MHU %s block - major:%d minor:%d", + mbox_mhuv3_str[data->frame], data->major, data->minor); + return -EIO; + } + + data->auto_op_full = !!read_bitmask32((mem_addr_t)&cfg->ctrl->feat_spt1, + FEAT_SPT1_AUTO_OP_SPT); + /* Request the PBX/MBX to remain operational */ + if (data->auto_op_full) { + write_bitmask32(0x1, (mem_addr_t)&cfg->ctrl->x_ctrl, CTRL_OP_REQ); + } + + LOG_DBG("Found MHU %s block - major:%d minor:%d\n" + " implem:0x%X rev:0x%X var:0x%X prod_id:0x%X", + mbox_mhuv3_str[data->frame], data->major, data->minor, + data->implem, data->rev, data->var, data->prod_id); + + for (uint32_t i = 0; i < NUM_EXT; i++) { + uint32_t ret; + /* + * Note that extensions initialization fails only when such + * extension initialization routine fails and the extensions + * was found to be supported in hardware and in software. + */ + ret = mhuv3_extension_init[i](dev); + if (ret) { + LOG_ERR("Failed to initialize %s %s: %d", + mbox_mhuv3_str[data->frame], + mbox_mhuv3_ext_str[i], + ret); + return -EIO; + } + } + + return 0; +} + +/** + * @brief Handles the PBX combined interrupt for the MHUv3 device. + * + * This function iterates through all available extensions and checks for channels + * that are part of the combined interrupt mechanism. + * + * @param dev Pointer to the device structure. + */ +static void mbox_mhuv3_pbx_comb_interrupt(const struct device *dev) +{ + uint32_t found = 0; + + for (uint32_t i = 0; i < NUM_EXT; i++) { + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[i]; + struct mbox_mhuv3_channel *chan; + + /* FCE does not participate to the PBX combined */ + if (i == FCE_EXT || ext->num_chans == 0) { + continue; + } + + chan = ext->chan_from_comb_irq_get(dev); + if (!chan) { + continue; + } + + found++; + + if (!chan->ops) { + LOG_WRN("TX Ack on UNBOUND channel (%u)", chan->ch_idx); + continue; + } + } + + if (found == 0) { + LOG_WRN("Failed to find channel for the TX interrupt"); + } +} + +/** + * @brief Handles the MBX combined interrupt for the MHUv3 device. + * + * This function iterates through all available extensions and checks for channels + * that are part of the combined interrupt mechanism. It reads data from the channel + * if available and invokes the appropriate callback for the channel. + * + * @param dev Pointer to the device structure. + */ +static void mbox_mhuv3_mbx_comb_interrupt(const struct device *dev) +{ + uint32_t found = 0; + + for (uint32_t i = 0; i < NUM_EXT; i++) { + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_extension *ext = &data->ext[i]; + struct mbox_mhuv3_channel *chan; + uint32_t flattened_ch_idx; + + if (ext->num_chans == 0) { + continue; + } + + chan = ext->chan_from_comb_irq_get(dev); + if (!chan) { + continue; + } + + found++; + + if (!chan->ops) { + LOG_WRN("RX Data on UNBOUND channel (%u)", chan->ch_idx); + } + + if (chan->ops->read_data) { + void *data = chan->ops->read_data(dev, chan); + + if (!data) { + LOG_ERR("Failed to read in-band data."); + goto rx_ack; + } + } + + /* Call channel call back */ + flattened_ch_idx = chan->ch_idx * MHUV3_FLAG_BITS + chan->doorbell; + chan->cb(dev, flattened_ch_idx, chan->user_data, NULL); + +rx_ack: + if (chan->ops->rx_complete) { + chan->ops->rx_complete(dev, chan); + } + } + + if (found == 0) { + LOG_ERR("Failed to find channel for the RX interrupt"); + } +} + +/** + * @brief Handles the combined interrupt for the MHUv3 device, based on the frame type. + * + * This function checks the current frame type of the MHUv3 device (PBX or MBX) and + * delegates the interrupt handling to the appropriate function. + * + * @param dev Pointer to the device structure. + */ +static void mbox_mhuv3_comb_interrupt(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + + if (data->frame == PBX_FRAME) { + mbox_mhuv3_pbx_comb_interrupt(dev); + } else { + mbox_mhuv3_mbx_comb_interrupt(dev); + } +} + +/** + * @brief Sets up the PBX for the MHUv3 device. + * + * This function initializes the PBX-related IRQs (interrupts) for the MHUv3 device. + * If no combined IRQ configuration is provided, it defaults to using PBX in Tx polling mode. + * + * @param dev Pointer to the device structure. + * @return 0 on success. + */ +static int mbox_mhuv3_setup_pbx(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (cfg->cmb_irq_config != NULL) { + cfg->cmb_irq_config(dev); + + for (uint32_t i = 0; i < NUM_EXT; i++) { + struct mbox_mhuv3_data *data = dev->data; + + if (data->ext[i].num_chans > 0) { + data->ext[i].combined_irq_setup(dev); + } + } + + LOG_DBG("MHUv3 PBX IRQs initialized."); + + return 0; + } + + LOG_INF("Using PBX in Tx polling mode."); + + return 0; +} + +/** + * @brief Sets up the MBX for the MHUv3 device. + * + * This function initializes the MBX-related IRQs (interrupts) for the MHUv3 device. + * + * @param dev Pointer to the device structure. + * @return 0 on success, -EINVAL if combined IRQ configuration is missing. + */ +static int mbox_mhuv3_setup_mbx(const struct device *dev) +{ + const struct mbox_mhuv3_config *cfg = dev->config; + + if (cfg->cmb_irq_config == NULL) { + LOG_ERR("MBX combined IRQ is missing!"); + return -EINVAL; + } + + cfg->cmb_irq_config(dev); + + for (uint32_t i = 0; i < NUM_EXT; i++) { + struct mbox_mhuv3_data *data = dev->data; + + if (data->ext[i].num_chans > 0) { + data->ext[i].combined_irq_setup(dev); + } + } + + LOG_DBG("MHUv3 MBX IRQs initialized."); + + return 0; +} + +/** + * @brief Initializes the IRQs for the MHUv3 device. + * + * This function initializes the IRQs (interrupts) based on the frame type + * of the MHUv3 device. + * + * @param dev Pointer to the device structure. + * @return 0 on success, or the error code returned by the setup functions. + */ +static int mbox_mhuv3_irqs_init(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + + LOG_DBG("Initializing %s block.", mbox_mhuv3_str[data->frame]); + + if (data->frame == PBX_FRAME) { + return mbox_mhuv3_setup_pbx(dev); + } + + return mbox_mhuv3_setup_mbx(dev); +} + +/** + * @brief Initializes the MHUv3 device. + * + * This function initializes the MHUv3 device by performing the following steps: + * 1. Initializes the frame type. + * 2. Initializes the IRQs. + * 3. Initializes the channels. + * + * If any of the initialization steps fail, the error code is returned immediately. + * + * @param dev Pointer to the device structure. + * @return 0 on success, or the error code from any of the initialization functions. + */ +static int mbox_mhuv3_init(const struct device *dev) +{ + int ret; + + ret = mbox_mhuv3_frame_init(dev); + if (ret) { + return ret; + } + + ret = mbox_mhuv3_irqs_init(dev); + if (ret) { + return ret; + } + + ret = mbox_mhuv3_initialize_channels(dev); + if (ret) { + return ret; + } + + return ret; +} + +/** + * @brief Attempts to register a callback for a specified channel on the sender. + * + * @param dev Pointer to the device structure. + * @param channel_id The ID of the channel for the callback. + * @param cb The callback function to be registered. + * @param user_data User data to be passed to the callback. + * + * @return Always returns -ENOTSUP as callback registration for a sender device is not supported. + */ +static int mbox_mhuv3_sender_register_callback(const struct device *dev, + mbox_channel_id_t channel_id, + mbox_callback_t cb, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel_id); + ARG_UNUSED(cb); + ARG_UNUSED(user_data); + + LOG_ERR("Trying to register a callback on a PBX MHUv3 frame"); + + return -ENOTSUP; +} + +/** + * @brief Registers a callback function for a specified channel on the receiver. + * + * This function associates a callback with the specified channel. The callback + * will be invoked when the receiver processes data for that channel. The user + * data will be passed to the callback when it is called. + * + * @param dev Pointer to the device structure. + * @param channel_id The channel ID for which the callback is being registered. + * @param cb The callback function to register for the receiver. + * @param user_data User-specific data that will be passed to the callback. + * + * @return 0 on success, or -EINVAL if the specified channel is invalid. + */ +static int mbox_mhuv3_receiver_register_callback(const struct device *dev, + mbox_channel_id_t channel_id, + mbox_callback_t cb, void *user_data) +{ + struct mbox_mhuv3_channel *chan = mbox_mhuv3_get_channel(dev, channel_id); + + if (!chan) { + return -EINVAL; + } + + chan->cb = cb; + chan->user_data = user_data; + + return 0; +} + +/** + * @brief Registers a callback function for a specified channel, based on the frame type. + * + * @param dev Pointer to the device structure. + * @param channel_id The channel ID for which the callback is being registered. + * @param cb The callback function to register. + * @param user_data User-specific data that will be passed to the callback. + * + * @return 0 on success, or an error code from the sender or receiver callback registration. + */ +static int mbox_mhuv3_register_callback(const struct device *dev, + mbox_channel_id_t channel_id, + mbox_callback_t cb, void *user_data) +{ + struct mbox_mhuv3_data *data = dev->data; + + if (data->frame == PBX_FRAME) { + return mbox_mhuv3_sender_register_callback(dev, channel_id, cb, user_data); + } + + return mbox_mhuv3_receiver_register_callback(dev, channel_id, cb, user_data); +} + +/** + * @brief Retrieves the maximum transmission unit (MTU) for the sender device. + * + * @param dev Pointer to the device structure. + * + * @return The MTU size for the sender channel. + */ +static int mbox_mhuv3_sender_mtu_get(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + + /** + * TODO: The maximum transmit units depends on the channel extension. + * The type of extension, channel and doorbell should be + * extracted from a struct passed to this function from device tree. + * Until this is changed, Pin the type to Doorbell extension which + * is the only supported extension. + */ + + enum mbox_mhuv3_extension_type type = DBE_EXT; + + return data->ext[type].mtu_get(); +} + +/** + * @brief Attempts to retrieve the maximum transmission unit (MTU) for the receiver device. + * + * @param dev Pointer to the device structure. + * + * @return Always returns 0. + */ +static int mbox_mhuv3_receiver_mtu_get(const struct device *dev) +{ + ARG_UNUSED(dev); + + LOG_ERR("Trying to get maximum transmit units on a MBX MHUv3 frame"); + + return 0; +} + +/** + * @brief Retrieves the Maximum Transmission Unit (MTU) for the MHUv3 device. + * + * This function determines the MTU based on the frame type (PBX or MBX) and calls + * the appropriate function to retrieve the MTU for the sender or receiver. + * + * @param dev Pointer to the device structure. + * + * @return The MTU value for the sender or receiver, depending on the frame type. + */ +static int mbox_mhuv3_mtu_get(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + + if (data->frame == PBX_FRAME) { + return mbox_mhuv3_sender_mtu_get(dev); + } + + return mbox_mhuv3_receiver_mtu_get(dev); +} + +/** + * @brief Retrieves the maximum number of channels available in the MHUv3 device. + * + * @param dev Pointer to the device structure. + * + * @return The number of available channels for the MHUv3 device. + */ +static uint32_t mbox_mhuv3_max_channels_get(const struct device *dev) +{ + struct mbox_mhuv3_data *data = dev->data; + + return data->num_chans; +} + +/** + * @brief Enables or disables a channel based on the MHUv3 frame type. + * + * This function handles enabling or disabling the transmission (TX) or reception (RX) + * functionality of a channel. + * + * @param dev The device handle representing the MHUv3 system. + * @param channel_id The ID of the channel to enable or disable. + * @param enabled Boolean flag to enable (true) or disable (false) the channel. + * + * @return 0 on success, or a negative error code on failure. + */ +static int mbox_mhuv3_set_enabled(const struct device *dev, + mbox_channel_id_t channel_id, bool enabled) +{ + struct mbox_mhuv3_data *data = dev->data; + struct mbox_mhuv3_channel *chan = mbox_mhuv3_get_channel(dev, channel_id); + int ret; + + if (!chan) { + return -EINVAL; + } + + if (data->frame == PBX_FRAME) { + if (enabled) { + ret = chan->ops->tx_enable(dev, chan); + } else { + ret = chan->ops->tx_disable(dev, chan); + } + } else { + if (enabled) { + ret = chan->ops->rx_enable(dev, chan); + } else { + ret = chan->ops->rx_disable(dev, chan); + } + } + + return ret; +} + +static DEVICE_API(mbox, mhuv3_driver_api) = { + .send = mbox_mhuv3_send_data, + .register_callback = mbox_mhuv3_register_callback, + .mtu_get = mbox_mhuv3_mtu_get, + .max_channels_get = mbox_mhuv3_max_channels_get, + .set_enabled = mbox_mhuv3_set_enabled, +}; + +#define MHUV3_INIT(n) \ + \ +static void mbox_mhuv3_cmb_irq_config_##n(const struct device *dev) \ +{ \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(n, combined, irq), \ + DT_INST_IRQ_BY_NAME(n, combined, priority), \ + mbox_mhuv3_comb_interrupt, \ + DEVICE_DT_INST_GET(n), \ + 0); \ + irq_enable(DT_INST_IRQ_BY_NAME(n, combined, irq)); \ +} \ + \ +static const struct mbox_mhuv3_config mhuv3_cfg_##n = { \ + (struct ctrl_page *)DT_INST_REG_ADDR(n), \ + DT_INST_REG_ADDR(n), \ + mbox_mhuv3_cmb_irq_config_##n, \ +}; \ + \ +struct mbox_mhuv3_data mhuv3_data_##n; \ + \ +DEVICE_DT_INST_DEFINE(n, \ + &mbox_mhuv3_init, \ + NULL, \ + &mhuv3_data_##n, \ + &mhuv3_cfg_##n, \ + POST_KERNEL, \ + CONFIG_MBOX_INIT_PRIORITY, \ + &mhuv3_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MHUV3_INIT); diff --git a/dts/bindings/mbox/arm,mhuv3.yaml b/dts/bindings/mbox/arm,mhuv3.yaml new file mode 100644 index 00000000000..7b6ceee1736 --- /dev/null +++ b/dts/bindings/mbox/arm,mhuv3.yaml @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its +# affiliates +# +# SPDX-License-Identifier: Apache-2.0 + +description: ARM MHUv3 (Message Handling Unit v3) + +compatible: "arm,mhuv3" + +include: [base.yaml, mailbox-controller.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + interrupt-names: + required: true + +mbox-cells: + - channel