diff options
32 files changed, 4316 insertions, 268 deletions
@@ -147,7 +147,7 @@ config SYS_MALLOC_F_LEN config SYS_MALLOC_LEN hex "Define memory for Dynamic allocation" - depends on ARCH_ZYNQ || ARCH_VERSAL + depends on ARCH_ZYNQ || ARCH_VERSAL || ARCH_STM32MP help This defines memory to be allocated for Dynamic allocation TODO: Use for other architectures diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index efb9aab390..01ff57cf1b 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1488,6 +1488,7 @@ config ARCH_STM32MP imply CMD_DM imply CMD_POWEROFF imply ENV_VARS_UBOOT_RUNTIME_CONFIG + imply USE_PREBOOT help Support for STM32MP SoC family developed by STMicroelectronics, MPUs based on ARM cortex A core diff --git a/arch/arm/cpu/armv7/arch_timer.c b/arch/arm/cpu/armv7/arch_timer.c index 3db31c0209..5de63053d5 100644 --- a/arch/arm/cpu/armv7/arch_timer.c +++ b/arch/arm/cpu/armv7/arch_timer.c @@ -49,6 +49,9 @@ unsigned long long get_ticks(void) ulong timer_get_boot_us(void) { + if (!gd->arch.timer_rate_hz) + timer_init(); + return lldiv(get_ticks(), gd->arch.timer_rate_hz / 1000000); } diff --git a/arch/arm/dts/stm32mp15-ddr3-1x4Gb-1066-binG.dtsi b/arch/arm/dts/stm32mp15-ddr3-1x4Gb-1066-binG.dtsi index 7d9b95ccf1..dc30360b0a 100644 --- a/arch/arm/dts/stm32mp15-ddr3-1x4Gb-1066-binG.dtsi +++ b/arch/arm/dts/stm32mp15-ddr3-1x4Gb-1066-binG.dtsi @@ -16,8 +16,8 @@ * address mapping : RBC * Tc > + 85C : N */ -#define DDR_MEM_NAME "DDR3-1066/888 bin G 1x4Gb 533MHz v1.43" -#define DDR_MEM_SPEED 533 +#define DDR_MEM_NAME "DDR3-1066/888 bin G 1x4Gb 533MHz v1.44" +#define DDR_MEM_SPEED 533000 #define DDR_MEM_SIZE 0x20000000 #define DDR_MSTR 0x00041401 @@ -108,11 +108,11 @@ #define DDR_DX1DLLCR 0x40000000 #define DDR_DX1DQTR 0xFFFFFFFF #define DDR_DX1DQSTR 0x3DB02000 -#define DDR_DX2GCR 0x0000CE81 +#define DDR_DX2GCR 0x0000CE80 #define DDR_DX2DLLCR 0x40000000 #define DDR_DX2DQTR 0xFFFFFFFF #define DDR_DX2DQSTR 0x3DB02000 -#define DDR_DX3GCR 0x0000CE81 +#define DDR_DX3GCR 0x0000CE80 #define DDR_DX3DLLCR 0x40000000 #define DDR_DX3DQTR 0xFFFFFFFF #define DDR_DX3DQSTR 0x3DB02000 diff --git a/arch/arm/dts/stm32mp15-ddr3-2x4Gb-1066-binG.dtsi b/arch/arm/dts/stm32mp15-ddr3-2x4Gb-1066-binG.dtsi index 8a5a821ec4..8158a56f13 100644 --- a/arch/arm/dts/stm32mp15-ddr3-2x4Gb-1066-binG.dtsi +++ b/arch/arm/dts/stm32mp15-ddr3-2x4Gb-1066-binG.dtsi @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause /* * Copyright (C) 2018, STMicroelectronics - All Rights Reserved - */ - -/* STM32MP157C ED1 and ED2 BOARD configuration + * + * STM32MP157C ED1 BOARD configuration * 2x DDR3L 4Gb each, 16-bit, 533MHz, Single Die Package in flyby topology. * Reference used NT5CC256M16DP-DI from NANYA * @@ -15,10 +14,11 @@ * timing mode optimized * Scheduling/QoS options : type = 2 * address mapping : RBC + * Tc > + 85C : N */ -#define DDR_MEM_NAME "DDR3-1066 bin G 2x4Gb 533MHz v1.36" -#define DDR_MEM_SPEED 533 +#define DDR_MEM_NAME "DDR3-1066/888 bin G 2x4Gb 533MHz v1.44" +#define DDR_MEM_SPEED 533000 #define DDR_MEM_SIZE 0x40000000 #define DDR_MSTR 0x00040401 @@ -62,7 +62,7 @@ #define DDR_ADDRMAP11 0x00000000 #define DDR_ODTCFG 0x06000600 #define DDR_ODTMAP 0x00000001 -#define DDR_SCHED 0x00001201 +#define DDR_SCHED 0x00000C01 #define DDR_SCHED1 0x00000000 #define DDR_PERFHPR1 0x01000001 #define DDR_PERFLPR1 0x08000200 @@ -74,15 +74,15 @@ #define DDR_PCCFG 0x00000010 #define DDR_PCFGR_0 0x00010000 #define DDR_PCFGW_0 0x00000000 -#define DDR_PCFGQOS0_0 0x02100B03 +#define DDR_PCFGQOS0_0 0x02100C03 #define DDR_PCFGQOS1_0 0x00800100 -#define DDR_PCFGWQOS0_0 0x01100B03 +#define DDR_PCFGWQOS0_0 0x01100C03 #define DDR_PCFGWQOS1_0 0x01000200 #define DDR_PCFGR_1 0x00010000 #define DDR_PCFGW_1 0x00000000 -#define DDR_PCFGQOS0_1 0x02100B03 -#define DDR_PCFGQOS1_1 0x00800100 -#define DDR_PCFGWQOS0_1 0x01100B03 +#define DDR_PCFGQOS0_1 0x02100C03 +#define DDR_PCFGQOS1_1 0x00800040 +#define DDR_PCFGWQOS0_1 0x01100C03 #define DDR_PCFGWQOS1_1 0x01000200 #define DDR_PGCR 0x01442E02 #define DDR_PTR0 0x0022AA5B @@ -100,7 +100,7 @@ #define DDR_MR2 0x00000208 #define DDR_MR3 0x00000000 #define DDR_ODTCR 0x00010000 -#define DDR_ZQ0CR1 0x0000005B +#define DDR_ZQ0CR1 0x00000038 #define DDR_DX0GCR 0x0000CE81 #define DDR_DX0DLLCR 0x40000000 #define DDR_DX0DQTR 0xFFFFFFFF diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index 73aa382712..77f66c65c0 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -17,12 +17,20 @@ config SPL select SPL_DM_RESET select SPL_SERIAL_SUPPORT select SPL_SYSCON + imply BOOTSTAGE_STASH if SPL_BOOTSTAGE + imply SPL_BOOTSTAGE if BOOTSTAGE imply SPL_DISPLAY_PRINT imply SPL_LIBDISK_SUPPORT config SYS_SOC default "stm32mp" +config SYS_MALLOC_LEN + default 0x2000000 + +config ENV_SIZE + default 0x1000 + config TARGET_STM32MP1 bool "Support stm32mp1xx" select ARCH_SUPPORT_PSCI if !STM32MP1_TRUSTED @@ -33,6 +41,10 @@ config TARGET_STM32MP1 select STM32_RCC select STM32_RESET select SYS_ARCH_TIMER + imply BOOTCOUNT_LIMIT + imply BOOTSTAGE + imply CMD_BOOTCOUNT + imply CMD_BOOTSTAGE imply SYSRESET_PSCI if STM32MP1_TRUSTED imply SYSRESET_SYSCON if !STM32MP1_TRUSTED help @@ -70,6 +82,18 @@ config SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_MMC2 Partition on the second MMC to load U-Boot from when the MMC is being used in raw mode +config BOOTSTAGE_STASH_ADDR + default 0xC3000000 + +if BOOTCOUNT_LIMIT +config SYS_BOOTCOUNT_SINGLEWORD + default y + +# TAMP_BOOTCOUNT = TAMP_BACKUP_REGISTER(21) +config SYS_BOOTCOUNT_ADDR + default 0x5C00A154 +endif + if DEBUG_UART config DEBUG_UART_BOARD_INIT diff --git a/arch/arm/mach-stm32mp/bsec.c b/arch/arm/mach-stm32mp/bsec.c index 9ed8d8c56c..0166649685 100644 --- a/arch/arm/mach-stm32mp/bsec.c +++ b/arch/arm/mach-stm32mp/bsec.c @@ -7,9 +7,9 @@ #include <dm.h> #include <misc.h> #include <asm/io.h> -#include <linux/iopoll.h> #include <asm/arch/stm32mp1_smc.h> #include <linux/arm-smccc.h> +#include <linux/iopoll.h> #define BSEC_OTP_MAX_VALUE 95 diff --git a/arch/arm/mach-stm32mp/include/mach/ddr.h b/arch/arm/mach-stm32mp/include/mach/ddr.h index 18575842ba..b8a17cfbdd 100644 --- a/arch/arm/mach-stm32mp/include/mach/ddr.h +++ b/arch/arm/mach-stm32mp/include/mach/ddr.h @@ -6,6 +6,13 @@ #ifndef __MACH_STM32MP_DDR_H_ #define __MACH_STM32MP_DDR_H_ -int board_ddr_power_init(void); +/* DDR power initializations */ +enum ddr_type { + STM32MP_DDR3, + STM32MP_LPDDR2, + STM32MP_LPDDR3, +}; + +int board_ddr_power_init(enum ddr_type ddr_type); #endif diff --git a/arch/arm/mach-stm32mp/include/mach/stm32.h b/arch/arm/mach-stm32mp/include/mach/stm32.h index c526c88e3e..6795352044 100644 --- a/arch/arm/mach-stm32mp/include/mach/stm32.h +++ b/arch/arm/mach-stm32mp/include/mach/stm32.h @@ -88,6 +88,7 @@ enum boot_device { #define TAMP_BACKUP_MAGIC_NUMBER TAMP_BACKUP_REGISTER(4) #define TAMP_BACKUP_BRANCH_ADDRESS TAMP_BACKUP_REGISTER(5) #define TAMP_BOOT_CONTEXT TAMP_BACKUP_REGISTER(20) +#define TAMP_BOOTCOUNT TAMP_BACKUP_REGISTER(21) #define TAMP_BOOT_MODE_MASK GENMASK(15, 8) #define TAMP_BOOT_MODE_SHIFT 8 diff --git a/arch/arm/mach-stm32mp/psci.c b/arch/arm/mach-stm32mp/psci.c index c2dff38c36..139bb09292 100644 --- a/arch/arm/mach-stm32mp/psci.c +++ b/arch/arm/mach-stm32mp/psci.c @@ -47,14 +47,14 @@ static u32 __secure stm32mp_get_gicd_base_address(void) return (periphbase & CBAR_MASK) + GIC_DIST_OFFSET; } -static void __secure stm32mp_smp_kick_all_cpus(void) +static void __secure stm32mp_raise_sgi0(int cpu) { u32 gic_dist_addr; gic_dist_addr = stm32mp_get_gicd_base_address(); - /* kick all CPUs (except this one) by writing to GICD_SGIR */ - writel(1U << 24, gic_dist_addr + GICD_SGIR); + /* ask cpu with SGI0 */ + writel((BIT(cpu) << 16), gic_dist_addr + GICD_SGIR); } void __secure psci_arch_cpu_entry(void) @@ -62,6 +62,9 @@ void __secure psci_arch_cpu_entry(void) u32 cpu = psci_get_cpu_id(); psci_set_state(cpu, PSCI_AFFINITY_LEVEL_ON); + + /* reset magic in TAMP register */ + writel(0xFFFFFFFF, TAMP_BACKUP_MAGIC_NUMBER); } int __secure psci_features(u32 function_id, u32 psci_fid) @@ -127,6 +130,16 @@ int __secure psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, if (psci_state[cpu] == PSCI_AFFINITY_LEVEL_ON) return ARM_PSCI_RET_ALREADY_ON; + /* reset magic in TAMP register */ + if (readl(TAMP_BACKUP_MAGIC_NUMBER)) + writel(0xFFFFFFFF, TAMP_BACKUP_MAGIC_NUMBER); + /* + * ROM code need a first SGI0 after core reset + * core is ready when magic is set to 0 in ROM code + */ + while (readl(TAMP_BACKUP_MAGIC_NUMBER)) + stm32mp_raise_sgi0(cpu); + /* store target PC and context id*/ psci_save(cpu, pc, context_id); @@ -142,7 +155,8 @@ int __secure psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, writel(BOOT_API_A7_CORE0_MAGIC_NUMBER, TAMP_BACKUP_MAGIC_NUMBER); - stm32mp_smp_kick_all_cpus(); + /* Generate an IT to start the core */ + stm32mp_raise_sgi0(cpu); return ARM_PSCI_RET_SUCCESS; } diff --git a/board/st/stm32mp1/board.c b/board/st/stm32mp1/board.c index 5c1acca20d..c3d832f584 100644 --- a/board/st/stm32mp1/board.c +++ b/board/st/stm32mp1/board.c @@ -38,9 +38,10 @@ void board_debug_uart_init(void) #endif #ifdef CONFIG_PMIC_STPMIC1 -int board_ddr_power_init(void) +int board_ddr_power_init(enum ddr_type ddr_type) { struct udevice *dev; + bool buck3_at_1800000v = false; int ret; ret = uclass_get_device_by_driver(UCLASS_PMIC, @@ -49,53 +50,127 @@ int board_ddr_power_init(void) /* No PMIC on board */ return 0; - /* VTT = Set LDO3 to sync mode */ - ret = pmic_reg_read(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3)); - if (ret < 0) - return ret; - - ret &= ~STPMIC1_LDO3_MODE; - ret &= ~STPMIC1_LDO12356_VOUT_MASK; - ret |= STPMIC1_LDO_VOUT(STPMIC1_LDO3_DDR_SEL); - - ret = pmic_reg_write(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), - ret); - if (ret < 0) - return ret; - - /* VDD_DDR = Set BUCK2 to 1.35V */ - ret = pmic_clrsetbits(dev, - STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), - STPMIC1_BUCK_VOUT_MASK, - STPMIC1_BUCK2_1350000V); - if (ret < 0) - return ret; - - /* Enable VDD_DDR = BUCK2 */ - ret = pmic_clrsetbits(dev, - STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), - STPMIC1_BUCK_ENA, STPMIC1_BUCK_ENA); - if (ret < 0) - return ret; - - mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); - - /* Enable VREF */ - ret = pmic_clrsetbits(dev, STPMIC1_REFDDR_MAIN_CR, - STPMIC1_VREF_ENA, STPMIC1_VREF_ENA); - if (ret < 0) - return ret; - - mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); - - /* Enable LDO3 */ - ret = pmic_clrsetbits(dev, - STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), - STPMIC1_LDO_ENA, STPMIC1_LDO_ENA); - if (ret < 0) - return ret; - - mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + switch (ddr_type) { + case STM32MP_DDR3: + /* VTT = Set LDO3 to sync mode */ + ret = pmic_reg_read(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3)); + if (ret < 0) + return ret; + + ret &= ~STPMIC1_LDO3_MODE; + ret &= ~STPMIC1_LDO12356_VOUT_MASK; + ret |= STPMIC1_LDO_VOUT(STPMIC1_LDO3_DDR_SEL); + + ret = pmic_reg_write(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), + ret); + if (ret < 0) + return ret; + + /* VDD_DDR = Set BUCK2 to 1.35V */ + ret = pmic_clrsetbits(dev, + STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), + STPMIC1_BUCK_VOUT_MASK, + STPMIC1_BUCK2_1350000V); + if (ret < 0) + return ret; + + /* Enable VDD_DDR = BUCK2 */ + ret = pmic_clrsetbits(dev, + STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), + STPMIC1_BUCK_ENA, STPMIC1_BUCK_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + /* Enable VREF */ + ret = pmic_clrsetbits(dev, STPMIC1_REFDDR_MAIN_CR, + STPMIC1_VREF_ENA, STPMIC1_VREF_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + /* Enable VTT = LDO3 */ + ret = pmic_clrsetbits(dev, + STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), + STPMIC1_LDO_ENA, STPMIC1_LDO_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + break; + + case STM32MP_LPDDR2: + case STM32MP_LPDDR3: + /* + * configure VDD_DDR1 = LDO3 + * Set LDO3 to 1.8V + * + bypass mode if BUCK3 = 1.8V + * + normal mode if BUCK3 != 1.8V + */ + ret = pmic_reg_read(dev, + STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK3)); + if (ret < 0) + return ret; + + if ((ret & STPMIC1_BUCK3_1800000V) == STPMIC1_BUCK3_1800000V) + buck3_at_1800000v = true; + + ret = pmic_reg_read(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3)); + if (ret < 0) + return ret; + + ret &= ~STPMIC1_LDO3_MODE; + ret &= ~STPMIC1_LDO12356_VOUT_MASK; + ret |= STPMIC1_LDO3_1800000; + if (buck3_at_1800000v) + ret |= STPMIC1_LDO3_MODE; + + ret = pmic_reg_write(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), + ret); + if (ret < 0) + return ret; + + /* VDD_DDR2 : Set BUCK2 to 1.2V */ + ret = pmic_clrsetbits(dev, + STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), + STPMIC1_BUCK_VOUT_MASK, + STPMIC1_BUCK2_1200000V); + if (ret < 0) + return ret; + + /* Enable VDD_DDR1 = LDO3 */ + ret = pmic_clrsetbits(dev, STPMIC1_LDOX_MAIN_CR(STPMIC1_LDO3), + STPMIC1_LDO_ENA, STPMIC1_LDO_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + /* Enable VDD_DDR2 =BUCK2 */ + ret = pmic_clrsetbits(dev, + STPMIC1_BUCKX_MAIN_CR(STPMIC1_BUCK2), + STPMIC1_BUCK_ENA, STPMIC1_BUCK_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + /* Enable VREF */ + ret = pmic_clrsetbits(dev, STPMIC1_REFDDR_MAIN_CR, + STPMIC1_VREF_ENA, STPMIC1_VREF_ENA); + if (ret < 0) + return ret; + + mdelay(STPMIC1_DEFAULT_START_UP_DELAY_MS); + + break; + + default: + break; + }; return 0; } diff --git a/common/Kconfig b/common/Kconfig index 1a1951f874..c759952b80 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -247,6 +247,7 @@ config USE_PREBOOT config PREBOOT string "preboot default value" depends on USE_PREBOOT + default "" help This is the default of "preboot" environment variable. diff --git a/common/Makefile b/common/Makefile index 8c92feb399..c7e41ef307 100644 --- a/common/Makefile +++ b/common/Makefile @@ -124,6 +124,7 @@ endif obj-y += cli.o obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o +obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += cli_simple.o cli_readline.o obj-$(CONFIG_DFU_OVER_USB) += dfu.o obj-y += command.o obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o diff --git a/doc/device-tree-bindings/clock/st,stm32mp1.txt b/doc/device-tree-bindings/clock/st,stm32mp1.txt index ffcf8cd31d..02e14609bb 100644 --- a/doc/device-tree-bindings/clock/st,stm32mp1.txt +++ b/doc/device-tree-bindings/clock/st,stm32mp1.txt @@ -1,185 +1,186 @@ STMicroelectronics STM32MP1 clock tree initialization ===================================================== -The STM32MP clock tree initialization is based on device tree information -for RCC IP and on fixed clocks. +The STM32MP1 clock tree initialization is based on device tree information +for RCC IP node (st,stm32mp1-rcc) and on fixed-clock nodes. -------------------------------- -RCC CLOCK = st,stm32mp1-rcc-clk -------------------------------- +RCC IP = st,stm32mp1-rcc +======================== The RCC IP is both a reset and a clock controller but this documentation only describes the fields added for clock tree initialization which are not present -in Linux binding. +in Linux binding for compatible "st,stm32mp1-rcc" defined in st,stm32mp1-rcc.txt +file. -Please refer to ../mfd/st,stm32-rcc.txt for all the other properties common -with Linux. +The added properties for clock tree initialization are: Required properties: +- st,clksrc : The clock sources configuration array in a platform specific + order. -- compatible: Should be "st,stm32mp1-rcc-clk" + For the STM32MP15x family there are 9 clock sources selector which are + configured in the following order: + MPU AXI MCU PLL12 PLL3 PLL4 RTC MCO1 MCO2 -- st,clksrc : The clock source in this order + Clock source configuration values are defined by macros CLK_<NAME>_<SOURCE> + from dt-bindings/clock/stm32mp1-clksrc.h. - for STM32MP15x: 9 clock sources are requested - MPU AXI MCU PLL12 PLL3 PLL4 RTC MCO1 MCO2 - - with value equals to RCC clock specifier as defined in - dt-bindings/clock/stm32mp1-clksrc.h: CLK_<NAME>_<SOURCE> - -- st,clkdiv : The div parameters in this order - for STM32MP15x: 11 dividers value are requested + Example: + st,clksrc = < + CLK_MPU_PLL1P + CLK_AXI_PLL2P + CLK_MCU_PLL3P + CLK_PLL12_HSE + CLK_PLL3_HSE + CLK_PLL4_HSE + CLK_RTC_LSE + CLK_MCO1_DISABLED + CLK_MCO2_DISABLED + >; + +- st,clkdiv : The clock main dividers value specified in an array + in a platform specific order. + + When used, it shall describe the whole clock dividers tree. + + For the STM32MP15x family there are 11 dividers values expected. + They shall be configured in the following order: MPU AXI MCU APB1 APB2 APB3 APB4 APB5 RTC MCO1 MCO2 - with DIV coding defined in RCC associated register RCC_xxxDIVR - - most the case, it is: + The each divider value uses the DIV coding defined in RCC associated + register RCC_xxxDIVR. In most the case, it is: 0x0: not divided 0x1: division by 2 0x2: division by 4 0x3: division by 8 ... - but for RTC MCO1 MCO2, the coding is different: + Note that for RTC MCO1 MCO2, the coding is different: 0x0: not divided 0x1: division by 2 0x2: division by 3 0x3: division by 4 ... -Optional Properties: -- st,pll - PLL children node for PLL1 to PLL4 : (see ref manual for details) - with associated index 0 to 3 (st,pll@0 to st,pll@4) - PLLx is off when the associated node is absent + Example: + st,clkdiv = < + 1 /*MPU*/ + 0 /*AXI*/ + 0 /*MCU*/ + 1 /*APB1*/ + 1 /*APB2*/ + 1 /*APB3*/ + 1 /*APB4*/ + 2 /*APB5*/ + 23 /*RTC*/ + 0 /*MCO1*/ + 0 /*MCO2*/ + >; - - Sub-nodes: +Optional Properties: +- st,pll : A specific PLL configuration, including frequency. - - cfg: The parameters for PLL configuration in this order: - DIVM DIVN DIVP DIVQ DIVR Output + PLL children nodes for PLL1 to PLL4 (see ref manual for details) + are listed with associated index 0 to 3 (st,pll@0 to st,pll@3). + PLLx is off when the associated node is absent. - with DIV value as defined in RCC spec: - 0x0: bypass (division by 1) - 0x1: division by 2 - 0x2: division by 3 - 0x3: division by 4 - ... + Here are the available properties for each PLL node: - and Output = bitfield for each output value = 1:ON/0:OFF - BIT(0) => output P : DIVPEN - BIT(1) => output Q : DIVQEN - BIT(2) => output R : DIVREN - NB : macro PQR(p,q,r) can be used to build this value - with p,p,r = 0 or 1 + - cfg: The parameters for PLL configuration in the following order: + DIVM DIVN DIVP DIVQ DIVR Output. - - frac : Fractional part of the multiplication factor - (optional, PLL is in integer mode when absent) + DIVx values are defined as in RCC spec: + 0x0: bypass (division by 1) + 0x1: division by 2 + 0x2: division by 3 + 0x3: division by 4 + ... - - csg : Clock Spreading Generator (optional) - with parameters in this order: - MOD_PER INC_STEP SSCG_MODE + Output contains a bitfield for each output value (1:ON/0:OFF) + BIT(0) => output P : DIVPEN + BIT(1) => output Q : DIVQEN + BIT(2) => output R : DIVREN + NB: macro PQR(p,q,r) can be used to build this value + with p,q,r = 0 or 1. + + - frac : Fractional part of the multiplication factor + (optional, PLL is in integer mode when absent). + + - csg : Clock Spreading Generator (optional) with parameters in the + following order: MOD_PER INC_STEP SSCG_MODE. + + MOD_PER: Modulation Period Adjustment + INC_STEP: Modulation Depth Adjustment + SSCG_MODE: Spread spectrum clock generator mode, with associated + defined from stm32mp1-clksrc.h: + - SSCG_MODE_CENTER_SPREAD = 0 + - SSCG_MODE_DOWN_SPREAD = 1 + + Example: + st,pll@0 { + cfg = < 1 53 0 0 0 1 >; + frac = < 0x810 >; + }; + st,pll@1 { + cfg = < 1 43 1 0 0 PQR(0,1,1) >; + csg = < 10 20 1 >; + }; + st,pll@2 { + cfg = < 2 85 3 13 3 0 >; + csg = < 10 20 SSCG_MODE_CENTER_SPREAD >; + }; + st,pll@3 { + cfg = < 2 78 4 7 9 3 >; + }; - * MOD_PER: Modulation Period Adjustment - * INC_STEP: Modulation Depth Adjustment - * SSCG_MODE: Spread spectrum clock generator mode - you can use associated defines from stm32mp1-clksrc.h - * SSCG_MODE_CENTER_SPREAD = 0 - * SSCG_MODE_DOWN_SPREAD = 1 +- st,pkcs : used to configure the peripherals kernel clock selection. + The property is a list of peripheral kernel clock source identifiers defined + by macros CLK_<KERNEL-CLOCK>_<PARENT-CLOCK> as defined by header file + dt-bindings/clock/stm32mp1-clksrc.h. -- st,pkcs : used to configure the peripherals kernel clock selection - containing a list of peripheral kernel clock source identifier as defined - in the file dt-bindings/clock/stm32mp1-clksrc.h + st,pkcs may not list all the kernel clocks and has no ordering requirements. Example: + st,pkcs = < + CLK_STGEN_HSE + CLK_CKPER_HSI + CLK_USBPHY_PLL2P + CLK_DSI_PLL2Q + CLK_I2C46_HSI + CLK_UART1_HSI + CLK_UART24_HSI + >; - rcc: rcc@50000000 { - compatible = "syscon", "simple-mfd"; - - reg = <0x50000000 0x1000>; - - rcc_clk: rcc-clk@50000000 { - #clock-cells = <1>; - compatible = "st,stm32mp1-rcc-clk"; - - st,clksrc = < CLK_MPU_PLL1P - CLK_AXI_PLL2P - CLK_MCU_HSI - CLK_PLL12_HSE - CLK_PLL3_HSE - CLK_PLL4_HSE - CLK_RTC_HSE - CLK_MCO1_DISABLED - CLK_MCO2_DISABLED - >; - - st,clkdiv = < - 1 /*MPU*/ - 0 /*AXI*/ - 0 /*MCU*/ - 1 /*APB1*/ - 1 /*APB2*/ - 1 /*APB3*/ - 1 /*APB4*/ - 5 /*APB5*/ - 23 /*RTC*/ - 0 /*MCO1*/ - 0 /*MCO2*/ - >; - - st,pll@0 { - cfg = < 1 53 0 0 0 1 >; - frac = < 0x810 >; - }; - st,pll@1 { - cfg = < 1 43 1 0 0 PQR(0,1,1) >; - csg = < 10 20 1 >; - }; - st,pll@2 { - cfg = < 2 85 3 13 3 0 >; - csg = < 10 20 SSCG_MODE_CENTER_SPREAD >; - }; - st,pll@3 { - cfg = < 2 78 4 7 9 3 >; - }; - st,pkcs = < - CLK_STGEN_HSE - CLK_CKPER_HSI - CLK_USBPHY_PLL2P - CLK_DSI_PLL2Q - >; - }; - }; - --------------------------- other clocks = fixed-clock --------------------------- +========================== + The clock tree is also based on 5 fixed-clock in clocks node used to define the state of associated ST32MP1 oscillators: -- clk-lsi -- clk-lse -- clk-hsi -- clk-hse -- clk-csi + - clk-lsi + - clk-lse + - clk-hsi + - clk-hse + - clk-csi At boot the clock tree initialization will -- enable the oscillator present in device tree -- disable HSI oscillator if the node is absent (always activated by bootrom) + - enable oscillators present in device tree + - disable HSI oscillator if the node is absent (always activated by bootrom) Optional properties : a) for external oscillator: "clk-lse", "clk-hse" - 4 optional fields are managed - - "st,bypass" Configure the oscillator bypass mode (HSEBYP, LSEBYP) - - "st,digbypass" Configure the bypass mode as full-swing digital signal - (DIGBYP) - - "st,css" Activate the clock security system (HSECSSON, LSECSSON) - - "st,drive" (only for LSE) value of the drive for the oscillator - (see LSEDRV_ define in the file dt-bindings/clock/stm32mp1-clksrc.h) - - Example board file: + 4 optional fields are managed + - "st,bypass" configures the oscillator bypass mode (HSEBYP, LSEBYP) + - "st,digbypass" configures the bypass mode as full-swing digital + signal (DIGBYP) + - "st,css" activates the clock security system (HSECSSON, LSECSSON) + - "st,drive" (only for LSE) contains the value of the drive for the + oscillator (see LSEDRV_ defined in the file + dt-bindings/clock/stm32mp1-clksrc.h) + Example board file: / { clocks { clk_hse: clk-hse { @@ -200,13 +201,12 @@ a) for external oscillator: "clk-lse", "clk-hse" b) for internal oscillator: "clk-hsi" - internally HSI clock is fixed to 64MHz for STM32MP157 soc - in device tree clk-hsi is the clock after HSIDIV (ck_hsi in RCC doc) - So this clock frequency is used to compute the expected HSI_DIV - for the clock tree initialisation - - ex: for HSIDIV = /1 + Internally HSI clock is fixed to 64MHz for STM32MP157 SoC. + In device tree, clk-hsi is the clock after HSIDIV (clk_hsi in RCC + doc). So this clock frequency is used to compute the expected HSI_DIV + for the clock tree initialization. + Example with HSIDIV = /1: / { clocks { clk_hsi: clk-hsi { @@ -216,8 +216,7 @@ b) for internal oscillator: "clk-hsi" }; }; - ex: for HSIDIV = /2 - + Example with HSIDIV = /2 / { clocks { clk_hsi: clk-hsi { @@ -226,3 +225,151 @@ b) for internal oscillator: "clk-hsi" clock-frequency = <32000000>; }; }; + +Example of clock tree initialization +==================================== + +/ { + clocks { + u-boot,dm-pre-reloc; + clk_hse: clk-hse { + u-boot,dm-pre-reloc; + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24000000>; + st,digbypass; + }; + + clk_hsi: clk-hsi { + u-boot,dm-pre-reloc; + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <64000000>; + }; + + clk_lse: clk-lse { + u-boot,dm-pre-reloc; + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32768>; + }; + + clk_lsi: clk-lsi { + u-boot,dm-pre-reloc; + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <32000>; + }; + + clk_csi: clk-csi { + u-boot,dm-pre-reloc; + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <4000000>; + }; + }; + + soc { + + rcc: rcc@50000000 { + u-boot,dm-pre-reloc; + compatible = "st,stm32mp1-rcc", "syscon"; + reg = <0x50000000 0x1000>; + #clock-cells = <1>; + #reset-cells = <1>; + interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>; + + st,clksrc = < + CLK_MPU_PLL1P + CLK_AXI_PLL2P + CLK_MCU_PLL3P + CLK_PLL12_HSE + CLK_PLL3_HSE + CLK_PLL4_HSE + CLK_RTC_LSE + CLK_MCO1_DISABLED + CLK_MCO2_DISABLED + >; + + st,clkdiv = < + 1 /*MPU*/ + 0 /*AXI*/ + 0 /*MCU*/ + 1 /*APB1*/ + 1 /*APB2*/ + 1 /*APB3*/ + 1 /*APB4*/ + 2 /*APB5*/ + 23 /*RTC*/ + 0 /*MCO1*/ + 0 /*MCO2*/ + >; + + st,pkcs = < + CLK_CKPER_HSE + CLK_FMC_ACLK + CLK_QSPI_ACLK + CLK_ETH_DISABLED + CLK_SDMMC12_PLL4P + CLK_DSI_DSIPLL + CLK_STGEN_HSE + CLK_USBPHY_HSE + CLK_SPI2S1_PLL3Q + CLK_SPI2S23_PLL3Q + CLK_SPI45_HSI + CLK_SPI6_HSI + CLK_I2C46_HSI + CLK_SDMMC3_PLL4P + CLK_USBO_USBPHY + CLK_ADC_CKPER + CLK_CEC_LSE + CLK_I2C12_HSI + CLK_I2C35_HSI + CLK_UART1_HSI + CLK_UART24_HSI + CLK_UART35_HSI + CLK_UART6_HSI + CLK_UART78_HSI + CLK_SPDIF_PLL4P + CLK_FDCAN_PLL4Q + CLK_SAI1_PLL3Q + CLK_SAI2_PLL3Q + CLK_SAI3_PLL3Q + CLK_SAI4_PLL3Q + CLK_RNG1_LSI + CLK_RNG2_LSI + CLK_LPTIM1_PCLK1 + CLK_LPTIM23_PCLK3 + CLK_LPTIM45_LSE + >; + + /* VCO = 1300.0 MHz => P = 650 (CPU) */ + pll1: st,pll@0 { + cfg = < 2 80 0 0 0 PQR(1,0,0) >; + frac = < 0x800 >; + u-boot,dm-pre-reloc; + }; + + /* VCO = 1066.0 MHz => P = 266 (AXI), Q = 533 (GPU), + R = 533 (DDR) */ + pll2: st,pll@1 { + cfg = < 2 65 1 0 0 PQR(1,1,1) >; + frac = < 0x1400 >; + u-boot,dm-pre-reloc; + }; + + /* VCO = 417.8 MHz => P = 209, Q = 24, R = 11 */ + pll3: st,pll@2 { + cfg = < 1 33 1 16 36 PQR(1,1,1) >; + frac = < 0x1a04 >; + u-boot,dm-pre-reloc; + }; + + /* VCO = 594.0 MHz => P = 99, Q = 74, R = 74 */ + pll4: st,pll@3 { + cfg = < 3 98 5 7 7 PQR(1,1,1) >; + u-boot,dm-pre-reloc; + }; + }; + }; +}; diff --git a/doc/device-tree-bindings/memory-controllers/st,stm32mp1-ddr.txt b/doc/device-tree-bindings/memory-controllers/st,stm32mp1-ddr.txt index 3028636c45..ee708ce92c 100644 --- a/doc/device-tree-bindings/memory-controllers/st,stm32mp1-ddr.txt +++ b/doc/device-tree-bindings/memory-controllers/st,stm32mp1-ddr.txt @@ -16,7 +16,7 @@ included in STM32 Cube tool info attributes: ---------------- - st,mem-name : name for DDR configuration, simple string for information -- st,mem-speed : DDR expected speed for the setting in MHz +- st,mem-speed : DDR expected speed for the setting in kHz - st,mem-size : DDR mem size in byte @@ -102,7 +102,7 @@ controlleur attributes: phyc attributes: ---------------- - st,phy-reg : phy values depending of the DDR type (DDR3/LPDDR2/LPDDR3) - for STM32MP15x: 10 values are requested in this order + for STM32MP15x: 11 values are requested in this order PGCR ACIOCR DXCCR @@ -173,7 +173,7 @@ Example: "ddrphycapb"; st,mem-name = "DDR3 2x4Gb 533MHz"; - st,mem-speed = <533>; + st,mem-speed = <533000>; st,mem-size = <0x40000000>; st,ctl-reg = < diff --git a/drivers/clk/clk_stm32mp1.c b/drivers/clk/clk_stm32mp1.c index 24859fd054..6272b00b9e 100644 --- a/drivers/clk/clk_stm32mp1.c +++ b/drivers/clk/clk_stm32mp1.c @@ -1448,6 +1448,71 @@ static void pll_csg(struct stm32mp1_clk_priv *priv, int pll_id, u32 *csg) setbits_le32(priv->base + pll[pll_id].pllxcr, RCC_PLLNCR_SSCG_CTRL); } +static __maybe_unused int pll_set_rate(struct udevice *dev, + int pll_id, + int div_id, + unsigned long clk_rate) +{ + struct stm32mp1_clk_priv *priv = dev_get_priv(dev); + unsigned int pllcfg[PLLCFG_NB]; + ofnode plloff; + char name[12]; + const struct stm32mp1_clk_pll *pll = priv->data->pll; + enum stm32mp1_plltype type = pll[pll_id].plltype; + int divm, divn, divy; + int ret; + ulong fck_ref; + u32 fracv; + u64 value; + + if (div_id > _DIV_NB) + return -EINVAL; + + sprintf(name, "st,pll@%d", pll_id); + plloff = dev_read_subnode(dev, name); + if (!ofnode_valid(plloff)) + return -FDT_ERR_NOTFOUND; + + ret = ofnode_read_u32_array(plloff, "cfg", + pllcfg, PLLCFG_NB); + if (ret < 0) + return -FDT_ERR_NOTFOUND; + + fck_ref = pll_get_fref_ck(priv, pll_id); + + divm = pllcfg[PLLCFG_M]; + /* select output divider = 0: for _DIV_P, 1:_DIV_Q 2:_DIV_R */ + divy = pllcfg[PLLCFG_P + div_id]; + + /* For: PLL1 & PLL2 => VCO is * 2 but ck_pll_y is also / 2 + * So same final result than PLL2 et 4 + * with FRACV + * Fck_pll_y = Fck_ref * ((DIVN + 1) + FRACV / 2^13) + * / (DIVy + 1) * (DIVM + 1) + * value = (DIVN + 1) * 2^13 + FRACV / 2^13 + * = Fck_pll_y (DIVy + 1) * (DIVM + 1) * 2^13 / Fck_ref + */ + value = ((u64)clk_rate * (divy + 1) * (divm + 1)) << 13; + value = lldiv(value, fck_ref); + + divn = (value >> 13) - 1; + if (divn < DIVN_MIN || + divn > stm32mp1_pll[type].divn_max) { + pr_err("divn invalid = %d", divn); + return -EINVAL; + } + fracv = value - ((divn + 1) << 13); + pllcfg[PLLCFG_N] = divn; + + /* reconfigure PLL */ + pll_stop(priv, pll_id); + pll_config(priv, pll_id, pllcfg, fracv); + pll_start(priv, pll_id); + pll_output(priv, pll_id, pllcfg[PLLCFG_O]); + + return 0; +} + static int set_clksrc(struct stm32mp1_clk_priv *priv, unsigned int clksrc) { u32 address = priv->base + (clksrc >> 4); @@ -1820,6 +1885,11 @@ static ulong stm32mp1_clk_set_rate(struct clk *clk, unsigned long clk_rate) int p; switch (clk->id) { +#if defined(STM32MP1_CLOCK_TREE_INIT) && \ + defined(CONFIG_STM32MP1_DDR_INTERACTIVE) + case DDRPHYC: + break; +#endif case LTDC_PX: case DSI_PX: break; @@ -1833,6 +1903,19 @@ static ulong stm32mp1_clk_set_rate(struct clk *clk, unsigned long clk_rate) return -EINVAL; switch (p) { +#if defined(STM32MP1_CLOCK_TREE_INIT) && \ + defined(CONFIG_STM32MP1_DDR_INTERACTIVE) + case _PLL2_R: /* DDRPHYC */ + { + /* only for change DDR clock in interactive mode */ + ulong result; + + set_clksrc(priv, CLK_AXI_HSI); + result = pll_set_rate(clk->dev, _PLL2, _DIV_R, clk_rate); + set_clksrc(priv, CLK_AXI_PLL2P); + return result; + } +#endif case _PLL4_Q: /* for LTDC_PX and DSI_PX case */ return pll_set_output_rate(clk->dev, _PLL4, _DIV_Q, clk_rate); diff --git a/drivers/i2c/stm32f7_i2c.c b/drivers/i2c/stm32f7_i2c.c index 3872364d6b..50c4fd0de2 100644 --- a/drivers/i2c/stm32f7_i2c.c +++ b/drivers/i2c/stm32f7_i2c.c @@ -500,7 +500,7 @@ static int stm32_i2c_compute_solutions(struct stm32_i2c_setup *setup, af_delay_max = setup->analog_filter ? STM32_I2C_ANALOG_FILTER_DELAY_MAX : 0; - sdadel_min = setup->fall_time - i2c_specs[setup->speed].hddat_min - + sdadel_min = i2c_specs[setup->speed].hddat_min + setup->fall_time - af_delay_min - (setup->dnf + 3) * i2cclk; sdadel_max = i2c_specs[setup->speed].vddat_max - setup->rise_time - @@ -540,8 +540,12 @@ static int stm32_i2c_compute_solutions(struct stm32_i2c_setup *setup, p_prev = p; list_add_tail(&v->node, solutions); + break; } } + + if (p_prev == p) + break; } } diff --git a/drivers/ram/stm32mp1/Kconfig b/drivers/ram/stm32mp1/Kconfig index b9c816662c..2fd8c7b7e3 100644 --- a/drivers/ram/stm32mp1/Kconfig +++ b/drivers/ram/stm32mp1/Kconfig @@ -10,3 +10,40 @@ config STM32MP1_DDR family: support for LPDDR2, LPDDR3 and DDR3 the SDRAM parameters for controleur and phy need to be provided in device tree (computed by DDR tuning tools) + +config STM32MP1_DDR_INTERACTIVE + bool "STM32MP1 DDR driver : interactive support" + depends on STM32MP1_DDR + help + activate interactive support in STM32MP1 DDR controller driver + used for DDR tuning tools + to enter in intercative mode type 'd' during SPL DDR driver + initialisation + +config STM32MP1_DDR_INTERACTIVE_FORCE + bool "STM32MP1 DDR driver : force interactive mode" + depends on STM32MP1_DDR_INTERACTIVE + default n + help + force interactive mode in STM32MP1 DDR controller driver + skip the polling of character 'd' in console + useful when SPL is loaded in sysram + directly by programmer + +config STM32MP1_DDR_TESTS + bool "STM32MP1 DDR driver : tests support" + depends on STM32MP1_DDR_INTERACTIVE + default y + help + activate test support for interactive support in + STM32MP1 DDR controller driver: command test + +config STM32MP1_DDR_TUNING + bool "STM32MP1 DDR driver : support of tuning" + depends on STM32MP1_DDR_INTERACTIVE + default y + help + activate tuning command in STM32MP1 DDR interactive mode + used for DDR tuning tools + - DQ Deskew algorithm + - DQS Trimming diff --git a/drivers/ram/stm32mp1/Makefile b/drivers/ram/stm32mp1/Makefile index 79eb028fab..e1e9135603 100644 --- a/drivers/ram/stm32mp1/Makefile +++ b/drivers/ram/stm32mp1/Makefile @@ -5,3 +5,11 @@ obj-y += stm32mp1_ram.o obj-y += stm32mp1_ddr.o + +obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += stm32mp1_interactive.o +obj-$(CONFIG_STM32MP1_DDR_TESTS) += stm32mp1_tests.o +obj-$(CONFIG_STM32MP1_DDR_TUNING) += stm32mp1_tuning.o + +ifneq ($(DDR_INTERACTIVE),) +CFLAGS_stm32mp1_interactive.o += -DCONFIG_STM32MP1_DDR_INTERACTIVE_FORCE=y +endif diff --git a/drivers/ram/stm32mp1/stm32mp1_ddr.c b/drivers/ram/stm32mp1/stm32mp1_ddr.c index c7c3ba70a4..d765a46f7c 100644 --- a/drivers/ram/stm32mp1/stm32mp1_ddr.c +++ b/drivers/ram/stm32mp1/stm32mp1_ddr.c @@ -41,8 +41,32 @@ struct reg_desc { offsetof(struct stm32mp1_ddrphy, x),\ offsetof(struct y, x)} +#define DDR_REG_DYN(x) \ + {#x,\ + offsetof(struct stm32mp1_ddrctl, x),\ + INVALID_OFFSET} + +#define DDRPHY_REG_DYN(x) \ + {#x,\ + offsetof(struct stm32mp1_ddrphy, x),\ + INVALID_OFFSET} + +/*********************************************************** + * PARAMETERS: value get from device tree : + * size / order need to be aligned with binding + * modification NOT ALLOWED !!! + ***********************************************************/ +#define DDRCTL_REG_REG_SIZE 25 /* st,ctl-reg */ +#define DDRCTL_REG_TIMING_SIZE 12 /* st,ctl-timing */ +#define DDRCTL_REG_MAP_SIZE 9 /* st,ctl-map */ +#define DDRCTL_REG_PERF_SIZE 17 /* st,ctl-perf */ + +#define DDRPHY_REG_REG_SIZE 11 /* st,phy-reg */ +#define DDRPHY_REG_TIMING_SIZE 10 /* st,phy-timing */ +#define DDRPHY_REG_CAL_SIZE 12 /* st,phy-cal */ + #define DDRCTL_REG_REG(x) DDRCTL_REG(x, stm32mp1_ddrctrl_reg) -static const struct reg_desc ddr_reg[] = { +static const struct reg_desc ddr_reg[DDRCTL_REG_REG_SIZE] = { DDRCTL_REG_REG(mstr), DDRCTL_REG_REG(mrctrl0), DDRCTL_REG_REG(mrctrl1), @@ -71,7 +95,7 @@ static const struct reg_desc ddr_reg[] = { }; #define DDRCTL_REG_TIMING(x) DDRCTL_REG(x, stm32mp1_ddrctrl_timing) -static const struct reg_desc ddr_timing[] = { +static const struct reg_desc ddr_timing[DDRCTL_REG_TIMING_SIZE] = { DDRCTL_REG_TIMING(rfshtmg), DDRCTL_REG_TIMING(dramtmg0), DDRCTL_REG_TIMING(dramtmg1), @@ -87,7 +111,7 @@ static const struct reg_desc ddr_timing[] = { }; #define DDRCTL_REG_MAP(x) DDRCTL_REG(x, stm32mp1_ddrctrl_map) -static const struct reg_desc ddr_map[] = { +static const struct reg_desc ddr_map[DDRCTL_REG_MAP_SIZE] = { DDRCTL_REG_MAP(addrmap1), DDRCTL_REG_MAP(addrmap2), DDRCTL_REG_MAP(addrmap3), @@ -100,7 +124,7 @@ static const struct reg_desc ddr_map[] = { }; #define DDRCTL_REG_PERF(x) DDRCTL_REG(x, stm32mp1_ddrctrl_perf) -static const struct reg_desc ddr_perf[] = { +static const struct reg_desc ddr_perf[DDRCTL_REG_PERF_SIZE] = { DDRCTL_REG_PERF(sched), DDRCTL_REG_PERF(sched1), DDRCTL_REG_PERF(perfhpr1), @@ -121,7 +145,7 @@ static const struct reg_desc ddr_perf[] = { }; #define DDRPHY_REG_REG(x) DDRPHY_REG(x, stm32mp1_ddrphy_reg) -static const struct reg_desc ddrphy_reg[] = { +static const struct reg_desc ddrphy_reg[DDRPHY_REG_REG_SIZE] = { DDRPHY_REG_REG(pgcr), DDRPHY_REG_REG(aciocr), DDRPHY_REG_REG(dxccr), @@ -136,7 +160,7 @@ static const struct reg_desc ddrphy_reg[] = { }; #define DDRPHY_REG_TIMING(x) DDRPHY_REG(x, stm32mp1_ddrphy_timing) -static const struct reg_desc ddrphy_timing[] = { +static const struct reg_desc ddrphy_timing[DDRPHY_REG_TIMING_SIZE] = { DDRPHY_REG_TIMING(ptr0), DDRPHY_REG_TIMING(ptr1), DDRPHY_REG_TIMING(ptr2), @@ -150,7 +174,7 @@ static const struct reg_desc ddrphy_timing[] = { }; #define DDRPHY_REG_CAL(x) DDRPHY_REG(x, stm32mp1_ddrphy_cal) -static const struct reg_desc ddrphy_cal[] = { +static const struct reg_desc ddrphy_cal[DDRPHY_REG_CAL_SIZE] = { DDRPHY_REG_CAL(dx0dllcr), DDRPHY_REG_CAL(dx0dqtr), DDRPHY_REG_CAL(dx0dqstr), @@ -165,6 +189,45 @@ static const struct reg_desc ddrphy_cal[] = { DDRPHY_REG_CAL(dx3dqstr), }; +/************************************************************** + * DYNAMIC REGISTERS: only used for debug purpose (read/modify) + **************************************************************/ +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE +static const struct reg_desc ddr_dyn[] = { + DDR_REG_DYN(stat), + DDR_REG_DYN(init0), + DDR_REG_DYN(dfimisc), + DDR_REG_DYN(dfistat), + DDR_REG_DYN(swctl), + DDR_REG_DYN(swstat), + DDR_REG_DYN(pctrl_0), + DDR_REG_DYN(pctrl_1), +}; + +#define DDR_REG_DYN_SIZE ARRAY_SIZE(ddr_dyn) + +static const struct reg_desc ddrphy_dyn[] = { + DDRPHY_REG_DYN(pir), + DDRPHY_REG_DYN(pgsr), + DDRPHY_REG_DYN(zq0sr0), + DDRPHY_REG_DYN(zq0sr1), + DDRPHY_REG_DYN(dx0gsr0), + DDRPHY_REG_DYN(dx0gsr1), + DDRPHY_REG_DYN(dx1gsr0), + DDRPHY_REG_DYN(dx1gsr1), + DDRPHY_REG_DYN(dx2gsr0), + DDRPHY_REG_DYN(dx2gsr1), + DDRPHY_REG_DYN(dx3gsr0), + DDRPHY_REG_DYN(dx3gsr1), +}; + +#define DDRPHY_REG_DYN_SIZE ARRAY_SIZE(ddrphy_dyn) + +#endif + +/***************************************************************** + * REGISTERS ARRAY: used to parse device tree and interactive mode + *****************************************************************/ enum reg_type { REG_REG, REG_TIMING, @@ -173,6 +236,13 @@ enum reg_type { REGPHY_REG, REGPHY_TIMING, REGPHY_CAL, +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE +/* dynamic registers => managed in driver or not changed, + * can be dumped in interactive mode + */ + REG_DYN, + REGPHY_DYN, +#endif REG_TYPE_NB }; @@ -193,19 +263,26 @@ struct ddr_reg_info { const struct ddr_reg_info ddr_registers[REG_TYPE_NB] = { [REG_REG] = { - "static", ddr_reg, ARRAY_SIZE(ddr_reg), DDR_BASE}, + "static", ddr_reg, DDRCTL_REG_REG_SIZE, DDR_BASE}, [REG_TIMING] = { - "timing", ddr_timing, ARRAY_SIZE(ddr_timing), DDR_BASE}, + "timing", ddr_timing, DDRCTL_REG_TIMING_SIZE, DDR_BASE}, [REG_PERF] = { - "perf", ddr_perf, ARRAY_SIZE(ddr_perf), DDR_BASE}, + "perf", ddr_perf, DDRCTL_REG_PERF_SIZE, DDR_BASE}, [REG_MAP] = { - "map", ddr_map, ARRAY_SIZE(ddr_map), DDR_BASE}, + "map", ddr_map, DDRCTL_REG_MAP_SIZE, DDR_BASE}, [REGPHY_REG] = { - "static", ddrphy_reg, ARRAY_SIZE(ddrphy_reg), DDRPHY_BASE}, + "static", ddrphy_reg, DDRPHY_REG_REG_SIZE, DDRPHY_BASE}, [REGPHY_TIMING] = { - "timing", ddrphy_timing, ARRAY_SIZE(ddrphy_timing), DDRPHY_BASE}, + "timing", ddrphy_timing, DDRPHY_REG_TIMING_SIZE, DDRPHY_BASE}, [REGPHY_CAL] = { - "cal", ddrphy_cal, ARRAY_SIZE(ddrphy_cal), DDRPHY_BASE}, + "cal", ddrphy_cal, DDRPHY_REG_CAL_SIZE, DDRPHY_BASE}, +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE +[REG_DYN] = { + "dyn", ddr_dyn, DDR_REG_DYN_SIZE, DDR_BASE}, +[REGPHY_DYN] = { + "dyn", ddrphy_dyn, DDRPHY_REG_DYN_SIZE, DDRPHY_BASE}, +#endif + }; const char *base_name[] = { @@ -246,6 +323,231 @@ static void set_reg(const struct ddr_info *priv, } } +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE +static void stm32mp1_dump_reg_desc(u32 base_addr, const struct reg_desc *desc) +{ + unsigned int *ptr; + + ptr = (unsigned int *)(base_addr + desc->offset); + printf("%s= 0x%08x\n", desc->name, readl(ptr)); +} + +static void stm32mp1_dump_param_desc(u32 par_addr, const struct reg_desc *desc) +{ + unsigned int *ptr; + + ptr = (unsigned int *)(par_addr + desc->par_offset); + printf("%s= 0x%08x\n", desc->name, readl(ptr)); +} + +static const struct reg_desc *found_reg(const char *name, enum reg_type *type) +{ + unsigned int i, j; + const struct reg_desc *desc; + + for (i = 0; i < ARRAY_SIZE(ddr_registers); i++) { + desc = ddr_registers[i].desc; + for (j = 0; j < ddr_registers[i].size; j++) { + if (strcmp(name, desc[j].name) == 0) { + *type = i; + return &desc[j]; + } + } + } + *type = REG_TYPE_NB; + return NULL; +} + +int stm32mp1_dump_reg(const struct ddr_info *priv, + const char *name) +{ + unsigned int i, j; + const struct reg_desc *desc; + u32 base_addr; + enum base_type p_base; + enum reg_type type; + const char *p_name; + enum base_type filter = NONE_BASE; + int result = -1; + + if (name) { + if (strcmp(name, base_name[DDR_BASE]) == 0) + filter = DDR_BASE; + else if (strcmp(name, base_name[DDRPHY_BASE]) == 0) + filter = DDRPHY_BASE; + } + + for (i = 0; i < ARRAY_SIZE(ddr_registers); i++) { + p_base = ddr_registers[i].base; + p_name = ddr_registers[i].name; + if (!name || (filter == p_base || !strcmp(name, p_name))) { + result = 0; + desc = ddr_registers[i].desc; + base_addr = get_base_addr(priv, p_base); + printf("==%s.%s==\n", base_name[p_base], p_name); + for (j = 0; j < ddr_registers[i].size; j++) + stm32mp1_dump_reg_desc(base_addr, &desc[j]); + } + } + if (result) { + desc = found_reg(name, &type); + if (desc) { + p_base = ddr_registers[type].base; + base_addr = get_base_addr(priv, p_base); + stm32mp1_dump_reg_desc(base_addr, desc); + result = 0; + } + } + return result; +} + +void stm32mp1_edit_reg(const struct ddr_info *priv, + char *name, char *string) +{ + unsigned long *ptr, value; + enum reg_type type; + enum base_type base; + const struct reg_desc *desc; + u32 base_addr; + + desc = found_reg(name, &type); + + if (!desc) { + printf("%s not found\n", name); + return; + } + if (strict_strtoul(string, 16, &value) < 0) { + printf("invalid value %s\n", string); + return; + } + base = ddr_registers[type].base; + base_addr = get_base_addr(priv, base); + ptr = (unsigned long *)(base_addr + desc->offset); + writel(value, ptr); + printf("%s= 0x%08x\n", desc->name, readl(ptr)); +} + +static u32 get_par_addr(const struct stm32mp1_ddr_config *config, + enum reg_type type) +{ + u32 par_addr = 0x0; + + switch (type) { + case REG_REG: + par_addr = (u32)&config->c_reg; + break; + case REG_TIMING: + par_addr = (u32)&config->c_timing; + break; + case REG_PERF: + par_addr = (u32)&config->c_perf; + break; + case REG_MAP: + par_addr = (u32)&config->c_map; + break; + case REGPHY_REG: + par_addr = (u32)&config->p_reg; + break; + case REGPHY_TIMING: + par_addr = (u32)&config->p_timing; + break; + case REGPHY_CAL: + par_addr = (u32)&config->p_cal; + break; + case REG_DYN: + case REGPHY_DYN: + case REG_TYPE_NB: + par_addr = (u32)NULL; + break; + } + + return par_addr; +} + +int stm32mp1_dump_param(const struct stm32mp1_ddr_config *config, + const char *name) +{ + unsigned int i, j; + const struct reg_desc *desc; + u32 par_addr; + enum base_type p_base; + enum reg_type type; + const char *p_name; + enum base_type filter = NONE_BASE; + int result = -EINVAL; + + if (name) { + if (strcmp(name, base_name[DDR_BASE]) == 0) + filter = DDR_BASE; + else if (strcmp(name, base_name[DDRPHY_BASE]) == 0) + filter = DDRPHY_BASE; + } + + for (i = 0; i < ARRAY_SIZE(ddr_registers); i++) { + par_addr = get_par_addr(config, i); + if (!par_addr) + continue; + p_base = ddr_registers[i].base; + p_name = ddr_registers[i].name; + if (!name || (filter == p_base || !strcmp(name, p_name))) { + result = 0; + desc = ddr_registers[i].desc; + printf("==%s.%s==\n", base_name[p_base], p_name); + for (j = 0; j < ddr_registers[i].size; j++) + stm32mp1_dump_param_desc(par_addr, &desc[j]); + } + } + if (result) { + desc = found_reg(name, &type); + if (desc) { + par_addr = get_par_addr(config, type); + if (par_addr) { + stm32mp1_dump_param_desc(par_addr, desc); + result = 0; + } + } + } + return result; +} + +void stm32mp1_edit_param(const struct stm32mp1_ddr_config *config, + char *name, char *string) +{ + unsigned long *ptr, value; + enum reg_type type; + const struct reg_desc *desc; + u32 par_addr; + + desc = found_reg(name, &type); + if (!desc) { + printf("%s not found\n", name); + return; + } + if (strict_strtoul(string, 16, &value) < 0) { + printf("invalid value %s\n", string); + return; + } + par_addr = get_par_addr(config, type); + if (!par_addr) { + printf("no parameter %s\n", name); + return; + } + ptr = (unsigned long *)(par_addr + desc->par_offset); + writel(value, ptr); + printf("%s= 0x%08x\n", desc->name, readl(ptr)); +} +#endif + +__weak bool stm32mp1_ddr_interactive(void *priv, + enum stm32mp1_ddr_interact_step step, + const struct stm32mp1_ddr_config *config) +{ + return false; +} + +#define INTERACTIVE(step)\ + stm32mp1_ddr_interactive(priv, step, config) + static void ddrphy_idone_wait(struct stm32mp1_ddrphy *phy) { u32 pgsr; @@ -312,7 +614,7 @@ static void wait_operating_mode(struct ddr_info *priv, int mode) /* self-refresh due to software => check also STAT.selfref_type */ if (mode == DDRCTRL_STAT_OPERATING_MODE_SR) { mask |= DDRCTRL_STAT_SELFREF_TYPE_MASK; - stat |= DDRCTRL_STAT_SELFREF_TYPE_SR; + val |= DDRCTRL_STAT_SELFREF_TYPE_SR; } else if (mode == DDRCTRL_STAT_OPERATING_MODE_NORMAL) { /* normal mode: handle also automatic self refresh */ mask2 = DDRCTRL_STAT_OPERATING_MODE_MASK | @@ -355,7 +657,7 @@ void stm32mp1_refresh_restore(struct stm32mp1_ddrctl *ctl, } /* board-specific DDR power initializations. */ -__weak int board_ddr_power_init(void) +__weak int board_ddr_power_init(enum ddr_type ddr_type) { return 0; } @@ -365,15 +667,21 @@ void stm32mp1_ddr_init(struct ddr_info *priv, const struct stm32mp1_ddr_config *config) { u32 pir; - int ret; + int ret = -EINVAL; - ret = board_ddr_power_init(); + if (config->c_reg.mstr & DDRCTRL_MSTR_DDR3) + ret = board_ddr_power_init(STM32MP_DDR3); + else if (config->c_reg.mstr & DDRCTRL_MSTR_LPDDR2) + ret = board_ddr_power_init(STM32MP_LPDDR2); + else if (config->c_reg.mstr & DDRCTRL_MSTR_LPDDR3) + ret = board_ddr_power_init(STM32MP_LPDDR3); if (ret) panic("ddr power init failed\n"); +start: debug("name = %s\n", config->info.name); - debug("speed = %d MHz\n", config->info.speed); + debug("speed = %d kHz\n", config->info.speed); debug("size = 0x%x\n", config->info.size); /* * 1. Program the DWC_ddr_umctl2 registers @@ -389,7 +697,7 @@ void stm32mp1_ddr_init(struct ddr_info *priv, /* 1.2. start CLOCK */ if (stm32mp1_ddr_clk_enable(priv, config->info.speed)) - panic("invalid DRAM clock : %d MHz\n", + panic("invalid DRAM clock : %d kHz\n", config->info.speed); /* 1.3. deassert reset */ @@ -401,11 +709,12 @@ void stm32mp1_ddr_init(struct ddr_info *priv, */ clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAPBRST); -/* 1.4. wait 4 cycles for synchronization */ - asm(" nop"); - asm(" nop"); - asm(" nop"); - asm(" nop"); +/* 1.4. wait 128 cycles to permit initialization of end logic */ + udelay(2); + /* for PCLK = 133MHz => 1 us is enough, 2 to allow lower frequency */ + + if (INTERACTIVE(STEP_DDR_RESET)) + goto start; /* 1.5. initialize registers ddr_umctl2 */ /* Stop uMCTL2 before PHY is ready */ @@ -424,6 +733,9 @@ void stm32mp1_ddr_init(struct ddr_info *priv, set_reg(priv, REG_PERF, &config->c_perf); + if (INTERACTIVE(STEP_CTL_INIT)) + goto start; + /* 2. deassert reset signal core_ddrc_rstn, aresetn and presetn */ clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCORERST); clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAXIRST); @@ -436,6 +748,9 @@ void stm32mp1_ddr_init(struct ddr_info *priv, set_reg(priv, REGPHY_TIMING, &config->p_timing); set_reg(priv, REGPHY_CAL, &config->p_cal); + if (INTERACTIVE(STEP_PHY_INIT)) + goto start; + /* 4. Monitor PHY init status by polling PUBL register PGSR.IDONE * Perform DDR PHY DRAM initialization and Gate Training Evaluation */ @@ -492,4 +807,7 @@ void stm32mp1_ddr_init(struct ddr_info *priv, /* enable uMCTL2 AXI port 0 and 1 */ setbits_le32(&priv->ctl->pctrl_0, DDRCTRL_PCTRL_N_PORT_EN); setbits_le32(&priv->ctl->pctrl_1, DDRCTRL_PCTRL_N_PORT_EN); + + if (INTERACTIVE(STEP_DDR_READY)) + goto start; } diff --git a/drivers/ram/stm32mp1/stm32mp1_ddr.h b/drivers/ram/stm32mp1/stm32mp1_ddr.h index 3cd0161299..a8eed89e3c 100644 --- a/drivers/ram/stm32mp1/stm32mp1_ddr.h +++ b/drivers/ram/stm32mp1/stm32mp1_ddr.h @@ -157,7 +157,7 @@ struct stm32mp1_ddrphy_cal { struct stm32mp1_ddr_info { const char *name; - u16 speed; /* in MHZ */ + u32 speed; /* in kHZ */ u32 size; /* memory size in byte = col * row * width */ }; @@ -172,7 +172,7 @@ struct stm32mp1_ddr_config { struct stm32mp1_ddrphy_cal p_cal; }; -int stm32mp1_ddr_clk_enable(struct ddr_info *priv, u16 mem_speed); +int stm32mp1_ddr_clk_enable(struct ddr_info *priv, u32 mem_speed); void stm32mp1_ddrphy_init(struct stm32mp1_ddrphy *phy, u32 pir); void stm32mp1_refresh_disable(struct stm32mp1_ddrctl *ctl); void stm32mp1_refresh_restore(struct stm32mp1_ddrctl *ctl, diff --git a/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h b/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h index a606b2bcbe..9d33186b3a 100644 --- a/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h +++ b/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h @@ -234,6 +234,8 @@ struct stm32mp1_ddrphy { /* DDRCTRL REGISTERS */ #define DDRCTRL_MSTR_DDR3 BIT(0) +#define DDRCTRL_MSTR_LPDDR2 BIT(2) +#define DDRCTRL_MSTR_LPDDR3 BIT(3) #define DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK GENMASK(13, 12) #define DDRCTRL_MSTR_DATA_BUS_WIDTH_FULL (0 << 12) #define DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF (1 << 12) @@ -330,6 +332,7 @@ struct stm32mp1_ddrphy { #define DDRPHYC_DXNGCR_DXEN BIT(0) +#define DDRPHYC_DXNDLLCR_DLLSRST BIT(30) #define DDRPHYC_DXNDLLCR_DLLDIS BIT(31) #define DDRPHYC_DXNDLLCR_SDPHASE_MASK GENMASK(17, 14) #define DDRPHYC_DXNDLLCR_SDPHASE_SHIFT 14 diff --git a/drivers/ram/stm32mp1/stm32mp1_interactive.c b/drivers/ram/stm32mp1/stm32mp1_interactive.c new file mode 100644 index 0000000000..cc9b2e7c96 --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_interactive.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#include <common.h> +#include <console.h> +#include <cli.h> +#include <clk.h> +#include <malloc.h> +#include <ram.h> +#include <reset.h> +#include "stm32mp1_ddr.h" +#include "stm32mp1_tests.h" + +DECLARE_GLOBAL_DATA_PTR; + +enum ddr_command { + DDR_CMD_HELP, + DDR_CMD_INFO, + DDR_CMD_FREQ, + DDR_CMD_RESET, + DDR_CMD_PARAM, + DDR_CMD_PRINT, + DDR_CMD_EDIT, + DDR_CMD_STEP, + DDR_CMD_NEXT, + DDR_CMD_GO, + DDR_CMD_TEST, + DDR_CMD_TUNING, + DDR_CMD_UNKNOWN, +}; + +const char *step_str[] = { + [STEP_DDR_RESET] = "DDR_RESET", + [STEP_CTL_INIT] = "DDR_CTRL_INIT_DONE", + [STEP_PHY_INIT] = "DDR PHY_INIT_DONE", + [STEP_DDR_READY] = "DDR_READY", + [STEP_RUN] = "RUN" +}; + +enum ddr_command stm32mp1_get_command(char *cmd, int argc) +{ + const char *cmd_string[DDR_CMD_UNKNOWN] = { + [DDR_CMD_HELP] = "help", + [DDR_CMD_INFO] = "info", + [DDR_CMD_FREQ] = "freq", + [DDR_CMD_RESET] = "reset", + [DDR_CMD_PARAM] = "param", + [DDR_CMD_PRINT] = "print", + [DDR_CMD_EDIT] = "edit", + [DDR_CMD_STEP] = "step", + [DDR_CMD_NEXT] = "next", + [DDR_CMD_GO] = "go", +#ifdef CONFIG_STM32MP1_DDR_TESTS + [DDR_CMD_TEST] = "test", +#endif +#ifdef CONFIG_STM32MP1_DDR_TUNING + [DDR_CMD_TUNING] = "tuning", +#endif + }; + /* min and max number of argument */ + const char cmd_arg[DDR_CMD_UNKNOWN][2] = { + [DDR_CMD_HELP] = { 0, 0 }, + [DDR_CMD_INFO] = { 0, 255 }, + [DDR_CMD_FREQ] = { 0, 1 }, + [DDR_CMD_RESET] = { 0, 0 }, + [DDR_CMD_PARAM] = { 0, 2 }, + [DDR_CMD_PRINT] = { 0, 1 }, + [DDR_CMD_EDIT] = { 2, 2 }, + [DDR_CMD_STEP] = { 0, 1 }, + [DDR_CMD_NEXT] = { 0, 0 }, + [DDR_CMD_GO] = { 0, 0 }, +#ifdef CONFIG_STM32MP1_DDR_TESTS + [DDR_CMD_TEST] = { 0, 255 }, +#endif +#ifdef CONFIG_STM32MP1_DDR_TUNING + [DDR_CMD_TUNING] = { 0, 255 }, +#endif + }; + int i; + + for (i = 0; i < DDR_CMD_UNKNOWN; i++) + if (!strcmp(cmd, cmd_string[i])) { + if (argc - 1 < cmd_arg[i][0]) { + printf("no enought argument (min=%d)\n", + cmd_arg[i][0]); + return DDR_CMD_UNKNOWN; + } else if (argc - 1 > cmd_arg[i][1]) { + printf("too many argument (max=%d)\n", + cmd_arg[i][1]); + return DDR_CMD_UNKNOWN; + } else { + return i; + } + } + + printf("unknown command %s\n", cmd); + return DDR_CMD_UNKNOWN; +} + +static void stm32mp1_do_usage(void) +{ + const char *usage = { + "commands:\n\n" + "help displays help\n" + "info displays DDR information\n" + "info <param> <val> changes DDR information\n" + " with <param> = step, name, size or speed\n" + "freq displays the DDR PHY frequency in kHz\n" + "freq <freq> changes the DDR PHY frequency\n" + "param [type|reg] prints input parameters\n" + "param <reg> <val> edits parameters in step 0\n" + "print [type|reg] dumps registers\n" + "edit <reg> <val> modifies one register\n" + "step lists the available step\n" + "step <n> go to the step <n>\n" + "next goes to the next step\n" + "go continues the U-Boot SPL execution\n" + "reset reboots machine\n" +#ifdef CONFIG_STM32MP1_DDR_TESTS + "test [help] | <n> [...] lists (with help) or executes test <n>\n" +#endif +#ifdef CONFIG_STM32MP1_DDR_TUNING + "tuning [help] | <n> [...] lists (with help) or execute tuning <n>\n" +#endif + "\nwith for [type|reg]:\n" + " all registers if absent\n" + " <type> = ctl, phy\n" + " or one category (static, timing, map, perf, cal, dyn)\n" + " <reg> = name of the register\n" + }; + + puts(usage); +} + +static bool stm32mp1_check_step(enum stm32mp1_ddr_interact_step step, + enum stm32mp1_ddr_interact_step expected) +{ + if (step != expected) { + printf("invalid step %d:%s expecting %d:%s\n", + step, step_str[step], + expected, + step_str[expected]); + return false; + } + return true; +} + +static void stm32mp1_do_info(struct ddr_info *priv, + struct stm32mp1_ddr_config *config, + enum stm32mp1_ddr_interact_step step, + int argc, char * const argv[]) +{ + unsigned long value; + static char *ddr_name; + + if (argc == 1) { + printf("step = %d : %s\n", step, step_str[step]); + printf("name = %s\n", config->info.name); + printf("size = 0x%x\n", config->info.size); + printf("speed = %d kHz\n", config->info.speed); + return; + } + + if (argc < 3) { + printf("no enought parameter\n"); + return; + } + if (!strcmp(argv[1], "name")) { + u32 i, name_len = 0; + + for (i = 2; i < argc; i++) + name_len += strlen(argv[i]) + 1; + if (ddr_name) + free(ddr_name); + ddr_name = malloc(name_len); + config->info.name = ddr_name; + if (!ddr_name) { + printf("alloc error, length %d\n", name_len); + return; + } + strcpy(ddr_name, argv[2]); + for (i = 3; i < argc; i++) { + strcat(ddr_name, " "); + strcat(ddr_name, argv[i]); + } + printf("name = %s\n", ddr_name); + return; + } + if (!strcmp(argv[1], "size")) { + if (strict_strtoul(argv[2], 16, &value) < 0) { + printf("invalid value %s\n", argv[2]); + } else { + config->info.size = value; + printf("size = 0x%x\n", config->info.size); + } + return; + } + if (!strcmp(argv[1], "speed")) { + if (strict_strtoul(argv[2], 10, &value) < 0) { + printf("invalid value %s\n", argv[2]); + } else { + config->info.speed = value; + printf("speed = %d kHz\n", config->info.speed); + value = clk_get_rate(&priv->clk); + printf("DDRPHY = %ld kHz\n", value / 1000); + } + return; + } + printf("argument %s invalid\n", argv[1]); +} + +static bool stm32mp1_do_freq(struct ddr_info *priv, + int argc, char * const argv[]) +{ + unsigned long ddrphy_clk; + + if (argc == 2) { + if (strict_strtoul(argv[1], 0, &ddrphy_clk) < 0) { + printf("invalid argument %s", argv[1]); + return false; + } + if (clk_set_rate(&priv->clk, ddrphy_clk * 1000)) { + printf("ERROR: update failed!\n"); + return false; + } + } + ddrphy_clk = clk_get_rate(&priv->clk); + printf("DDRPHY = %ld kHz\n", ddrphy_clk / 1000); + if (argc == 2) + return true; + return false; +} + +static void stm32mp1_do_param(enum stm32mp1_ddr_interact_step step, + const struct stm32mp1_ddr_config *config, + int argc, char * const argv[]) +{ + switch (argc) { + case 1: + stm32mp1_dump_param(config, NULL); + break; + case 2: + if (stm32mp1_dump_param(config, argv[1])) + printf("invalid argument %s\n", + argv[1]); + break; + case 3: + if (!stm32mp1_check_step(step, STEP_DDR_RESET)) + return; + stm32mp1_edit_param(config, argv[1], argv[2]); + break; + } +} + +static void stm32mp1_do_print(struct ddr_info *priv, + int argc, char * const argv[]) +{ + switch (argc) { + case 1: + stm32mp1_dump_reg(priv, NULL); + break; + case 2: + if (stm32mp1_dump_reg(priv, argv[1])) + printf("invalid argument %s\n", + argv[1]); + break; + } +} + +static int stm32mp1_do_step(enum stm32mp1_ddr_interact_step step, + int argc, char * const argv[]) +{ + int i; + unsigned long value; + + switch (argc) { + case 1: + for (i = 0; i < ARRAY_SIZE(step_str); i++) + printf("%d:%s\n", i, step_str[i]); + break; + + case 2: + if ((strict_strtoul(argv[1], 0, + &value) < 0) || + value >= ARRAY_SIZE(step_str)) { + printf("invalid argument %s\n", + argv[1]); + goto end; + } + + if (value != STEP_DDR_RESET && + value <= step) { + printf("invalid target %d:%s, current step is %d:%s\n", + (int)value, step_str[value], + step, step_str[step]); + goto end; + } + printf("step to %d:%s\n", + (int)value, step_str[value]); + return (int)value; + }; + +end: + return step; +} + +#if defined(CONFIG_STM32MP1_DDR_TESTS) || defined(CONFIG_STM32MP1_DDR_TUNING) +static const char * const s_result[] = { + [TEST_PASSED] = "Pass", + [TEST_FAILED] = "Failed", + [TEST_ERROR] = "Error" +}; + +static void stm32mp1_ddr_subcmd(struct ddr_info *priv, + int argc, char *argv[], + const struct test_desc array[], + const int array_nb) +{ + int i; + unsigned long value; + int result; + char string[50] = ""; + + if (argc == 1) { + printf("%s:%d\n", argv[0], array_nb); + for (i = 0; i < array_nb; i++) + printf("%d:%s:%s\n", + i, array[i].name, array[i].usage); + return; + } + if (argc > 1 && !strcmp(argv[1], "help")) { + printf("%s:%d\n", argv[0], array_nb); + for (i = 0; i < array_nb; i++) + printf("%d:%s:%s:%s\n", i, + array[i].name, array[i].usage, array[i].help); + return; + } + + if ((strict_strtoul(argv[1], 0, &value) < 0) || + value >= array_nb) { + sprintf(string, "invalid argument %s", + argv[1]); + result = TEST_FAILED; + goto end; + } + + if (argc > (array[value].max_args + 2)) { + sprintf(string, "invalid nb of args %d, max %d", + argc - 2, array[value].max_args); + result = TEST_FAILED; + goto end; + } + + printf("execute %d:%s\n", (int)value, array[value].name); + clear_ctrlc(); + result = array[value].fct(priv->ctl, priv->phy, + string, argc - 2, &argv[2]); + +end: + printf("Result: %s [%s]\n", s_result[result], string); +} +#endif + +bool stm32mp1_ddr_interactive(void *priv, + enum stm32mp1_ddr_interact_step step, + const struct stm32mp1_ddr_config *config) +{ + const char *prompt = "DDR>"; + char buffer[CONFIG_SYS_CBSIZE]; + char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */ + int argc; + static int next_step = -1; + + if (next_step < 0 && step == STEP_DDR_RESET) { +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE_FORCE + gd->flags &= ~(GD_FLG_SILENT | + GD_FLG_DISABLE_CONSOLE); + next_step = STEP_DDR_RESET; +#else + unsigned long start = get_timer(0); + + while (1) { + if (tstc() && (getc() == 'd')) { + next_step = STEP_DDR_RESET; + break; + } + if (get_timer(start) > 100) + break; + } +#endif + } + + debug("** step %d ** %s / %d\n", step, step_str[step], next_step); + + if (next_step < 0) + return false; + + if (step < 0 || step > ARRAY_SIZE(step_str)) { + printf("** step %d ** INVALID\n", step); + return false; + } + + printf("%d:%s\n", step, step_str[step]); + printf("%s\n", prompt); + + if (next_step > step) + return false; + + while (next_step == step) { + cli_readline_into_buffer(prompt, buffer, 0); + argc = cli_simple_parse_line(buffer, argv); + if (!argc) + continue; + + switch (stm32mp1_get_command(argv[0], argc)) { + case DDR_CMD_HELP: + stm32mp1_do_usage(); + break; + + case DDR_CMD_INFO: + stm32mp1_do_info(priv, + (struct stm32mp1_ddr_config *)config, + step, argc, argv); + break; + + case DDR_CMD_FREQ: + if (stm32mp1_do_freq(priv, argc, argv)) + next_step = STEP_DDR_RESET; + break; + + case DDR_CMD_RESET: + do_reset(NULL, 0, 0, NULL); + break; + + case DDR_CMD_PARAM: + stm32mp1_do_param(step, config, argc, argv); + break; + + case DDR_CMD_PRINT: + stm32mp1_do_print(priv, argc, argv); + break; + + case DDR_CMD_EDIT: + stm32mp1_edit_reg(priv, argv[1], argv[2]); + break; + + case DDR_CMD_GO: + next_step = STEP_RUN; + break; + + case DDR_CMD_NEXT: + next_step = step + 1; + break; + + case DDR_CMD_STEP: + next_step = stm32mp1_do_step(step, argc, argv); + break; + +#ifdef CONFIG_STM32MP1_DDR_TESTS + case DDR_CMD_TEST: + if (!stm32mp1_check_step(step, STEP_DDR_READY)) + continue; + stm32mp1_ddr_subcmd(priv, argc, argv, test, test_nb); + break; +#endif + +#ifdef CONFIG_STM32MP1_DDR_TUNING + case DDR_CMD_TUNING: + if (!stm32mp1_check_step(step, STEP_DDR_READY)) + continue; + stm32mp1_ddr_subcmd(priv, argc, argv, + tuning, tuning_nb); + break; +#endif + + default: + break; + } + } + return next_step == STEP_DDR_RESET; +} diff --git a/drivers/ram/stm32mp1/stm32mp1_ram.c b/drivers/ram/stm32mp1/stm32mp1_ram.c index e45a3b2658..84e39d093b 100644 --- a/drivers/ram/stm32mp1/stm32mp1_ram.c +++ b/drivers/ram/stm32mp1/stm32mp1_ram.c @@ -20,7 +20,7 @@ static const char *const clkname[] = { "ddrphyc" /* LAST clock => used for get_rate() */ }; -int stm32mp1_ddr_clk_enable(struct ddr_info *priv, uint16_t mem_speed) +int stm32mp1_ddr_clk_enable(struct ddr_info *priv, uint32_t mem_speed) { unsigned long ddrphy_clk; unsigned long ddr_clk; @@ -43,13 +43,13 @@ int stm32mp1_ddr_clk_enable(struct ddr_info *priv, uint16_t mem_speed) priv->clk = clk; ddrphy_clk = clk_get_rate(&priv->clk); - debug("DDR: mem_speed (%d MHz), RCC %d MHz\n", - mem_speed, (u32)(ddrphy_clk / 1000 / 1000)); + debug("DDR: mem_speed (%d kHz), RCC %d kHz\n", + mem_speed, (u32)(ddrphy_clk / 1000)); /* max 10% frequency delta */ - ddr_clk = abs(ddrphy_clk - mem_speed * 1000 * 1000); - if (ddr_clk > (mem_speed * 1000 * 100)) { - pr_err("DDR expected freq %d MHz, current is %d MHz\n", - mem_speed, (u32)(ddrphy_clk / 1000 / 1000)); + ddr_clk = abs(ddrphy_clk - mem_speed * 1000); + if (ddr_clk > (mem_speed * 100)) { + pr_err("DDR expected freq %d kHz, current is %d kHz\n", + mem_speed, (u32)(ddrphy_clk / 1000)); return -EINVAL; } @@ -102,8 +102,8 @@ static __maybe_unused int stm32mp1_ddr_setup(struct udevice *dev) debug("%s: %s[0x%x] = %d\n", __func__, param[idx].name, param[idx].size, ret); if (ret) { - pr_err("%s: Cannot read %s\n", - __func__, param[idx].name); + pr_err("%s: Cannot read %s, error=%d\n", + __func__, param[idx].name, ret); return -EINVAL; } } diff --git a/drivers/ram/stm32mp1/stm32mp1_tests.c b/drivers/ram/stm32mp1/stm32mp1_tests.c new file mode 100644 index 0000000000..b6fb2a9c58 --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_tests.c @@ -0,0 +1,1426 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ +#include <common.h> +#include <console.h> +#include <asm/io.h> +#include <linux/log2.h> +#include "stm32mp1_tests.h" + +#define ADDR_INVALID 0xFFFFFFFF + +DECLARE_GLOBAL_DATA_PTR; + +static int get_bufsize(char *string, int argc, char *argv[], int arg_nb, + size_t *bufsize, size_t default_size) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 0, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value > STM32_DDR_SIZE || value == 0) { + sprintf(string, "invalid size %s", argv[arg_nb]); + return -1; + } + if (value & 0x3) { + sprintf(string, "unaligned size %s", + argv[arg_nb]); + return -1; + } + *bufsize = value; + } else { + if (default_size != STM32_DDR_SIZE) + *bufsize = default_size; + else + *bufsize = get_ram_size((long *)STM32_DDR_BASE, + STM32_DDR_SIZE); + } + return 0; +} + +static int get_nb_loop(char *string, int argc, char *argv[], int arg_nb, + u32 *nb_loop, u32 default_nb_loop) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 0, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value == 0) + printf("WARNING: infinite loop requested\n"); + *nb_loop = value; + } else { + *nb_loop = default_nb_loop; + } + + return 0; +} + +static int get_addr(char *string, int argc, char *argv[], int arg_nb, + u32 *addr) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 16, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value < STM32_DDR_BASE) { + sprintf(string, "too low address %s", argv[arg_nb]); + return -1; + } + if (value & 0x3 && value != ADDR_INVALID) { + sprintf(string, "unaligned address %s", + argv[arg_nb]); + return -1; + } + *addr = value; + } else { + *addr = STM32_DDR_BASE; + } + + return 0; +} + +static int get_pattern(char *string, int argc, char *argv[], int arg_nb, + u32 *pattern, u32 default_pattern) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 16, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + *pattern = value; + } else { + *pattern = default_pattern; + } + + return 0; +} + +static u32 check_addr(u32 addr, u32 value) +{ + u32 data = readl(addr); + + if (value != data) { + printf("0x%08x: 0x%08x <=> 0x%08x", addr, data, value); + data = readl(addr); + printf("(2nd read: 0x%08x)", data); + if (value == data) + printf("- read error"); + else + printf("- write error"); + printf("\n"); + return -1; + } + return 0; +} + +static int progress(u32 offset) +{ + if (!(offset & 0xFFFFFF)) { + putc('.'); + if (ctrlc()) { + printf("\ntest interrupted!\n"); + return 1; + } + } + return 0; +} + +static int test_loop_end(u32 *loop, u32 nb_loop, u32 progress) +{ + (*loop)++; + if (nb_loop && *loop >= nb_loop) + return 1; + if ((*loop) % progress) + return 0; + /* allow to interrupt the test only for progress step */ + if (ctrlc()) { + printf("test interrupted!\n"); + return 1; + } + printf("loop #%d\n", *loop); + return 0; +} + +/********************************************************************** + * + * Function: memTestDataBus() + * + * Description: Test the data bus wiring in a memory region by + * performing a walking 1's test at a fixed address + * within that region. The address is selected + * by the caller. + * + * Notes: + * + * Returns: 0 if the test succeeds. + * A non-zero result is the first pattern that failed. + * + **********************************************************************/ +static u32 databus(u32 *address) +{ + u32 pattern; + u32 read_value; + + /* Perform a walking 1's test at the given address. */ + for (pattern = 1; pattern != 0; pattern <<= 1) { + /* Write the test pattern. */ + writel(pattern, address); + + /* Read it back (immediately is okay for this test). */ + read_value = readl(address); + debug("%x: %x <=> %x\n", + (u32)address, read_value, pattern); + + if (read_value != pattern) + return pattern; + } + + return 0; +} + +/********************************************************************** + * + * Function: memTestAddressBus() + * + * Description: Test the address bus wiring in a memory region by + * performing a walking 1's test on the relevant bits + * of the address and checking for aliasing. This test + * will find single-bit address failures such as stuck + * -high, stuck-low, and shorted pins. The base address + * and size of the region are selected by the caller. + * + * Notes: For best results, the selected base address should + * have enough LSB 0's to guarantee single address bit + * changes. For example, to test a 64-Kbyte region, + * select a base address on a 64-Kbyte boundary. Also, + * select the region size as a power-of-two--if at all + * possible. + * + * Returns: NULL if the test succeeds. + * A non-zero result is the first address at which an + * aliasing problem was uncovered. By examining the + * contents of memory, it may be possible to gather + * additional information about the problem. + * + **********************************************************************/ +static u32 *addressbus(u32 *address, u32 nb_bytes) +{ + u32 mask = (nb_bytes / sizeof(u32) - 1); + u32 offset; + u32 test_offset; + u32 read_value; + + u32 pattern = 0xAAAAAAAA; + u32 antipattern = 0x55555555; + + /* Write the default pattern at each of the power-of-two offsets. */ + for (offset = 1; (offset & mask) != 0; offset <<= 1) + writel(pattern, &address[offset]); + + /* Check for address bits stuck high. */ + test_offset = 0; + writel(antipattern, &address[test_offset]); + + for (offset = 1; (offset & mask) != 0; offset <<= 1) { + read_value = readl(&address[offset]); + debug("%x: %x <=> %x\n", + (u32)&address[offset], read_value, pattern); + if (read_value != pattern) + return &address[offset]; + } + + writel(pattern, &address[test_offset]); + + /* Check for address bits stuck low or shorted. */ + for (test_offset = 1; (test_offset & mask) != 0; test_offset <<= 1) { + writel(antipattern, &address[test_offset]); + if (readl(&address[0]) != pattern) + return &address[test_offset]; + + for (offset = 1; (offset & mask) != 0; offset <<= 1) { + if (readl(&address[offset]) != pattern && + offset != test_offset) + return &address[test_offset]; + } + writel(pattern, &address[test_offset]); + } + + return NULL; +} + +/********************************************************************** + * + * Function: memTestDevice() + * + * Description: Test the integrity of a physical memory device by + * performing an increment/decrement test over the + * entire region. In the process every storage bit + * in the device is tested as a zero and a one. The + * base address and the size of the region are + * selected by the caller. + * + * Notes: + * + * Returns: NULL if the test succeeds. + * + * A non-zero result is the first address at which an + * incorrect value was read back. By examining the + * contents of memory, it may be possible to gather + * additional information about the problem. + * + **********************************************************************/ +static u32 *memdevice(u32 *address, u32 nb_bytes) +{ + u32 offset; + u32 nb_words = nb_bytes / sizeof(u32); + + u32 pattern; + u32 antipattern; + + puts("Fill with pattern"); + /* Fill memory with a known pattern. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + writel(pattern, &address[offset]); + if (progress(offset)) + return NULL; + } + + puts("\nCheck and invert pattern"); + /* Check each location and invert it for the second pass. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + if (readl(&address[offset]) != pattern) + return &address[offset]; + + antipattern = ~pattern; + writel(antipattern, &address[offset]); + if (progress(offset)) + return NULL; + } + + puts("\nCheck inverted pattern"); + /* Check each location for the inverted pattern and zero it. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + antipattern = ~pattern; + if (readl(&address[offset]) != antipattern) + return &address[offset]; + if (progress(offset)) + return NULL; + } + printf("\n"); + + return NULL; +} + +static enum test_result databuswalk0(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i; + u32 loop = 0, nb_loop; + u32 addr; + u32 error = 0; + u32 data; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + for (i = 0; i < 32; i++) + writel(~(1 << i), addr + 4 * i); + for (i = 0; i < 32; i++) { + data = readl(addr + 4 * i); + if (~(1 << i) != data) { + error |= 1 << i; + debug("%x: error %x expected %x => error:%x\n", + addr + 4 * i, data, ~(1 << i), error); + } + } + if (test_loop_end(&loop, nb_loop, 1000)) + break; + for (i = 0; i < 32; i++) + writel(0, addr + 4 * i); + } + if (error) { + sprintf(string, "loop %d: error for bits 0x%x", + loop, error); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops", loop); + return TEST_PASSED; +} + +static enum test_result databuswalk1(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i; + u32 loop = 0, nb_loop; + u32 addr; + u32 error = 0; + u32 data; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + for (i = 0; i < 32; i++) + writel(1 << i, addr + 4 * i); + for (i = 0; i < 32; i++) { + data = readl(addr + 4 * i); + if ((1 << i) != data) { + error |= 1 << i; + debug("%x: error %x expected %x => error:%x\n", + addr + 4 * i, data, (1 << i), error); + } + } + if (test_loop_end(&loop, nb_loop, 1000)) + break; + for (i = 0; i < 32; i++) + writel(0, addr + 4 * i); + } + if (error) { + sprintf(string, "loop %d: error for bits 0x%x", + loop, error); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops", loop); + return TEST_PASSED; +} + +static enum test_result test_databus(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + u32 error; + + if (get_addr(string, argc, argv, 0, &addr)) + return TEST_ERROR; + error = databus((u32 *)addr); + if (error) { + sprintf(string, "0x%x: error for bits 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x", addr); + return TEST_PASSED; +} + +static enum test_result test_addressbus(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + u32 bufsize; + u32 error; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (!is_power_of_2(bufsize)) { + sprintf(string, "size 0x%x is not a power of 2", + (u32)bufsize); + return TEST_ERROR; + } + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + error = (u32)addressbus((u32 *)addr, bufsize); + if (error) { + sprintf(string, "0x%x: error for address 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x, size 0x%x", + addr, bufsize); + return TEST_PASSED; +} + +static enum test_result test_memdevice(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + size_t bufsize; + u32 error; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + error = (u32)memdevice((u32 *)addr, (unsigned long)bufsize); + if (error) { + sprintf(string, "0x%x: error for address 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x, size 0x%x", + addr, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: sso + * + * Description: Test the Simultaneous Switching Output. + * Verifies succes sive reads and writes to the same memory word, + * holding one bit constant while toggling all other data bits + * simultaneously + * => stress the data bus over an address range + * + * The CPU writes to each address in the given range. + * For each bit, first the CPU holds the bit at 1 while + * toggling the other bits, and then the CPU holds the bit at 0 + * while toggling the other bits. + * After each write, the CPU reads the address that was written + * to verify that it contains the correct data + * + **********************************************************************/ +static enum test_result test_sso(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i, j; + u32 addr, bufsize, remaining, offset; + u32 error = 0; + u32 data; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running sso at 0x%x length 0x%x", addr, bufsize); + offset = addr; + remaining = bufsize; + while (remaining) { + for (i = 0; i < 32; i++) { + /* write pattern. */ + for (j = 0; j < 6; j++) { + switch (j) { + case 0: + case 2: + data = 1 << i; + break; + case 3: + case 5: + data = ~(1 << i); + break; + case 1: + data = ~0x0; + break; + case 4: + data = 0x0; + break; + } + + writel(data, offset); + error = check_addr(offset, data); + if (error) + goto end; + } + } + offset += 4; + remaining -= 4; + if (progress(offset << 7)) + goto end; + } + puts("\n"); + +end: + if (error) { + sprintf(string, "error for pattern 0x%x @0x%x", + data, offset); + return TEST_FAILED; + } + sprintf(string, "no error for sso at 0x%x length 0x%x", addr, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: Random + * + * Description: Verifies r/w with pseudo-ramdom value on one region + * + write the region (individual access) + * + memcopy to the 2nd region (try to use burst) + * + verify the 2 regions + * + **********************************************************************/ +static enum test_result test_random(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, offset, value = 0; + size_t bufsize; + u32 loop = 0, nb_loop; + u32 error = 0; + unsigned int seed; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + seed = rand(); + for (offset = addr; offset < addr + bufsize; offset += 4) + writel(rand(), offset); + + memcpy((void *)addr + bufsize, (void *)addr, bufsize); + + srand(seed); + for (offset = addr; offset < addr + 2 * bufsize; offset += 4) { + if (offset == (addr + bufsize)) + srand(seed); + value = rand(); + error = check_addr(offset, value); + if (error) + break; + if (progress(offset)) + return TEST_FAILED; + } + if (test_loop_end(&loop, nb_loop, 100)) + break; + } + + if (error) { + sprintf(string, + "loop %d: error for address 0x%x: 0x%x expected 0x%x", + loop, offset, readl(offset), value); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops, size 0x%x", + loop, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: noise + * + * Description: Verifies r/w while forcing switching of all data bus lines. + * optimised 4 iteration write/read/write/read cycles... + * for pattern and inversed pattern + * + **********************************************************************/ +void do_noise(u32 addr, u32 pattern, u32 *result) +{ + __asm__("push {R0-R11}"); + __asm__("mov r0, %0" : : "r" (addr)); + __asm__("mov r1, %0" : : "r" (pattern)); + __asm__("mov r11, %0" : : "r" (result)); + + __asm__("mvn r2, r1"); + + __asm__("str r1, [r0]"); + __asm__("ldr r3, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r4, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r5, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r6, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r7, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r8, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r9, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r10, [r0]"); + + __asm__("stmia R11!, {R3-R10}"); + + __asm__("pop {R0-R11}"); +} + +static enum test_result test_noise(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, pattern; + u32 result[8]; + int i; + enum test_result res = TEST_PASSED; + + if (get_pattern(string, argc, argv, 0, &pattern, 0xFFFFFFFF)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running noise for 0x%x at 0x%x\n", pattern, addr); + + do_noise(addr, pattern, result); + + for (i = 0; i < 0x8;) { + if (check_addr((u32)&result[i++], pattern)) + res = TEST_FAILED; + if (check_addr((u32)&result[i++], ~pattern)) + res = TEST_FAILED; + } + + return res; +} + +/********************************************************************** + * + * Function: noise_burst + * + * Description: Verifies r/w while forcing switching of all data bus lines. + * optimised write loop witrh store multiple to use burst + * for pattern and inversed pattern + * + **********************************************************************/ +void do_noise_burst(u32 addr, u32 pattern, size_t bufsize) +{ + __asm__("push {R0-R9}"); + __asm__("mov r0, %0" : : "r" (addr)); + __asm__("mov r1, %0" : : "r" (pattern)); + __asm__("mov r9, %0" : : "r" (bufsize)); + + __asm__("mvn r2, r1"); + __asm__("mov r3, r1"); + __asm__("mov r4, r2"); + __asm__("mov r5, r1"); + __asm__("mov r6, r2"); + __asm__("mov r7, r1"); + __asm__("mov r8, r2"); + + __asm__("loop1:"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("subs r9, r9, #128"); + __asm__("bge loop1"); + __asm__("pop {R0-R9}"); +} + +/* chunk size enough to allow interruption with Ctrl-C*/ +#define CHUNK_SIZE 0x8000000 +static enum test_result test_noise_burst(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, offset, pattern; + size_t bufsize, remaining, size; + int i; + enum test_result res = TEST_PASSED; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_pattern(string, argc, argv, 1, &pattern, 0xFFFFFFFF)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running noise burst for 0x%x at 0x%x + 0x%x", + pattern, addr, bufsize); + + offset = addr; + remaining = bufsize; + size = CHUNK_SIZE; + while (remaining) { + if (remaining < size) + size = remaining; + do_noise_burst(offset, pattern, size); + remaining -= size; + offset += size; + if (progress(offset)) { + res = TEST_FAILED; + goto end; + } + } + puts("\ncheck buffer"); + for (i = 0; i < bufsize;) { + if (check_addr(addr + i, pattern)) + res = TEST_FAILED; + i += 4; + if (check_addr(addr + i, ~pattern)) + res = TEST_FAILED; + i += 4; + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } +end: + puts("\n"); + return res; +} + +/********************************************************************** + * + * Function: pattern test + * + * Description: optimized loop for read/write pattern (array of 8 u32) + * + **********************************************************************/ +#define PATTERN_SIZE 8 +static enum test_result test_loop(const u32 *pattern, u32 *address, + const u32 bufsize) +{ + int i; + int j; + enum test_result res = TEST_PASSED; + u32 *offset, testsize, remaining; + + offset = address; + remaining = bufsize; + while (remaining) { + testsize = bufsize > 0x1000000 ? 0x1000000 : bufsize; + + __asm__("push {R0-R10}"); + __asm__("mov r0, %0" : : "r" (pattern)); + __asm__("mov r1, %0" : : "r" (offset)); + __asm__("mov r2, %0" : : "r" (testsize)); + __asm__("ldmia r0!, {R3-R10}"); + + __asm__("loop2:"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("subs r2, r2, #8"); + __asm__("bge loop2"); + __asm__("pop {R0-R10}"); + + offset += testsize; + remaining -= testsize; + if (progress((u32)offset)) { + res = TEST_FAILED; + goto end; + } + } + + puts("\ncheck buffer"); + for (i = 0; i < bufsize; i += PATTERN_SIZE * 4) { + for (j = 0; j < PATTERN_SIZE; j++, address++) + if (check_addr((u32)address, pattern[j])) { + res = TEST_FAILED; + goto end; + } + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + +end: + puts("\n"); + return res; +} + +const u32 pattern_div1_x16[PATTERN_SIZE] = { + 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, + 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF +}; + +const u32 pattern_div2_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, + 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000 +}; + +const u32 pattern_div4_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000 +}; + +const u32 pattern_div4_x32[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_zero_x16[PATTERN_SIZE] = { + 0x00000000, 0x00000000, 0x00000000, 0x0000FFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_zero_x32[PATTERN_SIZE] = { + 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_one_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +const u32 pattern_mostly_one_x32[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +#define NB_PATTERN 5 +static enum test_result test_freq_pattern(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + const u32 * const patterns_x16[NB_PATTERN] = { + pattern_div1_x16, + pattern_div2_x16, + pattern_div4_x16, + pattern_mostly_zero_x16, + pattern_mostly_one_x16, + }; + const u32 * const patterns_x32[NB_PATTERN] = { + pattern_div2_x16, + pattern_div4_x16, + pattern_div4_x32, + pattern_mostly_zero_x32, + pattern_mostly_one_x32 + }; + const char *patterns_comments[NB_PATTERN] = { + "switching at frequency F/1", + "switching at frequency F/2", + "switching at frequency F/4", + "mostly zero", + "mostly one" + }; + + enum test_result res = TEST_PASSED, pattern_res; + int i, bus_width; + const u32 **patterns; + u32 bufsize; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + + switch (readl(&ctl->mstr) & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK) { + case DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF: + case DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER: + bus_width = 16; + break; + default: + bus_width = 32; + break; + } + + printf("running test pattern at 0x%08x length 0x%x width = %d\n", + STM32_DDR_BASE, bufsize, bus_width); + + patterns = + (const u32 **)(bus_width == 16 ? patterns_x16 : patterns_x32); + + for (i = 0; i < NB_PATTERN; i++) { + printf("test data pattern %s:", patterns_comments[i]); + pattern_res = test_loop(patterns[i], (u32 *)STM32_DDR_BASE, + bufsize); + if (pattern_res != TEST_PASSED) { + printf("Failed\n"); + return pattern_res; + } + printf("Passed\n"); + } + + return res; +} + +/********************************************************************** + * + * Function: pattern test with size + * + * Description: loop for write pattern + * + **********************************************************************/ + +static enum test_result test_loop_size(const u32 *pattern, u32 size, + u32 *address, + const u32 bufsize) +{ + int i, j; + enum test_result res = TEST_PASSED; + u32 *p = address; + + for (i = 0; i < bufsize; i += size * 4) { + for (j = 0; j < size ; j++, p++) + *p = pattern[j]; + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + + puts("\ncheck buffer"); + p = address; + for (i = 0; i < bufsize; i += size * 4) { + for (j = 0; j < size; j++, p++) + if (check_addr((u32)p, pattern[j])) { + res = TEST_FAILED; + goto end; + } + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + +end: + puts("\n"); + return res; +} + +static enum test_result test_checkboard(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr; + int i; + + u32 checkboard[2] = {0x55555555, 0xAAAAAAAA}; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 2; i++) { + res = test_loop_size(checkboard, 2, (u32 *)addr, + bufsize); + if (res) + return res; + checkboard[0] = ~checkboard[0]; + checkboard[1] = ~checkboard[1]; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_blockseq(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 256; i++) { + value = i | i << 8 | i << 16 | i << 24; + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_walkbit0(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 64; i++) { + if (i < 32) + value = 1 << i; + else + value = 1 << (63 - i); + + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_walkbit1(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 64; i++) { + if (i < 32) + value = ~(1 << i); + else + value = ~(1 << (63 - i)); + + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +/* + * try to catch bad bits which are dependent on the current values of + * surrounding bits in either the same word32 + */ +static enum test_result test_bitspread(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, bitspread[4]; + int i, j; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 1; i < 32; i++) { + for (j = 0; j < i; j++) { + if (i < 32) + bitspread[0] = (1 << i) | (1 << j); + else + bitspread[0] = (1 << (63 - i)) | + (1 << (63 - j)); + bitspread[1] = bitspread[0]; + bitspread[2] = ~bitspread[0]; + bitspread[3] = ~bitspread[0]; + printf("pattern = %08x", bitspread[0]); + + res = test_loop_size(bitspread, 4, (u32 *)addr, + bufsize); + if (res != TEST_PASSED) + return res; + } + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_bitflip(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr; + int i; + + u32 bitflip[4]; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 32; i++) { + bitflip[0] = 1 << i; + bitflip[1] = bitflip[0]; + bitflip[2] = ~bitflip[0]; + bitflip[3] = bitflip[2]; + printf("pattern = %08x", bitflip[0]); + + res = test_loop_size(bitflip, 4, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +/********************************************************************** + * + * Function: infinite read access to DDR + * + * Description: continuous read the same pattern at the same address + * + **********************************************************************/ +static enum test_result test_read(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 *addr; + u32 data; + u32 loop = 0; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("random "); + random = true; + } + + printf("running at 0x%08x\n", (u32)addr); + + while (1) { + if (random) + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = readl(addr); + if (test_loop_end(&loop, 0, 1000)) + break; + } + sprintf(string, "0x%x: %x", (u32)addr, data); + + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: infinite write access to DDR + * + * Description: continuous write the same pattern at the same address + * + **********************************************************************/ +static enum test_result test_write(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 *addr; + u32 data = 0xA5A5AA55; + u32 loop = 0; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("random "); + random = true; + } + + printf("running at 0x%08x\n", (u32)addr); + + while (1) { + if (random) { + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = rand(); + } + writel(data, addr); + if (test_loop_end(&loop, 0, 1000)) + break; + } + sprintf(string, "0x%x: %x", (u32)addr, data); + + return TEST_PASSED; +} + +#define NB_TEST_INFINITE 2 +static enum test_result test_all(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED, result; + int i, nb_error = 0; + u32 loop = 0, nb_loop; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 1)) + return TEST_ERROR; + + while (!nb_error) { + /* execute all the test except the lasts which are infinite */ + for (i = 1; i < test_nb - NB_TEST_INFINITE; i++) { + printf("execute %d:%s\n", (int)i, test[i].name); + result = test[i].fct(ctl, phy, string, 0, NULL); + printf("result %d:%s = ", (int)i, test[i].name); + if (result != TEST_PASSED) { + nb_error++; + res = TEST_FAILED; + puts("Failed"); + } else { + puts("Passed"); + } + puts("\n\n"); + } + printf("loop %d: %d/%d test failed\n\n\n", + loop + 1, nb_error, test_nb - NB_TEST_INFINITE); + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + if (res != TEST_PASSED) { + sprintf(string, "loop %d: %d/%d test failed", loop, nb_error, + test_nb - NB_TEST_INFINITE); + } else { + sprintf(string, "loop %d: %d tests passed", loop, + test_nb - NB_TEST_INFINITE); + } + return res; +} + +/**************************************************************** + * TEST Description + ****************************************************************/ + +const struct test_desc test[] = { + {test_all, "All", "[loop]", "Execute all tests", 1 }, + {test_databus, "Simple DataBus", "[addr]", + "Verifies each data line by walking 1 on fixed address", + 1 + }, + {databuswalk0, "DataBusWalking0", "[loop] [addr]", + "Verifies each data bus signal can be driven low (32 word burst)", + 2 + }, + {databuswalk1, "DataBusWalking1", "[loop] [addr]", + "Verifies each data bus signal can be driven high (32 word burst)", + 2 + }, + {test_addressbus, "AddressBus", "[size] [addr]", + "Verifies each relevant bits of the address and checking for aliasing", + 2 + }, + {test_memdevice, "MemDevice", "[size] [addr]", + "Test the integrity of a physical memory (test every storage bit in the region)", + 2 + }, + {test_sso, "SimultaneousSwitchingOutput", "[size] [addr] ", + "Stress the data bus over an address range", + 2 + }, + {test_noise, "Noise", "[pattern] [addr]", + "Verifies r/w while forcing switching of all data bus lines.", + 3 + }, + {test_noise_burst, "NoiseBurst", "[size] [pattern] [addr]", + "burst transfers while forcing switching of the data bus lines", + 3 + }, + {test_random, "Random", "[size] [loop] [addr]", + "Verifies r/w and memcopy(burst for pseudo random value.", + 3 + }, + {test_freq_pattern, "FrequencySelectivePattern ", "[size]", + "write & test patterns: Mostly Zero, Mostly One and F/n", + 1 + }, + {test_blockseq, "BlockSequential", "[size] [loop] [addr]", + "test incremental pattern", + 3 + }, + {test_checkboard, "Checkerboard", "[size] [loop] [addr]", + "test checker pattern", + 3 + }, + {test_bitspread, "BitSpread", "[size] [loop] [addr]", + "test Bit Spread pattern", + 3 + }, + {test_bitflip, "BitFlip", "[size] [loop] [addr]", + "test Bit Flip pattern", + 3 + }, + {test_walkbit0, "WalkingOnes", "[size] [loop] [addr]", + "test Walking Ones pattern", + 3 + }, + {test_walkbit1, "WalkingZeroes", "[size] [loop] [addr]", + "test Walking Zeroes pattern", + 3 + }, + /* need to the the 2 last one (infinite) : skipped for test all */ + {test_read, "infinite read", "[addr]", + "basic test : infinite read access", 1}, + {test_write, "infinite write", "[addr]", + "basic test : infinite write access", 1}, +}; + +const int test_nb = ARRAY_SIZE(test); diff --git a/drivers/ram/stm32mp1/stm32mp1_tests.h b/drivers/ram/stm32mp1/stm32mp1_tests.h new file mode 100644 index 0000000000..55f5d6d93b --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_tests.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#ifndef _RAM_STM32MP1_TESTS_H_ +#define _RAM_STM32MP1_TESTS_H_ + +#include "stm32mp1_ddr_regs.h" + +enum test_result { + TEST_PASSED, + TEST_FAILED, + TEST_ERROR +}; + +struct test_desc { + enum test_result (*fct)(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, + int argc, char *argv[]); + const char *name; + const char *usage; + const char *help; + u8 max_args; +}; + +extern const struct test_desc test[]; +extern const int test_nb; + +extern const struct test_desc tuning[]; +extern const int tuning_nb; + +#endif diff --git a/drivers/ram/stm32mp1/stm32mp1_tuning.c b/drivers/ram/stm32mp1/stm32mp1_tuning.c new file mode 100644 index 0000000000..4e1c1fab54 --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_tuning.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ +#include <common.h> +#include <console.h> +#include <clk.h> +#include <ram.h> +#include <reset.h> +#include <asm/io.h> + +#include "stm32mp1_ddr_regs.h" +#include "stm32mp1_ddr.h" +#include "stm32mp1_tests.h" + +#define MAX_DQS_PHASE_IDX _144deg +#define MAX_DQS_UNIT_IDX 7 +#define MAX_GSL_IDX 5 +#define MAX_GPS_IDX 3 + +/* Number of bytes used in this SW. ( min 1--> max 4). */ +#define NUM_BYTES 4 + +enum dqs_phase_enum { + _36deg = 0, + _54deg = 1, + _72deg = 2, + _90deg = 3, + _108deg = 4, + _126deg = 5, + _144deg = 6 +}; + +/* BIST Result struct */ +struct BIST_result { + /* Overall test result: + * 0 Fail (any bit failed) , + * 1 Success (All bits success) + */ + bool test_result; + /* 1: true, all fail / 0: False, not all bits fail */ + bool all_bits_fail; + bool bit_i_test_result[8]; /* 0 fail / 1 success */ +}; + +/* a struct that defines tuning parameters of a byte. */ +struct tuning_position { + u8 phase; /* DQS phase */ + u8 unit; /* DQS unit delay */ + u32 bits_delay; /* Bits deskew in this byte */ +}; + +/* 36deg, 54deg, 72deg, 90deg, 108deg, 126deg, 144deg */ +const u8 dx_dll_phase[7] = {3, 2, 1, 0, 14, 13, 12}; + +static u8 BIST_error_max = 1; +static u32 BIST_seed = 0x1234ABCD; + +static u8 get_nb_bytes(struct stm32mp1_ddrctl *ctl) +{ + u32 data_bus = readl(&ctl->mstr) & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK; + u8 nb_bytes = NUM_BYTES; + + switch (data_bus) { + case DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF: + nb_bytes /= 2; + break; + case DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER: + nb_bytes /= 4; + break; + default: + break; + } + + return nb_bytes; +} + +static void itm_soft_reset(struct stm32mp1_ddrphy *phy) +{ + stm32mp1_ddrphy_init(phy, DDRPHYC_PIR_ITMSRST); +} + +/* Read DQ unit delay register and provides the retrieved value for DQS + * We are assuming that we have the same delay when clocking + * by DQS and when clocking by DQSN + */ +static u8 DQ_unit_index(struct stm32mp1_ddrphy *phy, u8 byte, u8 bit) +{ + u32 index; + u32 addr = DXNDQTR(phy, byte); + + /* We are assuming that we have the same delay when clocking by DQS + * and when clocking by DQSN : use only the low bits + */ + index = (readl(addr) >> DDRPHYC_DXNDQTR_DQDLY_SHIFT(bit)) + & DDRPHYC_DXNDQTR_DQDLY_LOW_MASK; + + pr_debug("%s: [%x]: %x => DQ unit index = %x\n", + __func__, addr, readl(addr), index); + + return index; +} + +/* Sets the DQS phase delay for a byte lane. + *phase delay is specified by giving the index of the desired delay + * in the dx_dll_phase array. + */ +static void DQS_phase_delay(struct stm32mp1_ddrphy *phy, u8 byte, u8 phase_idx) +{ + u8 sdphase_val = 0; + + /* Write DXNDLLCR.SDPHASE = dx_dll_phase(phase_index); */ + sdphase_val = dx_dll_phase[phase_idx]; + clrsetbits_le32(DXNDLLCR(phy, byte), + DDRPHYC_DXNDLLCR_SDPHASE_MASK, + sdphase_val << DDRPHYC_DXNDLLCR_SDPHASE_SHIFT); +} + +/* Sets the DQS unit delay for a byte lane. + * unit delay is specified by giving the index of the desired delay + * for dgsdly and dqsndly (same value). + */ +static void DQS_unit_delay(struct stm32mp1_ddrphy *phy, + u8 byte, u8 unit_dly_idx) +{ + /* Write the same value in DXNDQSTR.DQSDLY and DXNDQSTR.DQSNDLY */ + clrsetbits_le32(DXNDQSTR(phy, byte), + DDRPHYC_DXNDQSTR_DQSDLY_MASK | + DDRPHYC_DXNDQSTR_DQSNDLY_MASK, + (unit_dly_idx << DDRPHYC_DXNDQSTR_DQSDLY_SHIFT) | + (unit_dly_idx << DDRPHYC_DXNDQSTR_DQSNDLY_SHIFT)); + + /* After changing this value, an ITM soft reset (PIR.ITMSRST=1, + * plus PIR.INIT=1) must be issued. + */ + stm32mp1_ddrphy_init(phy, DDRPHYC_PIR_ITMSRST); +} + +/* Sets the DQ unit delay for a bit line in particular byte lane. + * unit delay is specified by giving the desired delay + */ +static void set_DQ_unit_delay(struct stm32mp1_ddrphy *phy, + u8 byte, u8 bit, + u8 dq_delay_index) +{ + u8 dq_bit_delay_val = dq_delay_index | (dq_delay_index << 2); + + /* same value on delay for clock DQ an DQS_b */ + clrsetbits_le32(DXNDQTR(phy, byte), + DDRPHYC_DXNDQTR_DQDLY_MASK + << DDRPHYC_DXNDQTR_DQDLY_SHIFT(bit), + dq_bit_delay_val << DDRPHYC_DXNDQTR_DQDLY_SHIFT(bit)); +} + +static void set_r0dgsl_delay(struct stm32mp1_ddrphy *phy, + u8 byte, u8 r0dgsl_idx) +{ + clrsetbits_le32(DXNDQSTR(phy, byte), + DDRPHYC_DXNDQSTR_R0DGSL_MASK, + r0dgsl_idx << DDRPHYC_DXNDQSTR_R0DGSL_SHIFT); +} + +static void set_r0dgps_delay(struct stm32mp1_ddrphy *phy, + u8 byte, u8 r0dgps_idx) +{ + clrsetbits_le32(DXNDQSTR(phy, byte), + DDRPHYC_DXNDQSTR_R0DGPS_MASK, + r0dgps_idx << DDRPHYC_DXNDQSTR_R0DGPS_SHIFT); +} + +/* Basic BIST configuration for data lane tests. */ +static void config_BIST(struct stm32mp1_ddrphy *phy) +{ + /* Selects the SDRAM bank address to be used during BIST. */ + u32 bbank = 0; + /* Selects the SDRAM row address to be used during BIST. */ + u32 brow = 0; + /* Selects the SDRAM column address to be used during BIST. */ + u32 bcol = 0; + /* Selects the value by which the SDRAM address is incremented + * for each write/read access. + */ + u32 bainc = 0x00000008; + /* Specifies the maximum SDRAM rank to be used during BIST. + * The default value is set to maximum ranks minus 1. + * must be 0 with single rank + */ + u32 bmrank = 0; + /* Selects the SDRAM rank to be used during BIST. + * must be 0 with single rank + */ + u32 brank = 0; + /* Specifies the maximum SDRAM bank address to be used during + * BIST before the address & increments to the next rank. + */ + u32 bmbank = 1; + /* Specifies the maximum SDRAM row address to be used during + * BIST before the address & increments to the next bank. + */ + u32 bmrow = 0x7FFF; /* To check */ + /* Specifies the maximum SDRAM column address to be used during + * BIST before the address & increments to the next row. + */ + u32 bmcol = 0x3FF; /* To check */ + u32 bmode_conf = 0x00000001; /* DRam mode */ + u32 bdxen_conf = 0x00000001; /* BIST on Data byte */ + u32 bdpat_conf = 0x00000002; /* Select LFSR pattern */ + + /*Setup BIST for DRAM mode, and LFSR-random data pattern.*/ + /*Write BISTRR.BMODE = 1?b1;*/ + /*Write BISTRR.BDXEN = 1?b1;*/ + /*Write BISTRR.BDPAT = 2?b10;*/ + + /* reset BIST */ + writel(0x3, &phy->bistrr); + + writel((bmode_conf << 3) | (bdxen_conf << 14) | (bdpat_conf << 17), + &phy->bistrr); + + /*Setup BIST Word Count*/ + /*Write BISTWCR.BWCNT = 16?b0008;*/ + writel(0x00000200, &phy->bistwcr); /* A multiple of BL/2 */ + + writel(bcol | (brow << 12) | (bbank << 28), &phy->bistar0); + writel(brank | (bmrank << 2) | (bainc << 4), &phy->bistar1); + + /* To check this line : */ + writel(bmcol | (bmrow << 12) | (bmbank << 28), &phy->bistar2); +} + +/* Select the Byte lane to be tested by BIST. */ +static void BIST_datx8_sel(struct stm32mp1_ddrphy *phy, u8 datx8) +{ + clrsetbits_le32(&phy->bistrr, + DDRPHYC_BISTRR_BDXSEL_MASK, + datx8 << DDRPHYC_BISTRR_BDXSEL_SHIFT); + + /*(For example, selecting Byte Lane 3, BISTRR.BDXSEL = 4?b0011)*/ + /* Write BISTRR.BDXSEL = datx8; */ +} + +/* Perform BIST Write_Read test on a byte lane and return test result. */ +static void BIST_test(struct stm32mp1_ddrphy *phy, u8 byte, + struct BIST_result *bist) +{ + bool result = true; /* BIST_SUCCESS */ + u32 cnt = 0; + u32 error = 0; + + bist->test_result = true; + +run: + itm_soft_reset(phy); + + /*Perform BIST Reset*/ + /* Write BISTRR.BINST = 3?b011; */ + clrsetbits_le32(&phy->bistrr, + 0x00000007, + 0x00000003); + + /*Re-seed LFSR*/ + /* Write BISTLSR.SEED = 32'h1234ABCD; */ + if (BIST_seed) + writel(BIST_seed, &phy->bistlsr); + else + writel(rand(), &phy->bistlsr); + + /* some delay to reset BIST */ + mdelay(1); + + /*Perform BIST Run*/ + clrsetbits_le32(&phy->bistrr, + 0x00000007, + 0x00000001); + /* Write BISTRR.BINST = 3?b001; */ + + /* Wait for a number of CTL clocks before reading BIST register*/ + /* Wait 300 ctl_clk cycles; ... IS it really needed?? */ + /* Perform BIST Instruction Stop*/ + /* Write BISTRR.BINST = 3?b010;*/ + + /* poll on BISTGSR.BDONE. If 0, wait. ++TODO Add timeout */ + while (!(readl(&phy->bistgsr) & DDRPHYC_BISTGSR_BDDONE)) + ; + + /*Check if received correct number of words*/ + /* if (Read BISTWCSR.DXWCNT = Read BISTWCR.BWCNT) */ + if (((readl(&phy->bistwcsr)) >> DDRPHYC_BISTWCSR_DXWCNT_SHIFT) == + readl(&phy->bistwcr)) { + /*Determine if there is a data comparison error*/ + /* if (Read BISTGSR.BDXERR = 1?b0) */ + if (readl(&phy->bistgsr) & DDRPHYC_BISTGSR_BDXERR) + result = false; /* BIST_FAIL; */ + else + result = true; /* BIST_SUCCESS; */ + } else { + result = false; /* BIST_FAIL; */ + } + + /* loop while success */ + cnt++; + if (result && cnt != 1000) + goto run; + + if (!result) + error++; + + if (error < BIST_error_max) { + if (cnt != 1000) + goto run; + bist->test_result = true; + } else { + bist->test_result = false; + } +} + +/* After running the deskew algo, this function applies the new DQ delays + * by reading them from the array "deskew_delay"and writing in PHY registers. + * The bits that are not deskewed parfectly (too much skew on them, + * or data eye very wide) are marked in the array deskew_non_converge. + */ +static void apply_deskew_results(struct stm32mp1_ddrphy *phy, u8 byte, + u8 deskew_delay[NUM_BYTES][8], + u8 deskew_non_converge[NUM_BYTES][8]) +{ + u8 bit_i; + u8 index; + + for (bit_i = 0; bit_i < 8; bit_i++) { + set_DQ_unit_delay(phy, byte, bit_i, deskew_delay[byte][bit_i]); + index = DQ_unit_index(phy, byte, bit_i); + pr_debug("Byte %d ; bit %d : The new DQ delay (%d) index=%d [delta=%d, 3 is the default]", + byte, bit_i, deskew_delay[byte][bit_i], + index, index - 3); + printf("Byte %d, bit %d, DQ delay = %d", + byte, bit_i, deskew_delay[byte][bit_i]); + if (deskew_non_converge[byte][bit_i] == 1) + pr_debug(" - not converged : still more skew"); + printf("\n"); + } +} + +/* DQ Bit de-skew algorithm. + * Deskews data lines as much as possible. + * 1. Add delay to DQS line until finding the failure + * (normally a hold time violation) + * 2. Reduce DQS line by small steps until finding the very first time + * we go back to "Pass" condition. + * 3. For each DQ line, Reduce DQ delay until finding the very first failure + * (normally a hold time fail) + * 4. When all bits are at their first failure delay, we can consider them + * aligned. + * Handle conrer situation (Can't find Pass-fail, or fail-pass transitions + * at any step) + * TODO Provide a return Status. Improve doc + */ +static enum test_result bit_deskew(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, char *string) +{ + /* New DQ delay value (index), set during Deskew algo */ + u8 deskew_delay[NUM_BYTES][8]; + /*If there is still skew on a bit, mark this bit. */ + u8 deskew_non_converge[NUM_BYTES][8]; + struct BIST_result result; + s8 dqs_unit_delay_index = 0; + u8 datx8 = 0; + u8 bit_i = 0; + s8 phase_idx = 0; + s8 bit_i_delay_index = 0; + u8 success = 0; + struct tuning_position last_right_ok; + u8 force_stop = 0; + u8 fail_found; + u8 error = 0; + u8 nb_bytes = get_nb_bytes(ctl); + /* u8 last_pass_dqs_unit = 0; */ + + memset(deskew_delay, 0, sizeof(deskew_delay)); + memset(deskew_non_converge, 0, sizeof(deskew_non_converge)); + + /*Disable DQS Drift Compensation*/ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_DFTCMP); + /*Disable all bytes*/ + /* Disable automatic power down of DLL and IOs when disabling + * a byte (To avoid having to add programming and delay + * for a DLL re-lock when later re-enabling a disabled Byte Lane) + */ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_PDDISDX); + + /* Disable all data bytes */ + clrbits_le32(&phy->dx0gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx1gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx2gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx3gcr, DDRPHYC_DXNGCR_DXEN); + + /* Config the BIST block */ + config_BIST(phy); + pr_debug("BIST Config done.\n"); + + /* Train each byte */ + for (datx8 = 0; datx8 < nb_bytes; datx8++) { + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + datx8 + 1, nb_bytes, error); + return TEST_FAILED; + } + pr_debug("\n======================\n"); + pr_debug("Start deskew byte %d .\n", datx8); + pr_debug("======================\n"); + /* Enable Byte (DXNGCR, bit DXEN) */ + setbits_le32(DXNGCR(phy, datx8), DDRPHYC_DXNGCR_DXEN); + + /* Select the byte lane for comparison of read data */ + BIST_datx8_sel(phy, datx8); + + /* Set all DQDLYn to maximum value. All bits within the byte + * will be delayed with DQSTR = 2 instead of max = 3 + * to avoid inter bits fail influence + */ + writel(0xAAAAAAAA, DXNDQTR(phy, datx8)); + + /* Set the DQS phase delay to 90 DEG (default). + * What is defined here is the index of the desired config + * in the PHASE array. + */ + phase_idx = _90deg; + + /* Set DQS unit delay to the max value. */ + dqs_unit_delay_index = MAX_DQS_UNIT_IDX; + DQS_unit_delay(phy, datx8, dqs_unit_delay_index); + DQS_phase_delay(phy, datx8, phase_idx); + + /* Issue a DLL soft reset */ + clrbits_le32(DXNDLLCR(phy, datx8), DDRPHYC_DXNDLLCR_DLLSRST); + setbits_le32(DXNDLLCR(phy, datx8), DDRPHYC_DXNDLLCR_DLLSRST); + + /* Test this typical init condition */ + BIST_test(phy, datx8, &result); + success = result.test_result; + + /* If the test pass in this typical condition, + * start the algo with it. + * Else, look for Pass init condition + */ + if (!success) { + pr_debug("Fail at init condtion. Let's look for a good init condition.\n"); + success = 0; /* init */ + /* Make sure we start with a PASS condition before + * looking for a fail condition. + * Find the first PASS PHASE condition + */ + + /* escape if we find a PASS */ + pr_debug("increase Phase idx\n"); + while (!success && (phase_idx <= MAX_DQS_PHASE_IDX)) { + DQS_phase_delay(phy, datx8, phase_idx); + BIST_test(phy, datx8, &result); + success = result.test_result; + phase_idx++; + } + /* if ended with success + * ==>> Restore the fist success condition + */ + if (success) + phase_idx--; /* because it ended with ++ */ + } + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + datx8 + 1, nb_bytes, error); + return TEST_FAILED; + } + /* We couldn't find a successful condition, its seems + * we have hold violation, lets try reduce DQS_unit Delay + */ + if (!success) { + /* We couldn't find a successful condition, its seems + * we have hold violation, lets try reduce DQS_unit + * Delay + */ + pr_debug("Still fail. Try decrease DQS Unit delay\n"); + + phase_idx = 0; + dqs_unit_delay_index = 0; + DQS_phase_delay(phy, datx8, phase_idx); + + /* escape if we find a PASS */ + while (!success && + (dqs_unit_delay_index <= + MAX_DQS_UNIT_IDX)) { + DQS_unit_delay(phy, datx8, + dqs_unit_delay_index); + BIST_test(phy, datx8, &result); + success = result.test_result; + dqs_unit_delay_index++; + } + if (success) { + /* Restore the first success condition*/ + dqs_unit_delay_index--; + /* last_pass_dqs_unit = dqs_unit_delay_index;*/ + DQS_unit_delay(phy, datx8, + dqs_unit_delay_index); + } else { + /* No need to continue, + * there is no pass region. + */ + force_stop = 1; + } + } + + /* There is an initial PASS condition + * Look for the first failing condition by PHASE stepping. + * This part of the algo can finish without converging. + */ + if (force_stop) { + printf("Result: Failed "); + printf("[Cannot Deskew lines, "); + printf("there is no PASS region]\n"); + error++; + continue; + } + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + datx8 + 1, nb_bytes, error); + return TEST_FAILED; + } + + pr_debug("there is a pass region for phase idx %d\n", + phase_idx); + pr_debug("Step1: Find the first failing condition\n"); + /* Look for the first failing condition by PHASE stepping. + * This part of the algo can finish without converging. + */ + + /* escape if we find a fail (hold time violation) + * condition at any bit or if out of delay range. + */ + while (success && (phase_idx <= MAX_DQS_PHASE_IDX)) { + DQS_phase_delay(phy, datx8, phase_idx); + BIST_test(phy, datx8, &result); + success = result.test_result; + phase_idx++; + } + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + datx8 + 1, nb_bytes, error); + return TEST_FAILED; + } + + /* if the loop ended with a failing condition at any bit, + * lets look for the first previous success condition by unit + * stepping (minimal delay) + */ + if (!success) { + pr_debug("Fail region (PHASE) found phase idx %d\n", + phase_idx); + pr_debug("Let's look for first success by DQS Unit steps\n"); + /* This part, the algo always converge */ + phase_idx--; + + /* escape if we find a success condition + * or if out of delay range. + */ + while (!success && dqs_unit_delay_index >= 0) { + DQS_unit_delay(phy, datx8, + dqs_unit_delay_index); + BIST_test(phy, datx8, &result); + success = result.test_result; + dqs_unit_delay_index--; + } + /* if the loop ended with a success condition, + * the last delay Right OK (before hold violation) + * condition is then defined as following: + */ + if (success) { + /* Hold the dely parameters of the the last + * delay Right OK condition. + * -1 to get back to current condition + */ + last_right_ok.phase = phase_idx; + /*+1 to get back to current condition */ + last_right_ok.unit = dqs_unit_delay_index + 1; + last_right_ok.bits_delay = 0xFFFFFFFF; + pr_debug("Found %d\n", dqs_unit_delay_index); + } else { + /* the last OK condition is then with the + * previous phase_idx. + * -2 instead of -1 because at the last + * iteration of the while(), + * we incremented phase_idx + */ + last_right_ok.phase = phase_idx - 1; + /* Nominal+1. Because we want the previous + * delay after reducing the phase delay. + */ + last_right_ok.unit = 1; + last_right_ok.bits_delay = 0xFFFFFFFF; + pr_debug("Not Found : try previous phase %d\n", + phase_idx - 1); + + DQS_phase_delay(phy, datx8, phase_idx - 1); + dqs_unit_delay_index = 0; + success = true; + while (success && + (dqs_unit_delay_index < + MAX_DQS_UNIT_IDX)) { + DQS_unit_delay(phy, datx8, + dqs_unit_delay_index); + BIST_test(phy, datx8, &result); + success = result.test_result; + dqs_unit_delay_index++; + pr_debug("dqs_unit_delay_index = %d, result = %d\n", + dqs_unit_delay_index, success); + } + + if (!success) { + last_right_ok.unit = + dqs_unit_delay_index - 1; + } else { + last_right_ok.unit = 0; + pr_debug("ERROR: failed region not FOUND"); + } + } + } else { + /* we can't find a failing condition at all bits + * ==> Just hold the last test condition + * (the max DQS delay) + * which is the most likely, + * the closest to a hold violation + * If we can't find a Fail condition after + * the Pass region, stick at this position + * In order to have max chances to find a fail + * when reducing DQ delays. + */ + last_right_ok.phase = MAX_DQS_PHASE_IDX; + last_right_ok.unit = MAX_DQS_UNIT_IDX; + last_right_ok.bits_delay = 0xFFFFFFFF; + pr_debug("Can't find the a fail condition\n"); + } + + /* step 2: + * if we arrive at this stage, it means that we found the last + * Right OK condition (by tweeking the DQS delay). Or we simply + * pushed DQS delay to the max + * This means that by reducing the delay on some DQ bits, + * we should find a failing condition. + */ + printf("Byte %d, DQS unit = %d, phase = %d\n", + datx8, last_right_ok.unit, last_right_ok.phase); + pr_debug("Step2, unit = %d, phase = %d, bits delay=%x\n", + last_right_ok.unit, last_right_ok.phase, + last_right_ok.bits_delay); + + /* Restore the last_right_ok condtion. */ + DQS_unit_delay(phy, datx8, last_right_ok.unit); + DQS_phase_delay(phy, datx8, last_right_ok.phase); + writel(last_right_ok.bits_delay, DXNDQTR(phy, datx8)); + + /* train each bit + * reduce delay on each bit, and perform a write/read test + * and stop at the very first time it fails. + * the goal is the find the first failing condition + * for each bit. + * When we achieve this condition< for all the bits, + * we are sure they are aligned (+/- step resolution) + */ + fail_found = 0; + for (bit_i = 0; bit_i < 8; bit_i++) { + if (ctrlc()) { + sprintf(string, + "interrupted at byte %d/%d, error=%d", + datx8 + 1, nb_bytes, error); + return error; + } + pr_debug("deskewing bit %d:\n", bit_i); + success = 1; /* init */ + /* Set all DQDLYn to maximum value. + * Only bit_i will be down-delayed + * ==> if we have a fail, it will be definitely + * from bit_i + */ + writel(0xFFFFFFFF, DXNDQTR(phy, datx8)); + /* Arriving at this stage, + * we have a success condition with delay = 3; + */ + bit_i_delay_index = 3; + + /* escape if bit delay is out of range or + * if a fatil occurs + */ + while ((bit_i_delay_index >= 0) && success) { + set_DQ_unit_delay(phy, datx8, + bit_i, + bit_i_delay_index); + BIST_test(phy, datx8, &result); + success = result.test_result; + bit_i_delay_index--; + } + + /* if escape with a fail condition + * ==> save this position for bit_i + */ + if (!success) { + /* save the delay position. + * Add 1 because the while loop ended with a --, + * and that we need to hold the last success + * delay + */ + deskew_delay[datx8][bit_i] = + bit_i_delay_index + 2; + if (deskew_delay[datx8][bit_i] > 3) + deskew_delay[datx8][bit_i] = 3; + + /* A flag that states we found at least a fail + * at one bit. + */ + fail_found = 1; + pr_debug("Fail found on bit %d, for delay = %d => deskew[%d][%d] = %d\n", + bit_i, bit_i_delay_index + 1, + datx8, bit_i, + deskew_delay[datx8][bit_i]); + } else { + /* if we can find a success condition by + * back-delaying this bit, just set the delay + * to 0 (the best deskew + * possible) and mark the bit. + */ + deskew_delay[datx8][bit_i] = 0; + /* set a flag that will be used later + * in the report. + */ + deskew_non_converge[datx8][bit_i] = 1; + pr_debug("Fail not found on bit %d => deskew[%d][%d] = %d\n", + bit_i, datx8, bit_i, + deskew_delay[datx8][bit_i]); + } + } + pr_debug("**********byte %d tuning complete************\n", + datx8); + /* If we can't find any failure by back delaying DQ lines, + * hold the default values + */ + if (!fail_found) { + for (bit_i = 0; bit_i < 8; bit_i++) + deskew_delay[datx8][bit_i] = 0; + pr_debug("The Deskew algorithm can't converge, there is too much margin in your design. Good job!\n"); + } + + apply_deskew_results(phy, datx8, deskew_delay, + deskew_non_converge); + /* Restore nominal value for DQS delay */ + DQS_phase_delay(phy, datx8, 3); + DQS_unit_delay(phy, datx8, 3); + /* disable byte after byte bits deskew */ + clrbits_le32(DXNGCR(phy, datx8), DDRPHYC_DXNGCR_DXEN); + } /* end of byte deskew */ + + /* re-enable all data bytes */ + setbits_le32(&phy->dx0gcr, DDRPHYC_DXNGCR_DXEN); + setbits_le32(&phy->dx1gcr, DDRPHYC_DXNGCR_DXEN); + setbits_le32(&phy->dx2gcr, DDRPHYC_DXNGCR_DXEN); + setbits_le32(&phy->dx3gcr, DDRPHYC_DXNGCR_DXEN); + + if (error) { + sprintf(string, "error = %d", error); + return TEST_FAILED; + } + + return TEST_PASSED; +} /* end function */ + +/* Trim DQS timings and set it in the centre of data eye. + * Look for a PPPPF region, then look for a FPPP region and finally select + * the mid of the FPPPPPF region + */ +static enum test_result eye_training(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, char *string) +{ + /*Stores the DQS trim values (PHASE index, unit index) */ + u8 eye_training_val[NUM_BYTES][2]; + u8 byte = 0; + struct BIST_result result; + s8 dqs_unit_delay_index = 0; + s8 phase_idx = 0; + s8 dqs_unit_delay_index_pass = 0; + s8 phase_idx_pass = 0; + u8 success = 0; + u8 left_phase_bound_found, right_phase_bound_found; + u8 left_unit_bound_found, right_unit_bound_found; + u8 left_bound_found, right_bound_found; + struct tuning_position left_bound, right_bound; + u8 error = 0; + u8 nb_bytes = get_nb_bytes(ctl); + + /*Disable DQS Drift Compensation*/ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_DFTCMP); + /*Disable all bytes*/ + /* Disable automatic power down of DLL and IOs when disabling a byte + * (To avoid having to add programming and delay + * for a DLL re-lock when later re-enabling a disabled Byte Lane) + */ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_PDDISDX); + + /*Disable all data bytes */ + clrbits_le32(&phy->dx0gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx1gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx2gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx3gcr, DDRPHYC_DXNGCR_DXEN); + + /* Config the BIST block */ + config_BIST(phy); + + for (byte = 0; byte < nb_bytes; byte++) { + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + byte + 1, nb_bytes, error); + return TEST_FAILED; + } + right_bound.phase = 0; + right_bound.unit = 0; + + left_bound.phase = 0; + left_bound.unit = 0; + + left_phase_bound_found = 0; + right_phase_bound_found = 0; + + left_unit_bound_found = 0; + right_unit_bound_found = 0; + + left_bound_found = 0; + right_bound_found = 0; + + /* Enable Byte (DXNGCR, bit DXEN) */ + setbits_le32(DXNGCR(phy, byte), DDRPHYC_DXNGCR_DXEN); + + /* Select the byte lane for comparison of read data */ + BIST_datx8_sel(phy, byte); + + /* Set DQS phase delay to the nominal value. */ + phase_idx = _90deg; + phase_idx_pass = phase_idx; + + /* Set DQS unit delay to the nominal value. */ + dqs_unit_delay_index = 3; + dqs_unit_delay_index_pass = dqs_unit_delay_index; + success = 0; + + pr_debug("STEP0: Find Init delay\n"); + /* STEP0: Find Init delay: a delay that put the system + * in a "Pass" condition then (TODO) update + * dqs_unit_delay_index_pass & phase_idx_pass + */ + DQS_unit_delay(phy, byte, dqs_unit_delay_index); + DQS_phase_delay(phy, byte, phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + /* If we have a fail in the nominal condition */ + if (!success) { + /* Look at the left */ + while (phase_idx >= 0 && !success) { + phase_idx--; + DQS_phase_delay(phy, byte, phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + } + } + if (!success) { + /* if we can't find pass condition, + * then look at the right + */ + phase_idx = _90deg; + while (phase_idx <= MAX_DQS_PHASE_IDX && + !success) { + phase_idx++; + DQS_phase_delay(phy, byte, + phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + } + } + /* save the pass condition */ + if (success) { + phase_idx_pass = phase_idx; + } else { + printf("Result: Failed "); + printf("[Cannot DQS timings, "); + printf("there is no PASS region]\n"); + error++; + continue; + } + + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + byte + 1, nb_bytes, error); + return TEST_FAILED; + } + pr_debug("STEP1: Find LEFT PHASE DQS Bound\n"); + /* STEP1: Find LEFT PHASE DQS Bound */ + while ((phase_idx >= 0) && + (phase_idx <= MAX_DQS_PHASE_IDX) && + !left_phase_bound_found) { + DQS_unit_delay(phy, byte, + dqs_unit_delay_index); + DQS_phase_delay(phy, byte, + phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + + /*TODO: Manage the case were at the beginning + * there is already a fail + */ + if (!success) { + /* the last pass condition */ + left_bound.phase = ++phase_idx; + left_phase_bound_found = 1; + } else if (success) { + phase_idx--; + } + } + if (!left_phase_bound_found) { + left_bound.phase = 0; + phase_idx = 0; + } + /* If not found, lets take 0 */ + + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + byte + 1, nb_bytes, error); + return TEST_FAILED; + } + pr_debug("STEP2: Find UNIT left bound\n"); + /* STEP2: Find UNIT left bound */ + while ((dqs_unit_delay_index >= 0) && + !left_unit_bound_found) { + DQS_unit_delay(phy, byte, + dqs_unit_delay_index); + DQS_phase_delay(phy, byte, phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + if (!success) { + left_bound.unit = + ++dqs_unit_delay_index; + left_unit_bound_found = 1; + left_bound_found = 1; + } else if (success) { + dqs_unit_delay_index--; + } + } + + /* If not found, lets take 0 */ + if (!left_unit_bound_found) + left_bound.unit = 0; + + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + byte + 1, nb_bytes, error); + return TEST_FAILED; + } + pr_debug("STEP3: Find PHase right bound\n"); + /* STEP3: Find PHase right bound, start with "pass" + * condition + */ + + /* Set DQS phase delay to the pass value. */ + phase_idx = phase_idx_pass; + + /* Set DQS unit delay to the pass value. */ + dqs_unit_delay_index = dqs_unit_delay_index_pass; + + while ((phase_idx <= MAX_DQS_PHASE_IDX) && + !right_phase_bound_found) { + DQS_unit_delay(phy, byte, + dqs_unit_delay_index); + DQS_phase_delay(phy, byte, phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + if (!success) { + /* the last pass condition */ + right_bound.phase = --phase_idx; + right_phase_bound_found = 1; + } else if (success) { + phase_idx++; + } + } + + /* If not found, lets take the max value */ + if (!right_phase_bound_found) { + right_bound.phase = MAX_DQS_PHASE_IDX; + phase_idx = MAX_DQS_PHASE_IDX; + } + + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d, error=%d", + byte + 1, nb_bytes, error); + return TEST_FAILED; + } + pr_debug("STEP4: Find UNIT right bound\n"); + /* STEP4: Find UNIT right bound */ + while ((dqs_unit_delay_index <= MAX_DQS_UNIT_IDX) && + !right_unit_bound_found) { + DQS_unit_delay(phy, byte, + dqs_unit_delay_index); + DQS_phase_delay(phy, byte, phase_idx); + BIST_test(phy, byte, &result); + success = result.test_result; + if (!success) { + right_bound.unit = + --dqs_unit_delay_index; + right_unit_bound_found = 1; + right_bound_found = 1; + } else if (success) { + dqs_unit_delay_index++; + } + } + /* If not found, lets take the max value */ + if (!right_unit_bound_found) + right_bound.unit = MAX_DQS_UNIT_IDX; + + /* If we found a regular FAil Pass FAil pattern + * FFPPPPPPFF + * OR PPPPPFF Or FFPPPPP + */ + + if (left_bound_found || right_bound_found) { + eye_training_val[byte][0] = (right_bound.phase + + left_bound.phase) / 2; + eye_training_val[byte][1] = (right_bound.unit + + left_bound.unit) / 2; + + /* If we already lost 1/2PHASE Tuning, + * let's try to recover by ++ on unit + */ + if (((right_bound.phase + left_bound.phase) % 2 == 1) && + eye_training_val[byte][1] != MAX_DQS_UNIT_IDX) + eye_training_val[byte][1]++; + pr_debug("** found phase : %d - %d & unit %d - %d\n", + right_bound.phase, left_bound.phase, + right_bound.unit, left_bound.unit); + pr_debug("** calculating mid region: phase: %d unit: %d (nominal is 3)\n", + eye_training_val[byte][0], + eye_training_val[byte][1]); + } else { + /* PPPPPPPPPP, we're already good. + * Set nominal values. + */ + eye_training_val[byte][0] = 3; + eye_training_val[byte][1] = 3; + } + DQS_phase_delay(phy, byte, eye_training_val[byte][0]); + DQS_unit_delay(phy, byte, eye_training_val[byte][1]); + + printf("Byte %d, DQS unit = %d, phase = %d\n", + byte, + eye_training_val[byte][1], + eye_training_val[byte][0]); + } + + if (error) { + sprintf(string, "error = %d", error); + return TEST_FAILED; + } + + return TEST_PASSED; +} + +static void display_reg_results(struct stm32mp1_ddrphy *phy, u8 byte) +{ + u8 i = 0; + + printf("Byte %d Dekew result, bit0 delay, bit1 delay...bit8 delay\n ", + byte); + + for (i = 0; i < 8; i++) + printf("%d ", DQ_unit_index(phy, byte, i)); + printf("\n"); + + printf("dxndllcr: [%08x] val:%08x\n", + DXNDLLCR(phy, byte), + readl(DXNDLLCR(phy, byte))); + printf("dxnqdstr: [%08x] val:%08x\n", + DXNDQSTR(phy, byte), + readl(DXNDQSTR(phy, byte))); + printf("dxndqtr: [%08x] val:%08x\n", + DXNDQTR(phy, byte), + readl(DXNDQTR(phy, byte))); +} + +/* analyse the dgs gating log table, and determine the midpoint.*/ +static u8 set_midpoint_read_dqs_gating(struct stm32mp1_ddrphy *phy, u8 byte, + u8 dqs_gating[NUM_BYTES] + [MAX_GSL_IDX + 1] + [MAX_GPS_IDX + 1]) +{ + /* stores the dqs gate values (gsl index, gps index) */ + u8 dqs_gate_values[NUM_BYTES][2]; + u8 gsl_idx, gps_idx = 0; + u8 left_bound_idx[2] = {0, 0}; + u8 right_bound_idx[2] = {0, 0}; + u8 left_bound_found = 0; + u8 right_bound_found = 0; + u8 intermittent = 0; + u8 value; + + for (gsl_idx = 0; gsl_idx <= MAX_GSL_IDX; gsl_idx++) { + for (gps_idx = 0; gps_idx <= MAX_GPS_IDX; gps_idx++) { + value = dqs_gating[byte][gsl_idx][gps_idx]; + if (value == 1 && left_bound_found == 0) { + left_bound_idx[0] = gsl_idx; + left_bound_idx[1] = gps_idx; + left_bound_found = 1; + } else if (value == 0 && + left_bound_found == 1 && + !right_bound_found) { + if (gps_idx == 0) { + right_bound_idx[0] = gsl_idx - 1; + right_bound_idx[1] = MAX_GPS_IDX; + } else { + right_bound_idx[0] = gsl_idx; + right_bound_idx[1] = gps_idx - 1; + } + right_bound_found = 1; + } else if (value == 1 && + right_bound_found == 1) { + intermittent = 1; + } + } + } + + /* if only ppppppp is found, there is no mid region. */ + if (left_bound_idx[0] == 0 && left_bound_idx[1] == 0 && + right_bound_idx[0] == 0 && right_bound_idx[1] == 0) + intermittent = 1; + + /*if we found a regular fail pass fail pattern ffppppppff + * or pppppff or ffppppp + */ + if (!intermittent) { + /*if we found a regular fail pass fail pattern ffppppppff + * or pppppff or ffppppp + */ + if (left_bound_found || right_bound_found) { + pr_debug("idx0(%d): %d %d idx1(%d) : %d %d\n", + left_bound_found, + right_bound_idx[0], left_bound_idx[0], + right_bound_found, + right_bound_idx[1], left_bound_idx[1]); + dqs_gate_values[byte][0] = + (right_bound_idx[0] + left_bound_idx[0]) / 2; + dqs_gate_values[byte][1] = + (right_bound_idx[1] + left_bound_idx[1]) / 2; + /* if we already lost 1/2gsl tuning, + * let's try to recover by ++ on gps + */ + if (((right_bound_idx[0] + + left_bound_idx[0]) % 2 == 1) && + dqs_gate_values[byte][1] != MAX_GPS_IDX) + dqs_gate_values[byte][1]++; + /* if we already lost 1/2gsl tuning and gps is on max*/ + else if (((right_bound_idx[0] + + left_bound_idx[0]) % 2 == 1) && + dqs_gate_values[byte][1] == MAX_GPS_IDX) { + dqs_gate_values[byte][1] = 0; + dqs_gate_values[byte][0]++; + } + /* if we have gsl left and write limit too close + * (difference=1) + */ + if (((right_bound_idx[0] - left_bound_idx[0]) == 1)) { + dqs_gate_values[byte][1] = (left_bound_idx[1] + + right_bound_idx[1] + + 4) / 2; + if (dqs_gate_values[byte][1] >= 4) { + dqs_gate_values[byte][0] = + right_bound_idx[0]; + dqs_gate_values[byte][1] -= 4; + } else { + dqs_gate_values[byte][0] = + left_bound_idx[0]; + } + } + pr_debug("*******calculating mid region: system latency: %d phase: %d********\n", + dqs_gate_values[byte][0], + dqs_gate_values[byte][1]); + pr_debug("*******the nominal values were system latency: 0 phase: 2*******\n"); + set_r0dgsl_delay(phy, byte, dqs_gate_values[byte][0]); + set_r0dgps_delay(phy, byte, dqs_gate_values[byte][1]); + } + } else { + /* if intermitant, restore defaut values */ + pr_debug("dqs gating:no regular fail/pass/fail found. defaults values restored.\n"); + set_r0dgsl_delay(phy, byte, 0); + set_r0dgps_delay(phy, byte, 2); + } + + /* return 0 if intermittent or if both left_bound + * and right_bound are not found + */ + return !(intermittent || (left_bound_found && right_bound_found)); +} + +static enum test_result read_dqs_gating(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string) +{ + /* stores the log of pass/fail */ + u8 dqs_gating[NUM_BYTES][MAX_GSL_IDX + 1][MAX_GPS_IDX + 1]; + u8 byte, gsl_idx, gps_idx = 0; + struct BIST_result result; + u8 success = 0; + u8 nb_bytes = get_nb_bytes(ctl); + + memset(dqs_gating, 0x0, sizeof(dqs_gating)); + + /*disable dqs drift compensation*/ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_DFTCMP); + /*disable all bytes*/ + /* disable automatic power down of dll and ios when disabling a byte + * (to avoid having to add programming and delay + * for a dll re-lock when later re-enabling a disabled byte lane) + */ + clrbits_le32(&phy->pgcr, DDRPHYC_PGCR_PDDISDX); + + /* disable all data bytes */ + clrbits_le32(&phy->dx0gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx1gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx2gcr, DDRPHYC_DXNGCR_DXEN); + clrbits_le32(&phy->dx3gcr, DDRPHYC_DXNGCR_DXEN); + + /* config the bist block */ + config_BIST(phy); + + for (byte = 0; byte < nb_bytes; byte++) { + if (ctrlc()) { + sprintf(string, "interrupted at byte %d/%d", + byte + 1, nb_bytes); + return TEST_FAILED; + } + /* enable byte x (dxngcr, bit dxen) */ + setbits_le32(DXNGCR(phy, byte), DDRPHYC_DXNGCR_DXEN); + + /* select the byte lane for comparison of read data */ + BIST_datx8_sel(phy, byte); + for (gsl_idx = 0; gsl_idx <= MAX_GSL_IDX; gsl_idx++) { + for (gps_idx = 0; gps_idx <= MAX_GPS_IDX; gps_idx++) { + if (ctrlc()) { + sprintf(string, + "interrupted at byte %d/%d", + byte + 1, nb_bytes); + return TEST_FAILED; + } + /* write cfg to dxndqstr */ + set_r0dgsl_delay(phy, byte, gsl_idx); + set_r0dgps_delay(phy, byte, gps_idx); + + BIST_test(phy, byte, &result); + success = result.test_result; + if (success) + dqs_gating[byte][gsl_idx][gps_idx] = 1; + itm_soft_reset(phy); + } + } + set_midpoint_read_dqs_gating(phy, byte, dqs_gating); + /* dummy reads */ + readl(0xc0000000); + readl(0xc0000000); + } + + /* re-enable drift compensation */ + /* setbits_le32(&phy->pgcr, DDRPHYC_PGCR_DFTCMP); */ + return TEST_PASSED; +} + +/**************************************************************** + * TEST + **************************************************************** + */ +static enum test_result do_read_dqs_gating(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, + char *argv[]) +{ + u32 rfshctl3 = readl(&ctl->rfshctl3); + u32 pwrctl = readl(&ctl->pwrctl); + enum test_result res; + + stm32mp1_refresh_disable(ctl); + res = read_dqs_gating(ctl, phy, string); + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + + return res; +} + +static enum test_result do_bit_deskew(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 rfshctl3 = readl(&ctl->rfshctl3); + u32 pwrctl = readl(&ctl->pwrctl); + enum test_result res; + + stm32mp1_refresh_disable(ctl); + res = bit_deskew(ctl, phy, string); + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + + return res; +} + +static enum test_result do_eye_training(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 rfshctl3 = readl(&ctl->rfshctl3); + u32 pwrctl = readl(&ctl->pwrctl); + enum test_result res; + + stm32mp1_refresh_disable(ctl); + res = eye_training(ctl, phy, string); + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + + return res; +} + +static enum test_result do_display(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int byte; + u8 nb_bytes = get_nb_bytes(ctl); + + for (byte = 0; byte < nb_bytes; byte++) + display_reg_results(phy, byte); + + return TEST_PASSED; +} + +static enum test_result do_bist_config(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + unsigned long value; + + if (argc > 0) { + if (strict_strtoul(argv[0], 0, &value) < 0) { + sprintf(string, "invalid nbErr %s", argv[0]); + return TEST_FAILED; + } + BIST_error_max = value; + } + if (argc > 1) { + if (strict_strtoul(argv[1], 0, &value) < 0) { + sprintf(string, "invalid Seed %s", argv[1]); + return TEST_FAILED; + } + BIST_seed = value; + } + printf("Bist.nbErr = %d\n", BIST_error_max); + if (BIST_seed) + printf("Bist.Seed = 0x%x\n", BIST_seed); + else + printf("Bist.Seed = random\n"); + + return TEST_PASSED; +} + +/**************************************************************** + * TEST Description + **************************************************************** + */ + +const struct test_desc tuning[] = { + {do_read_dqs_gating, "Read DQS gating", + "software read DQS Gating", "", 0 }, + {do_bit_deskew, "Bit de-skew", "", "", 0 }, + {do_eye_training, "Eye Training", "or DQS training", "", 0 }, + {do_display, "Display registers", "", "", 0 }, + {do_bist_config, "Bist config", "[nbErr] [seed]", + "configure Bist test", 2}, +}; + +const int tuning_nb = ARRAY_SIZE(tuning); diff --git a/drivers/serial/serial_stm32.c b/drivers/serial/serial_stm32.c index e31c87b9ac..cca8b707ac 100644 --- a/drivers/serial/serial_stm32.c +++ b/drivers/serial/serial_stm32.c @@ -269,7 +269,6 @@ static inline void _debug_uart_init(void) _stm32_serial_setbrg(base, uart_info, CONFIG_DEBUG_UART_CLOCK, CONFIG_BAUDRATE); - printf("DEBUG done\n"); } static inline void _debug_uart_putc(int c) @@ -278,7 +277,7 @@ static inline void _debug_uart_putc(int c) struct stm32_uart_info *uart_info = _debug_uart_info(); while (_stm32_serial_putc(base, uart_info, c) == -EAGAIN) - WATCHDOG_RESET(); + ; } DEBUG_UART_FUNCS diff --git a/env/Kconfig b/env/Kconfig index 70858d3b40..1e10c7a4c4 100644 --- a/env/Kconfig +++ b/env/Kconfig @@ -470,7 +470,7 @@ config ENV_EXT4_FILE It's a string of the EXT4 file name. This file use to store the environment (explicit path to the file) -if ARCH_ROCKCHIP || ARCH_SUNXI || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_VERSAL || ARC +if ARCH_ROCKCHIP || ARCH_SUNXI || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_VERSAL || ARC || ARCH_STM32MP config ENV_OFFSET hex "Environment Offset" diff --git a/env/common.c b/env/common.c index 324502ed82..bd340fe9d5 100644 --- a/env/common.c +++ b/env/common.c @@ -23,7 +23,10 @@ DECLARE_GLOBAL_DATA_PTR; #include <env_default.h> struct hsearch_data env_htab = { +#if CONFIG_IS_ENABLED(ENV_SUPPORT) + /* defined in flags.c, only compile with ENV_SUPPORT */ .change_ok = env_flags_validate, +#endif }; /* @@ -225,7 +228,9 @@ void env_relocate(void) #if defined(CONFIG_NEEDS_MANUAL_RELOC) env_reloc(); env_fix_drivers(); - env_htab.change_ok += gd->reloc_off; + + if (env_htab.change_ok) + env_htab.change_ok += gd->reloc_off; #endif if (gd->env_valid == ENV_INVALID) { #if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD) diff --git a/include/configs/stm32mp1.h b/include/configs/stm32mp1.h index fd6c97a0c6..e8be51a155 100644 --- a/include/configs/stm32mp1.h +++ b/include/configs/stm32mp1.h @@ -23,11 +23,6 @@ #endif /* - * malloc() pool size - */ -#define CONFIG_SYS_MALLOC_LEN SZ_32M - -/* * Configuration of the external SRAM memory used by U-Boot */ #define CONFIG_SYS_SDRAM_BASE STM32_DDR_BASE @@ -43,11 +38,6 @@ */ #define CONFIG_SYS_LOAD_ADDR STM32_DDR_BASE -/* - * Env parameters - */ -#define CONFIG_ENV_SIZE SZ_4K - /* ATAGs */ #define CONFIG_CMDLINE_TAG #define CONFIG_SETUP_MEMORY_TAGS @@ -95,8 +85,6 @@ * for nand boot, boot with on ubifs partition on nand * for nor boot, use the default order */ -#define CONFIG_PREBOOT - #define STM32MP_BOOTCMD "bootcmd_stm32mp=" \ "echo \"Boot over ${boot_device}${boot_instance}!\";" \ "if test ${boot_device} = serial || test ${boot_device} = usb;" \ diff --git a/tools/stm32image.c b/tools/stm32image.c index 08b32ba87d..ff3ec5f3f2 100644 --- a/tools/stm32image.c +++ b/tools/stm32image.c @@ -14,6 +14,8 @@ #define HEADER_VERSION_V1 0x1 /* default option : bit0 => no signature */ #define HEADER_DEFAULT_OPTION (cpu_to_le32(0x00000001)) +/* default binary type for U-Boot */ +#define HEADER_TYPE_UBOOT (cpu_to_le32(0x00000000)) struct stm32_header { uint32_t magic_number; @@ -29,7 +31,8 @@ struct stm32_header { uint32_t option_flags; uint32_t ecdsa_algorithm; uint32_t ecdsa_public_key[64 / 4]; - uint32_t padding[84 / 4]; + uint32_t padding[83 / 4]; + uint32_t binary_type; }; static struct stm32_header stm32image_header; @@ -43,6 +46,7 @@ static void stm32image_default_header(struct stm32_header *ptr) ptr->header_version[VER_MAJOR_IDX] = HEADER_VERSION_V1; ptr->option_flags = HEADER_DEFAULT_OPTION; ptr->ecdsa_algorithm = 1; + ptr->binary_type = HEADER_TYPE_UBOOT; } static uint32_t stm32image_checksum(void *start, uint32_t len) @@ -112,6 +116,8 @@ static void stm32image_print_header(const void *ptr) le32_to_cpu(stm32hdr->image_checksum)); printf("Option : 0x%08x\n", le32_to_cpu(stm32hdr->option_flags)); + printf("BinaryType : 0x%08x\n", + le32_to_cpu(stm32hdr->binary_type)); } static void stm32image_set_header(void *ptr, struct stat *sbuf, int ifd, |