/* * Copyright (c) 2024, Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include "mhu_v3_x.h" #include "mhu_v3_x_private.h" /* * Get the device base from the device struct. Return an error if the dev is * invalid. */ static enum mhu_v3_x_error_t get_dev_base(const struct mhu_v3_x_dev_t *dev, union _mhu_v3_x_frame_t **base) { if (dev == NULL) { return MHU_V_3_X_ERR_INVALID_PARAM; } /* Ensure driver has been initialized */ if (dev->is_initialized == false) { return MHU_V_3_X_ERR_NOT_INIT; } *base = (union _mhu_v3_x_frame_t *)dev->base; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_driver_init(struct mhu_v3_x_dev_t *dev) { uint32_t aidr = 0; uint8_t mhu_major_rev; union _mhu_v3_x_frame_t *p_mhu; if (dev == NULL) { return MHU_V_3_X_ERR_INVALID_PARAM; } /* Return if already initialized */ if (dev->is_initialized == true) { return MHU_V_3_X_ERR_NONE; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; /* Read revision from MHU hardware */ if (dev->frame == MHU_V3_X_PBX_FRAME) { aidr = p_mhu->pbx_frame.pbx_ctrl_page.pbx_aidr; } else if (dev->frame == MHU_V3_X_MBX_FRAME) { aidr = p_mhu->mbx_frame.mbx_ctrl_page.mbx_aidr; } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } /* Read the MHU Architecture Major Revision */ mhu_major_rev = ((aidr & MHU_ARCH_MAJOR_REV_MASK) >> MHU_ARCH_MAJOR_REV_OFF); /* Return error if the MHU major revision is not 3 */ if (mhu_major_rev != MHU_MAJOR_REV_V3) { /* Unsupported MHU version */ return MHU_V_3_X_ERR_UNSUPPORTED_VERSION; } /* Read the MHU Architecture Minor Revision */ dev->subversion = ((aidr & MHU_ARCH_MINOR_REV_MASK) >> MHU_ARCH_MINOR_REV_MASK); /* Return error if the MHU minor revision is not 0 */ if (dev->subversion != MHU_MINOR_REV_3_0) { /* Unsupported subversion */ return MHU_V_3_X_ERR_UNSUPPORTED_VERSION; } /* Initialize the Postbox/Mailbox to remain in operational state */ if (dev->frame == MHU_V3_X_PBX_FRAME) { p_mhu->pbx_frame.pbx_ctrl_page.pbx_ctrl |= MHU_V3_OP_REQ; } else if (dev->frame == MHU_V3_X_MBX_FRAME) { p_mhu->mbx_frame.mbx_ctrl_page.mbx_ctrl |= MHU_V3_OP_REQ; } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } dev->is_initialized = true; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_get_num_channel_implemented( const struct mhu_v3_x_dev_t *dev, enum mhu_v3_x_channel_type_t ch_type, uint8_t *num_ch) { enum mhu_v3_x_error_t status; union _mhu_v3_x_frame_t *p_mhu; if (num_ch == NULL) { return MHU_V_3_X_ERR_INVALID_PARAM; } /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only doorbell channel is supported */ if (ch_type != MHU_V3_X_CHANNEL_TYPE_DBCH) { return MHU_V_3_X_ERR_UNSUPPORTED; } /* Read the number of channels implemented in the MHU */ if (dev->frame == MHU_V3_X_PBX_FRAME) { *num_ch = (p_mhu->pbx_frame.pbx_ctrl_page.pbx_dbch_cfg0 + 1); } else if (dev->frame == MHU_V3_X_MBX_FRAME) { *num_ch = (p_mhu->mbx_frame.mbx_ctrl_page.mbx_dbch_cfg0 + 1); } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_clear(const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t flags) { union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; enum mhu_v3_x_error_t status; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only MBX can clear the Doorbell channel */ if (dev->frame != MHU_V3_X_MBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* Clear the bits in the doorbell channel */ mdbcw_reg[channel].mdbcw_clr |= flags; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_write(const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t flags) { union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_pdbcw_reg_t *pdbcw_reg; enum mhu_v3_x_error_t status; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only PBX can set the Doorbell channel value */ if (dev->frame != MHU_V3_X_PBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; pdbcw_reg = (struct _mhu_v3_x_pdbcw_reg_t *) &(p_mhu->pbx_frame.pdbcw_page); /* Write the value to the doorbell channel */ pdbcw_reg[channel].pdbcw_set |= flags; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_read(const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t *flags) { union _mhu_v3_x_frame_t *p_mhu; enum mhu_v3_x_error_t status; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; struct _mhu_v3_x_pdbcw_reg_t *pdbcw_reg; if (flags == NULL) { return MHU_V_3_X_ERR_INVALID_PARAM; } /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; if (dev->frame == MHU_V3_X_PBX_FRAME) { pdbcw_reg = (struct _mhu_v3_x_pdbcw_reg_t *) &(p_mhu->pbx_frame.pdbcw_page); /* Read the value from Postbox Doorbell status register */ *flags = pdbcw_reg[channel].pdbcw_st; } else if (dev->frame == MHU_V3_X_MBX_FRAME) { mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* Read the value from Mailbox Doorbell status register */ *flags = mdbcw_reg[channel].mdbcw_st; } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_mask_set( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t flags) { union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; enum mhu_v3_x_error_t status; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Doorbell channel mask is not applicable for PBX */ if (dev->frame != MHU_V3_X_MBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* Set the Doorbell channel mask */ mdbcw_reg[channel].mdbcw_msk_set |= flags; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_mask_clear( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t flags) { union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; enum mhu_v3_x_error_t status; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Doorbell channel mask is not applicable for PBX */ if (dev->frame != MHU_V3_X_MBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* Clear the Doorbell channel mask */ mdbcw_reg[channel].mdbcw_msk_clr = flags; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_doorbell_mask_get( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, uint32_t *flags) { union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; enum mhu_v3_x_error_t status; if (flags == NULL) { return MHU_V_3_X_ERR_INVALID_PARAM; } /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Doorbell channel mask is not applicable for PBX */ if (dev->frame != MHU_V3_X_MBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* Save the Doorbell channel mask status */ *flags = mdbcw_reg[channel].mdbcw_msk_st; return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_channel_interrupt_enable( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, enum mhu_v3_x_channel_type_t ch_type) { enum mhu_v3_x_error_t status; union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_pdbcw_reg_t *pdbcw_reg; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only doorbell channel is supported */ if (ch_type != MHU_V3_X_CHANNEL_TYPE_DBCH) { return MHU_V_3_X_ERR_UNSUPPORTED; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; if (dev->frame == MHU_V3_X_PBX_FRAME) { pdbcw_reg = (struct _mhu_v3_x_pdbcw_reg_t *) &(p_mhu->pbx_frame.pdbcw_page); /* * Enable this doorbell channel to generate interrupts for * transfer acknowledge events. */ pdbcw_reg[channel].pdbcw_int_en = MHU_V3_X_PDBCW_INT_X_TFR_ACK; /* * Enable this doorbell channel to contribute to the PBX * combined interrupt. */ pdbcw_reg[channel].pdbcw_ctrl = MHU_V3_X_PDBCW_CTRL_PBX_COMB_EN; } else if (dev->frame == MHU_V3_X_MBX_FRAME) { mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* * Enable this doorbell channel to contribute to the MBX * combined interrupt. */ mdbcw_reg[channel].mdbcw_ctrl = MHU_V3_X_MDBCW_CTRL_MBX_COMB_EN; } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_channel_interrupt_disable( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, enum mhu_v3_x_channel_type_t ch_type) { enum mhu_v3_x_error_t status; union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_pdbcw_reg_t *pdbcw_reg; struct _mhu_v3_x_mdbcw_reg_t *mdbcw_reg; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only doorbell channel is supported */ if (ch_type != MHU_V3_X_CHANNEL_TYPE_DBCH) { return MHU_V_3_X_ERR_UNSUPPORTED; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; if (dev->frame == MHU_V3_X_PBX_FRAME) { pdbcw_reg = (struct _mhu_v3_x_pdbcw_reg_t *) &(p_mhu->pbx_frame.pdbcw_page); /* Clear channel transfer acknowledge event interrupt */ pdbcw_reg[channel].pdbcw_int_clr = MHU_V3_X_PDBCW_INT_X_TFR_ACK; /* Disable channel transfer acknowledge event interrupt */ pdbcw_reg[channel].pdbcw_int_en &= ~(MHU_V3_X_PDBCW_INT_X_TFR_ACK); /* * Disable this doorbell channel from contributing to the PBX * combined interrupt. */ pdbcw_reg[channel].pdbcw_ctrl &= ~(MHU_V3_X_PDBCW_CTRL_PBX_COMB_EN); } else if (dev->frame == MHU_V3_X_MBX_FRAME) { mdbcw_reg = (struct _mhu_v3_x_mdbcw_reg_t *) &(p_mhu->mbx_frame.mdbcw_page); /* * Disable this doorbell channel from contributing to the MBX * combined interrupt. */ mdbcw_reg[channel].mdbcw_ctrl &= ~(MHU_V3_X_MDBCW_CTRL_MBX_COMB_EN); } else { /* Only PBX and MBX frames are supported. */ return MHU_V_3_X_ERR_UNSUPPORTED; } return MHU_V_3_X_ERR_NONE; } enum mhu_v3_x_error_t mhu_v3_x_channel_interrupt_clear( const struct mhu_v3_x_dev_t *dev, const uint32_t channel, enum mhu_v3_x_channel_type_t ch_type) { enum mhu_v3_x_error_t status; union _mhu_v3_x_frame_t *p_mhu; struct _mhu_v3_x_pdbcw_reg_t *pdbcw_reg; /* Get dev->base if it is valid or return an error if dev is not */ status = get_dev_base(dev, &p_mhu); if (status != MHU_V_3_X_ERR_NONE) { return status; } /* Only doorbell channel is supported */ if (ch_type != MHU_V3_X_CHANNEL_TYPE_DBCH) { return MHU_V_3_X_ERR_UNSUPPORTED; } /* * Only postbox doorbell channel transfer acknowledge interrupt can be * cleared manually. * * To clear MBX interrupt the unmasked status must be cleared using * mhu_v3_x_doorbell_clear. */ if (dev->frame != MHU_V3_X_PBX_FRAME) { return MHU_V_3_X_ERR_INVALID_PARAM; } p_mhu = (union _mhu_v3_x_frame_t *)dev->base; pdbcw_reg = (struct _mhu_v3_x_pdbcw_reg_t *)&( p_mhu->pbx_frame.pdbcw_page); /* Clear channel transfer acknowledge event interrupt */ pdbcw_reg[channel].pdbcw_int_clr |= 0x1; return MHU_V_3_X_ERR_NONE; }