diff options
Diffstat (limited to 'roms/u-boot/drivers/ram/stm32mp1')
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/Kconfig | 49 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/Makefile | 15 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.c | 846 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.h | 206 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h | 369 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_interactive.c | 497 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ram.c | 243 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.c | 1504 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.h | 34 | ||||
-rw-r--r-- | roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tuning.c | 1540 |
10 files changed, 5303 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/ram/stm32mp1/Kconfig b/roms/u-boot/drivers/ram/stm32mp1/Kconfig new file mode 100644 index 000000000..2fd8c7b7e --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/Kconfig @@ -0,0 +1,49 @@ + +config STM32MP1_DDR + bool "STM32MP1 DDR driver" + depends on DM && OF_CONTROL && ARCH_STM32MP + select RAM + select SPL_RAM if SPL + default y + help + activate STM32MP1 DDR controller driver for STM32MP1 soc + 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/roms/u-boot/drivers/ram/stm32mp1/Makefile b/roms/u-boot/drivers/ram/stm32mp1/Makefile new file mode 100644 index 000000000..e1e913560 --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +# +# Copyright (C) 2018, STMicroelectronics - All Rights Reserved +# + +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/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.c b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.c new file mode 100644 index 000000000..0457166b1 --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#define LOG_CATEGORY UCLASS_RAM + +#include <common.h> +#include <clk.h> +#include <log.h> +#include <ram.h> +#include <reset.h> +#include <timer.h> +#include <asm/io.h> +#include <asm/arch/ddr.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include "stm32mp1_ddr.h" +#include "stm32mp1_ddr_regs.h" + +#define RCC_DDRITFCR 0xD8 + +#define RCC_DDRITFCR_DDRCAPBRST (BIT(14)) +#define RCC_DDRITFCR_DDRCAXIRST (BIT(15)) +#define RCC_DDRITFCR_DDRCORERST (BIT(16)) +#define RCC_DDRITFCR_DPHYAPBRST (BIT(17)) +#define RCC_DDRITFCR_DPHYRST (BIT(18)) +#define RCC_DDRITFCR_DPHYCTLRST (BIT(19)) + +struct reg_desc { + const char *name; + u16 offset; /* offset for base address */ + u8 par_offset; /* offset for parameter array */ +}; + +#define INVALID_OFFSET 0xFF + +#define DDRCTL_REG(x, y) \ + {#x,\ + offsetof(struct stm32mp1_ddrctl, x),\ + offsetof(struct y, x)} + +#define DDRPHY_REG(x, y) \ + {#x,\ + 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[DDRCTL_REG_REG_SIZE] = { + DDRCTL_REG_REG(mstr), + DDRCTL_REG_REG(mrctrl0), + DDRCTL_REG_REG(mrctrl1), + DDRCTL_REG_REG(derateen), + DDRCTL_REG_REG(derateint), + DDRCTL_REG_REG(pwrctl), + DDRCTL_REG_REG(pwrtmg), + DDRCTL_REG_REG(hwlpctl), + DDRCTL_REG_REG(rfshctl0), + DDRCTL_REG_REG(rfshctl3), + DDRCTL_REG_REG(crcparctl0), + DDRCTL_REG_REG(zqctl0), + DDRCTL_REG_REG(dfitmg0), + DDRCTL_REG_REG(dfitmg1), + DDRCTL_REG_REG(dfilpcfg0), + DDRCTL_REG_REG(dfiupd0), + DDRCTL_REG_REG(dfiupd1), + DDRCTL_REG_REG(dfiupd2), + DDRCTL_REG_REG(dfiphymstr), + DDRCTL_REG_REG(odtmap), + DDRCTL_REG_REG(dbg0), + DDRCTL_REG_REG(dbg1), + DDRCTL_REG_REG(dbgcmd), + DDRCTL_REG_REG(poisoncfg), + DDRCTL_REG_REG(pccfg), +}; + +#define DDRCTL_REG_TIMING(x) DDRCTL_REG(x, stm32mp1_ddrctrl_timing) +static const struct reg_desc ddr_timing[DDRCTL_REG_TIMING_SIZE] = { + DDRCTL_REG_TIMING(rfshtmg), + DDRCTL_REG_TIMING(dramtmg0), + DDRCTL_REG_TIMING(dramtmg1), + DDRCTL_REG_TIMING(dramtmg2), + DDRCTL_REG_TIMING(dramtmg3), + DDRCTL_REG_TIMING(dramtmg4), + DDRCTL_REG_TIMING(dramtmg5), + DDRCTL_REG_TIMING(dramtmg6), + DDRCTL_REG_TIMING(dramtmg7), + DDRCTL_REG_TIMING(dramtmg8), + DDRCTL_REG_TIMING(dramtmg14), + DDRCTL_REG_TIMING(odtcfg), +}; + +#define DDRCTL_REG_MAP(x) DDRCTL_REG(x, stm32mp1_ddrctrl_map) +static const struct reg_desc ddr_map[DDRCTL_REG_MAP_SIZE] = { + DDRCTL_REG_MAP(addrmap1), + DDRCTL_REG_MAP(addrmap2), + DDRCTL_REG_MAP(addrmap3), + DDRCTL_REG_MAP(addrmap4), + DDRCTL_REG_MAP(addrmap5), + DDRCTL_REG_MAP(addrmap6), + DDRCTL_REG_MAP(addrmap9), + DDRCTL_REG_MAP(addrmap10), + DDRCTL_REG_MAP(addrmap11), +}; + +#define DDRCTL_REG_PERF(x) DDRCTL_REG(x, stm32mp1_ddrctrl_perf) +static const struct reg_desc ddr_perf[DDRCTL_REG_PERF_SIZE] = { + DDRCTL_REG_PERF(sched), + DDRCTL_REG_PERF(sched1), + DDRCTL_REG_PERF(perfhpr1), + DDRCTL_REG_PERF(perflpr1), + DDRCTL_REG_PERF(perfwr1), + DDRCTL_REG_PERF(pcfgr_0), + DDRCTL_REG_PERF(pcfgw_0), + DDRCTL_REG_PERF(pcfgqos0_0), + DDRCTL_REG_PERF(pcfgqos1_0), + DDRCTL_REG_PERF(pcfgwqos0_0), + DDRCTL_REG_PERF(pcfgwqos1_0), + DDRCTL_REG_PERF(pcfgr_1), + DDRCTL_REG_PERF(pcfgw_1), + DDRCTL_REG_PERF(pcfgqos0_1), + DDRCTL_REG_PERF(pcfgqos1_1), + DDRCTL_REG_PERF(pcfgwqos0_1), + DDRCTL_REG_PERF(pcfgwqos1_1), +}; + +#define DDRPHY_REG_REG(x) DDRPHY_REG(x, stm32mp1_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), + DDRPHY_REG_REG(dsgcr), + DDRPHY_REG_REG(dcr), + DDRPHY_REG_REG(odtcr), + DDRPHY_REG_REG(zq0cr1), + DDRPHY_REG_REG(dx0gcr), + DDRPHY_REG_REG(dx1gcr), + DDRPHY_REG_REG(dx2gcr), + DDRPHY_REG_REG(dx3gcr), +}; + +#define DDRPHY_REG_TIMING(x) DDRPHY_REG(x, stm32mp1_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), + DDRPHY_REG_TIMING(dtpr0), + DDRPHY_REG_TIMING(dtpr1), + DDRPHY_REG_TIMING(dtpr2), + DDRPHY_REG_TIMING(mr0), + DDRPHY_REG_TIMING(mr1), + DDRPHY_REG_TIMING(mr2), + DDRPHY_REG_TIMING(mr3), +}; + +#define DDRPHY_REG_CAL(x) DDRPHY_REG(x, stm32mp1_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), + DDRPHY_REG_CAL(dx1dllcr), + DDRPHY_REG_CAL(dx1dqtr), + DDRPHY_REG_CAL(dx1dqstr), + DDRPHY_REG_CAL(dx2dllcr), + DDRPHY_REG_CAL(dx2dqtr), + DDRPHY_REG_CAL(dx2dqstr), + DDRPHY_REG_CAL(dx3dllcr), + DDRPHY_REG_CAL(dx3dqtr), + 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, + REG_PERF, + REG_MAP, + 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 +}; + +enum base_type { + DDR_BASE, + DDRPHY_BASE, + NONE_BASE +}; + +struct ddr_reg_info { + const char *name; + const struct reg_desc *desc; + u8 size; + enum base_type base; +}; + +#define DDRPHY_REG_CAL(x) DDRPHY_REG(x, stm32mp1_ddrphy_cal) + +const struct ddr_reg_info ddr_registers[REG_TYPE_NB] = { +[REG_REG] = { + "static", ddr_reg, DDRCTL_REG_REG_SIZE, DDR_BASE}, +[REG_TIMING] = { + "timing", ddr_timing, DDRCTL_REG_TIMING_SIZE, DDR_BASE}, +[REG_PERF] = { + "perf", ddr_perf, DDRCTL_REG_PERF_SIZE, DDR_BASE}, +[REG_MAP] = { + "map", ddr_map, DDRCTL_REG_MAP_SIZE, DDR_BASE}, +[REGPHY_REG] = { + "static", ddrphy_reg, DDRPHY_REG_REG_SIZE, DDRPHY_BASE}, +[REGPHY_TIMING] = { + "timing", ddrphy_timing, DDRPHY_REG_TIMING_SIZE, DDRPHY_BASE}, +[REGPHY_CAL] = { + "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[] = { + [DDR_BASE] = "ctl", + [DDRPHY_BASE] = "phy", +}; + +static u32 get_base_addr(const struct ddr_info *priv, enum base_type base) +{ + if (base == DDRPHY_BASE) + return (u32)priv->phy; + else + return (u32)priv->ctl; +} + +static void set_reg(const struct ddr_info *priv, + enum reg_type type, + const void *param) +{ + unsigned int i; + unsigned int *ptr, value; + enum base_type base = ddr_registers[type].base; + u32 base_addr = get_base_addr(priv, base); + const struct reg_desc *desc = ddr_registers[type].desc; + + log_debug("init %s\n", ddr_registers[type].name); + for (i = 0; i < ddr_registers[type].size; i++) { + ptr = (unsigned int *)(base_addr + desc[i].offset); + if (desc[i].par_offset == INVALID_OFFSET) { + log_err("invalid parameter offset for %s", desc[i].name); + } else { + value = *((u32 *)((u32)param + + desc[i].par_offset)); + writel(value, ptr); + log_debug("[0x%x] %s= 0x%08x\n", + (u32)ptr, desc[i].name, value); + } + } +} + +#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; + int ret; + + ret = readl_poll_timeout(&phy->pgsr, pgsr, + pgsr & (DDRPHYC_PGSR_IDONE | + DDRPHYC_PGSR_DTERR | + DDRPHYC_PGSR_DTIERR | + DDRPHYC_PGSR_DFTERR | + DDRPHYC_PGSR_RVERR | + DDRPHYC_PGSR_RVEIRR), + 1000000); + log_debug("\n[0x%08x] pgsr = 0x%08x ret=%d\n", + (u32)&phy->pgsr, pgsr, ret); +} + +void stm32mp1_ddrphy_init(struct stm32mp1_ddrphy *phy, u32 pir) +{ + pir |= DDRPHYC_PIR_INIT; + writel(pir, &phy->pir); + log_debug("[0x%08x] pir = 0x%08x -> 0x%08x\n", + (u32)&phy->pir, pir, readl(&phy->pir)); + + /* need to wait 10 configuration clock before start polling */ + udelay(10); + + /* Wait DRAM initialization and Gate Training Evaluation complete */ + ddrphy_idone_wait(phy); +} + +/* start quasi dynamic register update */ +static void start_sw_done(struct stm32mp1_ddrctl *ctl) +{ + clrbits_le32(&ctl->swctl, DDRCTRL_SWCTL_SW_DONE); +} + +/* wait quasi dynamic register update */ +static void wait_sw_done_ack(struct stm32mp1_ddrctl *ctl) +{ + int ret; + u32 swstat; + + setbits_le32(&ctl->swctl, DDRCTRL_SWCTL_SW_DONE); + + ret = readl_poll_timeout(&ctl->swstat, swstat, + swstat & DDRCTRL_SWSTAT_SW_DONE_ACK, + 1000000); + if (ret) + panic("Timeout initialising DRAM : DDR->swstat = %x\n", + swstat); + + log_debug("[0x%08x] swstat = 0x%08x\n", (u32)&ctl->swstat, swstat); +} + +/* wait quasi dynamic register update */ +static void wait_operating_mode(struct ddr_info *priv, int mode) +{ + u32 stat, val, mask, val2 = 0, mask2 = 0; + int ret; + + mask = DDRCTRL_STAT_OPERATING_MODE_MASK; + val = mode; + /* self-refresh due to software => check also STAT.selfref_type */ + if (mode == DDRCTRL_STAT_OPERATING_MODE_SR) { + mask |= DDRCTRL_STAT_SELFREF_TYPE_MASK; + 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 | + DDRCTRL_STAT_SELFREF_TYPE_MASK; + val2 = DDRCTRL_STAT_OPERATING_MODE_SR | + DDRCTRL_STAT_SELFREF_TYPE_ASR; + } + + ret = readl_poll_timeout(&priv->ctl->stat, stat, + ((stat & mask) == val) || + (mask2 && ((stat & mask2) == val2)), + 1000000); + + if (ret) + panic("Timeout DRAM : DDR->stat = %x\n", stat); + + log_debug("[0x%08x] stat = 0x%08x\n", (u32)&priv->ctl->stat, stat); +} + +void stm32mp1_refresh_disable(struct stm32mp1_ddrctl *ctl) +{ + start_sw_done(ctl); + /* quasi-dynamic register update*/ + setbits_le32(&ctl->rfshctl3, DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH); + clrbits_le32(&ctl->pwrctl, DDRCTRL_PWRCTL_POWERDOWN_EN | + DDRCTRL_PWRCTL_SELFREF_EN); + clrbits_le32(&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); + wait_sw_done_ack(ctl); +} + +void stm32mp1_refresh_restore(struct stm32mp1_ddrctl *ctl, + u32 rfshctl3, u32 pwrctl) +{ + start_sw_done(ctl); + if (!(rfshctl3 & DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH)) + clrbits_le32(&ctl->rfshctl3, DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH); + if (pwrctl & DDRCTRL_PWRCTL_POWERDOWN_EN) + setbits_le32(&ctl->pwrctl, DDRCTRL_PWRCTL_POWERDOWN_EN); + if ((pwrctl & DDRCTRL_PWRCTL_SELFREF_EN)) + setbits_le32(&ctl->pwrctl, DDRCTRL_PWRCTL_SELFREF_EN); + setbits_le32(&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); + wait_sw_done_ack(ctl); +} + +/* board-specific DDR power initializations. */ +__weak int board_ddr_power_init(enum ddr_type ddr_type) +{ + return 0; +} + +__maybe_unused +void stm32mp1_ddr_init(struct ddr_info *priv, + const struct stm32mp1_ddr_config *config) +{ + u32 pir; + int ret = -EINVAL; + char bus_width; + + switch (config->c_reg.mstr & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK) { + case DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER: + bus_width = 8; + break; + case DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF: + bus_width = 16; + break; + default: + bus_width = 32; + break; + } + + + if (config->c_reg.mstr & DDRCTRL_MSTR_DDR3) + ret = board_ddr_power_init(STM32MP_DDR3); + else if (config->c_reg.mstr & DDRCTRL_MSTR_LPDDR2) { + if (bus_width == 32) + ret = board_ddr_power_init(STM32MP_LPDDR2_32); + else + ret = board_ddr_power_init(STM32MP_LPDDR2_16); + } else if (config->c_reg.mstr & DDRCTRL_MSTR_LPDDR3) { + if (bus_width == 32) + ret = board_ddr_power_init(STM32MP_LPDDR3_32); + else + ret = board_ddr_power_init(STM32MP_LPDDR3_16); + } + if (ret) + panic("ddr power init failed\n"); + +start: + log_debug("name = %s\n", config->info.name); + log_debug("speed = %d kHz\n", config->info.speed); + log_debug("size = 0x%x\n", config->info.size); +/* + * 1. Program the DWC_ddr_umctl2 registers + * 1.1 RESETS: presetn, core_ddrc_rstn, aresetn + */ + /* Assert All DDR part */ + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAPBRST); + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAXIRST); + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCORERST); + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYAPBRST); + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYRST); + setbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYCTLRST); + +/* 1.2. start CLOCK */ + if (stm32mp1_ddr_clk_enable(priv, config->info.speed)) + panic("invalid DRAM clock : %d kHz\n", + config->info.speed); + +/* 1.3. deassert reset */ + /* de-assert PHY rstn and ctl_rstn via DPHYRST and DPHYCTLRST */ + clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYRST); + clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYCTLRST); + /* De-assert presetn once the clocks are active + * and stable via DDRCAPBRST bit + */ + clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DDRCAPBRST); + +/* 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 */ + clrbits_le32(&priv->ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); + log_debug("[0x%08x] dfimisc = 0x%08x\n", + (u32)&priv->ctl->dfimisc, readl(&priv->ctl->dfimisc)); + + set_reg(priv, REG_REG, &config->c_reg); + set_reg(priv, REG_TIMING, &config->c_timing); + set_reg(priv, REG_MAP, &config->c_map); + + /* skip CTRL init, SDRAM init is done by PHY PUBL */ + clrsetbits_le32(&priv->ctl->init0, + DDRCTRL_INIT0_SKIP_DRAM_INIT_MASK, + DDRCTRL_INIT0_SKIP_DRAM_INIT_NORMAL); + + 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); + clrbits_le32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_DPHYAPBRST); + +/* 3. start PHY init by accessing relevant PUBL registers + * (DXGCR, DCR, PTR*, MR*, DTPR*) + */ + set_reg(priv, REGPHY_REG, &config->p_reg); + set_reg(priv, REGPHY_TIMING, &config->p_timing); + if (config->p_cal_present) + 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 + */ + ddrphy_idone_wait(priv->phy); + +/* 5. Indicate to PUBL that controller performs SDRAM initialization + * by setting PIR.INIT and PIR CTLDINIT and pool PGSR.IDONE + * DRAM init is done by PHY, init0.skip_dram.init = 1 + */ + pir = DDRPHYC_PIR_DLLSRST | DDRPHYC_PIR_DLLLOCK | DDRPHYC_PIR_ZCAL | + DDRPHYC_PIR_ITMSRST | DDRPHYC_PIR_DRAMINIT | DDRPHYC_PIR_ICPC; + + if (config->c_reg.mstr & DDRCTRL_MSTR_DDR3) + pir |= DDRPHYC_PIR_DRAMRST; /* only for DDR3 */ + + stm32mp1_ddrphy_init(priv->phy, pir); + +/* 6. SET DFIMISC.dfi_init_complete_en to 1 */ + /* Enable quasi-dynamic register programming*/ + start_sw_done(priv->ctl); + setbits_le32(&priv->ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); + wait_sw_done_ack(priv->ctl); + +/* 7. Wait for DWC_ddr_umctl2 to move to normal operation mode + * by monitoring STAT.operating_mode signal + */ + /* wait uMCTL2 ready */ + + wait_operating_mode(priv, DDRCTRL_STAT_OPERATING_MODE_NORMAL); + + if (config->p_cal_present) { + log_debug("DDR DQS training skipped.\n"); + } else { + log_debug("DDR DQS training : "); +/* 8. Disable Auto refresh and power down by setting + * - RFSHCTL3.dis_au_refresh = 1 + * - PWRCTL.powerdown_en = 0 + * - DFIMISC.dfiinit_complete_en = 0 + */ + stm32mp1_refresh_disable(priv->ctl); + +/* 9. Program PUBL PGCR to enable refresh during training and rank to train + * not done => keep the programed value in PGCR + */ + +/* 10. configure PUBL PIR register to specify which training step to run */ + /* warning : RVTRN is not supported by this PUBL */ + stm32mp1_ddrphy_init(priv->phy, DDRPHYC_PIR_QSTRN); + +/* 11. monitor PUB PGSR.IDONE to poll cpmpletion of training sequence */ + ddrphy_idone_wait(priv->phy); + +/* 12. set back registers in step 8 to the orginal values if desidered */ + stm32mp1_refresh_restore(priv->ctl, config->c_reg.rfshctl3, + config->c_reg.pwrctl); + } /* if (config->p_cal_present) */ + + /* 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/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.h b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.h new file mode 100644 index 000000000..4998f0443 --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#ifndef _RAM_STM32MP1_DDR_H +#define _RAM_STM32MP1_DDR_H + +enum stm32mp1_ddr_interact_step { + STEP_DDR_RESET, + STEP_CTL_INIT, + STEP_PHY_INIT, + STEP_DDR_READY, + STEP_RUN, +}; + +/* DDR CTL and DDR PHY REGISTERS */ +struct stm32mp1_ddrctl; +struct stm32mp1_ddrphy; + +/** + * struct ddr_info + * + * @dev: pointer for the device + * @info: UCLASS RAM information + * @ctl: DDR controleur base address + * @clk: DDR clock + * @phy: DDR PHY base address + * @rcc: rcc base address + */ +struct ddr_info { + struct udevice *dev; + struct ram_info info; + struct clk clk; + struct stm32mp1_ddrctl *ctl; + struct stm32mp1_ddrphy *phy; + u32 rcc; +}; + +struct stm32mp1_ddrctrl_reg { + u32 mstr; + u32 mrctrl0; + u32 mrctrl1; + u32 derateen; + u32 derateint; + u32 pwrctl; + u32 pwrtmg; + u32 hwlpctl; + u32 rfshctl0; + u32 rfshctl3; + u32 crcparctl0; + u32 zqctl0; + u32 dfitmg0; + u32 dfitmg1; + u32 dfilpcfg0; + u32 dfiupd0; + u32 dfiupd1; + u32 dfiupd2; + u32 dfiphymstr; + u32 odtmap; + u32 dbg0; + u32 dbg1; + u32 dbgcmd; + u32 poisoncfg; + u32 pccfg; + +}; + +struct stm32mp1_ddrctrl_timing { + u32 rfshtmg; + u32 dramtmg0; + u32 dramtmg1; + u32 dramtmg2; + u32 dramtmg3; + u32 dramtmg4; + u32 dramtmg5; + u32 dramtmg6; + u32 dramtmg7; + u32 dramtmg8; + u32 dramtmg14; + u32 odtcfg; +}; + +struct stm32mp1_ddrctrl_map { + u32 addrmap1; + u32 addrmap2; + u32 addrmap3; + u32 addrmap4; + u32 addrmap5; + u32 addrmap6; + u32 addrmap9; + u32 addrmap10; + u32 addrmap11; +}; + +struct stm32mp1_ddrctrl_perf { + u32 sched; + u32 sched1; + u32 perfhpr1; + u32 perflpr1; + u32 perfwr1; + u32 pcfgr_0; + u32 pcfgw_0; + u32 pcfgqos0_0; + u32 pcfgqos1_0; + u32 pcfgwqos0_0; + u32 pcfgwqos1_0; + u32 pcfgr_1; + u32 pcfgw_1; + u32 pcfgqos0_1; + u32 pcfgqos1_1; + u32 pcfgwqos0_1; + u32 pcfgwqos1_1; +}; + +struct stm32mp1_ddrphy_reg { + u32 pgcr; + u32 aciocr; + u32 dxccr; + u32 dsgcr; + u32 dcr; + u32 odtcr; + u32 zq0cr1; + u32 dx0gcr; + u32 dx1gcr; + u32 dx2gcr; + u32 dx3gcr; +}; + +struct stm32mp1_ddrphy_timing { + u32 ptr0; + u32 ptr1; + u32 ptr2; + u32 dtpr0; + u32 dtpr1; + u32 dtpr2; + u32 mr0; + u32 mr1; + u32 mr2; + u32 mr3; +}; + +struct stm32mp1_ddrphy_cal { + u32 dx0dllcr; + u32 dx0dqtr; + u32 dx0dqstr; + u32 dx1dllcr; + u32 dx1dqtr; + u32 dx1dqstr; + u32 dx2dllcr; + u32 dx2dqtr; + u32 dx2dqstr; + u32 dx3dllcr; + u32 dx3dqtr; + u32 dx3dqstr; +}; + +struct stm32mp1_ddr_info { + const char *name; + u32 speed; /* in kHZ */ + u32 size; /* memory size in byte = col * row * width */ +}; + +struct stm32mp1_ddr_config { + struct stm32mp1_ddr_info info; + struct stm32mp1_ddrctrl_reg c_reg; + struct stm32mp1_ddrctrl_timing c_timing; + struct stm32mp1_ddrctrl_map c_map; + struct stm32mp1_ddrctrl_perf c_perf; + struct stm32mp1_ddrphy_reg p_reg; + struct stm32mp1_ddrphy_timing p_timing; + struct stm32mp1_ddrphy_cal p_cal; + bool p_cal_present; +}; + +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, + u32 rfshctl3, + u32 pwrctl); + +void stm32mp1_ddr_init( + struct ddr_info *priv, + const struct stm32mp1_ddr_config *config); + +int stm32mp1_dump_reg(const struct ddr_info *priv, + const char *name); + +void stm32mp1_edit_reg(const struct ddr_info *priv, + char *name, + char *string); + +int stm32mp1_dump_param(const struct stm32mp1_ddr_config *config, + const char *name); + +void stm32mp1_edit_param(const struct stm32mp1_ddr_config *config, + char *name, + char *string); + +bool stm32mp1_ddr_interactive( + void *priv, + enum stm32mp1_ddr_interact_step step, + const struct stm32mp1_ddr_config *config); + +#endif diff --git a/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h new file mode 100644 index 000000000..3c8885a96 --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ddr_regs.h @@ -0,0 +1,369 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#ifndef _RAM_STM32MP1_DDR_REGS_H +#define _RAM_STM32MP1_DDR_REGS_H + +/* DDR3/LPDDR2/LPDDR3 Controller (DDRCTRL) registers */ +#include <linux/bitops.h> +struct stm32mp1_ddrctl { + u32 mstr ; /* 0x0 Master*/ + u32 stat; /* 0x4 Operating Mode Status*/ + u8 reserved008[0x10 - 0x8]; + u32 mrctrl0; /* 0x10 Control 0.*/ + u32 mrctrl1; /* 0x14 Control 1*/ + u32 mrstat; /* 0x18 Status*/ + u32 reserved01c; /* 0x1c */ + u32 derateen; /* 0x20 Temperature Derate Enable*/ + u32 derateint; /* 0x24 Temperature Derate Interval*/ + u8 reserved028[0x30 - 0x28]; + u32 pwrctl; /* 0x30 Low Power Control*/ + u32 pwrtmg; /* 0x34 Low Power Timing*/ + u32 hwlpctl; /* 0x38 Hardware Low Power Control*/ + u8 reserved03c[0x50 - 0x3C]; + u32 rfshctl0; /* 0x50 Refresh Control 0*/ + u32 reserved054; /* 0x54 Refresh Control 1*/ + u32 reserved058; /* 0x58 Refresh Control 2*/ + u32 reserved05C; + u32 rfshctl3; /* 0x60 Refresh Control 0*/ + u32 rfshtmg; /* 0x64 Refresh Timing*/ + u8 reserved068[0xc0 - 0x68]; + u32 crcparctl0; /* 0xc0 CRC Parity Control0*/ + u32 reserved0c4; /* 0xc4 CRC Parity Control1*/ + u32 reserved0c8; /* 0xc8 CRC Parity Control2*/ + u32 crcparstat; /* 0xcc CRC Parity Status*/ + u32 init0; /* 0xd0 SDRAM Initialization 0*/ + u32 init1; /* 0xd4 SDRAM Initialization 1*/ + u32 init2; /* 0xd8 SDRAM Initialization 2*/ + u32 init3; /* 0xdc SDRAM Initialization 3*/ + u32 init4; /* 0xe0 SDRAM Initialization 4*/ + u32 init5; /* 0xe4 SDRAM Initialization 5*/ + u32 reserved0e8; + u32 reserved0ec; + u32 dimmctl; /* 0xf0 DIMM Control*/ + u8 reserved0f4[0x100 - 0xf4]; + u32 dramtmg0; /* 0x100 SDRAM Timing 0*/ + u32 dramtmg1; /* 0x104 SDRAM Timing 1*/ + u32 dramtmg2; /* 0x108 SDRAM Timing 2*/ + u32 dramtmg3; /* 0x10c SDRAM Timing 3*/ + u32 dramtmg4; /* 0x110 SDRAM Timing 4*/ + u32 dramtmg5; /* 0x114 SDRAM Timing 5*/ + u32 dramtmg6; /* 0x118 SDRAM Timing 6*/ + u32 dramtmg7; /* 0x11c SDRAM Timing 7*/ + u32 dramtmg8; /* 0x120 SDRAM Timing 8*/ + u8 reserved124[0x138 - 0x124]; + u32 dramtmg14; /* 0x138 SDRAM Timing 14*/ + u32 dramtmg15; /* 0x13C SDRAM Timing 15*/ + u8 reserved140[0x180 - 0x140]; + u32 zqctl0; /* 0x180 ZQ Control 0*/ + u32 zqctl1; /* 0x184 ZQ Control 1*/ + u32 zqctl2; /* 0x188 ZQ Control 2*/ + u32 zqstat; /* 0x18c ZQ Status*/ + u32 dfitmg0; /* 0x190 DFI Timing 0*/ + u32 dfitmg1; /* 0x194 DFI Timing 1*/ + u32 dfilpcfg0; /* 0x198 DFI Low Power Configuration 0*/ + u32 reserved19c; + u32 dfiupd0; /* 0x1a0 DFI Update 0*/ + u32 dfiupd1; /* 0x1a4 DFI Update 1*/ + u32 dfiupd2; /* 0x1a8 DFI Update 2*/ + u32 reserved1ac; + u32 dfimisc; /* 0x1b0 DFI Miscellaneous Control*/ + u8 reserved1b4[0x1bc - 0x1b4]; + u32 dfistat; /* 0x1bc DFI Miscellaneous Control*/ + u8 reserved1c0[0x1c4 - 0x1c0]; + u32 dfiphymstr; /* 0x1c4 DFI PHY Master interface*/ + u8 reserved1c8[0x204 - 0x1c8]; + u32 addrmap1; /* 0x204 Address Map 1*/ + u32 addrmap2; /* 0x208 Address Map 2*/ + u32 addrmap3; /* 0x20c Address Map 3*/ + u32 addrmap4; /* 0x210 Address Map 4*/ + u32 addrmap5; /* 0x214 Address Map 5*/ + u32 addrmap6; /* 0x218 Address Map 6*/ + u8 reserved21c[0x224 - 0x21c]; + u32 addrmap9; /* 0x224 Address Map 9*/ + u32 addrmap10; /* 0x228 Address Map 10*/ + u32 addrmap11; /* 0x22C Address Map 11*/ + u8 reserved230[0x240 - 0x230]; + u32 odtcfg; /* 0x240 ODT Configuration*/ + u32 odtmap; /* 0x244 ODT/Rank Map*/ + u8 reserved248[0x250 - 0x248]; + u32 sched; /* 0x250 Scheduler Control*/ + u32 sched1; /* 0x254 Scheduler Control 1*/ + u32 reserved258; + u32 perfhpr1; /* 0x25c High Priority Read CAM 1*/ + u32 reserved260; + u32 perflpr1; /* 0x264 Low Priority Read CAM 1*/ + u32 reserved268; + u32 perfwr1; /* 0x26c Write CAM 1*/ + u8 reserved27c[0x300 - 0x270]; + u32 dbg0; /* 0x300 Debug 0*/ + u32 dbg1; /* 0x304 Debug 1*/ + u32 dbgcam; /* 0x308 CAM Debug*/ + u32 dbgcmd; /* 0x30c Command Debug*/ + u32 dbgstat; /* 0x310 Status Debug*/ + u8 reserved314[0x320 - 0x314]; + u32 swctl; /* 0x320 Software Programming Control Enable*/ + u32 swstat; /* 0x324 Software Programming Control Status*/ + u8 reserved328[0x36c - 0x328]; + u32 poisoncfg; /* 0x36c AXI Poison Configuration Register*/ + u32 poisonstat; /* 0x370 AXI Poison Status Register*/ + u8 reserved374[0x3fc - 0x374]; + + /* Multi Port registers */ + u32 pstat; /* 0x3fc Port Status*/ + u32 pccfg; /* 0x400 Port Common Configuration*/ + + /* PORT 0 */ + u32 pcfgr_0; /* 0x404 Configuration Read*/ + u32 pcfgw_0; /* 0x408 Configuration Write*/ + u8 reserved40c[0x490 - 0x40c]; + u32 pctrl_0; /* 0x490 Port Control Register */ + u32 pcfgqos0_0; /* 0x494 Read QoS Configuration 0*/ + u32 pcfgqos1_0; /* 0x498 Read QoS Configuration 1*/ + u32 pcfgwqos0_0; /* 0x49c Write QoS Configuration 0*/ + u32 pcfgwqos1_0; /* 0x4a0 Write QoS Configuration 1*/ + u8 reserved4a4[0x4b4 - 0x4a4]; + + /* PORT 1 */ + u32 pcfgr_1; /* 0x4b4 Configuration Read*/ + u32 pcfgw_1; /* 0x4b8 Configuration Write*/ + u8 reserved4bc[0x540 - 0x4bc]; + u32 pctrl_1; /* 0x540 Port 2 Control Register */ + u32 pcfgqos0_1; /* 0x544 Read QoS Configuration 0*/ + u32 pcfgqos1_1; /* 0x548 Read QoS Configuration 1*/ + u32 pcfgwqos0_1; /* 0x54c Write QoS Configuration 0*/ + u32 pcfgwqos1_1; /* 0x550 Write QoS Configuration 1*/ +}; + +/* DDR Physical Interface Control (DDRPHYC) registers*/ +struct stm32mp1_ddrphy { + u32 ridr; /* 0x00 R Revision Identification*/ + u32 pir; /* 0x04 R/W PHY Initialization*/ + u32 pgcr; /* 0x08 R/W PHY General Configuration*/ + u32 pgsr; /* 0x0C PHY General Status*/ + u32 dllgcr; /* 0x10 R/W DLL General Control*/ + u32 acdllcr; /* 0x14 R/W AC DLL Control*/ + u32 ptr0; /* 0x18 R/W PHY Timing 0*/ + u32 ptr1; /* 0x1C R/W PHY Timing 1*/ + u32 ptr2; /* 0x20 R/W PHY Timing 2*/ + u32 aciocr; /* 0x24 AC I/O Configuration*/ + u32 dxccr; /* 0x28 DATX8 Common Configuration*/ + u32 dsgcr; /* 0x2C DDR System General Configuration*/ + u32 dcr; /* 0x30 DRAM Configuration*/ + u32 dtpr0; /* 0x34 DRAM Timing Parameters0*/ + u32 dtpr1; /* 0x38 DRAM Timing Parameters1*/ + u32 dtpr2; /* 0x3C DRAM Timing Parameters2*/ + u32 mr0; /* 0x40 Mode 0*/ + u32 mr1; /* 0x44 Mode 1*/ + u32 mr2; /* 0x48 Mode 2*/ + u32 mr3; /* 0x4C Mode 3*/ + u32 odtcr; /* 0x50 ODT Configuration*/ + u32 dtar; /* 0x54 data training address*/ + u32 dtdr0; /* 0x58 */ + u32 dtdr1; /* 0x5c */ + u8 res1[0x0c0 - 0x060]; /* 0x60 */ + u32 dcuar; /* 0xc0 Address*/ + u32 dcudr; /* 0xc4 DCU Data*/ + u32 dcurr; /* 0xc8 DCU Run*/ + u32 dculr; /* 0xcc DCU Loop*/ + u32 dcugcr; /* 0xd0 DCU General Configuration*/ + u32 dcutpr; /* 0xd4 DCU Timing Parameters */ + u32 dcusr0; /* 0xd8 DCU Status 0*/ + u32 dcusr1; /* 0xdc DCU Status 1*/ + u8 res2[0x100 - 0xe0]; /* 0xe0 */ + u32 bistrr; /* 0x100 BIST Run*/ + u32 bistmskr0; /* 0x104 BIST Mask 0*/ + u32 bistmskr1; /* 0x108 BIST Mask 0*/ + u32 bistwcr; /* 0x10c BIST Word Count*/ + u32 bistlsr; /* 0x110 BIST LFSR Seed*/ + u32 bistar0; /* 0x114 BIST Address 0*/ + u32 bistar1; /* 0x118 BIST Address 1*/ + u32 bistar2; /* 0x11c BIST Address 2*/ + u32 bistupdr; /* 0x120 BIST User Data Pattern*/ + u32 bistgsr; /* 0x124 BIST General Status*/ + u32 bistwer; /* 0x128 BIST Word Error*/ + u32 bistber0; /* 0x12c BIST Bit Error 0*/ + u32 bistber1; /* 0x130 BIST Bit Error 1*/ + u32 bistber2; /* 0x134 BIST Bit Error 2*/ + u32 bistwcsr; /* 0x138 BIST Word Count Status*/ + u32 bistfwr0; /* 0x13c BIST Fail Word 0*/ + u32 bistfwr1; /* 0x140 BIST Fail Word 1*/ + u8 res3[0x178 - 0x144]; /* 0x144 */ + u32 gpr0; /* 0x178 General Purpose 0 (GPR0)*/ + u32 gpr1; /* 0x17C General Purpose 1 (GPR1)*/ + u32 zq0cr0; /* 0x180 zq 0 control 0 */ + u32 zq0cr1; /* 0x184 zq 0 control 1 */ + u32 zq0sr0; /* 0x188 zq 0 status 0 */ + u32 zq0sr1; /* 0x18C zq 0 status 1 */ + u8 res4[0x1C0 - 0x190]; /* 0x190 */ + u32 dx0gcr; /* 0x1c0 Byte lane 0 General Configuration*/ + u32 dx0gsr0; /* 0x1c4 Byte lane 0 General Status 0*/ + u32 dx0gsr1; /* 0x1c8 Byte lane 0 General Status 1*/ + u32 dx0dllcr; /* 0x1cc Byte lane 0 DLL Control*/ + u32 dx0dqtr; /* 0x1d0 Byte lane 0 DQ Timing*/ + u32 dx0dqstr; /* 0x1d4 Byte lane 0 DQS Timing*/ + u8 res5[0x200 - 0x1d8]; /* 0x1d8 */ + u32 dx1gcr; /* 0x200 Byte lane 1 General Configuration*/ + u32 dx1gsr0; /* 0x204 Byte lane 1 General Status 0*/ + u32 dx1gsr1; /* 0x208 Byte lane 1 General Status 1*/ + u32 dx1dllcr; /* 0x20c Byte lane 1 DLL Control*/ + u32 dx1dqtr; /* 0x210 Byte lane 1 DQ Timing*/ + u32 dx1dqstr; /* 0x214 Byte lane 1 QS Timing*/ + u8 res6[0x240 - 0x218]; /* 0x218 */ + u32 dx2gcr; /* 0x240 Byte lane 2 General Configuration*/ + u32 dx2gsr0; /* 0x244 Byte lane 2 General Status 0*/ + u32 dx2gsr1; /* 0x248 Byte lane 2 General Status 1*/ + u32 dx2dllcr; /* 0x24c Byte lane 2 DLL Control*/ + u32 dx2dqtr; /* 0x250 Byte lane 2 DQ Timing*/ + u32 dx2dqstr; /* 0x254 Byte lane 2 QS Timing*/ + u8 res7[0x280 - 0x258]; /* 0x258 */ + u32 dx3gcr; /* 0x280 Byte lane 3 General Configuration*/ + u32 dx3gsr0; /* 0x284 Byte lane 3 General Status 0*/ + u32 dx3gsr1; /* 0x288 Byte lane 3 General Status 1*/ + u32 dx3dllcr; /* 0x28c Byte lane 3 DLL Control*/ + u32 dx3dqtr; /* 0x290 Byte lane 3 DQ Timing*/ + u32 dx3dqstr; /* 0x294 Byte lane 3 QS Timing*/ +}; + +#define DXN(phy, offset, byte) ((u32)(phy) + (offset) + ((u32)(byte) * 0x40)) +#define DXNGCR(phy, byte) DXN(phy, 0x1c0, byte) +#define DXNDLLCR(phy, byte) DXN(phy, 0x1cc, byte) +#define DXNDQTR(phy, byte) DXN(phy, 0x1d0, byte) +#define DXNDQSTR(phy, byte) DXN(phy, 0x1d4, byte) + +/* 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) +#define DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER (2 << 12) +#define DDRCTRL_MSTR_DLL_OFF_MODE BIT(15) + +#define DDRCTRL_STAT_OPERATING_MODE_MASK GENMASK(2, 0) +#define DDRCTRL_STAT_OPERATING_MODE_NORMAL 1 +#define DDRCTRL_STAT_OPERATING_MODE_SR 3 +#define DDRCTRL_STAT_SELFREF_TYPE_MASK GENMASK(5, 4) +#define DDRCTRL_STAT_SELFREF_TYPE_ASR (3 << 4) +#define DDRCTRL_STAT_SELFREF_TYPE_SR (2 << 4) + +#define DDRCTRL_MRCTRL0_MR_TYPE_WRITE 0 +/* only one rank supported */ +#define DDRCTRL_MRCTRL0_MR_RANK_SHIFT 4 +#define DDRCTRL_MRCTRL0_MR_RANK_ALL \ + (0x1 << DDRCTRL_MRCTRL0_MR_RANK_SHIFT) +#define DDRCTRL_MRCTRL0_MR_ADDR_SHIFT 12 +#define DDRCTRL_MRCTRL0_MR_ADDR_MASK GENMASK(15, 12) +#define DDRCTRL_MRCTRL0_MR_WR BIT(31) + +#define DDRCTRL_MRSTAT_MR_WR_BUSY BIT(0) + +#define DDRCTRL_PWRCTL_SELFREF_EN BIT(0) +#define DDRCTRL_PWRCTL_POWERDOWN_EN BIT(1) +#define DDRCTRL_PWRCTL_SELFREF_SW BIT(5) + +#define DDRCTRL_RFSHCTL3_DIS_AUTO_REFRESH BIT(0) + +#define DDRCTRL_RFSHTMG_T_RFC_NOM_X1_X32_MASK GENMASK(27, 16) +#define DDRCTRL_RFSHTMG_T_RFC_NOM_X1_X32_SHIFT 16 + +#define DDRCTRL_INIT0_SKIP_DRAM_INIT_MASK (0xC0000000) +#define DDRCTRL_INIT0_SKIP_DRAM_INIT_NORMAL (BIT(30)) + +#define DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN BIT(0) + +#define DDRCTRL_DBG1_DIS_HIF BIT(1) + +#define DDRCTRL_DBGCAM_WR_DATA_PIPELINE_EMPTY BIT(29) +#define DDRCTRL_DBGCAM_RD_DATA_PIPELINE_EMPTY BIT(28) +#define DDRCTRL_DBGCAM_DBG_WR_Q_EMPTY BIT(26) +#define DDRCTRL_DBGCAM_DBG_LPR_Q_DEPTH GENMASK(12, 8) +#define DDRCTRL_DBGCAM_DBG_HPR_Q_DEPTH GENMASK(4, 0) +#define DDRCTRL_DBGCAM_DATA_PIPELINE_EMPTY \ + (DDRCTRL_DBGCAM_WR_DATA_PIPELINE_EMPTY | \ + DDRCTRL_DBGCAM_RD_DATA_PIPELINE_EMPTY) +#define DDRCTRL_DBGCAM_DBG_Q_DEPTH \ + (DDRCTRL_DBGCAM_DBG_WR_Q_EMPTY | \ + DDRCTRL_DBGCAM_DBG_LPR_Q_DEPTH | \ + DDRCTRL_DBGCAM_DBG_HPR_Q_DEPTH) + +#define DDRCTRL_DBGCMD_RANK0_REFRESH BIT(0) + +#define DDRCTRL_DBGSTAT_RANK0_REFRESH_BUSY BIT(0) + +#define DDRCTRL_SWCTL_SW_DONE BIT(0) + +#define DDRCTRL_SWSTAT_SW_DONE_ACK BIT(0) + +#define DDRCTRL_PCTRL_N_PORT_EN BIT(0) + +/* DDRPHYC registers */ +#define DDRPHYC_PIR_INIT BIT(0) +#define DDRPHYC_PIR_DLLSRST BIT(1) +#define DDRPHYC_PIR_DLLLOCK BIT(2) +#define DDRPHYC_PIR_ZCAL BIT(3) +#define DDRPHYC_PIR_ITMSRST BIT(4) +#define DDRPHYC_PIR_DRAMRST BIT(5) +#define DDRPHYC_PIR_DRAMINIT BIT(6) +#define DDRPHYC_PIR_QSTRN BIT(7) +#define DDRPHYC_PIR_ICPC BIT(16) +#define DDRPHYC_PIR_ZCALBYP BIT(30) +#define DDRPHYC_PIR_INITSTEPS_MASK GENMASK(31, 7) + +#define DDRPHYC_PGCR_DFTCMP BIT(2) +#define DDRPHYC_PGCR_PDDISDX BIT(24) +#define DDRPHYC_PGCR_RFSHDT_MASK GENMASK(28, 25) + +#define DDRPHYC_PGSR_IDONE BIT(0) +#define DDRPHYC_PGSR_DTERR BIT(5) +#define DDRPHYC_PGSR_DTIERR BIT(6) +#define DDRPHYC_PGSR_DFTERR BIT(7) +#define DDRPHYC_PGSR_RVERR BIT(8) +#define DDRPHYC_PGSR_RVEIRR BIT(9) + +#define DDRPHYC_DLLGCR_BPS200 BIT(23) + +#define DDRPHYC_ACDLLCR_DLLDIS BIT(31) + +#define DDRPHYC_ZQ0CRN_ZDATA_MASK GENMASK(27, 0) +#define DDRPHYC_ZQ0CRN_ZDATA_SHIFT 0 +#define DDRPHYC_ZQ0CRN_ZDEN BIT(28) + +#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 + +#define DDRPHYC_DXNDQTR_DQDLY_SHIFT(bit) (4 * (bit)) +#define DDRPHYC_DXNDQTR_DQDLY_MASK GENMASK(3, 0) +#define DDRPHYC_DXNDQTR_DQDLY_LOW_MASK GENMASK(1, 0) +#define DDRPHYC_DXNDQTR_DQDLY_HIGH_MASK GENMASK(3, 2) + +#define DDRPHYC_DXNDQSTR_DQSDLY_MASK GENMASK(22, 20) +#define DDRPHYC_DXNDQSTR_DQSDLY_SHIFT 20 +#define DDRPHYC_DXNDQSTR_DQSNDLY_MASK GENMASK(25, 23) +#define DDRPHYC_DXNDQSTR_DQSNDLY_SHIFT 23 +#define DDRPHYC_DXNDQSTR_R0DGSL_MASK GENMASK(2, 0) +#define DDRPHYC_DXNDQSTR_R0DGSL_SHIFT 0 +#define DDRPHYC_DXNDQSTR_R0DGPS_MASK GENMASK(13, 12) +#define DDRPHYC_DXNDQSTR_R0DGPS_SHIFT 12 + +#define DDRPHYC_BISTRR_BDXSEL_MASK GENMASK(22, 19) +#define DDRPHYC_BISTRR_BDXSEL_SHIFT 19 + +#define DDRPHYC_BISTGSR_BDDONE BIT(0) +#define DDRPHYC_BISTGSR_BDXERR BIT(2) + +#define DDRPHYC_BISTWCSR_DXWCNT_SHIFT 16 + +/* PWR registers */ +#define PWR_CR3 0x00C +#define PWR_CR3_DDRSRDIS BIT(11) +#define PWR_CR3_DDRRETEN BIT(12) + +#endif diff --git a/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_interactive.c b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_interactive.c new file mode 100644 index 000000000..8c2310ac9 --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_interactive.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#define LOG_CATEGORY UCLASS_RAM + +#include <common.h> +#include <command.h> +#include <console.h> +#include <cli.h> +#include <clk.h> +#include <log.h> +#include <malloc.h> +#include <ram.h> +#include <reset.h> +#include <asm/global_data.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, speed or cal\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); + printf("cal = %d\n", config->p_cal_present); + 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; + } + if (!strcmp(argv[1], "cal")) { + if (strict_strtoul(argv[2], 10, &value) < 0 || + (value != 0 && value != 1)) { + printf("invalid value %s\n", argv[2]); + } else { + config->p_cal_present = value; + printf("cal = %d\n", config->p_cal_present); + } + 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) +{ + 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() && (getchar() == 'd')) { + next_step = STEP_DDR_RESET; + break; + } + if (get_timer(start) > 100) + break; + } +#endif + } + + log_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]); + + if (next_step > step) + return false; + + while (next_step == step) { + cli_readline_into_buffer("DDR>", 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/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ram.c b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ram.c new file mode 100644 index 000000000..26f0b4f1e --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_ram.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#define LOG_CATEGORY UCLASS_RAM + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <init.h> +#include <log.h> +#include <ram.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include "stm32mp1_ddr.h" + +static const char *const clkname[] = { + "ddrc1", + "ddrc2", + "ddrcapb", + "ddrphycapb", + "ddrphyc" /* LAST clock => used for get_rate() */ +}; + +int stm32mp1_ddr_clk_enable(struct ddr_info *priv, uint32_t mem_speed) +{ + unsigned long ddrphy_clk; + unsigned long ddr_clk; + struct clk clk; + int ret; + unsigned int idx; + + for (idx = 0; idx < ARRAY_SIZE(clkname); idx++) { + ret = clk_get_by_name(priv->dev, clkname[idx], &clk); + + if (!ret) + ret = clk_enable(&clk); + + if (ret) { + log_err("error for %s : %d\n", clkname[idx], ret); + return ret; + } + } + + priv->clk = clk; + ddrphy_clk = clk_get_rate(&priv->clk); + + log_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); + if (ddr_clk > (mem_speed * 100)) { + log_err("DDR expected freq %d kHz, current is %d kHz\n", + mem_speed, (u32)(ddrphy_clk / 1000)); + return -EINVAL; + } + + return 0; +} + +__weak int board_stm32mp1_ddr_config_name_match(struct udevice *dev, + const char *name) +{ + return 0; /* Always match */ +} + +static ofnode stm32mp1_ddr_get_ofnode(struct udevice *dev) +{ + const char *name; + ofnode node; + + dev_for_each_subnode(node, dev) { + name = ofnode_get_property(node, "compatible", NULL); + + if (!board_stm32mp1_ddr_config_name_match(dev, name)) + return node; + } + + return dev_ofnode(dev); +} + +static __maybe_unused int stm32mp1_ddr_setup(struct udevice *dev) +{ + struct ddr_info *priv = dev_get_priv(dev); + int ret; + unsigned int idx; + struct clk axidcg; + struct stm32mp1_ddr_config config; + ofnode node = stm32mp1_ddr_get_ofnode(dev); + +#define PARAM(x, y, z) \ + { .name = x, \ + .offset = offsetof(struct stm32mp1_ddr_config, y), \ + .size = sizeof(config.y) / sizeof(u32), \ + .present = z, \ + } + +#define CTL_PARAM(x) PARAM("st,ctl-"#x, c_##x, NULL) +#define PHY_PARAM(x) PARAM("st,phy-"#x, p_##x, NULL) +#define PHY_PARAM_OPT(x) PARAM("st,phy-"#x, p_##x, &config.p_##x##_present) + + const struct { + const char *name; /* name in DT */ + const u32 offset; /* offset in config struct */ + const u32 size; /* size of parameters */ + bool * const present; /* presence indication for opt */ + } param[] = { + CTL_PARAM(reg), + CTL_PARAM(timing), + CTL_PARAM(map), + CTL_PARAM(perf), + PHY_PARAM(reg), + PHY_PARAM(timing), + PHY_PARAM_OPT(cal) + }; + + config.info.speed = ofnode_read_u32_default(node, "st,mem-speed", 0); + config.info.size = ofnode_read_u32_default(node, "st,mem-size", 0); + config.info.name = ofnode_read_string(node, "st,mem-name"); + if (!config.info.name) { + dev_dbg(dev, "no st,mem-name\n"); + return -EINVAL; + } + printf("RAM: %s\n", config.info.name); + + for (idx = 0; idx < ARRAY_SIZE(param); idx++) { + ret = ofnode_read_u32_array(node, param[idx].name, + (void *)((u32)&config + + param[idx].offset), + param[idx].size); + dev_dbg(dev, "%s: %s[0x%x] = %d\n", __func__, + param[idx].name, param[idx].size, ret); + if (ret && + (ret != -FDT_ERR_NOTFOUND || !param[idx].present)) { + dev_err(dev, "Cannot read %s, error=%d\n", + param[idx].name, ret); + return -EINVAL; + } + if (param[idx].present) { + /* save presence of optional parameters */ + *param[idx].present = true; + if (ret == -FDT_ERR_NOTFOUND) { + *param[idx].present = false; +#ifdef CONFIG_STM32MP1_DDR_INTERACTIVE + /* reset values if used later */ + memset((void *)((u32)&config + + param[idx].offset), + 0, param[idx].size * sizeof(u32)); +#endif + } + } + } + + ret = clk_get_by_name(dev, "axidcg", &axidcg); + if (ret) { + dev_dbg(dev, "%s: Cannot found axidcg\n", __func__); + return -EINVAL; + } + clk_disable(&axidcg); /* disable clock gating during init */ + + stm32mp1_ddr_init(priv, &config); + + clk_enable(&axidcg); /* enable clock gating */ + + /* check size */ + dev_dbg(dev, "get_ram_size(%x, %x)\n", + (u32)priv->info.base, (u32)STM32_DDR_SIZE); + + priv->info.size = get_ram_size((long *)priv->info.base, + STM32_DDR_SIZE); + + dev_dbg(dev, "info.size: %x\n", (u32)priv->info.size); + + /* check memory access for all memory */ + if (config.info.size != priv->info.size) { + printf("DDR invalid size : 0x%x, expected 0x%x\n", + priv->info.size, config.info.size); + return -EINVAL; + } + return 0; +} + +static int stm32mp1_ddr_probe(struct udevice *dev) +{ + struct ddr_info *priv = dev_get_priv(dev); + struct regmap *map; + int ret; + + priv->dev = dev; + + ret = regmap_init_mem(dev_ofnode(dev), &map); + if (ret) + return log_ret(ret); + + priv->ctl = regmap_get_range(map, 0); + priv->phy = regmap_get_range(map, 1); + + priv->rcc = STM32_RCC_BASE; + + priv->info.base = STM32_DDR_BASE; + +#if !defined(CONFIG_TFABOOT) && \ + (!defined(CONFIG_SPL) || defined(CONFIG_SPL_BUILD)) + priv->info.size = 0; + ret = stm32mp1_ddr_setup(dev); + + return log_ret(ret); +#else + ofnode node = stm32mp1_ddr_get_ofnode(dev); + priv->info.size = ofnode_read_u32_default(node, "st,mem-size", 0); + return 0; +#endif +} + +static int stm32mp1_ddr_get_info(struct udevice *dev, struct ram_info *info) +{ + struct ddr_info *priv = dev_get_priv(dev); + + *info = priv->info; + + return 0; +} + +static struct ram_ops stm32mp1_ddr_ops = { + .get_info = stm32mp1_ddr_get_info, +}; + +static const struct udevice_id stm32mp1_ddr_ids[] = { + { .compatible = "st,stm32mp1-ddr" }, + { } +}; + +U_BOOT_DRIVER(ddr_stm32mp1) = { + .name = "stm32mp1_ddr", + .id = UCLASS_RAM, + .of_match = stm32mp1_ddr_ids, + .ops = &stm32mp1_ddr_ops, + .probe = stm32mp1_ddr_probe, + .priv_auto = sizeof(struct ddr_info), +}; diff --git a/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.c b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.c new file mode 100644 index 000000000..64262f1aa --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.c @@ -0,0 +1,1504 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#define LOG_CATEGORY UCLASS_RAM + +#include <common.h> +#include <console.h> +#include <init.h> +#include <log.h> +#include <rand.h> +#include <watchdog.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <linux/log2.h> +#include "stm32mp1_tests.h" + +#define ADDR_INVALID 0xFFFFFFFF + +#define PATTERN_DEFAULT "-" + +DECLARE_GLOBAL_DATA_PTR; + +static int get_bufsize(char *string, int argc, char *argv[], int arg_nb, + size_t *bufsize, size_t default_size, size_t min_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 < min_size) { + sprintf(string, "invalid size %s (min=%d)", + argv[arg_nb], min_size); + return -1; + } + if (value & (min_size - 1)) { + sprintf(string, "unaligned size %s (min=%d)", + argv[arg_nb], min_size); + 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 (!strcmp(argv[arg_nb], PATTERN_DEFAULT)) { + *pattern = default_pattern; + return 0; + } + 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); + WATCHDOG_RESET(); + + 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); + log_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]); + log_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; + log_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; + log_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, STM32_DDR_SIZE, 4)) + 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; + + printf("running at 0x%08x length 0x%x\n", addr, bufsize); + 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, 4)) + 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 * 1024, 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, 8)) + 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; + + bufsize /= 2; + printf("running %d loops copy from 0x%x to 0x%x (buffer size=0x%x)\n", + nb_loop, addr, addr + bufsize, bufsize); + while (!error) { + seed = rand(); + for (offset = 0; offset < bufsize; offset += 4) + writel(rand(), addr + offset); + + memcpy((void *)addr + bufsize, (void *)addr, bufsize); + + srand(seed); + for (offset = 0; offset < 2 * bufsize; offset += 4) { + if (offset == bufsize) + srand(seed); + value = rand(); + error = check_addr(addr + offset, value); + if (error) + break; + if (progress(offset)) + return TEST_FAILED; + } + if (test_loop_end(&loop, nb_loop, 100)) + break; + } + putc('\n'); + + 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, 128)) + 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 = (u32)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, #128"); + __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, addr; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024, 128)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + 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", + addr, 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 *)addr, 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, 8)) + 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, 4)) + 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, 4)) + 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, 4)) + 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, 32)) + 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, 32)) + 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; + int i, size = 1024 * 1024; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if (get_pattern(string, argc, argv, 1, &data, 0xA5A5AA55)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("running random\n"); + random = true; + } else { + printf("running at 0x%08x with pattern=0x%08x\n", + (u32)addr, data); + writel(data, addr); + } + + while (1) { + for (i = 0; i < size; i++) { + if (random) + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = readl(addr); + } + if (test_loop_end(&loop, 0, 1)) + break; + } + if (random) + sprintf(string, "%d loops random", loop); + else + sprintf(string, "%d loops at 0x%x: %x", loop, (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; + u32 loop = 0; + int i, size = 1024 * 1024; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if (get_pattern(string, argc, argv, 1, &data, 0xA5A5AA55)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("running random\n"); + random = true; + } else { + printf("running at 0x%08x with pattern 0x%08x\n", + (u32)addr, data); + } + + while (1) { + for (i = 0; i < size; i++) { + if (random) { + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = rand(); + } + writel(data, addr); + } + if (test_loop_end(&loop, 0, 1)) + break; + } + if (random) + sprintf(string, "%d loops random", loop); + else + sprintf(string, "%d loops at 0x%x: %x", loop, (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, j, nb_error = 0, len; + u32 loop = 0, nb_loop; + int argc_test; + char *argv_test[4]; + char loop_string[] = "1"; + char pattern_string[] = PATTERN_DEFAULT; + u32 *addr; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 1)) + return TEST_ERROR; + + if (get_addr(string, argc, argv, 2, (u32 *)&addr)) + 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++) { + argc_test = 0; + j = 0; + len = strlen(test[i].usage); + if (argc > 1 && j < len && + !strncmp("[size]", &test[i].usage[j], 6)) { + argv_test[argc_test++] = argv[1]; + j += 7; + } + if (argc > 2) { + if (j < len && + !strncmp("[loop]", &test[i].usage[j], 6)) { + argv_test[argc_test++] = loop_string; + j += 7; + } + if (j < len && + !strncmp("[pattern]", &test[i].usage[j], + 9)) { + argv_test[argc_test++] = pattern_string; + j += 10; + } + if (j < len && + !strncmp("[addr]", &test[i].usage[j], 6)) { + argv_test[argc_test++] = argv[2]; + j += 7; + } + } + printf("execute %d:%s\n", (int)i, test[i].name); + result = test[i].fct(ctl, phy, string, + argc_test, argv_test); + 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] [size] [addr]", "Execute all tests", 3 }, + {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] [addr]", + "write & test patterns: Mostly Zero, Mostly One and F/n", + 2 + }, + {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] [pattern]", + "basic test : infinite read access (random: addr=0xFFFFFFFF)", 2}, + {test_write, "infinite write", "[addr] [pattern]", + "basic test : infinite write access (random: addr=0xFFFFFFFF)", 2}, +}; + +const int test_nb = ARRAY_SIZE(test); diff --git a/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.h b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tests.h new file mode 100644 index 000000000..55f5d6d93 --- /dev/null +++ b/roms/u-boot/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/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tuning.c b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tuning.c new file mode 100644 index 000000000..c8cd7c3ce --- /dev/null +++ b/roms/u-boot/drivers/ram/stm32mp1/stm32mp1_tuning.c @@ -0,0 +1,1540 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#define LOG_CATEGORY UCLASS_RAM + +#include <common.h> +#include <console.h> +#include <clk.h> +#include <log.h> +#include <ram.h> +#include <rand.h> +#include <reset.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/iopoll.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 u8 get_nb_bank(struct stm32mp1_ddrctl *ctl) +{ + /* Count bank address bits */ + u8 bits = 0; + u32 reg, val; + + reg = readl(&ctl->addrmap1); + /* addrmap1.addrmap_bank_b1 */ + val = (reg & GENMASK(5, 0)) >> 0; + if (val <= 31) + bits++; + /* addrmap1.addrmap_bank_b2 */ + val = (reg & GENMASK(13, 8)) >> 8; + if (val <= 31) + bits++; + /* addrmap1.addrmap_bank_b3 */ + val = (reg & GENMASK(21, 16)) >> 16; + if (val <= 31) + bits++; + + return bits; +} + +static u8 get_nb_col(struct stm32mp1_ddrctl *ctl) +{ + u8 bits; + u32 reg, val; + + /* Count column address bits, start at 2 for b0 and b1 (fixed) */ + bits = 2; + + reg = readl(&ctl->addrmap2); + /* addrmap2.addrmap_col_b2 */ + val = (reg & GENMASK(3, 0)) >> 0; + if (val <= 7) + bits++; + /* addrmap2.addrmap_col_b3 */ + val = (reg & GENMASK(11, 8)) >> 8; + if (val <= 7) + bits++; + /* addrmap2.addrmap_col_b4 */ + val = (reg & GENMASK(19, 16)) >> 16; + if (val <= 7) + bits++; + /* addrmap2.addrmap_col_b5 */ + val = (reg & GENMASK(27, 24)) >> 24; + if (val <= 7) + bits++; + + reg = readl(&ctl->addrmap3); + /* addrmap3.addrmap_col_b6 */ + val = (reg & GENMASK(3, 0)) >> 0; + if (val <= 7) + bits++; + /* addrmap3.addrmap_col_b7 */ + val = (reg & GENMASK(11, 8)) >> 8; + if (val <= 7) + bits++; + /* addrmap3.addrmap_col_b8 */ + val = (reg & GENMASK(19, 16)) >> 16; + if (val <= 7) + bits++; + /* addrmap3.addrmap_col_b9 */ + val = (reg & GENMASK(27, 24)) >> 24; + if (val <= 7) + bits++; + + reg = readl(&ctl->addrmap4); + /* addrmap4.addrmap_col_b10 */ + val = (reg & GENMASK(3, 0)) >> 0; + if (val <= 7) + bits++; + /* addrmap4.addrmap_col_b11 */ + val = (reg & GENMASK(11, 8)) >> 8; + if (val <= 7) + bits++; + + return bits; +} + +static u8 get_nb_row(struct stm32mp1_ddrctl *ctl) +{ + /* Count row address bits */ + u8 bits = 0; + u32 reg, val; + + reg = readl(&ctl->addrmap5); + /* addrmap5.addrmap_row_b0 */ + val = (reg & GENMASK(3, 0)) >> 0; + if (val <= 11) + bits++; + /* addrmap5.addrmap_row_b1 */ + val = (reg & GENMASK(11, 8)) >> 8; + if (val <= 11) + bits++; + /* addrmap5.addrmap_row_b2_10 */ + val = (reg & GENMASK(19, 16)) >> 16; + if (val <= 11) + bits += 9; + else + printf("warning: addrmap5.addrmap_row_b2_10 not supported\n"); + /* addrmap5.addrmap_row_b11 */ + val = (reg & GENMASK(27, 24)) >> 24; + if (val <= 11) + bits++; + + reg = readl(&ctl->addrmap6); + /* addrmap6.addrmap_row_b12 */ + val = (reg & GENMASK(3, 0)) >> 0; + if (val <= 7) + bits++; + /* addrmap6.addrmap_row_b13 */ + val = (reg & GENMASK(11, 8)) >> 8; + if (val <= 7) + bits++; + /* addrmap6.addrmap_row_b14 */ + val = (reg & GENMASK(19, 16)) >> 16; + if (val <= 7) + bits++; + /* addrmap6.addrmap_row_b15 */ + val = (reg & GENMASK(27, 24)) >> 24; + if (val <= 7) + bits++; + + return bits; +} + +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; + + log_debug("[%x]: %x => DQ unit index = %x\n", 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_ddrctl *ctl, + struct stm32mp1_ddrphy *phy) +{ + u8 nb_bank = get_nb_bank(ctl); + u8 nb_row = get_nb_row(ctl); + u8 nb_col = get_nb_col(ctl); + + /* 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 << nb_bank) - 1; + /* Specifies the maximum SDRAM row address to be used during + * BIST before the address & increments to the next bank. + */ + u32 bmrow = (1 << nb_row) - 1; + /* Specifies the maximum SDRAM column address to be used during + * BIST before the address & increments to the next row. + */ + u32 bmcol = (1 << nb_col) - 1; + + 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); + 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; + u32 val; + int ret; + + 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 */ + udelay(10); + + /*Perform BIST Run*/ + clrsetbits_le32(&phy->bistrr, + 0x00000007, + 0x00000001); + /* Write BISTRR.BINST = 3?b001; */ + + /* poll on BISTGSR.BDONE and wait max 1000 us */ + ret = readl_poll_timeout(&phy->bistgsr, val, + val & DDRPHYC_BISTGSR_BDDONE, 1000); + + if (ret < 0) { + printf("warning: BIST timeout\n"); + result = false; /* BIST_FAIL; */ + /*Perform BIST Stop */ + clrsetbits_le32(&phy->bistrr, 0x00000007, 0x00000002); + } else { + /*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); + log_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) + log_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(ctl, phy); + log_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; + } + log_debug("\n======================\n"); + log_debug("Start deskew byte %d .\n", datx8); + log_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) { + log_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 */ + log_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 + */ + log_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; + } + + log_debug("there is a pass region for phase idx %d\n", + phase_idx); + log_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) { + log_debug("Fail region (PHASE) found phase idx %d\n", + phase_idx); + log_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; + log_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; + log_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++; + log_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; + log_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; + log_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); + log_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; + } + log_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; + log_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; + log_debug("Fail not found on bit %d => deskew[%d][%d] = %d\n", + bit_i, datx8, bit_i, + deskew_delay[datx8][bit_i]); + } + } + log_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; + log_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(ctl, 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; + + log_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; + } + log_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; + } + log_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; + } + log_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; + } + log_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]++; + log_debug("** found phase : %d - %d & unit %d - %d\n", + right_bound.phase, left_bound.phase, + right_bound.unit, left_bound.unit); + log_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) { + log_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]; + } + } + log_debug("*******calculating mid region: system latency: %d phase: %d********\n", + dqs_gate_values[byte][0], + dqs_gate_values[byte][1]); + log_debug("*******the nominal values were system latency: 0 phase: 2*******\n"); + } + } else { + /* if intermitant, restore defaut values */ + log_debug("dqs gating:no regular fail/pass/fail found. defaults values restored.\n"); + dqs_gate_values[byte][0] = 0; + dqs_gate_values[byte][1] = 2; + } + set_r0dgsl_delay(phy, byte, dqs_gate_values[byte][0]); + set_r0dgps_delay(phy, byte, dqs_gate_values[byte][1]); + printf("Byte %d, R0DGSL = %d, R0DGPS = %d\n", + byte, dqs_gate_values[byte][0], dqs_gate_values[byte][1]); + + /* 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(ctl, 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); + u32 derateen = readl(&ctl->derateen); + enum test_result res; + + writel(0x0, &ctl->derateen); + stm32mp1_refresh_disable(ctl); + + res = read_dqs_gating(ctl, phy, string); + + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + writel(derateen, &ctl->derateen); + + 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); + u32 derateen = readl(&ctl->derateen); + enum test_result res; + + writel(0x0, &ctl->derateen); + stm32mp1_refresh_disable(ctl); + + res = bit_deskew(ctl, phy, string); + + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + writel(derateen, &ctl->derateen); + + 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); + u32 derateen = readl(&ctl->derateen); + enum test_result res; + + writel(0x0, &ctl->derateen); + stm32mp1_refresh_disable(ctl); + + res = eye_training(ctl, phy, string); + + stm32mp1_refresh_restore(ctl, rfshctl3, pwrctl); + writel(derateen, &ctl->derateen); + + 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); |