From a7ba802dbead12610ec07ff596b22ecd5e3f5770 Mon Sep 17 00:00:00 2001 From: Ziad Elhanafy Date: Tue, 19 Nov 2024 08:57:41 +0000 Subject: [PATCH] drivers: mbox: Add Arm MHUv3 Mailbox driver This adds new Arm MHUv3 mailbox driver. The Arm MHUv3 (Message Handling Unit version 3) is a hardware block designed for inter-processor communication in SoCs. The ARM MHUv3 can optionally support various extensions to enable different kinds of transport to be used for communication. At the moment only the doorbell extension is supported in the driver. For more information refer to Arm Message Handling Unit Architecture https://documentation-service.arm.com/static/65f01fbab5e3c10fe1335edf Signed-off-by: Ziad Elhanafy --- drivers/mbox/CMakeLists.txt | 1 + drivers/mbox/Kconfig | 1 + drivers/mbox/Kconfig.mhuv3 | 27 + drivers/mbox/mbox_mhuv3.c | 1563 ++++++++++++++++++++++++++++++ dts/bindings/mbox/arm,mhuv3.yaml | 23 + 5 files changed, 1615 insertions(+) create mode 100644 drivers/mbox/Kconfig.mhuv3 create mode 100644 drivers/mbox/mbox_mhuv3.c create mode 100644 dts/bindings/mbox/arm,mhuv3.yaml 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