| /* |
| * Copyright (C) 2018 Marvell International Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * https://spdx.org/licenses |
| */ |
| |
| #include <assert.h> |
| |
| #include <platform_def.h> |
| |
| #include <arch_helpers.h> |
| #include <common/debug.h> |
| #include <drivers/delay_timer.h> |
| #include <lib/mmio.h> |
| |
| #include <plat_pm_trace.h> |
| #include <mss_scp_bootloader.h> |
| #include <mss_ipc_drv.h> |
| #include <mss_mem.h> |
| #include <mss_scp_bl2_format.h> |
| |
| #define MSS_DMA_SRCBR(base) (base + 0xC0) |
| #define MSS_DMA_DSTBR(base) (base + 0xC4) |
| #define MSS_DMA_CTRLR(base) (base + 0xC8) |
| #define MSS_M3_RSTCR(base) (base + 0xFC) |
| |
| #define MSS_DMA_CTRLR_SIZE_OFFSET (0) |
| #define MSS_DMA_CTRLR_REQ_OFFSET (15) |
| #define MSS_DMA_CTRLR_REQ_SET (1) |
| #define MSS_DMA_CTRLR_ACK_OFFSET (12) |
| #define MSS_DMA_CTRLR_ACK_MASK (0x1) |
| #define MSS_DMA_CTRLR_ACK_READY (1) |
| #define MSS_M3_RSTCR_RST_OFFSET (0) |
| #define MSS_M3_RSTCR_RST_OFF (1) |
| |
| #define MSS_DMA_TIMEOUT 1000 |
| #define MSS_EXTERNAL_SPACE 0x50000000 |
| #define MSS_EXTERNAL_ADDR_MASK 0xfffffff |
| |
| #define DMA_SIZE 128 |
| |
| #define MSS_HANDSHAKE_TIMEOUT 50 |
| |
| #define MG_CM3_SRAM_BASE(CP) (MVEBU_CP_REGS_BASE(CP) + 0x100000) |
| |
| static int mss_check_image_ready(volatile struct mss_pm_ctrl_block *mss_pm_crtl) |
| { |
| int timeout = MSS_HANDSHAKE_TIMEOUT; |
| |
| /* Wait for SCP to signal it's ready */ |
| while ((mss_pm_crtl->handshake != MSS_ACKNOWLEDGMENT) && |
| (timeout-- > 0)) |
| mdelay(1); |
| |
| if (mss_pm_crtl->handshake != MSS_ACKNOWLEDGMENT) |
| return -1; |
| |
| mss_pm_crtl->handshake = HOST_ACKNOWLEDGMENT; |
| |
| return 0; |
| } |
| |
| static int mg_image_load(uintptr_t src_addr, uint32_t size, uintptr_t mg_regs) |
| { |
| if (size > MG_SRAM_SIZE) { |
| ERROR("image is too big to fit into MG CM3 memory\n"); |
| return 1; |
| } |
| |
| NOTICE("Loading MG image from address 0x%lx Size 0x%x to MG at 0x%lx\n", |
| src_addr, size, mg_regs); |
| |
| /* Copy image to MG CM3 SRAM */ |
| memcpy((void *)mg_regs, (void *)src_addr, size); |
| |
| /* |
| * Don't release MG CM3 from reset - it will be done by next step |
| * bootloader (e.g. U-Boot), when appriopriate device-tree setup (which |
| * has enabeld 802.3. auto-neg) will be choosen. |
| */ |
| |
| return 0; |
| } |
| |
| static int mss_image_load(uint32_t src_addr, uint32_t size, uintptr_t mss_regs) |
| { |
| uint32_t i, loop_num, timeout; |
| |
| /* Check if the img size is not bigger than ID-RAM size of MSS CM3 */ |
| if (size > MSS_IDRAM_SIZE) { |
| ERROR("image is too big to fit into MSS CM3 memory\n"); |
| return 1; |
| } |
| |
| NOTICE("Loading MSS image from addr. 0x%x Size 0x%x to MSS at 0x%lx\n", |
| src_addr, size, mss_regs); |
| /* load image to MSS RAM using DMA */ |
| loop_num = (size / DMA_SIZE) + (((size & (DMA_SIZE - 1)) == 0) ? 0 : 1); |
| |
| for (i = 0; i < loop_num; i++) { |
| /* write destination and source addresses */ |
| mmio_write_32(MSS_DMA_SRCBR(mss_regs), |
| MSS_EXTERNAL_SPACE | |
| ((src_addr & MSS_EXTERNAL_ADDR_MASK) + |
| (i * DMA_SIZE))); |
| mmio_write_32(MSS_DMA_DSTBR(mss_regs), (i * DMA_SIZE)); |
| |
| dsb(); /* make sure DMA data is ready before triggering it */ |
| |
| /* set the DMA control register */ |
| mmio_write_32(MSS_DMA_CTRLR(mss_regs), ((MSS_DMA_CTRLR_REQ_SET |
| << MSS_DMA_CTRLR_REQ_OFFSET) | |
| (DMA_SIZE << MSS_DMA_CTRLR_SIZE_OFFSET))); |
| |
| /* Poll DMA_ACK at MSS_DMACTLR until it is ready */ |
| timeout = MSS_DMA_TIMEOUT; |
| while (timeout) { |
| if ((mmio_read_32(MSS_DMA_CTRLR(mss_regs)) >> |
| MSS_DMA_CTRLR_ACK_OFFSET & MSS_DMA_CTRLR_ACK_MASK) |
| == MSS_DMA_CTRLR_ACK_READY) { |
| break; |
| } |
| |
| udelay(50); |
| timeout--; |
| } |
| |
| if (timeout == 0) { |
| ERROR("\nDMA failed to load MSS image\n"); |
| return 1; |
| } |
| } |
| |
| bl2_plat_configure_mss_windows(mss_regs); |
| |
| /* Release M3 from reset */ |
| mmio_write_32(MSS_M3_RSTCR(mss_regs), (MSS_M3_RSTCR_RST_OFF << |
| MSS_M3_RSTCR_RST_OFFSET)); |
| |
| NOTICE("Done\n"); |
| |
| return 0; |
| } |
| |
| /* Load image to MSS AP and do PM related initialization |
| * Note that this routine is different than other CM3 loading routines, because |
| * firmware for AP is dedicated for PM and therefore some additional PM |
| * initialization is required |
| */ |
| static int mss_ap_load_image(uintptr_t single_img, |
| uint32_t image_size, uint32_t ap_idx) |
| { |
| volatile struct mss_pm_ctrl_block *mss_pm_crtl; |
| int ret; |
| |
| /* TODO: add PM Control Info from platform */ |
| mss_pm_crtl = (struct mss_pm_ctrl_block *)MSS_SRAM_PM_CONTROL_BASE; |
| mss_pm_crtl->ipc_version = MV_PM_FW_IPC_VERSION; |
| mss_pm_crtl->num_of_clusters = PLAT_MARVELL_CLUSTER_COUNT; |
| mss_pm_crtl->num_of_cores_per_cluster = |
| PLAT_MARVELL_CLUSTER_CORE_COUNT; |
| mss_pm_crtl->num_of_cores = PLAT_MARVELL_CLUSTER_COUNT * |
| PLAT_MARVELL_CLUSTER_CORE_COUNT; |
| mss_pm_crtl->pm_trace_ctrl_base_address = AP_MSS_ATF_CORE_CTRL_BASE; |
| mss_pm_crtl->pm_trace_info_base_address = AP_MSS_ATF_CORE_INFO_BASE; |
| mss_pm_crtl->pm_trace_info_core_size = AP_MSS_ATF_CORE_INFO_SIZE; |
| VERBOSE("MSS Control Block = 0x%x\n", MSS_SRAM_PM_CONTROL_BASE); |
| VERBOSE("mss_pm_crtl->ipc_version = 0x%x\n", |
| mss_pm_crtl->ipc_version); |
| VERBOSE("mss_pm_crtl->num_of_cores = 0x%x\n", |
| mss_pm_crtl->num_of_cores); |
| VERBOSE("mss_pm_crtl->num_of_clusters = 0x%x\n", |
| mss_pm_crtl->num_of_clusters); |
| VERBOSE("mss_pm_crtl->num_of_cores_per_cluster = 0x%x\n", |
| mss_pm_crtl->num_of_cores_per_cluster); |
| VERBOSE("mss_pm_crtl->pm_trace_ctrl_base_address = 0x%x\n", |
| mss_pm_crtl->pm_trace_ctrl_base_address); |
| VERBOSE("mss_pm_crtl->pm_trace_info_base_address = 0x%x\n", |
| mss_pm_crtl->pm_trace_info_base_address); |
| VERBOSE("mss_pm_crtl->pm_trace_info_core_size = 0x%x\n", |
| mss_pm_crtl->pm_trace_info_core_size); |
| |
| /* TODO: add checksum to image */ |
| VERBOSE("Send info about the SCP_BL2 image to be transferred to SCP\n"); |
| |
| ret = mss_image_load(single_img, image_size, |
| bl2_plat_get_ap_mss_regs(ap_idx)); |
| if (ret != 0) { |
| ERROR("SCP Image load failed\n"); |
| return -1; |
| } |
| |
| /* check that the image was loaded successfully */ |
| ret = mss_check_image_ready(mss_pm_crtl); |
| if (ret != 0) |
| NOTICE("SCP Image doesn't contain PM firmware\n"); |
| |
| return 0; |
| } |
| |
| /* Load CM3 image (single_img) to CM3 pointed by cm3_type */ |
| static int load_img_to_cm3(enum cm3_t cm3_type, |
| uintptr_t single_img, uint32_t image_size) |
| { |
| int ret, ap_idx, cp_index; |
| uint32_t ap_count = bl2_plat_get_ap_count(); |
| |
| switch (cm3_type) { |
| case MSS_AP: |
| for (ap_idx = 0; ap_idx < ap_count; ap_idx++) { |
| NOTICE("Load image to AP%d MSS\n", ap_idx); |
| ret = mss_ap_load_image(single_img, image_size, ap_idx); |
| if (ret != 0) |
| return ret; |
| } |
| break; |
| case MSS_CP0: |
| case MSS_CP1: |
| case MSS_CP2: |
| case MSS_CP3: |
| /* MSS_AP = 0 |
| * MSS_CP1 = 1 |
| * . |
| * . |
| * MSS_CP3 = 4 |
| * Actual CP index is MSS_CPX - 1 |
| */ |
| cp_index = cm3_type - 1; |
| for (ap_idx = 0; ap_idx < ap_count; ap_idx++) { |
| /* Check if we should load this image |
| * according to number of CPs |
| */ |
| if (bl2_plat_get_cp_count(ap_idx) <= cp_index) { |
| NOTICE("Skipping MSS CP%d related image\n", |
| cp_index); |
| break; |
| } |
| |
| NOTICE("Load image to CP%d MSS AP%d\n", |
| cp_index, ap_idx); |
| ret = mss_image_load(single_img, image_size, |
| bl2_plat_get_cp_mss_regs( |
| ap_idx, cp_index)); |
| if (ret != 0) { |
| ERROR("SCP Image load failed\n"); |
| return -1; |
| } |
| } |
| break; |
| case MG_CP0: |
| case MG_CP1: |
| case MG_CP2: |
| cp_index = cm3_type - MG_CP0; |
| if (bl2_plat_get_cp_count(0) <= cp_index) { |
| NOTICE("Skipping MG CP%d related image\n", |
| cp_index); |
| break; |
| } |
| NOTICE("Load image to CP%d MG\n", cp_index); |
| ret = mg_image_load(single_img, image_size, |
| MG_CM3_SRAM_BASE(cp_index)); |
| if (ret != 0) { |
| ERROR("SCP Image load failed\n"); |
| return -1; |
| } |
| break; |
| default: |
| ERROR("SCP_BL2 wrong img format (cm3_type=%d)\n", cm3_type); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* The Armada 8K has 5 service CPUs and Armada 7K has 3. Therefore it was |
| * required to provide a method for loading firmware to all of the service CPUs. |
| * To achieve that, the scp_bl2 image in fact is file containing up to 5 |
| * concatenated firmwares and this routine splits concatenated image into single |
| * images dedicated for appropriate service CPU and then load them. |
| */ |
| static int split_and_load_bl2_image(void *image) |
| { |
| file_header_t *file_hdr; |
| img_header_t *img_hdr; |
| uintptr_t single_img; |
| int i; |
| |
| file_hdr = (file_header_t *)image; |
| |
| if (file_hdr->magic != FILE_MAGIC) { |
| ERROR("SCP_BL2 wrong img format\n"); |
| return -1; |
| } |
| |
| if (file_hdr->nr_of_imgs > MAX_NR_OF_FILES) { |
| ERROR("SCP_BL2 concatenated image contains too many images\n"); |
| return -1; |
| } |
| |
| img_hdr = (img_header_t *)((uintptr_t)image + sizeof(file_header_t)); |
| single_img = (uintptr_t)image + sizeof(file_header_t) + |
| sizeof(img_header_t) * file_hdr->nr_of_imgs; |
| |
| NOTICE("SCP_BL2 contains %d concatenated images\n", |
| file_hdr->nr_of_imgs); |
| for (i = 0; i < file_hdr->nr_of_imgs; i++) { |
| |
| /* Before loading make sanity check on header */ |
| if (img_hdr->version != HEADER_VERSION) { |
| ERROR("Wrong header, img corrupted exiting\n"); |
| return -1; |
| } |
| |
| load_img_to_cm3(img_hdr->type, single_img, img_hdr->length); |
| |
| /* Prepare offsets for next run */ |
| single_img += img_hdr->length; |
| img_hdr++; |
| } |
| |
| return 0; |
| } |
| |
| int scp_bootloader_transfer(void *image, unsigned int image_size) |
| { |
| #ifdef SCP_BL2_BASE |
| assert((uintptr_t) image == SCP_BL2_BASE); |
| #endif |
| |
| VERBOSE("Concatenated img size %d\n", image_size); |
| |
| if (image_size == 0) { |
| ERROR("SCP_BL2 image size can't be 0 (current size = 0x%x)\n", |
| image_size); |
| return -1; |
| } |
| |
| if (split_and_load_bl2_image(image)) |
| return -1; |
| |
| return 0; |
| } |