Add basic SPL infrastructure for IPQ5210 SoC. This handles basic serial console init, identifying the boot media, loading the additional firmware binaries to setup DDR, TFA and eventually jump to U-Boot.
Signed-off-by: Varadarajan Narayanan <[email protected]> --- v4: Move MMU disable to save_boot_params() Get PBL shared info to U-Boot and identify boot medium Move maybe unused symbol inside #if IS_ENABLED(...) v3: Move SMEM updates to separate patch Loop upto if_tbl->num_entries instead of MAX_ENTRIES Remove invalid 'if (!fit)' check Return failure if qclib_post_process_from_spl fails v2: Remove couple of unused local variables --- arch/arm/Kconfig | 5 +- arch/arm/mach-snapdragon/Makefile | 3 + arch/arm/mach-snapdragon/spl.c | 678 ++++++++++++++++++++++++++++++ 3 files changed, 684 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-snapdragon/spl.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 514bf2000b4..371916cf9b4 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1147,12 +1147,13 @@ config ARCH_SNAPDRAGON select SPMI select BOARD_LATE_INIT select OF_BOARD - select SAVE_PREV_BL_FDT_ADDR if !ENABLE_ARM_SOC_BOOT0_HOOK - select LINUX_KERNEL_IMAGE_HEADER if !ENABLE_ARM_SOC_BOOT0_HOOK + select SAVE_PREV_BL_FDT_ADDR if !ENABLE_ARM_SOC_BOOT0_HOOK && !SPL + select LINUX_KERNEL_IMAGE_HEADER if !ENABLE_ARM_SOC_BOOT0_HOOK && !SPL select SYSRESET select SYSRESET_PSCI select ANDROID_BOOT_IMAGE_IGNORE_BLOB_ADDR select MMU_PGPROT + select SUPPORT_SPL imply OF_UPSTREAM imply CMD_DM imply DM_USB_GADGET diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index 343e825c6fd..70a2ce585f2 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -2,6 +2,9 @@ # # (C) Copyright 2015 Mateusz Kulikowski <[email protected]> +ifndef CONFIG_XPL_BUILD obj-y += board.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o obj-$(CONFIG_OF_LIVE) += of_fixup.o +endif +obj-$(CONFIG_SPL_BUILD) += spl.o diff --git a/arch/arm/mach-snapdragon/spl.c b/arch/arm/mach-snapdragon/spl.c new file mode 100644 index 00000000000..73e8ca5d459 --- /dev/null +++ b/arch/arm/mach-snapdragon/spl.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include <hang.h> +#include <cpu_func.h> +#include <init.h> +#include <image.h> +#include <spl.h> +#include <spl_load.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <atf_common.h> +#include <linux/err.h> +#include <dm/device-internal.h> +#include <part.h> +#include <blk.h> +#include <dm/uclass.h> +#include "qcom-priv.h" + +DECLARE_GLOBAL_DATA_PTR; + +#define QCOM_SPL_TCSR_REG_ADDR 0x195c100 +#define QCOM_SPL_DLOAD_MASK BIT(4) +#define QCOM_SPL_DLOAD_SHFT 0x4 + +#define QCOM_SPL_IS_DLOAD_BIT_SET ((readl(QCOM_SPL_TCSR_REG_ADDR) & \ + QCOM_SPL_DLOAD_MASK) >> \ + QCOM_SPL_DLOAD_SHFT) + +#define QCOM_SPL_FIT_IMG_PARTITION "0:BOOTLDR" + +#define MAGIC_KEY "QCLIB_CB" +#define MAX_ENTRIES 0xF +#define IF_TABLE_VERSION 0x1 +#define QCCONFIG "qc_config" +#define QCSDI "qcsdi" + +/** + * struct interface_table_entry - Meta data for blobs in QCLIB interface + * @entry_name: Name of the data blob (e.g., "dcb_settings"). + * @address: Address of the data blob. + * @size: Size of the data blob. + * @attributes: Attributes for the blob (e.g., save to storage). + */ +struct interface_table_entry { + char entry_name[24]; + u64 address; + u32 size; + u32 attributes; +}; + +/** + * struct interface_table - QCLIB Interface table header + * @magic_key: Magic key for validation ("QCLIB_CB"). + * @version: Interface table version. + * @num_entries: Number of valid entries. + * @max_entries: Maximum allowable entries. + * @global_attributes: Flags for global attributes (e.g., SDI path). + * @reserved1: Reserved for future use. + * @reserved2: Reserved for future use. + * @if_table_entries: Array of interface table entries. + */ +struct interface_table { + char magic_key[8]; + u32 version; + u32 num_entries; + u32 max_entries; + u32 global_attributes; + u32 reserved1; + u32 reserved2; + struct interface_table_entry if_table_entries[MAX_ENTRIES]; +}; + +/** + * qcom_spl_jump_img_entry_t - Type definition for image entry point functions. + * @arg1: First argument passed to the entry point. + * @arg2: Second argument passed to the entry point. + */ +typedef void (*qcom_spl_jump_img_entry_t)(void *arg1, void *arg2); + +/* + * Global QCSDI address populated by qclib_post_process_from_spl + * Placed in .data section to ensure it persists + */ +static u64 g_qcsdi_address __section(".data"); + +/** + * lowlevel_init() - Early low-level initialization. + * + * This function performs very early hardware initialization, + * specifically disabling the MMU if enabled by PBL. + */ +void lowlevel_init(void) +{ +} + +/** + * qcom_spl_error_handler() - Centralized SPL error handler. + * @arg: Generic argument (unused). + * + * This function is invoked upon critical errors during the SPL boot process. + */ +void qcom_spl_error_handler(void *arg) +{ + pr_err("Entered the SPL Error Handler\n"); + hang(); +} + +/** + * qcom_spl_malloc_init_f() - Initialize malloc for SPL. + * + * This function initializes the malloc subsystem using the memory region + */ +void qcom_spl_malloc_init_f(void) +{ + if (!CONFIG_IS_ENABLED(SYS_MALLOC_F)) + return; + /* + * Set up by crt0.S + */ + assert(gd->malloc_base); + gd->malloc_limit = CONFIG_VAL(SYS_MALLOC_F_LEN); + gd->malloc_ptr = 0; + + mem_malloc_init(gd->malloc_base, gd->malloc_limit); + gd->flags |= GD_FLG_FULL_MALLOC_INIT; +} + +/** + * qcom_spl_get_fit_img_entry_point() - Get entry point from FIT image node. + * @fit: Pointer to the FIT image blob. + * @node: Node ID within the FIT image. + * @entry_point: Pointer to store the retrieved entry point. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int qcom_spl_get_fit_img_entry_point(void *fit, int node, + u64 *entry_point) +{ + int ret; + + if (!fit) { + pr_err("FIT image blob is NULL\n"); + return -EINVAL; + } + if (node <= 0) { + pr_err("Invalid FIT node ID %d\n", node); + return -EINVAL; + } + if (!entry_point) { + pr_err("Entry point pointer is NULL\n"); + return -EINVAL; + } + + ret = fit_image_get_entry(fit, node, (ulong *)entry_point); + if (ret) { + pr_debug("No entry point for node %d, trying load address\n", + node); + ret = fit_image_get_load(fit, node, (ulong *)entry_point); + if (ret) + pr_err("No load address for node %d (%d)\n", node, ret); + } + + return ret; +} + +/** + * qcom_spl_get_iftbl_entry_by_name() - Get an interface table entry by name. + * @if_tbl: Pointer to the QCLIB interface table. + * @name: Name of the entry to find. + * @entry: Pointer to a buffer where the found entry will be copied. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int qcom_spl_get_iftbl_entry_by_name(struct interface_table *if_tbl, + char *name, + struct interface_table_entry *entry) +{ + uint uc_index; + + if (!if_tbl) { + pr_err("Invalid interface table\n"); + return -EINVAL; + } + if (!name) { + pr_err("Invalid name\n"); + return -EINVAL; + } + if (!entry) { + pr_err("Invalid entry pointer\n"); + return -EINVAL; + } + + for (uc_index = 0; uc_index < if_tbl->num_entries; uc_index++) { + if (!strcmp(if_tbl->if_table_entries[uc_index].entry_name, name)) { + memcpy(entry, + &if_tbl->if_table_entries[uc_index], + sizeof(struct interface_table_entry)); + return 0; + } + } + pr_err("Interface table entry '%s' not found\n", name); + + return -ENOENT; +} + +/** + * qclib_post_process_from_spl() - Post-process QCLIB image from SPL FIT address + * + * This function performs the same operations as qclib_post_process() but + * takes no arguments. It gets the FIT image from CONFIG_SPL_LOAD_FIT_ADDRESS + * and finds the qcom-lib-1 node automatically. + * + * Return: 0 on success, or a negative error code on failure. + */ +int qclib_post_process_from_spl(void) +{ + int ret; + int entry_idx; + int images_node; + int qcconfig_node; + int qclib_node; + const void *fit; + struct interface_table if_tbl; + struct interface_table_entry qcsdi_entry; + qcom_spl_jump_img_entry_t qclib_entry; + u64 entry_point; + + /* Get FIT image from SPL load address */ + fit = (const void *)CONFIG_SPL_LOAD_FIT_ADDRESS; + + pr_debug("QCLIB post-processing from SPL: fit=%p\n", fit); + + /* + * Find "images" node in FIT (get it once and reuse) + */ + images_node = fdt_subnode_offset(fit, 0, "images"); + if (images_node < 0) { + pr_err("Failed to find images node in FIT\n"); + return -ENOENT; + } + + /* + * Find "qcom-config-1" image node + */ + qcconfig_node = fdt_subnode_offset(fit, images_node, "qcom-config-1"); + if (qcconfig_node < 0) { + pr_err("Failed to find qcom-config-1 node in FIT\n"); + return -ENOENT; + } + + /* + * Find "qcom-lib-1" image node + */ + qclib_node = fdt_subnode_offset(fit, images_node, "qcom-lib-1"); + if (qclib_node < 0) { + pr_err("Failed to find qcom-lib-1 node in FIT\n"); + return -ENOENT; + } + + /* + * Initialize the local interface table + */ + memset(&if_tbl, 0, sizeof(struct interface_table)); + memcpy(if_tbl.magic_key, MAGIC_KEY, strlen(MAGIC_KEY)); + + if_tbl.version = IF_TABLE_VERSION; + if_tbl.num_entries = 0; + if_tbl.max_entries = MAX_ENTRIES; + + /* + * Add QCCONFIG entry to the interface table + */ + entry_idx = 0; + memcpy(if_tbl.if_table_entries[entry_idx].entry_name, + QCCONFIG, strlen(QCCONFIG)); + + ret = qcom_spl_get_fit_img_entry_point((void *)fit, + qcconfig_node, + &if_tbl.if_table_entries[entry_idx].address); + if (ret) { + pr_err("Failed to get qcom-config-1 entry point (%d)\n", ret); + return ret; + } + if_tbl.if_table_entries[entry_idx].attributes = 0; + if_tbl.num_entries = entry_idx + 1; + + /* + * Add QCSDI entry to the interface table + */ + entry_idx++; + memcpy(if_tbl.if_table_entries[entry_idx].entry_name, + QCSDI, strlen(QCSDI)); + + if_tbl.if_table_entries[entry_idx].address = 0; + if_tbl.if_table_entries[entry_idx].attributes = 0; + if_tbl.num_entries = entry_idx + 1; + + /* + * Get qcom-lib-1 entry point + */ + ret = qcom_spl_get_fit_img_entry_point((void *)fit, + qclib_node, + &entry_point); + if (ret) { + pr_err("Failed to get qcom-lib-1 entry point (%d)\n", ret); + return ret; + } + + qclib_entry = (qcom_spl_jump_img_entry_t)entry_point; + + pr_info("Jumping to qcom-lib-1 at 0x%llx\n", entry_point); + qclib_entry(&if_tbl, NULL); + + /* Parse the interface table to extract QCSDI address */ + ret = qcom_spl_get_iftbl_entry_by_name(&if_tbl, QCSDI, &qcsdi_entry); + if (ret) { + pr_err("Failed to get QCSDI entry from interface table (%d)\n", ret); + return ret; + } + + g_qcsdi_address = qcsdi_entry.address; + pr_info("QCSDI address: 0x%llx\n", g_qcsdi_address); + + return 0; +} + +/** + * spl_get_load_buffer() - Allocate a cache-aligned buffer for image loading. + * @offset: Offset (unused, typically 0 for SPL). + * @size: Size of the buffer to allocate. + * + * Return: Pointer to the allocated buffer, or NULL on failure. + */ +struct legacy_img_hdr *spl_get_load_buffer(ssize_t offset, size_t size) +{ + return (void *)(CONFIG_SPL_LOAD_FIT_ADDRESS); +} + +/** + * board_spl_fit_buffer_addr() - Get the address of the FIT image buffer. + * @fit_size: Size of the FIT image. + * @sectors: Number of sectors. + * @bl_len: Block length. + * + * Return: Address of the FIT image buffer. + */ +void *board_spl_fit_buffer_addr(ulong fit_size, int sectors, int bl_len) +{ + return spl_get_load_buffer(0, sectors * bl_len); +} + +/** + * bl2_plat_get_bl31_params_v2() - Retrieve and fixup BL31 parameters. + * @bl32_entry: Entry point for BL32 (OP-TEE). + * @bl33_entry: Entry point for BL33 (U-Boot/kernel). + * @fdt_addr: Address of the Device Tree Blob (FDT). + * + * Return: Pointer to the populated BL31 parameters structure. + */ +struct bl_params *bl2_plat_get_bl31_params_v2(uintptr_t bl32_entry, + uintptr_t bl33_entry, + uintptr_t fdt_addr) +{ + struct bl_params *bl_params; + struct bl_params_node *node; + + /* + * Populate the bl31 params with default values. + */ + bl_params = bl2_plat_get_bl31_params_v2_default(bl32_entry, bl33_entry, + fdt_addr); + + /* + * Fixup the bl31 params based on platform requirements. + */ + for_each_bl_params_node(bl_params, node) { + if (node->image_id == ATF_BL31_IMAGE_ID) { + /* + * Pass QCSDI address to BL31 via arg0 + * This address was populated by qclib_post_process() + */ + if (g_qcsdi_address == 0) + pr_warn("QCSDI address not set, BL31 may not function correctly\n"); + + node->ep_info->args.arg0 = g_qcsdi_address; + pr_debug("Setting BL31 arg0 to QCSDI address: 0x%llx\n", g_qcsdi_address); + } + } + + return bl_params; +} + +/** + * qcom_spl_loader_pre_ddr() - SPL loader for pre-DDR stage. + * @boot_device:Type of boot device. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int qcom_spl_loader_pre_ddr(u8 boot_device) +{ + struct spl_image_loader *loader, *drv; + struct spl_image_info spl_image = { 0 }; + struct spl_boot_device boot_dev = { .boot_device = boot_device, }; + int ret = -ENODEV, n_ents; + + drv = ll_entry_start(struct spl_image_loader, spl_image_loader); + n_ents = ll_entry_count(struct spl_image_loader, spl_image_loader); + + for (loader = drv; loader && (loader != drv + n_ents); loader++) { + if (boot_device != loader->boot_device) + continue; + + ret = loader->load_image(&spl_image, &boot_dev); + if (!ret) + break; + + printf("%s: Error: %d\n", __func__, ret); + } + + return ret; +} + +#if CONFIG_IS_ENABLED(MMC) +/** + * spl_find_partition_info() - Find partition information by name + * @uclass_id: Device class ID (UCLASS_MMC) + * @device_num: Device number within the class + * @part_name: Name of the partition to find + * @info: Pointer to store partition information + * + * This function provides partition lookup logic for MMC. + * Return: Partition number on success, negative error code on failure + */ +static int spl_find_partition_info(enum uclass_id uclass_id, int device_num, + const char *part_name, + struct disk_partition *info) +{ + int ret; + struct blk_desc *desc; + + if (!part_name || !info) { + printf("Invalid parameters for partition lookup\n"); + return -EINVAL; + } + + /* + * Get block device descriptor + */ + desc = blk_get_devnum_by_uclass_id(uclass_id, device_num); + if (!desc) { + printf("Block device not found for class %d, device %d\n", + uclass_id, device_num); + return -ENODEV; + } + + /* + * Initialize partition table if needed + */ + if (desc->part_type == PART_TYPE_UNKNOWN) { + printf("Initializing partition table\n"); + /* + * Prefer EFI/GPT + */ + desc->part_type = PART_TYPE_EFI; + } + + /* + * Find partition by name + */ + ret = part_get_info_by_name(desc, part_name, info); + if (ret < 0) { + printf("Partition '%s' not found\n", part_name); + return -ENOENT; + } + + printf("Found partition '%s' at partition number %d\n", part_name, ret); + return ret; +} + +/** + * spl_mmc_boot_mode() - Determine the boot mode for MMC + * @mmc: Pointer to the MMC device + * @boot_device: Boot device ID + * + * Return: MMCSD_MODE_RAW to use raw partition access + */ +u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device) +{ + return MMCSD_MODE_RAW; +} + +/** + * spl_mmc_boot_partition() - Determine which partition to boot from + * @boot_device: Boot device ID + * + * Return: Partition number to boot from, or default partition on error + */ +int spl_mmc_boot_partition(const u32 boot_device) +{ + int ret; + struct disk_partition info; + + /* + * Use common partition lookup function + */ + ret = spl_find_partition_info(UCLASS_MMC, 0, QCOM_SPL_FIT_IMG_PARTITION, &info); + if (ret < 0) { + printf("Using default MMC partition %d\n", + CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION); + return CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION; + } + + return ret; +} + +/** + * spl_mmc_get_uboot_raw_sector() - Find the raw sector offset + * @mmc: Pointer to the MMC device + * @raw_sect: Sector + * + * Return: 0 if the image is at the starting of the partition without any offset. + */ +unsigned long spl_mmc_get_uboot_raw_sector(struct mmc *mmc, ulong raw_sect) +{ + return 0; +} +#endif /* CONFIG_IS_ENABLED(MMC) */ + +static struct pbl_shared_data g_psd __section(".data"); + +void save_boot_params(ulong r0, ulong r1, ulong r2, ulong r3) +{ + unsigned long sctlr; + struct pbl_shared_data *psd; + + sctlr = get_sctlr(); + set_sctlr(sctlr & ~(CR_M)); /* Disable MMU */ + + psd = (struct pbl_shared_data *)r0; + + if (!psd || psd->num_of_entries < PBL_SHARED_DATA_PARAM_MAX) + goto out; + + memcpy(&g_psd, psd, sizeof(g_psd)); + +out: + save_boot_params_ret(); +} + +/** + * spl_boot_device() - Determine the boot device. + * + * Return: The mapped boot device type, + * or BOOT_DEVICE_NONE if the device is invalid. + */ +u32 spl_boot_device(void) +{ + struct pbl_shared_data *psd = &g_psd; + +#ifdef DEBUG + for (int i = 0; psd && i < psd->num_of_entries; i++) { + printf("entry[0x%x] = %d 0x%08x %d\n", i, + psd->entry[i].param_id, psd->entry[i].value, + psd->entry[i].valid); + } +#endif + + if (psd->entry[PSD_ID_IS_EDL_MODE].valid && + psd->entry[PSD_ID_IS_EDL_MODE].value) { + printf("Selected boot device: DFU\n"); + return BOOT_DEVICE_DFU; + } + + if (psd->entry[PSD_ID_BOOT_MEDIA_TYPE].valid) { + switch (psd->entry[PSD_ID_BOOT_MEDIA_TYPE].value) { + case PSD_MMC_FLASH: + printf("Selected boot device: MMC\n"); + return BOOT_DEVICE_MMC1; + case PSD_NOR_FLASH: + printf("Selected boot device: NOR\n"); + return BOOT_DEVICE_NOR; + case PSD_NAND_FLASH: + printf("Selected boot device: NAND\n"); + return BOOT_DEVICE_NAND; + case PSD_UFS_FLASH: + printf("Selected boot device: UFS\n"); + return BOOT_DEVICE_UFS; + } + } + + pr_err("No boot device configured\n"); + return BOOT_DEVICE_NONE; +} + +#if defined(CONFIG_SPL_BUILD) +/** + * board_init_f() - Main entry point for SPL. + * @dummy: Dummy argument (unused). + */ +void board_init_f(ulong dummy) +{ + int ret; + + memset(__bss_start, 0, __bss_end - __bss_start); /* Clear BSS */ + + qcom_spl_malloc_init_f(); + + ret = spl_early_init(); + if (ret) { + pr_debug("spl_early_init() failed (%d)\n", ret); + goto fail; + } + + preloader_console_init(); + + ret = qcom_spl_loader_pre_ddr(spl_boot_device()); + if (ret) { + pr_debug("qcom_spl_loader_pre_ddr() failed (%d)\n", ret); + goto fail; + } + + ret = qclib_post_process_from_spl(); + if (ret) { + pr_debug("qclib_post_process_from_spl() failed (%d)\n", ret); + goto fail; + } + + board_init_r(NULL, 0); + +fail: + if (ret) + qcom_spl_error_handler(NULL); +} +#endif /* CONFIG_SPL_BUILD */ + +int board_fit_config_name_match(const char *name) +{ + /* + * SPL loads the pre-HLOS images from bootldr FIT image + * as below + * + * In board_init_f() - Matches "pre-ddr" configuration node and + * load the images mentioned in its <loadables> + * + * In board_init_r() - Matches "post-ddr" configuration node and + * load the images mentioned in its <loadables> + * + */ + if (!(gd->flags & GD_FLG_SPL_INIT)) { + if (!strcmp(name, "pre-ddr")) { + printf("Selected FIT Config: %s\n", name); + return 0; + } + } else { + if (!strcmp(name, "post-ddr")) { + printf("Selected FIT Config: %s\n", name); + return 0; + } + } + + return -EINVAL; +} + +int board_fdt_blob_setup(void **fdtp) +{ + return 0; +} + +void reset_cpu(void) +{ + /* + * Empty placeholder for arch/arm/lib/reset.c:do_reset(), + * to avoid "undefined reference to `reset_cpu'" + */ +} -- 2.34.1

