The FPGA+ARM Armadeus APF51 board: Setup notes

This post was written by eli on September 29, 2011
Posted Under: ARM,FPGA,Linux


I got myself an Armadeus APF51 board for some work combining a fullblown ARM processor running embedded Linux with a recent Xilinx FPGA. I wrote down some setup notes for possibly future need while setting it up for work, and they are below. There is not really something undocumented here, but it’s more convenient to have the info organized according to my own workflow.

This is not a substitute for Armadeus own documentation, of course. Just my own jots.

First steps

Power supply: Completely inconsistent about the voltage. In the datasheet is says both 5V and 8V, the Wiki says 6V. So I looked up the power regulator on the schematics, and it turns out it can take 3V to 28V.

But there’s a 1.6A fuse on the power input, so maybe they’re afraid that the fuse will blow due to a low input voltage (= high current for the same power needs). And still.

It’s also worth to note that the IO_L41N_GCLK8_M1CASN_1 wire, which goes to the board’s FPGA button, also goes to one of the board’s pin headers. This is an unfortunate miswiring, because the wire is pulled up, so it’s not so good as an output from the FPGA. I wanted a continuous row of pins to use for debugging, but it didn’t work out that well on this pin header. I suppose this wouldn’t happen, had the names of the wires been less quirky.

And if we’re at it, the button appears to have had some mechanical problems, so there was a need to push it firmly quite a few times before it got fairly responsive.

The connection to the PC goes through a Serial-to-USB adapter, MCP2200, to be accurate. Too bad I needed to download the drivers for Windows XP here (on this page). And there’s also a hotfix that needs to be downloaded from Microsoft. This is more or less where I asked myself why I bother to set it up on Windows.

Surprisingly enough, the serial communication works like a charm with my Fedora 12. This is one of those rare cases where Linux has better driver support…

Issuing the “dhcp” command to U-boot makes a DHCP request on the network, and also sets the Linux boot parameters so Linux also has the same network configuration. If the network connection isn’t necessary with U-boot, it’s also OK to go

# udhcpc --now

after Linux has booted.

To get telnet access, activate inetd simply with

# inetd

(I added this as a last row in /etc/init.d/rcS. It’s a simple, working and politically incorrect hack).

There are two users of interest in the system: “root” and “default”. Neither require a password, but only “default” works on a telnet connection (an old useless security precaution, I guess).

Setting up the buildroot software environment

This involves downloading the buildroot bundle, setting it up, and running the build process through. I’ve dedicated a separate post to my own experiences doing this.

Note that I assume that this process has been run through properly below.

Booting over TFTP

# mkdir /var/lib/tftpboot/armadeus
# cp /path/to/armadeus-4.0/buildroot/output/images/apf51-linux.bin /var/lib/tftpboot/armadeus

And then on the U-boot console:

BIOS> dhcp                                                                     
FEC_MXC: Link is up - 100/Full                                                 
BOOTP broadcast 1                                                              
DHCP client bound to address                                         
BIOS> tftpboot 0x90800000                   
FEC_MXC: Link is up - 100/Full                                                 
Using FEC_MXC device                                                           
TFTP from server; our IP address is
Filename '/armadeus/apf51-linux.bin'.                                          
Load address: 0x90800000                                                       
Loading: #################################################################     
Bytes transferred = 2611736 (27da18 hex)                                       
BIOS> setenv bootargs console=ttymxc2,115200 mtdparts=mxc_nand:1M(U-boot)ro,1M(U-boot_env),1M(firmware),8M(kernel),-(rootfs) ubi.mtd=rootfs root=ubi0:rootfs rootfstype=ubifs
BIOS> bootm 0x90800000                                                         
## Booting kernel from Legacy Image at 90800000 ...                            
 Image Name:   Linux-                                                
 Image Type:   ARM Linux Kernel Image (uncompressed)                         
 Data Size:    2611672 Bytes =  2.5 MB                                       
 Load Address: 90008000                                                      
 Entry Point:  90008000                                                      
 Verifying Checksum ... OK                                                   
 Loading Kernel Image ... OK                                                 

Starting kernel ...                                                             

Uncompressing Linux... done, booting the kernel.                               
Linux version (eli@ocho.localdomain) (gcc version 4.4.5 (Buildroot 2010.11) ) #1 PREEMPT Mon Oct 3 12:22:26 IST 2011
CPU: ARMv7 Processor [412fc085] revision 5 (ARMv7), cr=10c53c7f                
CPU: VIPT nonaliasing data cache, VIPT aliasing instruction cache              
Machine: Armadeus APF51

Note that the changes done with setenv do not survive to the next boot.  Use the “saveenv” command to save the environment to flash.

The above can be shortened with

BIOS> setenv netkernel 'dhcp; tftpboot 0x90800000 ${serverip}:/armadeus/apf51-linux.bin; setenv bootargs ${console} ${mtdparts}; run addipargs addubifsargs; bootm 0x90800000'
BIOS> run netkernel

The ${serverip} variable was used here, so the underlying assumption is that the DHCP server is the same as the TFTP server (I’m not on the clear on whether ${serverip} points at the DHCP server or the server given as the “next server” by DHCP).

And the nice thing is that “saveenv” will save the netkernel command if issued after setenv.

Root over NFS

Create a directory to be exposed over NFS, and go

# tar -xf /path/to/armadeus-4.0/buildroot/output/images/apf51-rootfs.tar

in the directory just created.

For running the kernel from flash, but root on NFS,:

BIOS> setenv rootnfsboot 'dhcp; setenv bootargs ${console} ${mtdparts} rootfstype=nfs root=/dev/nfs nfsroot=${serverip}:/armadeus_root; run addipargs; setenv autostart yes;nboot.jffs2 90800000 0 ${kernel_offset}'

Again, ${serverip} is used, so this works if the NFS server is the same as DHCP server (or the “next-server” option has been fiddled with, which may or may not be helpful).

For running both kernel and root from server, the command goes:

BIOS> setenv netboot 'dhcp; tftpboot 0x90800000 ${serverip}:/armadeus/apf51-linux.bin; setenv bootargs ${console} ${mtdparts} rootfstype=nfs root=/dev/nfs nfsroot=${serverip}:/armadeus_root; run addipargs; bootm 0x90800000'

And when this is to be permanent, change bootcmd (‘run ubifsboot’ by default):

BIOS> setenv bootcmd 'run rootnfsboot'
BIOS> setenv bootdelay 2
BIOS> saveenv

The bootdelay variable makes the time window for halting automatic boot significantly shorter, but hey, I’m quick.

This was my final preference: Kernel from NAND (it’s read only anyhow, so it won’t suffer from crashes) but root over NFS.

Compiling a userspace applications

A typical makefile for crosscompilation of a simple userspace application can look like



CFLAGS=-Wall -I. -O3 -lm


OBJECTS=#somefile.o somefile2.o etc



 rm -f *~ $(APPLICATION) *.

Of course, “/path/to” is the path to where the Armadeus Buildroot is placed.

Note the -lm flag, which is there to demonstrate support of libm.

Compiling a kernel module

It’s worth to note, that CONFIG_MODVERSIONS is not set on the default kernel configuration (as seems to be the widespread setting), so one can compile a kernel module against a different kernel than the one the module will run on. This is pretty convenient, but it’s also an opening to nasty crashes if the kernel module expects a different API than the one it finds in the running kernel. So by all means, keep improvisations to a minimum.

This is the Makefile for compiling the frandom module with a simple “make”:

# Makefile for 2.6 kernels

export CROSS_COMPILE=/path/to/armadeus-4.0/buildroot/output/build/staging_dir/usr/bin/arm-unknown-linux-uclibcgnueabi-

obj-m    := frandom.o

KDIR := /path/to/armadeus-4.0/buildroot/output/build/linux-
PWD := $(shell pwd)


 @rm -f *.ko *.o modules.order Module.symvers *.mod.? *~
 @rm -rf .tmp_versions

Again, “/path/to” is the path to where the Armadeus Buildroot is placed. Note that the sub-make is given CROSS_COMPILE explicitly. It’s not clear to me why this is necessary, but without this, the native compiler runs and complains about not recognizing the architecture.

The external memory interface (EIM)

This issue is covered in several posts. You may want to start on this one.

Pad multiplexing on the i.MX51 processor

A lot of pins can be connected to several different internal modules, depending on the setting of the IOMUX module (there are independent settings for more or less each pin), as detailed in Appendix A of the device’s reference manual (MCIMX51RM.pdf). The MUX settings is done with registers named e.g. IOMUXC_OBSERVE_MUX_n, IOMUXC_SW_MUX_CTL_PAD_EIM_DAn and IOMUXC_SW_MUX_CTL_PAD_EIM_An (where “n” is a number). These registers are based at 0x73FA8000 (IOMUXC, or MX51_IOMUXC_BASE_ADDR in mx51.h header file), to which the offsets given in Appendix A are added.

The mx51_map_io() function in arch/arm/mach-mx5/mm.c calls mxc_iomux_v3_init() (defined in arch/arm/plat-mxc/iomux-v3.c) to setup the base pointer then used by mxc_iomux_v3_setup_pad() and mxc_iomux_v3_setup_multiple_pads() in the same C file. The latter is called by apf51_board_init() in arch/arm/mach-mx5/board-apf51.c with the apf51_pads array, defined in the same C file. The initialization of the array is based upon constants in arch/arm/plat-mxc/include/mach/iomux-mx51.h, which is the actual place to look for how the pads are set up. The IOMUX_PAD() macro is defined in arch/arm/plat-mxc/include/mach/iomux-v3.h, according to which the second argument is the offset of the IOMUX register, and the third one is the mux mode to set.

To make things even more complicated, the iomux-mx51.h defines pad definitions such as _MX51_PAD_DI1_D0_CS__GPIO3_3 (note the ‘_’ prefix) pointing at offset 0x2b4 and IOMUX mode 4 (ALT4) but without any pad control. Later down in the file, MX51_PAD_NANDF_WE_B__GPIO3_3 (no prefix) is defined by ORing the prefixed constant with the similar name with MX51_GPIO_PAD_CTRL. So it all makes sense, but is nevertheless pretty tricky to figure out. See page A-184 (page 3370 in the pdf file) for a confirmation of that this is the right thing to do.

The pad mux configuration is by no means complete. For example, the EIM_DAn pads only have the definition for ALT0 (which is using these pads as address-data lines), but the legal ALT1 definition is absent. Not that someone is expected to use it, and still. It also appears like the corresponding registers are never set, but that the default, which is ALT0, is relied upon.

So the bottom line of all this is that the if you want to know how the pads are multiplexed, the apf51_pads array in arch/arm/mach-mx5/board-apf51.c tells the story.

The initialization of apf51_fpga_pre()

In drivers/armadeus/fpga/dev_tools/loader/apf51-fpga-loader.c the apf51_fpga_pre() function has a section going as follows:

 temp_rcr1 =  __raw_readl( MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + MXC_CS1RCR1_ADDR );
 __raw_writel( 0x01000010, MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + MXC_CS1RCR1_ADDR );

 temp_wcr1 = __raw_readl(MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + MXC_CS1WCR1_ADDR);
 __raw_writel( 0x01000008, MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + MXC_CS1WCR1_ADDR );

 /* change emi_clk_sel to ensure blck smaller than 50MHz */
 temp_clk = __raw_readl( MX51_IO_ADDRESS(MX51_CCM_BASE_ADDR)+ MXC_CCM_CBCDR );
 __raw_writel( temp_clk | EMI_CLK_SEL, MX51_IO_ADDRESS(MX51_CCM_BASE_ADDR)+ MXC_CCM_CBCDR );

MXC_CS1RCR1_ADDR is defined as Ox20 and MXC_CS1WCR1_ADDR defined Ox28 in the same file. MX51_WEIM_BASE_ADDR is Ox83fda000, indeed the base address for WEIM related registers. MX51_CCM_BASE_ADDR is Ox73fd4000, and MXC_CCM_CBCDR is Ox14.

CS1RCR1 means Chip Select 1 Read Configuration Register 1. The way it’s sets read wait state control to 1 (minimal value) and sets one clock cycle (not minimum) between the beginning of a read access and CS assertion. CS1WCR1is Chip Select 1 Write Configuration Register 1. It’s set up so write wait states to 1 (minimum) and 1 clock cycle between beginning of write access and CS assertion. So these two don’t supply any drama.

CBCDR is CCM Bus Clock Divider Register. It’s changed to set bit 26, which means to derive clock from AHB clock root.

The WEIM configuration register (having offset Ox90) was found to have the value 00000021, which is the reset value + bit 0 set. Setting bit 0 causes BCLK to run all the time, and not only during bursts, as the reference manual keeps warning.

Some board related linux files:

  • Board-specific definitions: arch/arm/mach-mx5/board-apf51.c
  • Memory map define file: arch/arm/plat-mxc/include/mach/mx51.h
  • Main platform file (?): arch/arm/mach-integrator/include/mach/platform.h



Reader Comments

Hello Eli,

APF51Dev power supply description isn’t inconsistent but probably not enough detailed. Indeed you can use 3 power sources for it:
* “Wall” with standard jack input (+8V to +28V)
* Li-Po/Li-Ion battery (+3,6 or 3,7V)
* USB OTG (+5V)

We updated the wiki accordingly. Thanks for your remarks and have fun with the APF51 !

Best regards

Written By Julien Boibessot on October 12th, 2011 @ 10:13

Add a Comment

required, use real name
required, will not be published
optional, your blog address