When developing device drivers and porting code to new hardware, programmers will often operate with source code in one hand and data sheet in the other, and it’s often necessary to look at the state of a device’s register set.
Under Linux, you can access registers, or any area of physical memory through the
/dev/mem pseudo-device and the wonders of the
mmap system call. To use it, you open the
mmap the page in which a register is located, then use the pointer returned to read and/or write the data.
I suspect that you can find one of these in every embedded programmer’s toolkit, but at Boundary Devices, we’ve developed a tool known as
devregs that allows you to put a little structure around this facility. It allows you to give names to particular physical memory areas and to describe the bits within a register in the text file
If you look in the git repository, you’ll see two files, named
devregs_imx53.dat which contain a subset of register definitions for the i.MX51 and i.MX53 processors.
We don’t have a proper repository for this type of tool, but you can currently find the source code for our tool in our i.MX utility repository. The file
devregs.cpp is self-contained, so I won’t explain compilation.
Usage for reads:
To read one or more registers, use
devregs with a single parameter that’s either an address or a register name.
root@freescale ~$ devregs 0x73f88000 :0x73f88000 =0x803dffaf
If a register address matches a register in
/etc/devregs.dat, you’ll see the register name:
root@freescale ~$ devregs 0x73f88000 GPIO2_DR:0x73f88000 =0x803dffaf
If used with a register name, any bitfields defined will be shown:
root@freescale ~$ devregs UART1_UFCR UART1_UFCR:0x73fc0090 =0x0801 TXTL 10-15 =0x2 RFDIV 7- 9 =0x0 DCEDTE 6- 6 =0x0 RXTL 0- 5 =0x1
Usage for writes:
To write to a register, use
devregs with two parameters, the address, register, or field name and the value.
The following example shows how to change
GPIO2:2 from an input to an output by tweaking the
root@freescale ~$ devregs GPIO2_GDIR GPIO2_GDIR:0x73f88004 =0x0002c0a4 root@freescale ~$ devregs GPIO2_GDIR 0x2c0a0 GPIO2_GDIR:0x73f88004 =0x0002c0a4 GPIO2_GDIR:0x73f88004 == 0x0002c0a4...0x0002c0a0 root@freescale ~$ devregs GPIO2_GDIR GPIO2_GDIR:0x73f88004 =0x0002c0a0
Note that we try to use register names exactly as defined in the data sheets within the
devregs.dat files. This makes it much easier when looking up values.
As mentioned earlier, the file /etc/devregs.dat is a text file which defines a set of registers.
The simplest entries in the file simply have register names and addresses separated by whitespace as shown in this example for the registers that control GPIO bank 2. Each line defines
GPIO2_BASE 0x53F88000 GPIO2_DR 0x53F88000 GPIO2_GDIR 0x53F88004 GPIO2_PSR 0x53F88008 GPIO2_ICR1 0x53F8800C GPIO2_ICR2 0x53F88010 GPIO2_IMR 0x53F88014 GPIO2_ISR 0x53F88018 GPIO2_EDGESEL 0x53F8801C
Some registers on the i.MX5x aren’t four-byte addressable though (notably the UART and I2C registers). They must be read with word (2-byte) or single byte accesses. You can define such registers by appending either
.B to the address. This works either on the command line:
root@freescale ~$ devregs 0X73F98000.W WDOG1_BASE_ADDR:0x73f98000 =0x0030
or in the
To save time and mental energy reading and writing bitfields in a register, you can specify bitfields on the command-line.
Fields are specified in the form
start-end in either order:
root@freescale ~$ devregs GPIO2_GDIR:2-0 GPIO2_GDIR:0x73f88004 =0x0002c0a4 2-0 0- 2 =0x4 root@freescale ~$ devregs GPIO2_GDIR:0-2 GPIO2_GDIR:0x73f88004 =0x0002c0a4 0-2 0- 2 =0x4
You can also specify a single bit, which is useful when toggling a single bit as I did in the previous example:
root@freescale ~$ devregs GPIO2_GDIR:2 0 GPIO2_GDIR:0x73f88004 =0x0002c0a4 2 2- 2 =0x1 GPIO2_GDIR:0x73f88004 == 0x0002c0a4...0x0002c0a0 root@freescale ~$ devregs GPIO2_GDIR:2 GPIO2_GDIR:0x73f88004 =0x0002c0a0 2 2- 2 =0x0
/etc/devregs.dat, you can also give bit fields names by starting a line with a colon (the whitespace is just for readability). When specified after a register, the bit fields apply to that single register.
IOMUXC_GPR2 0x53FA8008 :COUNTER_RESET_VAL:21-20 :LVDS_CLK_SHIFT:18-16 :BGREF_RRMODE:15 :DI1_VS_POLARITY:10 :DI0_VS_POLARITY:9 :BIT_MAPPING_CH1:8 :DATA_WIDTH_CH1:7 :BIT_MAPPING_CH0:6 :DATA_WIDTH_CH0:5 :SPLIT_MODE_EN:4 :CH1_MODE:3-2 :CH0_MODE:1-0
Because the layout of a register is often repeated for multiple instances of UARTs or I2C devices for example, the parser for
/etc/devregs.dat allows you to define a named set of bit fields and then use it for multiple register definitions.
The set of bit fields is defined by starting a line in the file with a slash (/) and following it with a set of fields:
/uart_urxd :CHARRDY:15 :ERR:14 :OVRRUN:13 :FRMERR:12 :BRK:11 :PRERR:10 :DATA:7-0
You can then refer to the named set of fields using this form:
UART0_URXD 0x73FBC000.W :uart_urxd/
Please note that using
/dev/mem is completely dangerous and only available when running as
root. Only do it if you know what you’re doing.
Okay, now don’t say I didn’t warn you.