Introduction
This document lists the steps involved in the process of compiling a custom FPGA Soft IP (Hi-Resolution Timer IP) to creating a Linux kernel Driver and Application Software.
It describes in detail the Altera Specific tools required to build the binaries and also provides links to general reference documentation to help understand the necessary steps involved in developing software for embedded systems.
What you will need
- A supported SoC development board. In this lab we will be using Arria10. The steps should work for other development boards with difference being a board compatible hardware design and Soft IP. Some development boards might also require an additional preloader which inturn loads the next stage, Uboot(eg: Cyclone V/ Arria V).
- A blank SD card to use with your development board and SD card reader.
- One USB cable to connect the console port of your development board to your development host and allow a terminal emulation program to interact with the serial console.
- A terminal emulator to run on your development host, like Putty, minicom, etc.
- Access to GitHub
- Some knowledge of how to use Altera's Quartus/Qsys , SoCEDS and ARM Development Studio and Linux.
- Install Altera Tools - Quartus and SoCEDS and ARM Development Studio.
Source Files
Will be updated shortly.
The following will be demonstrated
- Overview and How To compile the hardware project
- Arria10 Boot Flow overview
- Toolchain
- Overview and How To generate BootLoader
- How to create Device tree overlay
- Linux Kernel Compilation
- Overview of Root FileSystem
- Linux kernel driver for Soft IP
- Userspace Application
This section presents the necessary board settings in order to run the generated binaries on the Altera Arria 10SoC development board.

First, confirm the following:
* DDR4 memory card is installed on HPS Memory socket
* SD Boot Card is installed on Boot Flash socket
Then the board switches need to be configured as follows:
* SW1: OFF-OFF-ON-ON
* SW2: all OFF
* SW3: OF-ON-ON-ON-ON-OFF-OFF-OFF
* SW4: all OFF
Also, the board jumpers need to be configured as follows:
* Place jumpers J16, J17
* Place jumper on J32 between pins 9 and 10 (1.8V option)
* Place jumper on J42 between pins 9 and 10 (1.8V option)
* Leave all other jumpers unplaced
Hardware Project
The Soft IP timer is designed such that, the clock works at 250Mhz(T=4ns). This timer when enabled will start to count up until the Roll Over value is reached, after this instance the timer will roll back to 0. Soft Ip timer provides a capability of One shot timer. One shot timer ensures timer is triggered only once ie when timer reaches the load value(Roll Over value) assigned by the user.
Eg: To trigger a One shot timer at 10secs we have to set the load value(Rollover value) to 2500million ie (10*10^9ns)/4ns = 2500million.

*LWF2H bridge- It's a 32bit wide AXI-4 interface. It's useful for accessing the control and status registers of soft peripherals.The bridge provides a 2MB address space and access to logic, peripherals and memory implemented in the FPGA logic.
Attached document provides details regarding Register Map and register programming:
Programmable_timer.pdf
Timer IP Memory Map
Offset |
Access |
Register |
---|
0x00 |
R |
Clock Frequency [31:0] |
0x04 |
R/W |
Control [31:0] |
0x08 |
R/Wclr |
Status [31:0] |
0x0C |
R |
Reserved |
0x10 |
R |
Timer Value [31:0] |
0x14 |
R |
Timer Value [63:32] |
0x18 |
R/W |
Rollover Value [31:0] |
0x1C |
R/W |
Rollover Value [63:32] |
Timer IP Register Descriptions
Clock Frequency is a 32-bit register that holds the frequency of the timer in Hz. If the frequency is unknown this value will be -1.
Control is a 32-bit register containing the following bit fields:
Name |
Bit Offset |
Access |
Description |
---|
TIMER_ENABLE |
0 |
R/W |
This bit will enable the timer to count up. The timer must be disabled before the timer value is initialized and the rollover value set to the appropriate number of clock cycles. If software disables the timer then the interrupt will automatically be masked. This prevents the corner case of the timer being disabled at the same time the interrupt is being generated. |
INTERRUPT_ENABLE |
1 |
R/W |
Interrupt enable bit, clear this bit any time the TIMER_ENABLE bit is deasserted. This bit can be set at the same time TIMER_ENABLE is set. Interrupts also require TIMER_ENABLE to be set to for the INTERRUPT_ENABLE bit to take effect. |
SINGLE_SHOT_ENABLE |
2 |
R/W |
Enable bit for setting up single shot mode. Single shot mode causes the timer to reach the rollover value then the timer will roll back to 0 and the TIMER_ENABLE bit will clear disabling the timer. |
CLEAR_TIMER |
3 |
R0/W |
The clear timer bit will reset the timer back to 0. This bit should only be written when the timer is disabled. This bit is self clearing and will always return a 0 when read. |
Reserved |
31:4 |
R |
Unused bits, will return 0 when read |
Status is a 32-bit register containing the following bit fields:
Name |
Bit Offset |
Access |
Description |
---|
INTERRUPT_STATUS |
0 |
R/Wclr |
Reflects the state of the interrupt line. Writing a 1 to this bit clears the interrupt. This bit will only be set if INTERRUPT_ENABLE is set. If software clears the interrupt at the same time a new interrupt fires, the interrupt will win and remain asserted. |
SINGLE_SHOT_COMPLETE |
1 |
R/Wclr |
When SINGLE_SHOT_ENABLE is set and the timer reaches the rollover value this bit is set. Writing a 1 to this bit clears this status. If this bit is cleared while SINGLE_SHOT_ENABLE is set then the timer will start counting from 0 again. |
Reserved |
31:2 |
R |
Unused bits, will return 0 when read |
Timer Value contains the 64-bit timer count reflecting the current state of the timer value. Reading from the lower 32-bits (offset 0x10) of this register captures the timer value into the upper 32-bit register (offset 0x14).
This ensures the timer value is captured as an atomic operation. Before the timer is enabled software should ensure that the timer value has been cleared.
Rollover Value is a 64-bit value that represents the highest timer value before it rolls back to 0. The encoding of this register is the highest timer value minus 1 due to pipelining built into the hardware. So if you required a 10 clock cycle timer you set this value to 8 so that after the 10th cycle the timer returns to 0. This value needs to be set to 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF if the entire timer resolution is to be used. When interrupts are enabled the rollover value represents the clock cycle before the interrupt is asserted. This value must get initialized before the timer is enabled.
How To compile the hardware project
The hardware design uses A10 GHRD as a baseline while adding a Timer component.
To compile the hardware project:
- Host PC using Linux (CentOS 6.5)
- Altera Quartus II v16.1
- Hardware project archive
Procedure:
Project Archive |
Hires_Timer_IP.tar.gz |
Quartus Project |
~/Hires_Timer_IP/A10_SoC.qpf |
Qsys File |
~/Hires_Timer_IP/hps_system.qsys |
1. Retrieve and the archive file containing the hardware design and save it in the home folder.
2. Extract the files from the archive
$ cd ~
$ tar xzf Hires_Timer_IP.tar.gz
3. Make all the files writable (optional)
$ chmod +w -R ~/Hires_Timer_IP/
4. Start Quartus tool by double clicking the icon on the desktop, or by running it from the command line:
$ ~/intelFPGA/16.1/quartus/bin/quartus --64bit
5. In Quartus, go to File → Open Project
..., browse to the file
~/Hires_Timer_IP/A10_SoC.qpf and click
Open.

6. Quartus will load the project.
7.Compile the project.
Generated Files
This section presents the name and location of the files resulted from compiling the hardware design.
File |
Description |
---|
~/Hires_Timer_IP/output_files/A10_SoC.sof |
SRAM Object File - for programming FPGA SRAM Object File - for programming FPGA |
~/Hires_Timer_IP/hps_system.sopcinfo |
SOPC Info File - Used by Device Tree Generator |
~/Hires_Timer_IP/hps_isw_handoff |
Handoff folder - Used by bootloader Generator |
Converting .sof to .rbf
The SOF (SRAM Object File) file can use used to program the FPGA from the Quartus Programmer tool.
However, for the purpose of programming the FPGA from software, the SOF file needs to be converted to a RBF (Raw Binary File) format.
1. Start an embedded command shell
$ ~/intelFPGA/16.1/embedded/embedded_command_shell.sh
2. Go to the GHRD folder
$ cd ~/Hires_Timer_IP/
3. Convert the file
$ quartus_cpf -c --hps -o bitstream_compression=on output_files/ghrd_10as066n2.sof output_files/ghrd_10as066n2.rbf
This will create the following file in the ~/Hires_Timer_IP/output_files/ folder:
* ghrd_10as066n2.periph.rbf - IO ring configuration file
* ghrd_10as066n2.core.rbf - FPGA fabric configuration file
Boot Flow
Includes the following stages
- BootROM
- U-Boot
- Linux

The following table presents a short description of the different boot stages:
Stage |
Description |
BootROM |
Brings the processor out of reset, performs minimal configuration and loads U-Boot into OCRAM |
U-boot |
Configures IO, FPGA, Setup PLLs and clocking, brings up DDRAM, loads Linux kernel |
Linux |
Contains the process and memory management, network stack, device drivers and runs the end application |
Elaborating the above steps
The boot ROM code, located in HPS, brings the processor out of reset and puts the processor in a stable known state. The boot ROM code is only aware of the second-stage boot loader and not aware of any potential subsequent software stages. During this time, the boot ROM also seamlessly handles any error conditions that may occur.
The boot loader is located external to the HPS, either in off-chip flash memory or within the FPGA. If the FPGA is used, the second stage boot loader can execute directly from the FPGA without the need to copy it to on-chip RAM. The uboot then locates and loads Linux.Linux Boots and mounts root file system.
Bootloader:
The main task of S/W bootloader is to load the OS and pass over the execution to it after setting up necessary environment for its setup. For this, the bootloader must first initialize the DDRAM (this includes setting up the controller).While accessing the flash memory, it should also perform bad block management.It also programs the FPGA. In Uboot configuration file, You will will find Env variables to set the file names for the device tree blob and root file system image and commands to load the Root Filesystem.
Go through file : ~/Hires_Timer_IP/software/uboot_bsp/uboot-spcfpga/include/configs/socfpga_arria10.h
Linux:When the kernel is loaded, it immediately initializes and configures all kernel features (serial console, kernel services - memory allocation, scheduling, file cache...) , configures the various hardware attached to the system, I/O subsystems, and storage devices and executes initcalls.
Altera specifics
If the bootloader is located in external flash then boot source is determined by a combination of boot fuses and BSEL pins. The options for boot sources are FPGA, NAND, SD/MMC, QSPI. To understand more about the settings for BSEL and boot fuses please read the
boot user guide .For all
flash devices, there is an area of memory called boot area and contains up to four bootloader images. The flash devices operate in raw and MBR(partition) mode. In raw mode, the boot image is located at the start of the flash memory device, at offset 0x0. In MBR mode:
- The boot image is read from a custom partition (0xA2)
- The first image is located at the beginning of the partition, at offset 0x0
- Start address = partition start address
Reference
- Embedded Linux overview Good reference material to get an overall understanding of different stages in embedded Linux development.
- Arria10 Boot Guide Info related to Altera A10 specific boot settings
- Altera Linux Workshop Altera Linux overview, covers boot flow, tools , configuration options and hardware address map overview
- NAND boot Steps to boot linux when the boot source is NAND
- QSPI boot Steps to boot linux when the boot source is QSPI
- SDCARD boot Steps to boot linux when the boot source is SD card
Compiler that runs on the development machine, but generates code for the target .In case the U-boot or Linux kernel git trees are directly used, the build toolchain has to be manually downloaded. This section contains the instructions on how to download and setup the toolchain for this case. Note that the build toolchain is provided with the Poky Yocto recipes. So when the Yocto Project git tree is used, the recipes will download the build chain automatically.
The steps necessary to download and setup the build toolchain are:
$ cd ~
$ wget https://releases.linaro.org/archive/14.04/components/toolchain/binaries/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux.tar.bz2</span>
$ tar xjf gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux.tar.bz2
$ export CROSS_COMPILE=~/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux/bin/arm-linux-gnueabihf-
Kernel Driver
Driver Model:
There are several kind of devices connected to CPU using different kinds of bus interfaces.
Some are discoverable (Pci , USB) . So when devices are connected to them, they are enumerated by the kernel and are given a unique identification to communicate with the CPU. The drivers probe routine gets triggered to initialize the device. There are other non-discoverable devices that are attached to the system (devices under i2c, spi and also in our case hardware modules). The kernel needs to be told about these devices. This is done using a device tree. The device tree explicitly tells how the peripherals are connected to the processor. This data is known as platform data and the compatible property suggest which driver would handle the device.
In device tree – check device tree section for detailed explanation
high_res_timer: timer_driver@0x100000000 {
compatible = "altr,timer_driver";
reg = <0x00000001 0x00000000 0x00000020>;
interrupt-parent = <&intc>;
interrupts = <0 19 4>;
}; //end high_res_timer@0x100000000
On the other side, in device driver code –
Binding the driver to the device:
Done by using Id_table
/* Specify which device tree devices this driver supports */
static struct of_device_id timer_driver_dt_ids[] = {
{
.compatible = "altr,timer_driver"
},
{ /* end of table */ }
};
/* Inform the kernel about the devices this driver supports */
MODULE_DEVICE_TABLE(of, timer_driver_dt_ids);
static struct platform_driver timer_platform = {
.probe = timer_probe,
.remove = timer_remove,
.driver = {
.name = "timer_driver",
.owner = THIS_MODULE,
.of_match_table = timer_driver_dt_ids
}
};
When platform driver structure is declared, it stores a pointer to “of_device_id”. The compatible property in that id table should be the same as mentioned in the dts. Now, when driver with the name timer_driver will get registered with platform bus, the probe routine will be called.
In the probe routine, the platform_get_resource() provides the ‘reg’ property mentioned in the dtb ie base address and range. Using which driver can request the memory region from the kernel and map it by doing devm_ioremap_resource.

driver_probe
Function platform_get_irq() provides the property which is described by “interrupts” in dtb file. Thus, device tree provides all the board or Soft IP related information to the driver.
Misc Driver
When modules are required to register their own small drivers, we use misc drivers. In Linux, every device is identified by two numbers major and minor. Major number 10 is allocated for a misc driver. Modules can register individual minor numbers with the misc driver and take care of small devices (soft Ip), needing only a single point of entry.
The driver module uses the provided ‘misc_register’ and ‘misc_deregister’ to create its entry point for a minor number.
The miscdevice structure has to be also be populated with information –
- minor number: Every misc device must feature a different minor number, because such a number is the only link between the file in /dev and the driver.
- name: the name for this device. Users will find the name in the /proc/misc file.
- fops: pointer to the file operations which must be used to act on the device
In this driver we will implement the following 4 operations for the device
Open, Read, Write, Unlocked_ioctl. We need a structure file_operation and the structure is defined in linux/fs.h

The column on the left is the name of the operation that we want to support and, the value on the right is the name of the function that will implement the operation
Probe routine:
In this routine when the Soft Ip registers with the driver and memory is mapped, we also initialize the Soft IP. The initialization enables the one short interrupt which will fire an interrupt 10sec after the module is loaded as load value is set 2500million.
IRQ routine:
ISR routine is enabled by devm_request_irq().The ISR routine clears the Interrupt status. After this no more interrupts are generated.
Ioctl call:
Ioctl which stand for Input Output control is a system call used in linux to implement system calls which are not available in the kernel by default.
To implement a new ioctl command we need to follow the following steps.
- Define the ioctl code in kernel driver code and include the same in the application code as well. The definition is follows #define "ioctl name" __IOX("magic number","command number","argument type")
where IOX can be :
-
- "IO"
- If the command neither reads any data from the user nor writes any data to the userspace.
- "IOW"
- If the commands needs to write some to the kernel space.
- "IOR"
- If the command needs to read some thing from the kernel space.
- "IOWR"
- If the command does both read as well as write from the user
The Magic Number is a unique number or character that will differentiate our set of ioctl calls from the other ioctl calls.
Command Number is the number that is assigned to the ioctl. It is this number that is used to differentiate the commands from one another .
The last is the type of data that will be written in case of __IOW or read in case of __IOR or both read as well as write in case of __IOWR. In the case of _IO we need not pass anything.
- Add the header file linux/ioctl.h to make use of the above mentioned calls. Let us call the ioctl that we will create as "IOCTL_SET_LOADVAL" #define MAGIC_NO 100 #define IOCTL_SET_LOADVAL _IOWR(MAGIC_NO,0,unsigned int) The above is defined in both kernel module code as well as application code.
- The next step is to implement the ioctl call we defined in to the corresponding driver. Function prototype: long ioctl(struct file *filp,unsigned int cmd,unsigned long arg) Our driver timer_ioctl is called when the user issues an ioctl command from the application code and passes a load value. The ioctl routine does the necessary formatting to send the information to the write call.
Write call:
Is where most of the work is done for our timer driver. We make use of wait_event_interruptible to make sure that write call doesn’t interfere with the ISR or doesn’t get processed while the interrupt is being serviced.
If the wait ends successfully, we unset the interrupt flag and then use the load value passed by the user through the IOCTL call in the application code to initialize register to again trigger a one shot interrupt.
In our ISR, we must add some code to set the interrupt flag and wake up the processes waiting on the wait queue.
interrupt_flag = 1;
wake_up_interruptible(&interrupt_wq);
This
link better explains the sleep mechanism used in drivers.
To compile and build the kernel and module
These steps are for building a kernel module/driver out of tree. For more details refer:
https://www.kernel.org/doc/Documentation/kbuild/modules.txt
- Start an Embedded Command Shell
- Download kernel source from git tree https://github.com/altera-opensource/linux-socfpga - Branch: socfpga-4.1.22-ltsi or Tag: ACDS16.1_REL_GSRD_PR
- Create a directory for the Linux driver file and corresponding Makefile in the same path as linux-socfpga source.
mkdir hires_timer_driver
- Copy the provided driver file Hires_Timer_Linux_Driver_files/hires_timer.c and Hires_Timer_Linux_Driver_files/Makefile into the directory "hires_timer_driver"
- Ensure that the Makefile points to the correct Kernel source for which you want to build the driver.
The command to build an external module is:
$ make -C <path_to_kernel_src> M=$PWD
- export CROSS_COMPILE as described in Toolchain section
- export ARCH=arm
- make
- The required kernel module is created - hires_timer.ko
Userspace Application
The userspace application provides a wrapper to the driver where most of the work is done.
All it does is opens the /dev node created for the device (when you register a misc device in driver code by calling ‘misc_register()’, a /dev node is created with the name you provided) and takes user input – ‘w’ to either pass a load value which will eventually trigger a one shot interrupt or take user input – ‘r’ to read the timer register value and print to the console.
Compile and build the application-
- Create a directory for the Userspace Application. Find the required files in the APP directory of the HIRES_TIMER.tgz
mkdir hires_timer_app
cp hires_timer_test.c hires_timer_app/
cp Makefile hires_timer_app/
- export CROSS_COMPILE as described in Toolchain section
- export ARCH=arm
- Run ‘make’
- Executable “hires_timer_test” is created.
Device Tree and how to create an Overlay
A device tree is a data structure that describes the System-on- Chip(
SoC) hardware: its various hardware components, and their relationship with one another. In the past, such information was hardcoded into the kernel for each
SoC, and device trees were invented to curb this practice by providing a standard way to pass hardware description to the kernel. Much like source code, device trees can exist as human readable source files (.dts, .dtsi), or as compiled blobs (.dtb). Before a device tree is passed to the kernel, its source must be compiled into a blob.
Linux uses DT data for three major purposes:
- platform identification,
- runtime configuration, and
- device population.
Each module in the device tree is defined by a node and all its properties are defined under the node.
For information on syntax: visit the official
Device Tree Usage
Device tree overlay for our Example: Create a file timer.dts
/dts-v1/ /plugin/;
/ {
fragment@0 {
target-path = "/soc/base_fpga_region";
#address-cells = <1>;
#size-cells = <1>;
__overlay__ {
#address-cells = <2>;
#size-cells = <1>;
ranges = <0x00000001 0x00000000 0xff200000 0x00000020>;
external-fpga-config;
high_res_timer: timer_driver@0x100000000 {
compatible = "altr,timer_driver";
reg = <0x00000001 0x00000000 0x00000020>;
interrupt-parent = <&intc>;
interrupts = <0 19 4>;
}; //end high_res_timer@0x100000000
};
};
};
The device tree is made of two part's
- The live device tree prior to overlay being added
- The device tree overlay
The live Device Tree must contain an FPGA Region, an FPGA Manager, and any FPGA Bridges. The FPGA Region's "fpga-mgr" property specifies the manager by phandle to handle programming the FPGA. If FPGA Bridges need to be involved, they are specified in the FPGA Region by the "fpga-bridges" property.
The Device Tree Overlay will contain:
- "target-path" or "target" The insertion point where the the contents of the overlay will go into the live tree. target-path is a full path, while target is a phandle.
- "ranges" The address space mapping from processor to FPGA bus(ses).
- child nodes corresponding to hardware that will be loaded in this region of the FPGA.
- "firmware-name" Specifies the name of the FPGA image file on the firmware search path. The search path is described in the firmware class documentation.
In the example above, when an overlay is applied targeting base_fpga_region, fpga_mgr@ffd03000 is used to program the FPGA. During programming, the firmware specified in the overlay is loaded to the FPGA using the FPGA manager specified in the region. If FPGA programming succeeds, the overlay makes it into the live device tree. The high_res_timer child device is then populated. If FPGA programming fails, the overlay is rejected. Since in our case FPGA is already programmed by the uboot, we have to define the boolean property external-fpga-config. Then the FPGA Region can be used to add child nodes for the devices that are in the FPGA.
Our Soft IP(High Resolution Timer) is hanging off the Light Weight
H2F bridge(
LWH2F) at offset 0x0 and using the FPGA to HPS IRQ #0.
The ‘ranges’ property describes the base address and address range for child node high_res_timer. The first entry in ‘reg’ under high_res_timer is an index/chip select, in our case since the device is hanging of
LWH2F - the chip select is 1. That along with the second entry is the reg address 0x100000000 which corresponds to base address 0xff200000 (Default
Lwh2F base address)+ 0x00000000(offset of timer node). The third entry tells a range of addresses dedicated to the child node which will be memory mapped by the linux driver to talk to the device.
Determining what to fill in interrupt property:
The first number (zero) is a flag indicating if the interrupt is an SPI (shared peripheral interrupt)or PPI interrupts. 0 for SPI interrupts, 1 for PPI interrupts
The second number is related to the interrupt number.Referring to section GIC Interrupt Map(on page 9-13), we see that
F2S_FPGA_IRQ0 has GIC interrupt Number 51, section General purpose signals(on page 28-16) tells which HPS component can be configured to provide FPGA to HPS interrupts. By this we conclude that since the interrupt used is FPGA to IRQ #0 in our Soft IP, we will have to set the interrupt value to (GIC interrupt number -32) = 19
The third number is the type of interrupt. Three values are possible:
- — Leave it as it was (power-up default or what the bootloader set it to, if it did)
- — Rising edge
- — Level sensitive, active high
Next, we'll execute the command to compile the timer.dts into the device tree overlay compiled format (.dtbo):
Start an Embedded Command Shell and run
dtc -I dts -O dtb -@ -o timer.dtbo timer.dts -f
This device tree overlay can be inserted into the live device tree after kernel boot up by issuing command-
dtbt -a dtbo-filename -p path
In our case, dtbt -a timer.dtbo -p /home/root
Reference:
Test linux boot and linux driver
Now, that we have all the required binaries we can start to populate the SD card.
SD card layout:

The following table summarizes the information that is stored on the SD card:
Location |
File Name |
Description |
Partition 1 |
socfpga_arria10_socdk_sdmmc.dtb |
Linux Device Tree Blob file |
^ |
ghrd_10as066n2.core.rbf |
Compressed FPGA configuration file |
^ |
ghrd_10as066n2.periph.rbf |
Compressed FPGA IO configuration file |
^ |
zImage |
Compressed Linux kernel image file |
Partition 2 |
various |
Linux root filesystem |
Partition 3 |
n/a |
U-Boot Binary and U-Boot Device Tree image |
- Download the compressed SD card image archive for tool version 16.1 from here: https://releases.rocketboards.org/2016.10/gsrd/bin/linux-socfpga-gsrd-16.1-a10-bin.tar.gz
- Extract the SD card image "sdimage.img" from this compressed archive using WinZip or similar.
- Use Win32DiskImager to write the image to the SD card. The tool can be downloaded from here: http://sourceforge.net/projects/win32diskimager/files/latest/download
- Update ghrd_10as066n2.core.rbf , ghrd_10as066n2.periph.rbf. The uboot, zImage, default device tree & root filesystem remains unchanged. To update these images in the SD card –
File |
Update procedure |
zImage |
Mount /dev/sdx1 (FAT) on the host machine and update files accordingly: $ sudo mkdir sdcard $ sudo mount /dev/sdx1 sdcard/ $ sudo cp sdcard/ $ sudo umount sdcard |
ghrd_10as066n2.core.rbf |
^ |
ghrd_10as066n2.periph.rbf |
^ |
socfpga_arria10_socdk_sdmmc.dtb |
^ |
uboot_w_dtb-mkpimage.bin |
In Embedded command shell (windows), $ alt-boot-disk-util -B uboot_w_dtb-mkpimage.bin -a write -d |
root filesystem |
Mount /dev/sdx2 (ext3 FS) on the host machine and updatefiles accordingly |
- Mount /dev/sdx2 (root filesystem partition) on host machine
$ mkdir ~/mnt
$ mount /dev/sdc2 /mnt
- Create the device tree overlay file timer.dtbo as described in Device tree section of this guide.
- Copy “timer.dtbo” executable into the “/root” directory in the root filesystem
- Copy “hires_timer.ko” executable into the “/root” directory in the root filesystem
- Copy "hires_timer_test" userspace application executable into the “/root” directory in the root filesystem
sudo cp timer.dtbo <path-to-rootfilesystem-partition>/root
sudo cp hires_timer.ko <path-to-rootfilesystem-partition>/root
sudo cp hires_timer_test <path-to-rootfilesystem-partition>/root
sync
- Insert the Sd card into the board and boot it up.
- At the linux kernel prompt
* Since we use a device tree overlay for our Soft IP driver, we have to issue command described in the Device tree overlay section.
dtbt -a timer.dtbo -p /home/root
* Insert the kernel module by issuing command
insmod hires_timer.ko
* As described earlier, insmod invokes probe routine and probe routine reads the device tree for details related to IRQ, register mapping. It also initializes registers and enable one shot timer to fire interrupt after 10sec.
* Hence, in the snapshot below you see a message from ISR routine after 10sec. Check the seconds duration in the timer stamp above (07:36:46 - 07:36:36 = 10sec).

driver_load
- Test the Userspace Application, After Linux boots up and hire_timer.ko is inserted, you can run command as shown below.
