summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-08-12 08:31:54 -0600
committerTom Rini <[email protected]>2026-08-12 08:31:54 -0600
commit0b581a9612e46310ee52bf8359165950d38b2a2b (patch)
tree3f7519c37681014044639dc902dbea0dea3f6f1f
parent77bc0326456d9d6a83aa7adac201d1c97a008b61 (diff)
parent86cc9f2fb5637d64e2da78c43fdccf4c50daef8c (diff)
Merge tag 'xilinx-for-v2026.10-rc3' of https://git.u-boot-project.org/u-boot/custodians/u-boot-amd
AMD/Xilinx changes for v2026.10-rc3 Versal Gen 2: - Add support for 2VM3654 device - Add missing MAINTAINERS fragment AMD: - Fix fwu dfu_string generation not to alter with generic DFU Firmware/ufs: - UFS interface cleanup - Remove ZynqMP firmware dependency from UFS zynqmp_gqspi: - Fix quad mode bus conflict MB-V: - shrink SPL size and enforce size limits - remake ELF from BIN
-rw-r--r--MAINTAINERS6
-rw-r--r--arch/arm/mach-versal2/Makefile1
-rw-r--r--arch/arm/mach-versal2/cpu.c40
-rw-r--r--arch/arm/mach-versal2/include/mach/hardware.h87
-rw-r--r--arch/arm/mach-versal2/include/mach/sys_proto.h7
-rw-r--r--arch/arm/mach-versal2/lowlevel_init.S40
-rw-r--r--board/amd/versal2/board.c39
-rw-r--r--board/xilinx/common/board.c37
-rw-r--r--board/xilinx/versal/board.c39
-rw-r--r--configs/xilinx_mbv32_defconfig5
-rw-r--r--configs/xilinx_zynqmp_mini_defconfig20
-rw-r--r--configs/xilinx_zynqmp_mini_qspi_defconfig6
-rw-r--r--drivers/firmware/firmware-zynqmp.c104
-rw-r--r--drivers/spi/zynqmp_gqspi.c43
-rw-r--r--drivers/ufs/Kconfig2
-rw-r--r--drivers/ufs/ufs-amd-versal2.c133
-rw-r--r--include/configs/amd_versal2.h9
-rw-r--r--include/zynqmp_firmware.h13
18 files changed, 388 insertions, 243 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index e1379a6a3e7..eb48eea55c5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -821,6 +821,12 @@ F: drivers/spi/cadence_ospi_versal.c
F: drivers/watchdog/xilinx_wwdt.c
N: (?<!uni)versal
+ARM VERSAL GEN 2
+M: Michal Simek <[email protected]>
+S: Maintained
+T: git https://git.u-boot-project.org/u-boot/custodians/u-boot-microblaze.git
+F: arch/arm/mach-versal2/
+
ARM VERSATILE EXPRESS DRIVERS
M: Liviu Dudau <[email protected]>
S: Maintained
diff --git a/arch/arm/mach-versal2/Makefile b/arch/arm/mach-versal2/Makefile
index 96497b1dfd0..73b3c8a6f35 100644
--- a/arch/arm/mach-versal2/Makefile
+++ b/arch/arm/mach-versal2/Makefile
@@ -8,3 +8,4 @@
obj-y += clk.o
obj-y += cpu.o
+obj-y += lowlevel_init.o
diff --git a/arch/arm/mach-versal2/cpu.c b/arch/arm/mach-versal2/cpu.c
index 6cc6592b0fc..d72f66f4fba 100644
--- a/arch/arm/mach-versal2/cpu.c
+++ b/arch/arm/mach-versal2/cpu.c
@@ -11,6 +11,7 @@
#include <malloc.h>
#include <time.h>
#include <vsprintf.h>
+#include <wait_bit.h>
#include <asm/armv8/mmu.h>
#include <asm/cache.h>
#include <asm/global_data.h>
@@ -160,6 +161,45 @@ u8 __weak versal2_get_bootmode(void)
return bootmode;
}
+/*
+ * Wait for the M-PHY TX/RX config-ready status to settle (all bits cleared) or
+ * @timeout_us to elapse. The direct-MMIO fallback owns the poll loop, mirroring
+ * the EEMI backend; the timeout budget is owned by the caller.
+ */
+int __weak zynqmp_pm_wait_mphy_tx_rx_config_ready(u32 timeout_us)
+{
+ return wait_for_bit_le32((void *)(uintptr_t)(PMXC_SLCR_BASE_ADDRESS +
+ PMXC_TX_RX_CFG_RDY),
+ TX_RX_CFG_RDY_MASK, false, timeout_us / 1000,
+ false);
+}
+
+int __weak zynqmp_pm_wait_sram_init_done(u32 timeout_us)
+{
+ return wait_for_bit_le32((void *)(uintptr_t)(PMXC_SLCR_BASE_ADDRESS +
+ PMXC_SRAM_CSR),
+ SRAM_CSR_INIT_DONE_MASK, true, timeout_us / 1000,
+ false);
+}
+
+int __weak zynqmp_pm_set_sram_bypass(void)
+{
+ u32 sram_csr;
+
+ sram_csr = readl(PMXC_SLCR_BASE_ADDRESS + PMXC_SRAM_CSR);
+ sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
+ sram_csr |= SRAM_CSR_BYPASS_MASK;
+ writel(sram_csr, PMXC_SLCR_BASE_ADDRESS + PMXC_SRAM_CSR);
+
+ return 0;
+}
+
+int __weak zynqmp_pm_get_ufs_calibration_values(u32 *value)
+{
+ *value = readl(PMXC_EFUSE_CACHE_BASE_ADDRESS + PMXC_UFS_CAL_1_OFFSET);
+ return 0;
+}
+
void versal2_timer_setup(void)
{
u32 val;
diff --git a/arch/arm/mach-versal2/include/mach/hardware.h b/arch/arm/mach-versal2/include/mach/hardware.h
index 1bebf20910a..4ff732d660f 100644
--- a/arch/arm/mach-versal2/include/mach/hardware.h
+++ b/arch/arm/mach-versal2/include/mach/hardware.h
@@ -4,34 +4,6 @@
* Copyright (C) 2022 - 2025, Advanced Micro Devices, Inc.
*/
-#ifndef __ASSEMBLY__
-#include <linux/bitops.h>
-#endif
-
-struct crlapb_regs {
- u32 reserved0[67];
- u32 cpu_r5_ctrl;
- u32 reserved;
- u32 iou_switch_ctrl; /* 0x114 */
- u32 reserved1[13];
- u32 timestamp_ref_ctrl; /* 0x14c */
- u32 reserved3[108];
- u32 rst_cpu_r5;
- u32 reserved2[17];
- u32 rst_timestamp; /* 0x348 */
-};
-
-struct iou_scntrs_regs {
- u32 counter_control_register; /* 0x0 */
- u32 reserved0[7];
- u32 base_frequency_id_register; /* 0x20 */
-};
-
-struct crp_regs {
- u32 reserved0[128];
- u32 boot_mode_usr; /* 0x200 */
-};
-
#define VERSAL2_CRL_APB_BASEADDR 0xEB5E0000
#define VERSAL2_CRP_BASEADDR 0xF1260000
#define VERSAL2_IOU_SCNTR_SECURE 0xEC920000
@@ -41,10 +13,6 @@ struct crp_regs {
#define IOU_SWITCH_CTRL_DIVISOR0_SHIFT 8
#define IOU_SCNTRS_CONTROL_EN 1
-#define crlapb_base ((struct crlapb_regs *)VERSAL2_CRL_APB_BASEADDR)
-#define crp_base ((struct crp_regs *)VERSAL2_CRP_BASEADDR)
-#define iou_scntr_secure ((struct iou_scntrs_regs *)VERSAL2_IOU_SCNTR_SECURE)
-
#define PMC_TAP 0xF11A0000
#define PMC_TAP_IDCODE (PMC_TAP + 0)
@@ -76,15 +44,6 @@ struct crp_regs {
#define PMC_MULTI_BOOT_REG 0xF1110004
#define PMC_MULTI_BOOT_MASK 0x1FFF
-enum versal2_platform {
- VERSAL2_SILICON = 0,
- VERSAL2_SPP = 1,
- VERSAL2_EMU = 2,
- VERSAL2_QEMU = 3,
- VERSAL2_SPP_MMD = 5,
- VERSAL2_EMU_MMD = 6,
-};
-
#define VERSAL2_SLCR_BASEADDR 0xF1060000
#define VERSAL_AXI_MUX_SEL (VERSAL2_SLCR_BASEADDR + 0x504)
#define VERSAL_OSPI_LINEAR_MODE BIT(1)
@@ -106,5 +65,51 @@ enum versal2_platform {
#define PMXC_SRAM_CSR 0x4C
#define PMXC_TX_RX_CFG_RDY 0x54
+#define SRAM_CSR_INIT_DONE_MASK BIT(0)
+#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1)
+#define SRAM_CSR_BYPASS_MASK BIT(2)
+#define TX_RX_CFG_RDY_MASK GENMASK(3, 0)
+
#define PMC_GLOBAL_PGGS3_REG 0xF111005C
#define PMC_GLOBAL_PGGS4_REG 0xF1110060
+
+#ifndef __ASSEMBLY__
+#include <linux/bitops.h>
+
+struct crlapb_regs {
+ u32 reserved0[67];
+ u32 cpu_r5_ctrl;
+ u32 reserved;
+ u32 iou_switch_ctrl; /* 0x114 */
+ u32 reserved1[13];
+ u32 timestamp_ref_ctrl; /* 0x14c */
+ u32 reserved3[108];
+ u32 rst_cpu_r5;
+ u32 reserved2[17];
+ u32 rst_timestamp; /* 0x348 */
+};
+
+struct iou_scntrs_regs {
+ u32 counter_control_register; /* 0x0 */
+ u32 reserved0[7];
+ u32 base_frequency_id_register; /* 0x20 */
+};
+
+struct crp_regs {
+ u32 reserved0[128];
+ u32 boot_mode_usr; /* 0x200 */
+};
+
+#define crlapb_base ((struct crlapb_regs *)VERSAL2_CRL_APB_BASEADDR)
+#define crp_base ((struct crp_regs *)VERSAL2_CRP_BASEADDR)
+#define iou_scntr_secure ((struct iou_scntrs_regs *)VERSAL2_IOU_SCNTR_SECURE)
+
+enum versal2_platform {
+ VERSAL2_SILICON = 0,
+ VERSAL2_SPP = 1,
+ VERSAL2_EMU = 2,
+ VERSAL2_QEMU = 3,
+ VERSAL2_SPP_MMD = 5,
+ VERSAL2_EMU_MMD = 6,
+};
+#endif /* __ASSEMBLY__ */
diff --git a/arch/arm/mach-versal2/include/mach/sys_proto.h b/arch/arm/mach-versal2/include/mach/sys_proto.h
index b8d12d1dd3b..d678adf9c26 100644
--- a/arch/arm/mach-versal2/include/mach/sys_proto.h
+++ b/arch/arm/mach-versal2/include/mach/sys_proto.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2021 - 2022, Xilinx, Inc.
- * Copyright (C) 2022 - 2024, Advanced Micro Devices, Inc.
+ * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc.
*/
#ifndef _ASM_ARCH_SYS_PROTO_H
@@ -22,4 +22,9 @@ u8 versal2_get_bootmode(void);
/* EL3 clock/timer register setup, called from board_early_init_r() */
void versal2_timer_setup(void);
+int zynqmp_pm_wait_mphy_tx_rx_config_ready(u32 timeout_us);
+int zynqmp_pm_wait_sram_init_done(u32 timeout_us);
+int zynqmp_pm_set_sram_bypass(void);
+int zynqmp_pm_get_ufs_calibration_values(u32 *value);
+
#endif /* _ASM_ARCH_SYS_PROTO_H */
diff --git a/arch/arm/mach-versal2/lowlevel_init.S b/arch/arm/mach-versal2/lowlevel_init.S
new file mode 100644
index 00000000000..8bfe50b806e
--- /dev/null
+++ b/arch/arm/mach-versal2/lowlevel_init.S
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SoC specific lowlevel_init for AMD Versal Gen 2
+ *
+ * Copyright (C) 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <asm-offsets.h>
+#include <config.h>
+#include <linux/linkage.h>
+#include <asm/macro.h>
+#include <asm/arch/hardware.h>
+
+ENTRY(lowlevel_init)
+ mov x29, lr /* Save LR */
+
+#if defined(CONFIG_GICV3)
+ branch_if_slave x0, 1f
+ ldr x0, =GICD_BASE
+ bl gic_init_secure
+1:
+ /*
+ * Pick the redistributor base for this part. Default to GICR_BASE
+ * and switch to GICR_BASE_2VM3654 when the PMC TAP IDCODE matches
+ * the 2VM3654 device.
+ */
+ ldr x0, =GICR_BASE
+ ldr x1, =PMC_TAP_IDCODE
+ ldr w1, [x1]
+ ldr w2, =GICR_IDCODE_2VM3654
+ cmp w1, w2
+ b.ne 3f
+ ldr x0, =GICR_BASE_2VM3654
+3:
+ bl gic_init_secure_percpu
+#endif
+
+ mov lr, x29 /* Restore LR */
+ ret
+ENDPROC(lowlevel_init)
diff --git a/board/amd/versal2/board.c b/board/amd/versal2/board.c
index 2afd283b8dd..15b79bbea69 100644
--- a/board/amd/versal2/board.c
+++ b/board/amd/versal2/board.c
@@ -11,7 +11,6 @@
#include <env.h>
#include <efi_loader.h>
#include <fdtdec.h>
-#include <fwu.h>
#include <init.h>
#include <env_internal.h>
#include <log.h>
@@ -362,8 +361,6 @@ enum env_location env_get_location(enum env_operation op, int prio)
#define DFU_ALT_BUF_LEN SZ_1K
-#if defined(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) && \
- !defined(CONFIG_FWU_MULTI_BANK_UPDATE)
static void mtd_found_part(u32 *base, u32 *size)
{
struct mtd_info *part, *mtd;
@@ -440,42 +437,6 @@ void configure_capsule_updates(void)
update_info.dfu_string = strdup(buf);
debug("Capsule DFU: %s\n", update_info.dfu_string);
}
-#endif
-
-#if defined(CONFIG_FWU_MULTI_BANK_UPDATE)
-
-/* Generate dfu_alt_info from partitions */
-void set_dfu_alt_info(char *interface, char *devstr)
-{
- int ret;
- struct mtd_info *mtd;
-
- /*
- * It is called multiple times for every image
- * per bank that's why enough to set it up once.
- */
- if (env_get("dfu_alt_info"))
- return;
-
- ALLOC_CACHE_ALIGN_BUFFER(char, buf, DFU_ALT_BUF_LEN);
- memset(buf, 0, DFU_ALT_BUF_LEN);
-
- mtd_probe_devices();
-
- mtd = get_mtd_device_nm("nor0");
- if (IS_ERR_OR_NULL(mtd))
- return;
-
- ret = fwu_gen_alt_info_from_mtd(buf, DFU_ALT_BUF_LEN, mtd);
- if (ret < 0) {
- log_err("Error: Failed to generate dfu_alt_info. (%d)\n", ret);
- return;
- }
- log_debug("Make dfu_alt_info: '%s'\n", buf);
-
- env_set("dfu_alt_info", buf);
-}
-#endif
int spi_get_env_dev(void)
{
diff --git a/board/xilinx/common/board.c b/board/xilinx/common/board.c
index 52a2e8767d8..f45b879736e 100644
--- a/board/xilinx/common/board.c
+++ b/board/xilinx/common/board.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2014 - 2022, Xilinx, Inc.
- * (C) Copyright 2022 - 2025, Advanced Micro Devices, Inc.
+ * (C) Copyright 2022 - 2026, Advanced Micro Devices, Inc.
*
* Michal Simek <[email protected]>
*/
@@ -14,6 +14,8 @@
#include <init.h>
#include <jffs2/load_kernel.h>
#include <log.h>
+#include <memalign.h>
+#include <mtd.h>
#include <asm/io.h>
#include <asm/global_data.h>
#include <asm/sections.h>
@@ -22,6 +24,7 @@
#endif
#include <dm/uclass.h>
#include <i2c.h>
+#include <linux/err.h>
#include <linux/sizes.h>
#include <malloc.h>
#include <memtop.h>
@@ -65,6 +68,8 @@ struct efi_capsule_update_info update_info = {
.images = fw_images,
};
+#define DFU_ALT_BUF_LEN SZ_1K
+
#endif /* EFI_HAVE_CAPSULE_SUPPORT */
#define EEPROM_HEADER_MAGIC 0xdaaddeed
@@ -846,6 +851,36 @@ int fwu_platform_hook(struct udevice *dev, struct fwu_data *data)
/* Copy image type GUID */
memcpy(&fw_images[0].image_type_id, &img_entry->image_type_guid, 16);
+ /*
+ * Generate the capsule DFU string from the FWU metadata. This has to
+ * happen here, and not in configure_capsule_updates() called from
+ * board_late_init(), because the FWU data is only populated by
+ * fwu_boottime_checks() at EVT_POST_PREBOOT.
+ */
+ {
+ ALLOC_CACHE_ALIGN_BUFFER(char, buf, DFU_ALT_BUF_LEN);
+ struct mtd_info *mtd;
+ int ret;
+
+ memset(buf, 0, DFU_ALT_BUF_LEN);
+
+ mtd_probe_devices();
+
+ mtd = get_mtd_device_nm("nor0");
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ ret = fwu_gen_alt_info_from_mtd(buf, DFU_ALT_BUF_LEN, mtd);
+ if (ret < 0) {
+ log_err("Error: Failed to generate dfu_alt_info. (%d)\n", ret);
+ return ret;
+ }
+ log_debug("Make dfu_alt_info: '%s'\n", buf);
+
+ update_info.dfu_string = strdup(buf);
+ debug("Capsule DFU: %s\n", update_info.dfu_string);
+ }
+
if (IS_ENABLED(CONFIG_EFI_ESRT)) {
efi_status_t ret;
diff --git a/board/xilinx/versal/board.c b/board/xilinx/versal/board.c
index 0537517b1b2..e3e1085fce7 100644
--- a/board/xilinx/versal/board.c
+++ b/board/xilinx/versal/board.c
@@ -10,7 +10,6 @@
#include <env.h>
#include <efi_loader.h>
#include <fdtdec.h>
-#include <fwu.h>
#include <init.h>
#include <env_internal.h>
#include <log.h>
@@ -314,8 +313,6 @@ enum env_location env_get_location(enum env_operation op, int prio)
#define DFU_ALT_BUF_LEN SZ_1K
-#if defined(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) && \
- !defined(CONFIG_FWU_MULTI_BANK_UPDATE)
static void mtd_found_part(u32 *base, u32 *size)
{
struct mtd_info *part, *mtd;
@@ -392,39 +389,3 @@ void configure_capsule_updates(void)
update_info.dfu_string = strdup(buf);
debug("Capsule DFU: %s\n", update_info.dfu_string);
}
-#endif
-
-#if defined(CONFIG_FWU_MULTI_BANK_UPDATE)
-
-/* Generate dfu_alt_info from partitions */
-void set_dfu_alt_info(char *interface, char *devstr)
-{
- int ret;
- struct mtd_info *mtd;
-
- /*
- * It is called multiple times for every image
- * per bank that's why enough to set it up once.
- */
- if (env_get("dfu_alt_info"))
- return;
-
- ALLOC_CACHE_ALIGN_BUFFER(char, buf, DFU_ALT_BUF_LEN);
- memset(buf, 0, DFU_ALT_BUF_LEN);
-
- mtd_probe_devices();
-
- mtd = get_mtd_device_nm("nor0");
- if (IS_ERR_OR_NULL(mtd))
- return;
-
- ret = fwu_gen_alt_info_from_mtd(buf, DFU_ALT_BUF_LEN, mtd);
- if (ret < 0) {
- log_err("Error: Failed to generate dfu_alt_info. (%d)\n", ret);
- return;
- }
- log_debug("Make dfu_alt_info: '%s'\n", buf);
-
- env_set("dfu_alt_info", buf);
-}
-#endif
diff --git a/configs/xilinx_mbv32_defconfig b/configs/xilinx_mbv32_defconfig
index 097d6095df2..82bbba6d0f0 100644
--- a/configs/xilinx_mbv32_defconfig
+++ b/configs/xilinx_mbv32_defconfig
@@ -1,4 +1,7 @@
CONFIG_RISCV=y
+# CONFIG_SPL_USE_ARCH_MEMCPY is not set
+# CONFIG_SPL_USE_ARCH_MEMMOVE is not set
+# CONFIG_SPL_USE_ARCH_MEMSET is not set
CONFIG_SYS_MALLOC_LEN=0xe00000
CONFIG_NR_DRAM_BANKS=1
CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
@@ -38,6 +41,8 @@ CONFIG_SPL_SYS_MALLOC=y
CONFIG_SPL_HAS_CUSTOM_MALLOC_START=y
CONFIG_SPL_CUSTOM_SYS_MALLOC_ADDR=0x80000000
CONFIG_SPL_SYS_MALLOC_SIZE=0x200000
+# CONFIG_SPL_CPU is not set
+CONFIG_SPL_REMAKE_ELF=y
# CONFIG_CMD_MII is not set
CONFIG_CMD_SNTP=y
CONFIG_CMD_TIMER=y
diff --git a/configs/xilinx_zynqmp_mini_defconfig b/configs/xilinx_zynqmp_mini_defconfig
index 20e2ae0f003..35385d0079a 100644
--- a/configs/xilinx_zynqmp_mini_defconfig
+++ b/configs/xilinx_zynqmp_mini_defconfig
@@ -2,19 +2,27 @@ CONFIG_ARM=y
CONFIG_SYS_CONFIG_NAME="xilinx_zynqmp_mini"
CONFIG_SYS_ICACHE_OFF=y
CONFIG_ARCH_ZYNQMP=y
-CONFIG_TEXT_BASE=0xFFFC0000
+CONFIG_TEXT_BASE=0xFFFE0000
CONFIG_SYS_MALLOC_LEN=0x1a00
CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
CONFIG_CUSTOM_SYS_INIT_SP_ADDR=0xfffe0000
CONFIG_ENV_SIZE=0x80
CONFIG_DEFAULT_DEVICE_TREE="zynqmp-mini"
+CONFIG_SPL_STACK=0xfffffffc
+CONFIG_SPL_BSS_MAX_SIZE=0x80000
CONFIG_SYS_LOAD_ADDR=0x8000000
+CONFIG_SPL=y
+# CONFIG_SPL_FS_FAT is not set
+# CONFIG_SPL_LIBDISK_SUPPORT is not set
CONFIG_XILINX_MINI=y
CONFIG_SYS_MEM_RSVD_FOR_MMU=y
# CONFIG_PSCI_RESET is not set
CONFIG_SYS_MEMTEST_START=0x00000000
CONFIG_SYS_MEMTEST_END=0x00001000
+CONFIG_SKIP_RELOCATE=y
CONFIG_REMAKE_ELF=y
+CONFIG_HAS_BOARD_SIZE_LIMIT=y
+CONFIG_BOARD_SIZE_LIMIT=131072
# CONFIG_LEGACY_IMAGE_FORMAT is not set
# CONFIG_AUTOBOOT is not set
CONFIG_SYS_CBSIZE=1024
@@ -22,6 +30,14 @@ CONFIG_SYS_PBSIZE=1049
# CONFIG_DISPLAY_CPUINFO is not set
# CONFIG_BOARD_LATE_INIT is not set
CONFIG_CLOCKS=y
+CONFIG_SPL_MAX_SIZE=0x40000
+# CONFIG_SPL_BINMAN_SYMBOLS is not set
+# CONFIG_SPL_SHARES_INIT_SP_ADDR is not set
+CONFIG_SPL_HAVE_INIT_STACK=y
+CONFIG_SPL_SYS_MALLOC=y
+CONFIG_SPL_HAS_CUSTOM_MALLOC_START=y
+CONFIG_SPL_CUSTOM_SYS_MALLOC_ADDR=0x20000000
+CONFIG_SPL_SYS_MALLOC_SIZE=0x1000000
# CONFIG_CMDLINE_EDITING is not set
# CONFIG_AUTO_COMPLETE is not set
# CONFIG_SYS_LONGHELP is not set
@@ -50,10 +66,12 @@ CONFIG_SYS_ALT_MEMTEST=y
# CONFIG_CMD_SETEXPR is not set
CONFIG_CMD_CACHE=y
# CONFIG_CMD_SLEEP is not set
+CONFIG_SPL_OF_CONTROL=y
CONFIG_OF_EMBED=y
CONFIG_ENV_RELOC_GD_ENV_ADDR=y
CONFIG_NO_NET=y
# CONFIG_DM_DEVICE_REMOVE is not set
+CONFIG_SPL_DM_SEQ_ALIAS=y
# CONFIG_SIMPLE_BUS is not set
# CONFIG_DM_MAILBOX is not set
# CONFIG_MMC is not set
diff --git a/configs/xilinx_zynqmp_mini_qspi_defconfig b/configs/xilinx_zynqmp_mini_qspi_defconfig
index 03366a3b05a..9457dd0a672 100644
--- a/configs/xilinx_zynqmp_mini_qspi_defconfig
+++ b/configs/xilinx_zynqmp_mini_qspi_defconfig
@@ -2,7 +2,7 @@ CONFIG_ARM=y
CONFIG_SYS_CONFIG_NAME="xilinx_zynqmp_mini"
CONFIG_SYS_ICACHE_OFF=y
CONFIG_ARCH_ZYNQMP=y
-CONFIG_TEXT_BASE=0xFFFC0000
+CONFIG_TEXT_BASE=0xFFFE0000
CONFIG_SYS_MALLOC_LEN=0x1b00
CONFIG_NR_DRAM_BANKS=1
CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
@@ -19,8 +19,10 @@ CONFIG_XILINX_MINI=y
CONFIG_SYS_MEM_RSVD_FOR_MMU=y
CONFIG_ZYNQMP_NO_DDR=y
# CONFIG_PSCI_RESET is not set
-# CONFIG_EXPERT is not set
+CONFIG_SKIP_RELOCATE=y
CONFIG_REMAKE_ELF=y
+CONFIG_HAS_BOARD_SIZE_LIMIT=y
+CONFIG_BOARD_SIZE_LIMIT=131072
# CONFIG_LEGACY_IMAGE_FORMAT is not set
# CONFIG_AUTOBOOT is not set
# CONFIG_ARCH_FIXUP_FDT_MEMORY is not set
diff --git a/drivers/firmware/firmware-zynqmp.c b/drivers/firmware/firmware-zynqmp.c
index 6052a31b5b4..fae66ccb3d8 100644
--- a/drivers/firmware/firmware-zynqmp.c
+++ b/drivers/firmware/firmware-zynqmp.c
@@ -19,10 +19,10 @@
#include <asm/ptrace.h>
#include <asm/system.h>
#include <linux/bitfield.h>
+#include <linux/delay.h>
#if defined(CONFIG_ZYNQMP_IPI)
#include <mailbox.h>
-#include <asm/arch/sys_proto.h>
#define PMUFW_PAYLOAD_ARG_CNT 8
@@ -176,28 +176,106 @@ unsigned int zynqmp_firmware_version(void)
};
#if defined(CONFIG_ARCH_VERSAL2)
-int zynqmp_pm_ufs_get_txrx_cfgrdy(u32 *value)
+/*
+ * Poll the M-PHY TX/RX config-ready status until it settles or @timeout_us
+ * elapses. Legacy EEMI firmware only offers the per-read status primitive, so
+ * the poll loop lives here rather than in the UFS driver; the timeout budget is
+ * owned by the caller.
+ */
+int zynqmp_pm_wait_mphy_tx_rx_config_ready(u32 timeout_us)
{
- *value = readl(PMXC_SLCR_BASE_ADDRESS + PMXC_TX_RX_CFG_RDY);
- return 0;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ while (timeout_us--) {
+ ret = xilinx_pm_request(PM_IOCTL, PM_REGNODE_PMC_IOU_SLCR,
+ IOCTL_READ_REG, TXRX_CFGRDY_OFFSET, 0, 0,
+ 0, ret_payload);
+ if (ret)
+ return ret;
+
+ if (!(ret_payload[1] & TX_RX_CFG_RDY_MASK))
+ return 0;
+
+ udelay(1);
+ }
+
+ return -ETIMEDOUT;
}
-int zynqmp_pm_ufs_sram_csr_read(u32 *value)
+int zynqmp_pm_wait_sram_init_done(u32 timeout_us)
{
- *value = readl(PMXC_SLCR_BASE_ADDRESS + PMXC_SRAM_CSR);
- return 0;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ while (timeout_us--) {
+ ret = xilinx_pm_request(PM_IOCTL, PM_REGNODE_PMC_IOU_SLCR,
+ IOCTL_READ_REG, SRAM_CSR_OFFSET, 0, 0,
+ 0, ret_payload);
+ if (ret)
+ return ret;
+
+ if (ret_payload[1] & SRAM_CSR_INIT_DONE_MASK)
+ return 0;
+
+ udelay(1);
+ }
+
+ return -ETIMEDOUT;
}
-int zynqmp_pm_ufs_sram_csr_write(u32 *value)
+int zynqmp_pm_set_sram_bypass(void)
{
- writel(*value, PMXC_SLCR_BASE_ADDRESS + PMXC_SRAM_CSR);
- return 0;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ u32 sram_csr;
+ int ret;
+
+ ret = zynqmp_pm_is_function_supported(PM_IOCTL, IOCTL_MASK_WRITE_REG);
+ if (ret) {
+ printf("%s: IOCTL_MASK_WRITE_REG is not supported : %d\n"
+ , __func__, ret);
+ return 0;
+ }
+
+ ret = xilinx_pm_request(PM_IOCTL, PM_REGNODE_PMC_IOU_SLCR,
+ IOCTL_READ_REG, SRAM_CSR_OFFSET, 0, 0,
+ 0, ret_payload);
+ if (ret)
+ return ret;
+
+ sram_csr = ret_payload[1];
+ sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
+ sram_csr |= SRAM_CSR_BYPASS_MASK;
+
+ return xilinx_pm_request(PM_IOCTL, PM_REGNODE_PMC_IOU_SLCR,
+ IOCTL_MASK_WRITE_REG, SRAM_CSR_OFFSET,
+ GENMASK(2, 1), sram_csr, 0, NULL);
}
-int zynqmp_pm_ufs_cal_reg(u32 *value)
+int zynqmp_pm_get_ufs_calibration_values(u32 *value)
{
- *value = readl(PMXC_EFUSE_CACHE_BASE_ADDRESS + PMXC_UFS_CAL_1_OFFSET);
- return 0;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ if (!value)
+ return -EINVAL;
+
+ ret = zynqmp_pm_is_function_supported(PM_IOCTL, IOCTL_READ_REG);
+ if (ret) {
+ printf("%s: IOCTL_READ_REG is not supported : %d\n"
+ , __func__, ret);
+ return 0;
+ }
+
+ ret = xilinx_pm_request(PM_IOCTL, PM_REGNODE_EFUSE_CACHE,
+ IOCTL_READ_REG, UFS_CAL_1_OFFSET, 0, 0,
+ 0, ret_payload);
+ if (ret)
+ return ret;
+
+ *value = ret_payload[1];
+
+ return ret;
}
#endif /* CONFIG_ARCH_VERSAL2 */
diff --git a/drivers/spi/zynqmp_gqspi.c b/drivers/spi/zynqmp_gqspi.c
index 2a095d0c58e..f572f2b3f15 100644
--- a/drivers/spi/zynqmp_gqspi.c
+++ b/drivers/spi/zynqmp_gqspi.c
@@ -283,16 +283,21 @@ static u32 zynqmp_qspi_genfifo_mode(u8 buswidth)
}
}
-static void zynqmp_qspi_fill_gen_fifo(struct zynqmp_qspi_priv *priv,
- u32 gqspi_fifo_reg)
+static void zynqmp_qspi_write_gen_fifo(struct zynqmp_qspi_priv *priv,
+ u32 gqspi_fifo_reg)
{
struct zynqmp_qspi_regs *regs = priv->regs;
- u32 config_reg, ier;
- int ret = 0;
log_content("%s, GFIFO_CMD: 0x%X\n", __func__, gqspi_fifo_reg);
writel(gqspi_fifo_reg, &regs->genfifo);
+}
+
+static int zynqmp_qspi_start_gen_fifo(struct zynqmp_qspi_priv *priv)
+{
+ struct zynqmp_qspi_regs *regs = priv->regs;
+ u32 config_reg, ier;
+ int ret = 0;
config_reg = readl(&regs->confr);
/* Manual start if needed */
@@ -310,6 +315,15 @@ static void zynqmp_qspi_fill_gen_fifo(struct zynqmp_qspi_priv *priv,
if (ret)
log_warning("%s, Timeout\n", __func__);
+ return ret;
+}
+
+static int zynqmp_qspi_fill_gen_fifo(struct zynqmp_qspi_priv *priv,
+ u32 gqspi_fifo_reg)
+{
+ zynqmp_qspi_write_gen_fifo(priv, gqspi_fifo_reg);
+
+ return zynqmp_qspi_start_gen_fifo(priv);
}
static void zynqmp_qspi_chipselect(struct zynqmp_qspi_priv *priv, int is_on)
@@ -573,7 +587,7 @@ static void zynqmp_qspi_genfifo_cmd(struct zynqmp_qspi_priv *priv)
gen_fifo_cmd |= zynqmp_qspi_genfifo_mode(op->cmd.buswidth);
gen_fifo_cmd |= GQSPI_GFIFO_TX;
gen_fifo_cmd |= op->cmd.opcode;
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ zynqmp_qspi_write_gen_fifo(priv, gen_fifo_cmd);
/* Send address */
for (i = 0; i < op->addr.nbytes; i++) {
@@ -584,7 +598,7 @@ static void zynqmp_qspi_genfifo_cmd(struct zynqmp_qspi_priv *priv)
gen_fifo_cmd |= GQSPI_GFIFO_TX;
gen_fifo_cmd |= addr;
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ zynqmp_qspi_write_gen_fifo(priv, gen_fifo_cmd);
}
/* Send dummy */
@@ -596,7 +610,7 @@ static void zynqmp_qspi_genfifo_cmd(struct zynqmp_qspi_priv *priv)
gen_fifo_cmd &= ~(GQSPI_GFIFO_TX | GQSPI_GFIFO_RX);
gen_fifo_cmd |= GQSPI_GFIFO_DATA_XFR_MASK;
gen_fifo_cmd |= dummy_cycles;
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ zynqmp_qspi_write_gen_fifo(priv, gen_fifo_cmd);
}
}
@@ -644,7 +658,9 @@ static int zynqmp_qspi_genfifo_fill_tx(struct zynqmp_qspi_priv *priv)
while (priv->len) {
len = zynqmp_qspi_calc_exp(priv, &gen_fifo_cmd);
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ ret = zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ if (ret)
+ return ret;
if (gen_fifo_cmd & GQSPI_GFIFO_EXP_MASK)
ret = zynqmp_qspi_fill_tx_fifo(priv, 1 << len);
@@ -666,6 +682,7 @@ static int zynqmp_qspi_start_io(struct zynqmp_qspi_priv *priv,
struct zynqmp_qspi_regs *regs = priv->regs;
u32 last_bits;
u32 *traverse = buf;
+ int ret;
while (priv->len) {
len = zynqmp_qspi_calc_exp(priv, &gen_fifo_cmd);
@@ -674,7 +691,9 @@ static int zynqmp_qspi_start_io(struct zynqmp_qspi_priv *priv,
priv->bytes_to_receive = (1 << len);
else
priv->bytes_to_receive = len;
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ ret = zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ if (ret)
+ return ret;
/* Manual start */
config_reg = readl(&regs->confr);
@@ -741,7 +760,9 @@ static int zynqmp_qspi_start_dma(struct zynqmp_qspi_priv *priv,
while (priv->len) {
zynqmp_qspi_calc_exp(priv, &gen_fifo_cmd);
- zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ ret = zynqmp_qspi_fill_gen_fifo(priv, gen_fifo_cmd);
+ if (ret)
+ return ret;
}
ret = wait_for_bit_le32(&dma_regs->dmaisr,
@@ -886,6 +907,8 @@ static int zynqmp_qspi_exec_op(struct spi_slave *slave,
ret = zynqmp_qspi_genfifo_fill_rx(priv);
else if (op->data.dir == SPI_MEM_DATA_OUT)
ret = zynqmp_qspi_genfifo_fill_tx(priv);
+ else
+ ret = zynqmp_qspi_start_gen_fifo(priv);
zynqmp_qspi_chipselect(priv, 0);
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
index c1b84bd7559..6dffdc00b32 100644
--- a/drivers/ufs/Kconfig
+++ b/drivers/ufs/Kconfig
@@ -10,7 +10,7 @@ config UFS
config UFS_AMD_VERSAL2
bool "AMD Versal Gen 2 UFS controller platform driver"
- depends on UFS && ZYNQMP_FIRMWARE
+ depends on UFS && ARCH_VERSAL2
help
This selects the AMD specific additions to UFSHCD platform driver.
UFS host on AMD needs some vendor specific configuration before accessing
diff --git a/drivers/ufs/ufs-amd-versal2.c b/drivers/ufs/ufs-amd-versal2.c
index 3369d32d924..48854792cbf 100644
--- a/drivers/ufs/ufs-amd-versal2.c
+++ b/drivers/ufs/ufs-amd-versal2.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (C) 2024-2025 Advanced Micro Devices, Inc.
+ * Copyright (C) 2024-2026 Advanced Micro Devices, Inc.
*/
#include <clk.h>
@@ -14,14 +14,12 @@
#include <linux/time.h>
#include <reset.h>
+#include <asm/arch/sys_proto.h>
+
#include "ufs.h"
#include "ufshcd-dwc.h"
#include "ufshci-dwc.h"
-#define SRAM_CSR_INIT_DONE_MASK BIT(0)
-#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1)
-#define SRAM_CSR_BYPASS_MASK BIT(2)
-
#define MPHY_FAST_RX_AFE_CAL BIT(2)
#define MPHY_FW_CALIB_CFG_VAL BIT(8)
@@ -29,8 +27,6 @@
#define MPHY_RX_OVRD_VAL BIT(2)
#define MPHY_RX_ACK_MASK BIT(0)
-#define TX_RX_CFG_RDY_MASK GENMASK(3, 0)
-
#define TIMEOUT_MICROSEC 1000000L
struct ufs_versal2_priv {
@@ -227,7 +223,6 @@ static int ufs_versal2_setup_phy(struct ufs_hba *hba)
static int ufs_versal2_phy_init(struct ufs_hba *hba)
{
struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
- u32 reg, time_left;
int ret;
static const struct ufshcd_dme_attr_val rmmi_attrs[] = {
{ UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN, DME_LOCAL },
@@ -236,24 +231,15 @@ static int ufs_versal2_phy_init(struct ufs_hba *hba)
{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
};
- /* Wait for Tx/Rx config_rdy */
- time_left = TIMEOUT_MICROSEC;
- do {
- time_left--;
- ret = zynqmp_pm_ufs_get_txrx_cfgrdy(&reg);
- if (ret)
- return ret;
-
- reg &= TX_RX_CFG_RDY_MASK;
- if (!reg)
- break;
-
- mdelay(5);
- } while (time_left);
-
- if (!time_left) {
+ /*
+ * Wait for Tx/Rx config_rdy. The poll loop lives in the firmware
+ * backend (IO, EEMI or SCMI) so this driver stays backend-agnostic;
+ * the timeout budget stays here with the consumer.
+ */
+ ret = zynqmp_pm_wait_mphy_tx_rx_config_ready(TIMEOUT_MICROSEC);
+ if (ret) {
dev_err(hba->dev, "Tx/Rx configuration signal busy.\n");
- return -ETIMEDOUT;
+ return ret;
}
ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_attrs, ARRAY_SIZE(rmmi_attrs));
@@ -267,24 +253,11 @@ static int ufs_versal2_phy_init(struct ufs_hba *hba)
return ret;
}
- /* Wait for SRAM init done */
- time_left = TIMEOUT_MICROSEC;
- do {
- time_left--;
- ret = zynqmp_pm_ufs_sram_csr_read(&reg);
- if (ret)
- return ret;
-
- reg &= SRAM_CSR_INIT_DONE_MASK;
- if (reg)
- break;
-
- mdelay(5);
- } while (time_left);
-
- if (!time_left) {
+ /* Wait for SRAM init done (poll handled by the firmware backend). */
+ ret = zynqmp_pm_wait_sram_init_done(TIMEOUT_MICROSEC);
+ if (ret) {
dev_err(hba->dev, "SRAM initialization failed.\n");
- return -ETIMEDOUT;
+ return ret;
}
ret = ufs_versal2_setup_phy(hba);
@@ -329,7 +302,32 @@ static int ufs_versal2_init(struct ufs_hba *hba)
return PTR_ERR(priv->rstphy);
}
- ret = zynqmp_pm_ufs_cal_reg(&cal);
+ /* Assert RST_UFS Reset for UFS block in PMX_IOU */
+ ret = reset_assert(priv->rstc);
+ if (ret) {
+ dev_err(hba->dev, "host reset assert failed, err = %d\n", ret);
+ return ret;
+ }
+
+ /* Assert PHY reset */
+ ret = reset_assert(priv->rstphy);
+ if (ret) {
+ dev_err(hba->dev, "phy reset assert failed, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = zynqmp_pm_set_sram_bypass();
+ if (ret) {
+ dev_err(hba->dev, "Bypass SRAM interface failed, err = %d\n", ret);
+ return ret;
+ }
+
+ /* De Assert RST_UFS Reset for UFS block in PMX_IOU */
+ ret = reset_deassert(priv->rstc);
+ if (ret)
+ dev_err(hba->dev, "host reset deassert failed, err = %d\n", ret);
+
+ ret = zynqmp_pm_get_ufs_calibration_values(&cal);
if (ret)
return ret;
@@ -344,57 +342,12 @@ static int ufs_versal2_init(struct ufs_hba *hba)
static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
- struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
- u32 sram_csr;
- int ret;
-
- switch (status) {
- case PRE_CHANGE:
- /* Assert RST_UFS Reset for UFS block in PMX_IOU */
- ret = reset_assert(priv->rstc);
- if (ret) {
- dev_err(hba->dev, "ufshc reset assert failed, err = %d\n", ret);
- return ret;
- }
-
- /* Assert PHY reset */
- ret = reset_assert(priv->rstphy);
- if (ret) {
- dev_err(hba->dev, "ufsphy reset assert failed, err = %d\n", ret);
- return ret;
- }
-
- ret = zynqmp_pm_ufs_sram_csr_read(&sram_csr);
- if (ret)
- return ret;
-
- if (!priv->phy_mode) {
- sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
- sram_csr |= SRAM_CSR_BYPASS_MASK;
- } else {
- dev_err(hba->dev, "Invalid phy-mode %d.\n", priv->phy_mode);
- return -EINVAL;
- }
-
- ret = zynqmp_pm_ufs_sram_csr_write(&sram_csr);
- if (ret)
- return ret;
-
- /* De Assert RST_UFS Reset for UFS block in PMX_IOU */
- ret = reset_deassert(priv->rstc);
- if (ret)
- dev_err(hba->dev, "ufshc reset deassert failed, err = %d\n", ret);
+ int ret = 0;
- break;
- case POST_CHANGE:
+ if (status == POST_CHANGE) {
ret = ufs_versal2_phy_init(hba);
if (ret)
dev_err(hba->dev, "Phy init failed (%d)\n", ret);
-
- break;
- default:
- ret = -EINVAL;
- break;
}
return ret;
diff --git a/include/configs/amd_versal2.h b/include/configs/amd_versal2.h
index a07e12bd146..00627dcd943 100644
--- a/include/configs/amd_versal2.h
+++ b/include/configs/amd_versal2.h
@@ -19,6 +19,15 @@
#define GICD_BASE 0xe2000000
#define GICR_BASE 0xe2060000
+/*
+ * The 2VM3654 part has 4 APU cores and 3 GIC ITS blocks (vs 8 cores and a
+ * single ITS on the base part), which moves the redistributor region up by
+ * the two extra ITS blocks. The right base is selected at runtime in
+ * lowlevel_init() based on the PMC TAP IDCODE.
+ */
+#define GICR_BASE_2VM3654 0xe20a0000
+#define GICR_IDCODE_2VM3654 0x04d98093
+
/* Serial setup */
#define CFG_SYS_BAUDRATE_TABLE \
{ 4800, 9600, 19200, 38400, 57600, 115200 }
diff --git a/include/zynqmp_firmware.h b/include/zynqmp_firmware.h
index 0e545e3db1b..f753a67ac27 100644
--- a/include/zynqmp_firmware.h
+++ b/include/zynqmp_firmware.h
@@ -465,10 +465,6 @@ int zynqmp_mmio_read(const u32 address, u32 *value);
int zynqmp_mmio_write(const u32 address, const u32 mask, const u32 value);
int zynqmp_pm_feature(const u32 api_id);
u32 zynqmp_pm_get_bootmode_reg(void);
-int zynqmp_pm_ufs_get_txrx_cfgrdy(u32 *value);
-int zynqmp_pm_ufs_sram_csr_read(u32 *value);
-int zynqmp_pm_ufs_sram_csr_write(u32 *value);
-int zynqmp_pm_ufs_cal_reg(u32 *value);
u32 zynqmp_pm_get_pmc_multi_boot_reg(void);
u32 zynqmp_pm_get_pmc_global_pggs_reg(u32 reg_addr);
@@ -535,7 +531,14 @@ extern smc_call_handler_t __data smc_call_handler;
#define PM_DEV_OSPI (0x1822402aU)
-#define PM_REG_PGGS3 0x30004003
+#define PM_REGNODE_PMC_IOU_SLCR 0x30000002
+#define PM_REGNODE_EFUSE_CACHE 0x30000003
+#define PM_REG_PGGS3 0x30004003
+
+#define SRAM_CSR_OFFSET 0x104C
+#define TXRX_CFGRDY_OFFSET 0x1054
+#define UFS_CAL_1_OFFSET 0xBE8
+
#define PMC_GLOBAL_PGGS3_REG_NODE 0x1824C005
#endif /* _ZYNQMP_FIRMWARE_H_ */