arm64: zynqmp: spl: install a PMU firmware config object at runtime
authorLuca Ceresoli <luca@lucaceresoli.net>
Tue, 21 May 2019 16:06:43 +0000 (18:06 +0200)
committerMichal Simek <michal.simek@xilinx.com>
Tue, 30 Jul 2019 08:20:06 +0000 (10:20 +0200)
Optionally allow U-Boot to load a configuration object into the Power
Management Unit (PMU) firmware on Xilinx ZynqMP.

The configuration object is required by the PMU FW to enable most SoC
peripherals. So far the only way to boot using U-Boot SPL was to hard-code
the configuration object in the PMU firmware. Allow a different boot
process, where the PMU FW is equal for any ZynqMP chip and its
configuration is passed at runtime by U-Boot SPL.

All the code for Inter-processor communication with the PMU is isolated in
a new file (pmu_ipc.c). The code is inspired by the same feature as
implemented in the Xilinx First Stage Bootloader (FSBL) and Arm Trusted
Firmware:

 * https://github.com/Xilinx/embeddedsw/blob/fb647e6b4c00f5154eba52a88b948195b6f1dc2b/lib/sw_apps/zynqmp_fsbl/src/xfsbl_misc_drivers.c#L295
 * https://github.com/ARM-software/arm-trusted-firmware/blob/c48d02bade88b07fa7f43aa44e5217f68e5d047f/plat/xilinx/zynqmp/pm_service/pm_api_sys.c#L357

SPL logs on the console before loading the configuration object:

  U-Boot SPL 2019.07-rc1-00511-gaec224515c87 (May 15 2019 - 08:43:41 +0200)
  Loading PMUFW cfg obj (2008 bytes)
  EL Level: EL3
  ...

Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
arch/arm/mach-zynqmp/Kconfig
arch/arm/mach-zynqmp/Makefile
arch/arm/mach-zynqmp/include/mach/sys_proto.h
arch/arm/mach-zynqmp/pmu_ipc.c [new file with mode: 0644]
board/xilinx/zynqmp/Makefile
board/xilinx/zynqmp/pm_cfg_obj.S [new file with mode: 0644]
board/xilinx/zynqmp/pm_cfg_obj.h [new file with mode: 0644]
board/xilinx/zynqmp/zynqmp.c

index 9bb5a5c20201dc1f09fccbf13d1b2e8ebe69adbe..6cf17eb94e112b991ea42a4e6d5d1fe97e131fb1 100644 (file)
@@ -65,6 +65,24 @@ config PMUFW_INIT_FILE
          Include external PMUFW (Platform Management Unit FirmWare) to
          a Xilinx bootable image (boot.bin).
 
+config ZYNQMP_SPL_PM_CFG_OBJ_FILE
+       string "PMU firmware configuration object to load at runtime by SPL"
+       depends on SPL
+       help
+         Path to a binary PMU firmware configuration object to be linked
+         into U-Boot SPL and loaded at runtime into the PMU firmware.
+
+         The ZynqMP Power Management Unit (PMU) needs a configuration
+         object for most SoC peripherals to work. To have it loaded by
+         U-Boot SPL set here the file name (absolute path or relative to
+         the top source tree) of your configuration, which must be a
+         binary blob. It will be linked in the SPL binary and loaded
+         into the PMU firmware by U-Boot SPL during board
+         initialization.
+
+         Leave this option empty if your PMU firmware has a hard-coded
+         configuration object or you are loading it by any other means.
+
 config ZYNQMP_USB
        bool "Configure ZynqMP USB"
 
index 8a3b0747244aa17c347b203a01f144e793dd64f9..f3765e45b1b90337ba8c455c3c9b3b83a681343a 100644 (file)
@@ -8,3 +8,7 @@ obj-y   += cpu.o
 obj-$(CONFIG_MP)       += mp.o
 obj-$(CONFIG_SPL_BUILD) += spl.o handoff.o
 obj-$(CONFIG_ZYNQMP_PSU_INIT_ENABLED)  += psu_spl_init.o
+
+ifneq ($(CONFIG_ZYNQMP_SPL_PM_CFG_OBJ_FILE),"")
+obj-$(CONFIG_SPL_BUILD) += pmu_ipc.o
+endif
index 385c8825f2f6e735c86fedf0658d94ae86553a8b..915badc6fbee702ea93a208f8d4301191ded2f39 100644 (file)
@@ -72,4 +72,6 @@ int chip_id(unsigned char id);
 void tcm_init(u8 mode);
 #endif
 
+void zynqmp_pmufw_load_config_object(const void *cfg_obj, size_t size);
+
 #endif /* _ASM_ARCH_SYS_PROTO_H */
diff --git a/arch/arm/mach-zynqmp/pmu_ipc.c b/arch/arm/mach-zynqmp/pmu_ipc.c
new file mode 100644 (file)
index 0000000..d8858ea
--- /dev/null
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Inter-Processor Communication with the Platform Management Unit (PMU)
+ * firmware.
+ *
+ * (C) Copyright 2019 Luca Ceresoli
+ * Luca Ceresoli <luca@lucaceresoli.net>
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch/sys_proto.h>
+
+/* IPI bitmasks, register base and register offsets */
+#define IPI_BIT_MASK_APU      0x00001
+#define IPI_BIT_MASK_PMU0     0x10000
+#define IPI_REG_BASE_APU      0xFF300000
+#define IPI_REG_BASE_PMU0     0xFF330000
+#define IPI_REG_OFFSET_TRIG   0x00
+#define IPI_REG_OFFSET_OBR    0x04
+
+/* IPI mailbox buffer offsets */
+#define IPI_BUF_BASE_APU               0xFF990400
+#define IPI_BUF_OFFSET_TARGET_PMU      0x1C0
+#define IPI_BUF_OFFSET_REQ             0x00
+#define IPI_BUF_OFFSET_RESP            0x20
+
+#define PMUFW_PAYLOAD_ARG_CNT          8
+
+/* PMUFW commands */
+#define PMUFW_CMD_SET_CONFIGURATION    2
+
+static void pmu_ipc_send_request(const u32 *req, size_t req_len)
+{
+       u32 *mbx = (u32 *)(IPI_BUF_BASE_APU +
+                          IPI_BUF_OFFSET_TARGET_PMU +
+                          IPI_BUF_OFFSET_REQ);
+       size_t i;
+
+       for (i = 0; i < req_len; i++)
+               writel(req[i], &mbx[i]);
+}
+
+static void pmu_ipc_read_response(unsigned int *value, size_t count)
+{
+       u32 *mbx = (u32 *)(IPI_BUF_BASE_APU +
+                          IPI_BUF_OFFSET_TARGET_PMU +
+                          IPI_BUF_OFFSET_RESP);
+       size_t i;
+
+       for (i = 0; i < count; i++)
+               value[i] = readl(&mbx[i]);
+}
+
+/**
+ * Send request to PMU and get the response.
+ *
+ * @req:        Request buffer. Byte 0 is the API ID, other bytes are optional
+ *              parameters.
+ * @req_len:    Request length in number of 32-bit words.
+ * @res:        Response buffer. Byte 0 is the error code, other bytes are
+ *              optional parameters. Optional, if @res_maxlen==0 the parameters
+ *              will not be read.
+ * @res_maxlen: Space allocated for the response in number of 32-bit words.
+ *
+ * @return Error code returned by the PMU (i.e. the first word of the response)
+ */
+static int pmu_ipc_request(const u32 *req, size_t req_len,
+                          u32 *res, size_t res_maxlen)
+{
+       u32 status;
+
+       if (req_len > PMUFW_PAYLOAD_ARG_CNT ||
+           res_maxlen > PMUFW_PAYLOAD_ARG_CNT)
+               return -EINVAL;
+
+       pmu_ipc_send_request(req, req_len);
+
+       /* Raise Inter-Processor Interrupt to PMU and wait for response */
+       writel(IPI_BIT_MASK_PMU0, IPI_REG_BASE_APU + IPI_REG_OFFSET_TRIG);
+       do {
+               status = readl(IPI_REG_BASE_APU + IPI_REG_OFFSET_OBR);
+       } while (status & IPI_BIT_MASK_PMU0);
+
+       pmu_ipc_read_response(res, res_maxlen);
+
+       return 0;
+}
+
+/**
+ * Send a configuration object to the PMU firmware.
+ *
+ * @cfg_obj: Pointer to the configuration object
+ * @size:    Size of @cfg_obj in bytes
+ */
+void zynqmp_pmufw_load_config_object(const void *cfg_obj, size_t size)
+{
+       const u32 request[] = {
+               PMUFW_CMD_SET_CONFIGURATION,
+               (u32)((u64)cfg_obj)
+       };
+       u32 response;
+       int err;
+
+       printf("Loading PMUFW cfg obj (%ld bytes)\n", size);
+
+       err = pmu_ipc_request(request,  ARRAY_SIZE(request), &response, 1);
+       if (err)
+               panic("Cannot load PMUFW configuration object (%d)\n", err);
+       if (response != 0)
+               panic("PMUFW returned 0x%08x status!\n", response);
+}
index 80f8ca7e1e4be798c82b444c4b1d084cd7f9f964..b4d39edc118f93b70fdca24896c3e0020e41ada7 100644 (file)
@@ -33,6 +33,11 @@ ifneq ($(call ifdef_any_of, CONFIG_ZYNQMP_PSU_INIT_ENABLED CONFIG_SPL_BUILD),)
 obj-y += $(init-objs)
 endif
 
+ifneq ($(CONFIG_ZYNQMP_SPL_PM_CFG_OBJ_FILE),"")
+obj-$(CONFIG_SPL_BUILD) += pm_cfg_obj.o
+$(obj)/pm_cfg_obj.o: $(shell cd $(srctree); readlink -f $(CONFIG_ZYNQMP_SPL_PM_CFG_OBJ_FILE)) FORCE
+endif
+
 obj-$(CONFIG_MMC_SDHCI_ZYNQ) += tap_delays.o
 
 ifndef CONFIG_SPL_BUILD
diff --git a/board/xilinx/zynqmp/pm_cfg_obj.S b/board/xilinx/zynqmp/pm_cfg_obj.S
new file mode 100644 (file)
index 0000000..c4ca77e
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+.section .rodata
+
+.global zynqmp_pm_cfg_obj
+.type   zynqmp_pm_cfg_obj, @object
+.global zynqmp_pm_cfg_obj_size
+.type   zynqmp_pm_cfg_obj_size, @object
+
+zynqmp_pm_cfg_obj:
+.align 4
+.incbin CONFIG_ZYNQMP_SPL_PM_CFG_OBJ_FILE
+
+zynqmp_pm_cfg_obj_end:
+
+zynqmp_pm_cfg_obj_size:
+.int zynqmp_pm_cfg_obj_end - zynqmp_pm_cfg_obj
diff --git a/board/xilinx/zynqmp/pm_cfg_obj.h b/board/xilinx/zynqmp/pm_cfg_obj.h
new file mode 100644 (file)
index 0000000..86e7854
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2019 Luca Ceresoli <luca@lucaceresoli.net>
+ *
+ * Declaration of PMU config object binary blob linked in at build time.
+ */
+
+extern const u32 zynqmp_pm_cfg_obj[];
+extern const int zynqmp_pm_cfg_obj_size;
index c840e92d9c42e5aca559828d6af75fef0af09022..057ca1fbf7a015fcd568b8ca2fc147f91f14a229 100644 (file)
@@ -22,6 +22,8 @@
 #include <zynqmppl.h>
 #include <g_dnl.h>
 
+#include "pm_cfg_obj.h"
+
 DECLARE_GLOBAL_DATA_PTR;
 
 #if defined(CONFIG_FPGA) && defined(CONFIG_FPGA_ZYNQMPPL) && \
@@ -327,6 +329,13 @@ int board_early_init_f(void)
 
 int board_init(void)
 {
+#if defined(CONFIG_SPL_BUILD)
+       /* Check *at build time* if the filename is an non-empty string */
+       if (sizeof(CONFIG_ZYNQMP_SPL_PM_CFG_OBJ_FILE) > 1)
+               zynqmp_pmufw_load_config_object(zynqmp_pm_cfg_obj,
+                                               zynqmp_pm_cfg_obj_size);
+#endif
+
        printf("EL Level:\tEL%d\n", current_el());
 
 #if defined(CONFIG_FPGA) && defined(CONFIG_FPGA_ZYNQMPPL) && \