mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-16 09:34:18 +00:00

MHUv3 reworks parts of MHUv2 and introduces MHU extensions. There are currently 3 extensions: * Doorbell extension: which works like MHUv2 * FIFO extension: which uses a buffer for faster inband data transfer * Fastchannel extension: for fast data transfer Add MHUv3 driver with support for Doorbell extension for both postbox sender MHUs and mailbox receiver MHUs. Signed-off-by: Aziz IDOMAR <aziz.idomar@arm.com> Signed-off-by: Sayanta Pattanayak <sayanta.pattanayak@arm.com> Signed-off-by: Shriram K <shriram.k@arm.com> Signed-off-by: Vijayenthiran Subramaniam <vijayenthiran.subramaniam@arm.com> Signed-off-by: Joel Goddard <joel.goddard@arm.com> Change-Id: Icf49df56f1159f4c9830e0ffcda5b3a4bea8d2fd
475 lines
12 KiB
C
475 lines
12 KiB
C
/*
|
|
* Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#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;
|
|
}
|