Armadeus APF51 / Freescale i.MX51: A kit for reverse engineering the EIM bus

This post was written by eli on October 15, 2011
Posted Under: ARM,FPGA,Linux kernel,NXP (Freescale)

What we have here

As one can guess from my notes about the i.MX51′s external bus and the oscilloscope shots I’ve published, I made myself a small dissection kit for watching the bus’ lines activity with a digital oscilloscope.

This is a good time to mention, that the kit was done quickly and dirty, so the code below should not be taken as an example of proper coding for FPGA nor the Linux kernel. Seriously, this is just lab code.

Anyhow, this little kit consists of two parts

  • Verilog code and UCF for programming the FPGA. Except for blinking the LED (at 1 Hz), it also wires all EIM-bus related signals to the FPGA pin headers on the development board, so they can be sampled easily with oscilloscope’s probes. You can download the bitfile directly if your board has the LX9 FPGA, or implement it from the sources below.
  • A kernel module, which performs a single bus operation when it’s loaded. It’s explained further below. If you happen to be running on a 2.6.38.1 Linux kernel on your board (in particular the 2.6.38.1 which comes preloaded on the board), you may try using the precompiled kernel module. Or do it the “right way” and compile the module from the sources below.

The Verilog code below pretty much explains itself. And as the comments in the UCF say, the “debug_pins_outer” pin vector runs from pin #38 downwards continuously, on even pins only, on the outer FPGA pin header. This may sound complicated, but it simply means that out of the two rows of this pin header, only the row reached easily with a probe is used. And since pin #40 (in the corner) isn’t attached to the FPGA, debug_outer_pins[0] is connected to pin #38, debug_outer_pins[1] to #36 and so on.

As for the “debug_pin_inner” it goes more or less the same. Going from pin #3 for debug_inner_pins[0] and up on odd pin numbers, only the inner pin row of the inner pin header is used for easy physical access.

This may look like a weird choice of pin assignments, but this was the only way to get the vectors assigned on the pin headers without any gaps between them, so it’s easy to reach any signal in the vectors just by counting pins on the pin header.

Please make sure that the two “FPGA bank” jumpers are installed on your board, or nothing will appear on the pin headers. These jumpers were installed on the board as I got it, so just check it’s OK.

It’s also worth to note that debug_pins_outer[4] happens to be connected to a pin which is shared with a pushbutton on the board. Since the line is pulled up with a 10 kOhm resistor, this line may have some timing skew.

Simple use

Assuming that both the bitfile and the kernel module are in the currect directory, first load the FPGA if you haven’t done so already:

# load_fpga armaled.bit

A green LED should start blinking as a result of this. Note that according to Armadeus’ wiki page on the FPGA loader, armaled.bit should not be on the on-board flash. Copy it to /tmp first (which is on RAM) or load it from an net drive (e.g. NFS) like I did.

And then, to kick off a bus cycle, load the module and catch it on the oscilloscope:

# insmod eimtest.ko

And then unload the module, so you can load it again for the next try:

# rmmod eimtest

The relevant bus parameters can be set directly when loading the module. For example, to add an extra bus wait state, disable continuous bus clock, run at 1/4 bus rate and use bus address OxABC0, go:

# insmod eimtest.ko WSC=2 BCD=3 BCM=0 addr=0xabc0

A list of kernel module parameters, which in turn changes the bus parameters, is found in the kernel module’s source. Anything declared with “module_param” can be set. The defaults are given in the variable declarations. Setting the address and data is also possible, but be sure not to exceed the address 0xFFFC, or you’ll get a kernel oops. Also note that addresses not aligned to 32-bit words will produce several bus cycles.

The Verilog code

Note that the direct wire connections have a variable delay. This results in some unknown skew (1-2ns, I suppose) between the outputs.

module armaled
 (
 input ext_clk,
 output reg led,
 output irq,

 input [15:0] imx51_da,

 input imx51_cs1,
 input imx51_cs2,
 input imx51_adv,
 input imx51_we,
 input imx51_eb0,
 input imx51_eb1,
 input imx51_oe,
 input imx51_dtack,
 input imx51_wait,
 input imx51_bclk,
 input imx51_clko,

 output [13:0] debug_pins_inner,
 output [12:0] debug_pins_outer
 );

 reg [27:0] counter;

 assign     irq = 0;

 assign     debug_pins_outer[0] = imx51_bclk;
 assign     debug_pins_outer[1] = imx51_clko;
 assign     debug_pins_outer[2] = imx51_oe;
 assign     debug_pins_outer[3] = imx51_cs1;
 assign     debug_pins_outer[4] = imx51_cs2;
 assign     debug_pins_outer[5] = imx51_adv;
 assign     debug_pins_outer[6] = imx51_we;
 assign     debug_pins_outer[7] = imx51_eb0;
 assign     debug_pins_outer[8] = imx51_eb1;
 assign     debug_pins_outer[9] = imx51_dtack;
 assign     debug_pins_outer[10] = imx51_wait;
 assign     debug_pins_outer[12:11] = imx51_da[15:14];

 assign     debug_pins_inner = imx51_da[13:0];

 always @(posedge ext_clk)
 begin
 if (counter >= 47500000)
 begin
 led <= !led;
 counter <= 0;        
 end
 else
 counter <= counter + 1;
 end

endmodule

The UCF file

NET "ext_clk" TNM_NET = "TN_ext_clk";
TIMESPEC "TS_ext_clk" = PERIOD "TN_ext_clk" 10.4 ns HIGH 50 %;

NET "led" LOC="G14" | IOSTANDARD=LVCMOS33;# IO_L41P_GCLK9_IRDY1_M1RASN_1
#NET "button" LOC="G15" | IOSTANDARD=LVCMOS33;# IO_L41N_GCLK8_M1CASN_1
NET "ext_clk" LOC="N8" | IOSTANDARD=LVCMOS33;# = BCLK, IO_L29P_GCLK3_2
NET "irq" LOC="P3" | IOSTANDARD=LVCMOS33;# FPGA_INITB

# Debug pins.

# The "inner" set starts from pin #3, running on odd pins only (effectively
# covering the pins convenient to attach a scope's probe to)

NET "debug_pins_inner[0]" LOC="L2" | IOSTANDARD=LVCMOS33;# IO_L39P_M3LDQS_3
NET "debug_pins_inner[1]" LOC="J2" | IOSTANDARD=LVCMOS33;# IO_L41P_GCLK27_M3DQ4_3
NET "debug_pins_inner[2]" LOC="K4" | IOSTANDARD=LVCMOS33;# IO_L43P_GCLK23_M3RASN_3
NET "debug_pins_inner[3]" LOC="K5" | IOSTANDARD=LVCMOS33;# IO_L45P_M3A3_3
NET "debug_pins_inner[4]" LOC="C2" | IOSTANDARD=LVCMOS33;# IO_L83P_3
NET "debug_pins_inner[5]" LOC="D4" | IOSTANDARD=LVCMOS33;# IO_L53P_M3CKE_3
NET "debug_pins_inner[6]" LOC="K3" | IOSTANDARD=LVCMOS33;# IO_L40P_M3DQ6_3
NET "debug_pins_inner[7]" LOC="H3" | IOSTANDARD=LVCMOS33;# IO_L42P_GCLK25_TRDY2_M3UDM_3
NET "debug_pins_inner[8]" LOC="G2" | IOSTANDARD=LVCMOS33;# IO_L44P_GCLK21_M3A5_3
NET "debug_pins_inner[9]" LOC="F3" | IOSTANDARD=LVCMOS33;# IO_L46P_M3CLK_3
NET "debug_pins_inner[10]" LOC="D3" | IOSTANDARD=LVCMOS33;# IO_L54P_M3RESET_3
NET "debug_pins_inner[11]" LOC="E2" | IOSTANDARD=LVCMOS33;# IO_L52P_M3A8_3
NET "debug_pins_inner[12]" LOC="K13" | IOSTANDARD=LVCMOS33;# IO_L44P_A3_M1DQ6_1
NET "debug_pins_inner[13]" LOC="H13" | IOSTANDARD=LVCMOS33;# IO_L42P_GCLK7_M1UDM_1

# The "outer" set starts from pin #38, running on even pins only (effectively
# covering the pins convenient to attach a scope's probe to). Note that the
# vectors runs from high board pin number to low.

NET "debug_pins_outer[0]" LOC="B15" | IOSTANDARD=LVCMOS33;# IO_L1N_A24_VREF_1
NET "debug_pins_outer[1]" LOC="C15" | IOSTANDARD=LVCMOS33;# IO_L33N_A14_M1A4_1
NET "debug_pins_outer[2]" LOC="D15" | IOSTANDARD=LVCMOS33;# IO_L35N_A10_M1A2_1
NET "debug_pins_outer[3]" LOC="E15" | IOSTANDARD=LVCMOS33;# IO_L37N_A6_M1A1_1
NET "debug_pins_outer[4]" LOC="G15" | IOSTANDARD=LVCMOS33;# IO_L41N_GCLK8_M1CASN_1
NET "debug_pins_outer[5]" LOC="J15" | IOSTANDARD=LVCMOS33;# IO_L43N_GCLK4_M1DQ5_1
NET "debug_pins_outer[6]" LOC="L15" | IOSTANDARD=LVCMOS33;# IO_L45N_A0_M1LDQSN_1
NET "debug_pins_outer[7]" LOC="G12" | IOSTANDARD=LVCMOS33;# IO_L30N_A20_M1A11_1
NET "debug_pins_outer[8]" LOC="F12" | IOSTANDARD=LVCMOS33;# IO_L31N_A18_M1A12_1
NET "debug_pins_outer[9]" LOC="H11" | IOSTANDARD=LVCMOS33;# IO_L32N_A16_M1A9_1
NET "debug_pins_outer[10]" LOC="G13" | IOSTANDARD=LVCMOS33;# IO_L34N_A12_M1BA2_1
NET "debug_pins_outer[11]" LOC="J13" | IOSTANDARD=LVCMOS33;# IO_L36N_A8_M1BA1_1
NET "debug_pins_outer[12]" LOC="K11" | IOSTANDARD=LVCMOS33;# IO_L38N_A4_M1CLKN_1

# i.MX51 related pins

NET "imx51_cs1" LOC="R11" | IOSTANDARD=LVCMOS33;# EIM_CS1
NET "imx51_cs2" LOC="N9" | IOSTANDARD=LVCMOS33;# EIM_CS2
NET "imx51_adv" LOC="R9" | IOSTANDARD=LVCMOS33;# EIM_LBA
NET "imx51_we" LOC="R6" | IOSTANDARD=LVCMOS33;# EIM_RW
NET "imx51_eb0" LOC="P7" | IOSTANDARD=LVCMOS33;
NET "imx51_eb1" LOC="P13" | IOSTANDARD=LVCMOS33;
NET "imx51_oe" LOC="R7" | IOSTANDARD=LVCMOS33;
NET "imx51_dtack" LOC="N4" | IOSTANDARD=LVCMOS33;
NET "imx51_wait" LOC="R4" | IOSTANDARD=LVCMOS33;
NET "imx51_bclk" LOC="N12" | IOSTANDARD=LVCMOS33; # Hardwired to N8
NET "imx51_clko" LOC="N7" | IOSTANDARD=LVCMOS33;

NET "imx51_da[7]" LOC="P11" | IOSTANDARD=LVCMOS33;# EIM_DA7
NET "imx51_da[6]" LOC="M11" | IOSTANDARD=LVCMOS33;# EIM_DA6
NET "imx51_da[5]" LOC="N11" | IOSTANDARD=LVCMOS33;# EIM_DA5
NET "imx51_da[13]" LOC="R10" | IOSTANDARD=LVCMOS33;# EIM_DA13
NET "imx51_da[12]" LOC="L9" | IOSTANDARD=LVCMOS33;# EIM_DA12
NET "imx51_da[11]" LOC="M10" | IOSTANDARD=LVCMOS33;# EIM_DA11
NET "imx51_da[10]" LOC="M8" | IOSTANDARD=LVCMOS33;# EIM_DA10
NET "imx51_da[9]" LOC="K8" | IOSTANDARD=LVCMOS33;# EIM_DA9
NET "imx51_da[8]" LOC="L8" | IOSTANDARD=LVCMOS33;# EIM_DA8
NET "imx51_da[0]" LOC="N6" | IOSTANDARD=LVCMOS33;# EIM_DA0
NET "imx51_da[4]" LOC="P5" | IOSTANDARD=LVCMOS33;# EIM_DA4
NET "imx51_da[3]" LOC="R5" | IOSTANDARD=LVCMOS33;# EIM_DA3
NET "imx51_da[2]" LOC="L6" | IOSTANDARD=LVCMOS33;# EIM_DA2
NET "imx51_da[1]" LOC="L5" | IOSTANDARD=LVCMOS33;# EIM_DA1
NET "imx51_da[15]" LOC="M5" | IOSTANDARD=LVCMOS33;# EIM_DA15
NET "imx51_da[14]" LOC="N5" | IOSTANDARD=LVCMOS33;# EIM_DA14

The kernel module

It currently reads one word from the bus. A write operation is obtained by commenting and uncommenting in the region marked in red.

#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <asm/io.h>
#include <mach/iomux-mx51.h>
#include <mach/fpga.h>
#include <mach/hardware.h>

MODULE_DESCRIPTION("EIM interface test module");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eli Billauer");

#define EIMTEST ""

static int PSZ = 0;
static int AUS = 1;
static int BCS = 0;
static int BCD = 0;
static int BL = 0;
static int FL = 1; // Cover RFL and WFL alike
static int WC = 0;
static int ADH = 0;
static int WSC = 1;
static int ADVA = 0; // RADVA and WADVA
static int ADVN = 0; // RADVN and WADVN
static int OEA = 0;
static int CSA = 0; // RCSA and WCSA
static int RL = 0;
static int BEA = 0;
static int BE = 1;
static int WEA = 0;
static int INTPOL = 1; // Interrupt polarity
static int INTEN = 0; // Interrupt enable
static int GBCD = 0; // Burst clock divisor
static int BCM = 1; // Burst clock mode (set continuous here)
static int addr = 0x00001234;
static int data = 0xFFFF5555;

module_param(PSZ, int, 0);
module_param(AUS, int, 0);
module_param(BCS, int, 0);
module_param(BCD, int, 0);
module_param(BL, int, 0);
module_param(FL, int, 0);
module_param(WC, int, 0);
module_param(ADH, int, 0);
module_param(WSC, int, 0);
module_param(ADVA, int, 0);
module_param(ADVN, int, 0);
module_param(OEA, int, 0);
module_param(CSA, int, 0);
module_param(RL, int, 0);
module_param(BEA, int, 0);
module_param(BE, int, 0);
module_param(WEA, int, 0);
module_param(INTPOL, int, 0);
module_param(INTEN, int, 0);
module_param(GBCD, int, 0);
module_param(BCM, int, 0);
module_param(data, int, 0);
module_param(addr, int, 0);

static u32 readreg(int offset) {
 return __raw_readl( MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + offset);
}

static void writereg(int offset, u32 val) {
 __raw_writel(val, MX51_IO_ADDRESS(MX51_WEIM_BASE_ADDR) + offset);
}

static u32 bitfield(int shift, int bits, int val) {
 return ((val & ( ( 1 << bits ) - 1 ) ) << shift);
}

static void eimtest_cleanup_module(void) {

}

static int eimtest_init_module(void)
{
 int result = 0;
 void __iomem *cs2_base;
 u32 GCR1, GCR2, RCR1, RCR2, WCR1, WEIMCR;

 iomux_v3_cfg_t iomux_cs2 = MX51_PAD_EIM_CS2__EIM_CS2;

 mxc_iomux_v3_setup_pad(iomux_cs2);

 GCR1 = 0x0111008f |
 bitfield(28, 4, PSZ) |
 bitfield(23, 1, AUS) |
 bitfield(14, 2, BCS) |
 bitfield(12, 2, BCD) |
 bitfield(11, 1, WC) |
 bitfield(8, 3, BL) |
 bitfield(5, 1, FL) |
 bitfield(4, 1, FL);

 GCR2 = bitfield(0, 2, ADH);

 RCR1 =
 bitfield(24, 6, WSC) |
 bitfield(20, 3, ADVA) |
 bitfield(16, 3, ADVN) |
 bitfield(12, 3, OEA) |
 bitfield(4, 3, CSA);

 RCR2 =
 bitfield(8, 2, RL) |
 bitfield(4, 3, BEA) |
 bitfield(3, 1, BE);

 WCR1 =
 bitfield(30, 1, !BE) |
 bitfield(24, 6, WSC) |
 bitfield(21, 3, ADVA) |
 bitfield(18, 3, ADVN) |
 bitfield(15, 3, BEA) |
 bitfield(9, 3, WEA) |
 bitfield(3, 3, CSA);

 WEIMCR =
 bitfield(5, 1, INTPOL) |
 bitfield(4, 1, INTEN) |
 bitfield(1, 2, GBCD) |
 bitfield(0, 1, BCM);

 writereg(0x30, GCR1);
 writereg(0x34, GCR2);
 writereg(0x38, RCR1);
 writereg(0x3c, RCR2);
 writereg(0x40, WCR1);
 writereg(0x90, WEIMCR);

 printk(KERN_WARNING EIMTEST "CS2GCR1=%08x, CS2GCR2=%08x\n",
 readreg(0x30),
 readreg(0x34)
 );
 printk(KERN_WARNING EIMTEST "CS2RCR1=%08x, CS2RCR2=%08x\n",
 readreg(0x38),
 readreg(0x3c)
 );
 printk(KERN_WARNING EIMTEST "CS2WCR1=%08x, CS2WCR2=%08x\n",
 readreg(0x40),
 readreg(0x44)
 );

 printk(KERN_WARNING EIMTEST "WEIM Config register WCR=%08x\n",
 readreg(0x90));

 printk(KERN_WARNING EIMTEST "WEIM IP Access register WIAR=%08x\n",
 readreg(0x94));

 printk(KERN_WARNING EIMTEST "CCM_CBCDR=%08x\n",
 __raw_readl(MX51_IO_ADDRESS(0x73fd4014)));

 cs2_base = ioremap(MX51_CS2_BASE_ADDR, SZ_64K);

 if (!cs2_base) {
 printk(KERN_WARNING EIMTEST "Failed to obtain I/O space\n");
 return -ENODEV;
 }

 // Uncomment as necessary:

 //__raw_writel(data, cs2_base + addr);
 printk(KERN_WARNING EIMTEST "Read data=%08x\n",
 __raw_readl(cs2_base + addr));

 iounmap(cs2_base);

 return result;
}

module_init(eimtest_init_module);
module_exit(eimtest_cleanup_module);

The Makefile

This is a more-or-less standard Makefile for compiling a kernel. Please note that /path/to must be changed (twice) to where your Armadeus buildroot is, because both the crosscompiler and Linux kernel are referenced.

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

ifneq ($(KERNELRELEASE),)
obj-m    := eimtest.o

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

default:
 $(MAKE) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
 @rm -f *.ko *.o modules.order Module.symvers *.mod.? *~
 @rm -rf .tmp_versions module.target
 @rm -f .eimtest.*
endif

So that’s it. Hope it’s helpful!

Reader Comments

Hi did you observed that WEIM read cycle working fine ? Since we observed that addresslines DA[15:0] are shifted both AUS=1 or AUS=0 .
Where as Write working fine ?. Any advice welcome.

Thanks in advance.

#1 
Written By anbu.ub on February 14th, 2012 @ 19:19

Add a Comment

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