Steps to Port U-Boot to Custom i.MX6 Board

In this article, we list quick notes on how to port U-Boot to a new i.MX6 Solo board. The custom board is called PCB Artists iMX6 Solo Trainer. You can use these same steps to start up a fresh i.MX6 Solo PCB of your own.

Note that the changes will be required to hardware-dependent board code in imx6solotrainer.c and imx6solotrainer.h. You must adjust the code to suit your board, such as setting the DDR3 calibration parameters and SD interface to boot from.

Step 1: Create a board defconfig

  • Switch to u-boot/configs
  • Create the defconfig for the board imx6solotrainer_defconfig

Here is the minimal defconfig that we used for the i.MX6 Solo Trainer PCB.
The board boots from SD card and the bootloader also includes an SPL build to set up DDR3 before loading the full U-Boot bootloader on to the system memory.

The SPL build is optional but if your U-Boot needs more RAM than the i.MX6 has on board, you will need it to set up the DDR3 chips and then copy the U-Boot files over to the DDR3 memory. Full U-Boot then executes from the DDR3.

CONFIG_ARM=y
CONFIG_ARCH_MX6=y

CONFIG_SPL_GPIO_SUPPORT=y
CONFIG_SPL_LIBCOMMON_SUPPORT=y
CONFIG_SPL_LIBGENERIC_SUPPORT=y

CONFIG_TARGET_IMX6SOLOTRAINER=y

CONFIG_SPL_MMC_SUPPORT=y
CONFIG_SPL_SERIAL_SUPPORT=y
CONFIG_SPL_LIBDISK_SUPPORT=y
CONFIG_SPL_WATCHDOG_SUPPORT=y

CONFIG_CMD_IMLS=n
CONFIG_CMD_FLASH=n

CONFIG_CMD_MEMTEST=y

# The following 2 lines generate the SPL file!
CONFIG_DISTRO_DEFAULTS=y
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=arch/arm/imx-common/spl_sd.cfg"

CONFIG_BOARD_EARLY_INIT_F=y
CONFIG_SPL_BOARD_INIT=y
CONFIG_SPL_FRAMEWORK=y
CONFIG_SPL=y
CONFIG_SPL_ENV_SUPPORT=y
CONFIG_SPL_EXT_SUPPORT=y

Step 2: Modify the Kconfig file

  • Open u-boot/arch/arm/cpu/armv7/mx6/Kconfig
  • Add this new target to the Kconfig file, just like all the other entries that are already on there.

Here is the Kconfig entry that we added for the custom i.MX6 Solo Trainer board.

config TARGET_IMX6SOLOTRAINER
	bool "imx6solotrainer"
	select MX6SX
	select SUPPORT_SPL
	select DM
	select DM_THERMAL
	# select BOARD_EARLY_INIT_F

Step 3: Add the Custom Board Source Code

  • Switch to u-boot/board
  • Create a folder for your organization inside boards. We create a folder called pcbartists
  • Create a folder named by the board name inside this folder. We create a folder called imx6solotrainer.
  • Inside u-boot/board/pcbartists/imx6solotrainer, create
    – Kconfig
    – Makefile
    – imxsolotrainer.c

The minimal source code required to boot the i.MX6 Solo board is as follows.

// Source Code: imx6solotrainer.c

#include <asm/arch/clock.h>
#include <asm/arch/crm_regs.h>
#include <asm/arch/imx-regs.h>
#include <asm/arch/iomux.h>
#include <asm/arch/mx6-pins.h>
#include <asm/gpio.h>
#include <asm/imx-common/iomux-v3.h>
#include <mmc.h>
#include <fsl_esdhc.h>
#include <asm/arch/crm_regs.h>
#include <asm/io.h>
#include <asm/imx-common/mxc_i2c.h>
#include <asm/arch/sys_proto.h>
#include <spl.h>
#include <linux/sizes.h>
#include <common.h>
#include <i2c.h>
#include <miiphy.h>
#include <netdev.h>
#include <power/pmic.h>
#include <power/pfuze3000_pmic.h>
#include <malloc.h>

DECLARE_GLOBAL_DATA_PTR;

#define UART_PAD_CTRL  (PAD_CTL_PKE | PAD_CTL_PUE |		\
	PAD_CTL_PUS_100K_UP | PAD_CTL_SPEED_MED |		\
	PAD_CTL_DSE_40ohm   | PAD_CTL_SRE_FAST  | PAD_CTL_HYS)

#define USDHC_PAD_CTRL (PAD_CTL_PKE | PAD_CTL_PUE |		\
	PAD_CTL_PUS_22K_UP  | PAD_CTL_SPEED_LOW |		\
	PAD_CTL_DSE_80ohm   | PAD_CTL_SRE_FAST  | PAD_CTL_HYS)

int dram_init(void)
{
	gd->ram_size = imx_ddr_size();
	return 0;
}

int board_init(void)
{
    /* Address of boot parameters */
	gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;

	return 0;
}

static struct fsl_esdhc_cfg usdhc_cfg[1] = {
	{USDHC2_BASE_ADDR, 0, 4},
};

#define USDHC2_PWR_GPIO IMX_GPIO_NR(6, 1)
#define USDHC2_CD_GPIO	IMX_GPIO_NR(6, 2)

int board_mmc_getcd(struct mmc *mmc)
{
	return !gpio_get_value(USDHC2_CD_GPIO);
}

static iomux_v3_cfg_t const usdhc2_pads[] = {
	MX6_PAD_SD2_CLK__USDHC2_CLK | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	MX6_PAD_SD2_CMD__USDHC2_CMD | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	MX6_PAD_SD2_DATA0__USDHC2_DATA0 | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	MX6_PAD_SD2_DATA1__USDHC2_DATA1 | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	MX6_PAD_SD2_DATA2__USDHC2_DATA2 | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	MX6_PAD_SD2_DATA3__USDHC2_DATA3 | MUX_PAD_CTRL(USDHC_PAD_CTRL),
	/* CD pin */
	MX6_PAD_SD1_DATA0__GPIO6_IO_2 | MUX_PAD_CTRL(NO_PAD_CTRL),
	/* Power */
	MX6_PAD_SD1_CMD__GPIO6_IO_1 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

int board_mmc_init(bd_t *bis)
{
	imx_iomux_v3_setup_multiple_pads(usdhc2_pads, ARRAY_SIZE(usdhc2_pads));
	usdhc_cfg[0].sdhc_clk = mxc_get_clock(MXC_ESDHC2_CLK);
	usdhc_cfg[0].esdhc_base = USDHC2_BASE_ADDR;
	gpio_direction_input(USDHC2_CD_GPIO);
	gpio_direction_output(USDHC2_PWR_GPIO, 1);

	gd->arch.sdhc_clk = usdhc_cfg[0].sdhc_clk;
	return fsl_esdhc_initialize(bis, &usdhc_cfg[0]);
}

int checkboard(void)
{
	puts("Board: PCBArtists i.MX6 Solo Trainer\n");

	return 0;
}

static iomux_v3_cfg_t const uart1_pads[] = {
	MX6_PAD_GPIO1_IO04__UART1_TX | MUX_PAD_CTRL(UART_PAD_CTRL),
	MX6_PAD_GPIO1_IO05__UART1_RX | MUX_PAD_CTRL(UART_PAD_CTRL),
};

static void setup_iomux_uart(void)
{
	imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads));
}

static iomux_v3_cfg_t const peri_3v3_pads[] = {
	MX6_PAD_QSPI1A_DATA0__GPIO4_IO_16 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

int board_early_init_f(void)
{
	setup_iomux_uart();

	/* Enable PERI_3V3, which is used by SD2, ENET, LVDS, BT */
	imx_iomux_v3_setup_multiple_pads(peri_3v3_pads, ARRAY_SIZE(peri_3v3_pads));

	/* Active high for ncp692 */
	//gpio_direction_output(IMX_GPIO_NR(4, 16) , 1);

	return 0;
}

#ifdef CONFIG_SPL_BUILD
	#warning "CONFIG_SPL_BUILD is active!"

	static void ccgr_init(void)
	{
		struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;

		writel(0xFFFFFFFF, &ccm->CCGR0);
		writel(0xFFFFFFFF, &ccm->CCGR1);
		writel(0xFFFFFFFF, &ccm->CCGR2);
		writel(0xFFFFFFFF, &ccm->CCGR3);
		writel(0xFFFFFFFF, &ccm->CCGR4);
		writel(0xFFFFFFFF, &ccm->CCGR5);
		writel(0xFFFFFFFF, &ccm->CCGR6);
		writel(0xFFFFFFFF, &ccm->CCGR7);
	}

	#include <libfdt.h>
	#include <asm/arch/mx6-ddr.h>

	static const struct mx6sx_iomux_ddr_regs mx6_ddr_ioregs = {
		.dram_dqm0 = 0x00000028,
		.dram_dqm1 = 0x00000028,
		.dram_dqm2 = 0x00000028,
		.dram_dqm3 = 0x00000028,
		.dram_ras = 0x00000020,
		.dram_cas = 0x00000020,
		.dram_odt0 = 0x00000020,
		.dram_odt1 = 0x00000020,
		.dram_sdba2 = 0x00000000,
		.dram_sdcke0 = 0x00003000,
		.dram_sdcke1 = 0x00003000,
		.dram_sdclk_0 = 0x00000030,
		.dram_sdqs0 = 0x00000028,
		.dram_sdqs1 = 0x00000028,
		.dram_sdqs2 = 0x00000028,
		.dram_sdqs3 = 0x00000028,
		.dram_reset = 0x00000020,
	};

	static const struct mx6sx_iomux_grp_regs mx6_grp_ioregs = {
		.grp_addds = 0x00000020,
		.grp_ddrmode_ctl = 0x00020000,
		.grp_ddrpke = 0x00000000,
		.grp_ddrmode = 0x00020000,
		.grp_b0ds = 0x00000028,
		.grp_b1ds = 0x00000028,
		.grp_ctlds = 0x00000020,
		.grp_ddr_type = 0x000c0000,
		.grp_b2ds = 0x00000028,
		.grp_b3ds = 0x00000028,
	};
/*
	static const struct mx6_mmdc_calibration neo_mmcd_calib = {
		.p0_mpwldectrl0 = 0x000E000B,
		.p0_mpwldectrl1 = 0x000E0010,
		.p0_mpdgctrl0 = 0x41600158,
		.p0_mpdgctrl1 = 0x01500140,
		.p0_mprddlctl = 0x3A383E3E,
		.p0_mpwrdlctl = 0x3A383C38,
	};
*/
	static const struct mx6_mmdc_calibration neo_mmcd_calib = {
		.p0_mpwldectrl0 = 0x001F0022,
		.p0_mpwldectrl1 = 0x001F0022,
		.p0_mpdgctrl0 = 0x41440138,
		.p0_mpdgctrl1 = 0x01340128,
		.p0_mprddlctl = 0x42424240,
		.p0_mpwrdlctl = 0x38383834,
	};

	/* MT41K256M16 */
	static struct mx6_ddr3_cfg neo_mem_ddr = {
		.mem_speed = 1600,
		.density = 4,
		.width = 16,
		.banks = 8,
		.rowaddr = 15,
		.coladdr = 10,
		.pagesz = 2,
		.trcd = 1375,
		.trcmin = 4875,
		.trasmin = 3500,
	};

	static void spl_dram_init(void)
	{
		//int board = get_board_value();

		struct mx6_ddr_sysinfo sysinfo = {
			.dsize = 1, /* width of data bus: 1 = 32 bits */
			.cs_density = 24,
			.ncs = 1,
			.cs1_mirror = 0,
			.rtt_wr = 2,
			.rtt_nom = 2,		/* RTT_Nom = RZQ/2 */
			.walat = 1,		/* Write additional latency */
			.ralat = 5,		/* Read additional latency */
			.mif3_mode = 3,		/* Command prediction working mode */
			.bi_on = 1,		/* Bank interleaving enabled */
			.sde_to_rst = 0x10,	/* 14 cycles, 200us (JEDEC default) */
			.rst_to_cke = 0x23,	/* 33 cycles, 500us (JEDEC default) */
		};

		mx6sx_dram_iocfg(32, &mx6_ddr_ioregs, &mx6_grp_ioregs);
		mx6_dram_cfg(&sysinfo, &neo_mmcd_calib, &neo_mem_ddr);
	}

void board_init_f(ulong dummy)
{
	ccgr_init();

	/* setup AIPS and disable watchdog */
	arch_cpu_init();

	board_early_init_f();

	/* setup GP timer */
	timer_init();

	/* UART clocks enabled and gd valid - init serial console */
	preloader_console_init();

	/* DDR initialization */
	spl_dram_init();

	/* Clear the BSS. */
	memset(__bss_start, 0, __bss_end - __bss_start);

	/* load/boot image from boot device */
	board_init_r(NULL, 0);
}
#endif
# Source code for Makefile

obj-y := imx6solotrainer.o
# Source code for Kconfig

if TARGET_IMX6SOLOTRAINER
    
    config SYS_BOARD
        default "imx6solotrainer"
        
    config SYS_VENDOR
        default "pcbartists"
        
    config SYS_CONFIG_NAME
        default "imx6solotrainer"
        
endif

Step 4: Add Header File for Custom Board

  • Switch to u-boot/include/configs
  • Create header file for the custom board using the board name. Here we name it imx6solotrainer.h.

The minimal header file content required to boot the system is as follows.

#ifndef __CONFIG_H
#define __CONFIG_H

#include "mx6_common.h"
#include "imx6_spl.h"

/* Size of malloc() pool */
#define CONFIG_SYS_MALLOC_LEN		(3 * SZ_1M)

/* MMC Configuration */
#define CONFIG_SYS_FSL_ESDHC_ADDR	USDHC2_BASE_ADDR

#define CONFIG_MXC_UART
#define CONFIG_MXC_UART_BASE		UART1_BASE
#define CONFIG_SYS_MMC_ENV_DEV		0  /*USDHC2*/

/* Miscellaneous configurable options */
#define CONFIG_SYS_MEMTEST_START	0x80000000
#define CONFIG_SYS_MEMTEST_END		(CONFIG_SYS_MEMTEST_START + 0x10000)

/* Environment organization */
#define CONFIG_ENV_OFFSET		(8 * SZ_64K)
#define CONFIG_ENV_SIZE			SZ_8K
#define CONFIG_ENV_IS_IN_MMC

/* Physical Memory Map */
#define CONFIG_NR_DRAM_BANKS		1
#define PHYS_SDRAM			MMDC0_ARB_BASE_ADDR
#define CONFIG_SYS_SDRAM_BASE		PHYS_SDRAM
#define CONFIG_SYS_INIT_RAM_ADDR	IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE	IRAM_SIZE

#define CONFIG_SYS_INIT_SP_OFFSET \
	(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
	(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

#define CONFIG_SYS_MAX_FLASH_BANKS	1

#define BOOT_TARGET_DEVICES(func) \
	func(MMC, mmc, 0) \
	func(DHCP, dhcp, na)

#endif

Step 5: Build U-Boot as usual

Now that you have added the minimum necessary configuration information and source code, you can proceed to build U-boot as usual.

Next, follow the process for building U-Boot.
But this time, you can use

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make imx6solotrainer_defconfig
Change Log
  • Initial Release: 17 June 2021
References

None

You may also like

Leave a Comment

3 + 18 =