feat(amd): add test for register notifier EEMI API

Add a test case to validate the register notification handling
functionality of the EEMI API in the TF-A test framework. The test:

- Registers a notifier using `test_register_notifier()`.
- Waits for a notification within a predefined timeout.
- If received, unregisters the notifier using
  `test_unregister_notifier()`.

Change-Id: I4454ef8644851a2dedc0a224fa220062fed1d635
Signed-off-by: Madhav Bhatt <madhav.bhatt@amd.com>
diff --git a/tftf/tests/plat/amd/common/common_files/eemi_api.c b/tftf/tests/plat/amd/common/common_files/eemi_api.c
index 6dcb632..29d9426 100644
--- a/tftf/tests/plat/amd/common/common_files/eemi_api.c
+++ b/tftf/tests/plat/amd/common/common_files/eemi_api.c
@@ -7,6 +7,8 @@
 #include "eemi_api.h"
 #include "xpm_defs.h"
 
+xpm_notifier * notifier_store = NULL;
+
 static int do_feature_check(const uint32_t api_id)
 {
 	smc_args args;
@@ -101,3 +103,123 @@
 
 	return ret;
 }
+
+int xpm_request_node(const uint32_t device_id, const uint32_t capabilities,
+		     const uint32_t qos, const uint32_t ack)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+
+	return eemi_call(PM_REQUEST_NODE, ((uint64_t)capabilities << 32 | device_id),
+			((uint64_t)ack << 32 | qos), 0, 0, 0, 0, 0, ret_payload);
+}
+
+int xpm_release_node(const uint32_t device_id)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+
+	return eemi_call(PM_RELEASE_NODE, device_id, 0, 0, 0, 0, 0, 0, ret_payload);
+}
+
+int xpm_set_requirement(const uint32_t device_id, const uint32_t capabilities,
+			const uint32_t qos, const uint32_t ack)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+
+	return eemi_call(PM_SET_REQUIREMENT, ((uint64_t)capabilities << 32 | device_id),
+			((uint64_t)ack << 32 | qos), 0, 0, 0, 0, 0, ret_payload);
+}
+
+irq_handler_t xpm_notifier_cb(void *data)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+
+	/* Get the IPI payload from TF-A */
+	(void)eemi_call(PM_GET_CALLBACK_DATA, 0, 0, 0, 0, 0, 0, 0, ret_payload);
+
+	if (!notifier_store)
+		return NULL;
+
+	if (ret_payload[0] != PM_NOTIFY_CB) {
+		notifier_store->received = 0;
+		tftf_testcase_printf("unexpected callback type\n");
+	} else {
+		notifier_store->received = 1;
+	}
+
+	(void)notifier_store->callback(notifier_store);
+
+	return 0;
+}
+
+int xpm_register_notifier(xpm_notifier * const notifier)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+	uint32_t wake = 1U;
+	uint32_t enable = 1U;
+	uint32_t sgi_num = NOTIFIER_SGI;
+	uint32_t reset = 0U;
+	int ret;
+
+	if (!notifier)
+		return PM_RET_ERROR_ARGS;
+
+	/* Save the pointer to the structure so the notifier handler can access it */
+	notifier_store = notifier;
+
+	ret = tftf_irq_register_handler(sgi_num, (irq_handler_t)xpm_notifier_cb);
+	if (ret != PM_RET_SUCCESS) {
+		tftf_testcase_printf("failed to register the irq handler\n");
+		return ret;
+	}
+
+	tftf_irq_enable(sgi_num, IRQ_PRIORITY);
+
+	/* Register PM event notifier */
+	ret = eemi_call(PM_REGISTER_NOTIFIER, ((uint64_t)notifier->event << 32 | notifier->node),
+			((uint64_t)enable << 32 | wake), 0, 0, 0, 0, 0, ret_payload);
+	if (ret != PM_RET_SUCCESS) {
+		tftf_testcase_printf("failed to register the event notifier\n");
+		return ret;
+	}
+
+	/* Register the SGI number with TF-A */
+	ret = eemi_call(TF_A_PM_REGISTER_SGI, ((uint64_t)reset << 32 | sgi_num),
+			0, 0, 0, 0, 0, 0, ret_payload);
+	if (ret != PM_RET_SUCCESS)
+		tftf_testcase_printf("failed to register the SGI with TF-A\n");
+
+	return ret;
+}
+
+int xpm_unregister_notifier(xpm_notifier * const notifier)
+{
+	uint32_t ret_payload[PAYLOAD_ARG_CNT];
+	uint32_t wake = 0U;
+	uint32_t enable = 0U;
+	uint32_t reset = 1U;
+	int ret;
+
+	/* Unregister PM event notifier */
+	ret = eemi_call(PM_REGISTER_NOTIFIER, ((uint64_t)notifier->event << 32 | notifier->node),
+			((uint64_t)enable << 32 | wake), 0, 0, 0, 0, 0, ret_payload);
+	if (ret != PM_RET_SUCCESS) {
+		tftf_testcase_printf("failed to unregister the event notifier\n");
+		return ret;
+	}
+
+	/* Unregister the SGI number with TF-A */
+	ret = eemi_call(TF_A_PM_REGISTER_SGI, ((uint64_t)reset << 32 | 0),
+			0, 0, 0, 0, 0, 0, ret_payload);
+	if (ret != PM_RET_SUCCESS) {
+		tftf_testcase_printf("failed to unregister the SGI with TF-A\n");
+		return ret;
+	}
+
+	tftf_irq_disable(NOTIFIER_SGI);
+
+	tftf_irq_unregister_handler(NOTIFIER_SGI);
+	if (ret != PM_RET_SUCCESS)
+		tftf_testcase_printf("failed to unregister the IRQ handler\n");
+
+	return ret;
+}
diff --git a/tftf/tests/plat/amd/common/common_files/eemi_api.h b/tftf/tests/plat/amd/common/common_files/eemi_api.h
index 59de2ca..ebed5a7 100644
--- a/tftf/tests/plat/amd/common/common_files/eemi_api.h
+++ b/tftf/tests/plat/amd/common/common_files/eemi_api.h
@@ -9,8 +9,26 @@
 
 #include "xpm_defs.h"
 
+typedef struct xpm_notifier {
+	void(*const callback)(struct xpm_notifier * const notifier);	/**< Callback handler */
+	const uint32_t node;		/**< Node to receive notifications about */
+	uint32_t event;			/**< Event type to receive notifications about */
+	uint32_t received_event;	/**< Event received from PLM */
+	uint32_t flags;			/**< Flags */
+	uint32_t oppoint;		/**< Operating point in node */
+	uint32_t received;		/**< How many times the notification has been received */
+	struct xpm_notifier *next;	/**< Pointer to next notifier in linked list */
+} xpm_notifier;
+
 int xpm_get_api_version(uint32_t *version);
 int xpm_get_chip_id(uint32_t *id_code, uint32_t *version);
 int xpm_feature_check(const uint32_t api_id, uint32_t *const version);
+int xpm_request_node(const uint32_t device_id, const uint32_t capabilities,
+		     const uint32_t qos, const uint32_t ack);
+int xpm_release_node(const uint32_t device_id);
+int xpm_set_requirement(const uint32_t device_id, const uint32_t capabilities,
+			const uint32_t qos, const uint32_t ack);
+int xpm_register_notifier(xpm_notifier * const notifier);
+int xpm_unregister_notifier(xpm_notifier * const notifier);
 
 #endif /* __EEMI_API_H__ */
diff --git a/tftf/tests/plat/amd/common/common_files/xpm_defs.h b/tftf/tests/plat/amd/common/common_files/xpm_defs.h
index 90d5ddd..0e48418 100644
--- a/tftf/tests/plat/amd/common/common_files/xpm_defs.h
+++ b/tftf/tests/plat/amd/common/common_files/xpm_defs.h
@@ -13,6 +13,8 @@
 
 #define PM_SIP_SVC		0xC2000000U
 #define PAYLOAD_ARG_CNT		7U
+#define NOTIFIER_SGI           15U
+#define IRQ_PRIORITY		0U
 
 #define upper_32_bits(n)	((uint32_t)(((n) >> 32U)))
 #define lower_32_bits(n)	((uint32_t)((n) & 0xffffffffU))
@@ -27,6 +29,7 @@
 
 /* TF-A only commands */
 #define PM_GET_CALLBACK_DATA	0xa01U
+#define TF_A_PM_REGISTER_SGI	0xa04U
 
 /* API IDs */
 enum pm_api_id {
@@ -109,4 +112,26 @@
 	PM_API_MAX                                      /**< 0x4B */
 };
 
+/* Node capabilities */
+#define        PM_CAP_ACCESS            1U
+#define        PM_CAP_CONTEXT           2U
+
+/*
+ * PM notify events
+ */
+enum xpm_notify_event {
+	EVENT_STATE_CHANGE = 1U,                        /**< State change event */
+	EVENT_ZERO_USERS = 2U,                          /**< Zero user event */
+	EVENT_CPU_IDLE_FORCE_PWRDWN = 4U,               /**< CPU idle event during force pwr down */
+};
+
+/**
+ * PM API callback IDs
+ */
+enum pm_api_cb_id {
+	PM_INIT_SUSPEND_CB = 30U,                       /**< Suspend callback */
+	PM_ACKNOWLEDGE_CB = 31U,                        /**< Acknowledge callback */
+	PM_NOTIFY_CB = 32U,                             /**< Notify callback */
+};
+
 #endif /* XPM_DEFS_H_ */
diff --git a/tftf/tests/plat/amd/common/common_files/xpm_nodeid.h b/tftf/tests/plat/amd/common/common_files/xpm_nodeid.h
new file mode 100644
index 0000000..e485b67
--- /dev/null
+++ b/tftf/tests/plat/amd/common/common_files/xpm_nodeid.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef XPM_NODEID_H_
+#define XPM_NODEID_H_
+
+/*
+ * Device Nodes
+ */
+#define PM_DEV_USB_0		0x18224018U
+
+#endif /* XPM_NODEID_H_ */
diff --git a/tftf/tests/plat/amd/common/register_notifier_test/register_notifier_test.c b/tftf/tests/plat/amd/common/register_notifier_test/register_notifier_test.c
new file mode 100644
index 0000000..748c5f5
--- /dev/null
+++ b/tftf/tests/plat/amd/common/register_notifier_test/register_notifier_test.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "eemi_api.h"
+#include "xpm_defs.h"
+#include "xpm_nodeid.h"
+#include <timer.h>
+
+#define TIMEOUT_MS     0x1000U
+
+static void test_notify_cb(xpm_notifier *notifier);
+
+xpm_notifier notifier = {
+	.callback = test_notify_cb,
+	.node = PM_DEV_USB_0,
+	.event = EVENT_STATE_CHANGE,
+	.flags = 0,
+};
+
+/*
+ * This callback routine is invoked when the notification arrives.
+ */
+void test_notify_cb(xpm_notifier *notifier)
+{
+	tftf_testcase_printf("Received notification: Node=0x%x, Event=%d, OPP=%d\n",
+			     notifier->node, (int)notifier->event, (int)notifier->oppoint);
+}
+
+/*
+ * This function is used to register a notification with change state
+ * mode and change the requirements of the particular node.
+ */
+static int test_register_notifier(void)
+{
+	int32_t status;
+
+	status = xpm_request_node(notifier.node, PM_CAP_ACCESS, 0, 0);
+	if (status != PM_RET_SUCCESS) {
+		tftf_testcase_printf("%s ERROR to request the node for Node Id: 0x%x, "
+				     "Status: 0x%x\n", __func__, notifier.node, status);
+		return status;
+	}
+
+	status = xpm_register_notifier(&notifier);
+	if (status != PM_RET_SUCCESS) {
+		tftf_testcase_printf("%s ERROR to register the notifier, Status: 0x%x\n",
+				     __func__, status);
+		return status;
+	}
+
+	/*
+	 * Setting the requirement to 0 after registering the notifier triggers the
+	 * EVENT_STATE_CHANGE event for the PM_DEV_USB_0 node.
+	 */
+	status = xpm_set_requirement(notifier.node, 0, 0, 0);
+	if (status != PM_RET_SUCCESS)
+		tftf_testcase_printf("%s ERROR to set the requirement for Node Id: 0x%x, "
+				     "Status: 0x%x\n", __func__, notifier.node, status);
+
+	return status;
+}
+
+/*
+ * This function is used to unregister the notifier.
+ */
+static int test_unregister_notifier(void)
+{
+	int32_t status;
+
+	status = xpm_unregister_notifier(&notifier);
+	if (status != PM_RET_SUCCESS)
+		tftf_testcase_printf("%s ERROR to unregister the notifier\n", __func__);
+
+	status = xpm_release_node(notifier.node);
+	if (status != PM_RET_SUCCESS) {
+		tftf_testcase_printf("%s ERROR to release 0x%x node, "
+				     "Status: 0x%x\n", __func__, notifier.node, status);
+		return TEST_RESULT_FAIL;
+	}
+
+	return status;
+}
+
+/*
+ * This function tests the register notification handling in the test
+ * environment. It first attempts to register a notifier using
+ * test_register_notifier(). If successful, it waits for a notification to be
+ * received within a specified timeout period. If a notification is received
+ * before the timeout, it proceeds to unregister the notifier using
+ * test_unregister_notifier().
+ */
+test_result_t test_notifier_api(void)
+{
+	uint32_t timeout_counter = 0U;
+	int32_t status;
+	test_result_t result = TEST_RESULT_SUCCESS;
+
+	status = test_register_notifier();
+	if (status != PM_RET_SUCCESS)
+		result = TEST_RESULT_FAIL;
+
+	while ((notifier.received == 0) && (timeout_counter < TIMEOUT_MS)) {
+		tftf_timer_sleep(1); // Sleep for 1 millisecond
+		timeout_counter++;
+	}
+
+	if (!notifier.received) {
+		tftf_testcase_printf("%s ERROR timeout\n", __func__);
+		result = TEST_RESULT_FAIL;
+	}
+
+	status = test_unregister_notifier();
+	if (status != PM_RET_SUCCESS)
+		result = TEST_RESULT_FAIL;
+
+	return result;
+}
diff --git a/tftf/tests/tests-versal.xml b/tftf/tests/tests-versal.xml
index 547bf1d..ce681dd 100644
--- a/tftf/tests/tests-versal.xml
+++ b/tftf/tests/tests-versal.xml
@@ -15,6 +15,7 @@
       <testcase name="Read PM API Version" function="test_pm_api_version" />
       <testcase name="Get Platform Chip ID" function="test_get_chip_id" />
       <testcase name="Feature Check" function="test_feature_check" />
+      <testcase name="Notifier EEMI API" function="test_notifier_api" />
     </testsuite>
 
   </testsuites>