feat(ti): introduce PSCI Driver for AM62L
The AM62L is a lite, low power and performance optimized family of
application processors that are built for Linux application development
There is enough deviation from the previous K3 gen SoCs with regards to
how the PSCI functionality looks like on this device. For example, it
no longer does reset or LPSC turn ON/OFF operations using any external
Device Management entity like it did earlier.
The actual power ON/OFF operations will be implemented in a later stage
once all the PM related drivers are integrated. Such places in this
driver have been marked as TODO and will be replaced with actual call
to power on/off the cores at a later point.
This warranted the addition of a new am62l_psci driver for the AM62L
family of devices. For further details of how this new device looks
like, refer to the TRM [1].
[1] https://www.ti.com/lit/pdf/sprujb4
Change-Id: Ic53096e7bbc25fa55386ac4b6bef364dd6d0cf3b
Signed-off-by: Dhruva Gole <d-gole@ti.com>
diff --git a/plat/ti/k3low/common/am62l_psci.c b/plat/ti/k3low/common/am62l_psci.c
new file mode 100644
index 0000000..761d30f
--- /dev/null
+++ b/plat/ti/k3low/common/am62l_psci.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2025, Texas Instruments Incorporated - https://www.ti.com/
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+
+#include <arch_helpers.h>
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <lib/el3_runtime/cpu_data.h>
+#include <lib/mmio.h>
+#include <lib/psci/psci.h>
+#include <plat/common/platform.h>
+#include <ti_sci.h>
+#include <ti_sci_protocol.h>
+
+#include <k3_gicv3.h>
+#include <platform_def.h>
+
+uintptr_t am62l_sec_entrypoint;
+uintptr_t am62l_sec_entrypoint_glob;
+void __aligned(16) jump_to_atf_func(void *unused);
+
+static int am62l_pwr_domain_on(u_register_t mpidr)
+{
+ int32_t core, ret;
+ uint8_t proc_id;
+
+ core = plat_core_pos_by_mpidr(mpidr);
+ if (core < 0) {
+ ERROR("Could not get target core id: %d\n", core);
+ ret = PSCI_E_INTERN_FAIL;
+ }
+
+ proc_id = (uint8_t)(PLAT_PROC_START_ID + (uint32_t)core);
+
+ ret = ti_sci_proc_request(proc_id);
+ if (ret != 0) {
+ ERROR("Request for processor ID 0x%x failed: %d\n",
+ proc_id, ret);
+ ret = PSCI_E_INTERN_FAIL;
+ }
+
+ if (ret != PSCI_E_INTERN_FAIL) {
+ ret = ti_sci_proc_set_boot_cfg(proc_id, am62l_sec_entrypoint, 0, 0);
+ if (ret != 0) {
+ ERROR("Request to set core boot address failed: %d\n", ret);
+ ret = PSCI_E_INTERN_FAIL;
+ }
+ }
+
+ if (ret != PSCI_E_INTERN_FAIL) {
+ /* sanity check these are off before starting a core */
+ ret = ti_sci_proc_set_boot_ctrl(proc_id,
+ 0, PROC_BOOT_CTRL_FLAG_ARMV8_L2FLUSHREQ |
+ PROC_BOOT_CTRL_FLAG_ARMV8_AINACTS |
+ PROC_BOOT_CTRL_FLAG_ARMV8_ACINACTM);
+ if (ret != 0) {
+ ERROR("Request to clear boot config failed: %d\n", ret);
+ ret = PSCI_E_INTERN_FAIL;
+ }
+ }
+
+ if (ret != PSCI_E_INTERN_FAIL) {
+ /*
+ * TODO: Add the actual PM operation call
+ * to turn on the core here
+ */
+ ret = PSCI_E_SUCCESS;
+ }
+
+ return ret;
+}
+
+static void am62l_pwr_domain_off(const psci_power_state_t *target_state)
+{
+ /* At very least the local core should be powering down */
+ assert(((target_state)->pwr_domain_state[MPIDR_AFFLVL0]) == PLAT_MAX_OFF_STATE);
+
+ /* Prevent interrupts from spuriously waking up this cpu */
+ k3_gic_cpuif_disable();
+
+}
+
+static void am62l_pwr_down_domain(const psci_power_state_t *target_state)
+{
+ /* TODO: Add the actual pm operation call to turn off the core */
+}
+
+void am62l_pwr_domain_on_finish(const psci_power_state_t *target_state)
+{
+ k3_gic_pcpu_init();
+ k3_gic_cpuif_enable();
+}
+
+static void am62l_system_reset(void)
+{
+ mmio_write_32(WKUP_CTRL_MMR0_DEVICE_MANAGEMENT_BASE + WKUP_CTRL_MMR0_DEVICE_RESET_OFFSET,
+ 0x6);
+
+ /* Wait for reset to complete for 500ms before printing error */
+ mdelay(500);
+
+ /* Ideally we should not reach here */
+ ERROR("%s: Failed to reset device\n", __func__);
+}
+
+static plat_psci_ops_t am62l_plat_psci_ops = {
+ .pwr_domain_on = am62l_pwr_domain_on,
+ .pwr_domain_off = am62l_pwr_domain_off,
+ .pwr_domain_pwr_down = am62l_pwr_down_domain,
+ .pwr_domain_on_finish = am62l_pwr_domain_on_finish,
+ .system_reset = am62l_system_reset,
+};
+
+void __aligned(16) jump_to_atf_func(void *unused)
+{
+ /*
+ * MISRA Deviation observed:
+ * Rule 11.1 (MISRA C:2012) Prohibits conversion performed between a
+ * pointer to a function and another incompatible type.
+ * This conversion is required for handling secure boot entry points.
+ * The conversion is safe as the address is verified before execution.
+ */
+ void (*bl31_loc_warm_entry)(void) = (void *)am62l_sec_entrypoint_glob;
+
+ bl31_loc_warm_entry();
+}
+
+int plat_setup_psci_ops(uintptr_t sec_entrypoint,
+ const plat_psci_ops_t **psci_ops)
+{
+ am62l_sec_entrypoint_glob = sec_entrypoint;
+ /* Note that boot vector reg in sec mmr requires 16B aligned start address */
+ am62l_sec_entrypoint = (uint64_t)(void *)&jump_to_atf_func;
+ VERBOSE("am62l_sec_entrypoint = 0x%lx\n", am62l_sec_entrypoint);
+
+ *psci_ops = &am62l_plat_psci_ops;
+
+ return 0;
+}