driver development summary 26 1 - ArrowElectronics/Agilex-5 GitHub Wiki
Altera VVP Video Frame Reader โ Linux DRM Driver
Development Summary
Platform: Agilex 5 SoC (Arrow AXE5-Falcon)
Kernel: Linux 6.12 โ 6.18+
IP Core: Altera VVP Suite Video Frame Reader (VFR)
Pipeline: VFR โ CVO โ FR2CV โ TFP410PAP โ HDMI โ 1920ร1080p60
Table of Contents
- Linux Display Architecture โ DRM/KMS and CMA
- Objective
- Hardware Pipeline
- What We Built
- How the Driver Works
- Memory and Cache Coherency
- CVO Initialisation in Lite Mode
- No-Vblank Design
- Clock Domains
- Pixel Format Journey
- Key Register Values
- FPGA Configuration
- Driver Source Files
- Device Tree
- Kernel Configuration
1. Linux Display Architecture โ DRM/KMS and CMA
1.1 Why DRM/KMS?
Linux provides two display driver frameworks:
Legacy fbdev โ the original Linux framebuffer interface (/dev/fb0). Simple but limited โ no synchronisation, no hardware abstraction, no compositing support. Still used today by the kernel console (fbcon).
DRM/KMS (Direct Rendering Manager / Kernel Mode Setting) โ the modern Linux display framework introduced to replace fbdev. All modern user-space display stacks โ Wayland, X11, console โ use DRM/KMS.
DRM/KMS provides:
- Atomic modesetting โ all display parameters change in a single tear-free hardware commit
- GEM buffer management โ standardised GPU memory allocation and sharing
- Hardware abstraction โ connector/encoder/CRTC object model maps cleanly to physical hardware
- Page flip events โ precise timestamps for smooth animation and compositor synchronisation
- fbdev emulation โ legacy
/dev/fb0compatibility layer on top of DRM
For the VFR driver, DRM/KMS was chosen because it is the standard modern interface. Even though the VFR has no GPU and no 2D/3D acceleration, the DRM framework correctly models the display pipeline and provides the fbcon integration needed to show the Linux console on the HDMI display.
1.2 DRM Object Model
DRM models display hardware as a hierarchy of abstract objects:
drm_device โ top-level device, owns all objects
โ
โโโ drm_plane โ a scanout source (framebuffer backing a display layer)
โ โโโ drm_framebuffer โ pixel format + geometry metadata
โ โโโ drm_gem_dma_object โ actual memory allocation (CMA)
โ
โโโ drm_crtc โ display controller / timing engine
โ โโโ drm_crtc_state โ atomic state (mode, active, no_vblank...)
โ
โโโ drm_encoder โ signal format converter (LVDS, TMDS, parallel RGB...)
โ
โโโ drm_connector โ physical output connector (HDMI, DisplayPort, DSI...)
โโโ drm_display_mode โ timing parameters (H/V sync, pixel clock...)
How these map to the VFR hardware:
| DRM Object | VFR Hardware Equivalent |
|---|---|
drm_plane |
The CMA buffer that BASE_ADDR points to |
drm_crtc |
The VFR core itself โ the DMA engine and timing control |
drm_encoder |
Pass-through โ HDMI framing handled downstream by TFP410 |
drm_connector |
The HDMI output โ always connected, fixed 1080p60 mode |
1.3 Atomic Modesetting
Modern DRM uses an atomic API. All display state changes are bundled into a drm_atomic_state object and committed in a single operation. The hardware either gets the full new configuration or nothing โ no partial updates visible on screen.
The atomic commit path for this driver:
User-space or fbcon requests display update
โ
โผ
drm_atomic_helper_check() โ validates state against hardware constraints
โ
โผ
altera_vfr_commit_tail() โ custom non-blocking commit
โ
โโโ commit_modeset_disables()
โโโ commit_planes() โโโโโโโโโโโโโโโบ altera_vfr_plane_atomic_update()
โ โโโ vfr_program_bufset() or
โ vfr_update_base_addr()
โโโ commit_modeset_enables()
โโโ commit_hw_done()
โโโ cleanup_planes()
1.4 fbdev Emulation
The driver installs fbdev emulation via drm_client_setup_with_fourcc() (kernel 6.17+) or drm_fbdev_dma_setup() (kernel 6.12โ6.16). This creates a /dev/fb0 device backed by a DRM GEM buffer. The kernel console (fbcon) binds to /dev/fb0 and renders text characters as pixels. The VFR then DMA-reads those pixels continuously at 60 Hz and streams them to the display.
fbcon writes text characters as pixels
โ writes to /dev/fb0 virtual mapping
โผ
CMA buffer in DDR (0xFAE00000)
โ VFR AXI DMA reads at 60 Hz
โผ
HDMI display shows Linux console
This is why the Linux boot messages and login prompt appear on the HDMI display without any user-space display manager running.
1.5 CMA โ Contiguous Memory Allocator
What is CMA?
The Contiguous Memory Allocator (CMA) is a Linux kernel subsystem that reserves a pool of physically contiguous memory at boot time. It exists to serve devices that cannot use scatter-gather DMA โ devices whose hardware requires a flat, unbroken physical address range.
The VFR is exactly such a device. Its AXI DMA master takes a single BASE_ADDR register value and reads sequentially from BASE_ADDR to BASE_ADDR + (stride ร height). There is no scatter-gather address table โ the buffer must be contiguous in physical memory.
CMA Reservation
The CMA pool is reserved in the device tree:
reserved-memory {
cma: linux,cma {
size = <0x0 0x02000000>; /* 32 MiB */
reusable;
linux,cma-default;
};
};
At boot Linux confirms:
cma: Reserved 32 MiB at 0x00000000faa00000 on node -1
This reserves 0xFAA00000โ0xFC9FFFFF (32 MB) as a contiguous pool. The framebuffer allocation lands at 0xFAE00000 within this pool.
CMA Allocation for the Framebuffer
When drm_client_setup_with_fourcc() (or drm_fbdev_dma_setup() on older kernels) is called, the DRM GEM DMA helper allocates from the CMA pool:
/* Inside drm_gem_dma_create() */
obj->vaddr = dma_alloc_attrs(dev, size, &obj->dma_addr,
GFP_KERNEL | __GFP_NOWARN, attrs);
This returns:
obj->dma_addr=0xFAE00000โ physical address (written to VFRBASE_ADDR)obj->vaddr= kernel virtual address (used by CPU to write pixels)
Framebuffer Size
Width: 1920 pixels
Height: 1080 pixels
Format: XRGB8888 (4 bytes per pixel)
Stride: 1920 ร 4 = 7680 bytes per line
Total: 7680 ร 1080 = 8,294,400 bytes โ 7.9 MiB
This fits comfortably within the 32 MiB CMA reservation.
Why Contiguous Memory Matters for the VFR
Without CMA the Linux page allocator would allocate the framebuffer as scattered 4 KB pages at arbitrary physical addresses. The VFR cannot follow a scatter-gather list โ it would read garbage data from whatever happens to be at consecutive physical addresses after the first page.
With CMA the 8 MB framebuffer occupies a single flat physical address range:
0xFAE00000 โโโ VFR BASE_ADDR
0xFAE00000 + 8,294,400 = 0xFAFE8000 โโโ end of framebuffer
The VFR reads from 0xFAE00000 to 0xFAFE8000 in one continuous sweep per frame, then loops back and repeats at 60 Hz indefinitely.
DMA Mask
The driver sets a 32-bit DMA mask to ensure CMA allocations fall within the VFR's addressable range:
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
This tells the kernel the VFR AXI master can only address 0x00000000โ0xFFFFFFFF. All CMA allocations for this device will be within that range. Since the Agilex 5 HPS has 2 GB of DDR at 0x80000000โ0xFFFFFFFF, all allocations naturally fall within the 32-bit address space.
Verifying the CMA Allocation
After boot the DRM debugfs interface shows the exact allocation:
cat /sys/kernel/debug/dri/0/framebuffer
Output:
framebuffer[37]:
allocated by = [fbcon]
format=XR24 little-endian (0x34325258)
size=1920x1080
pitch[0]=7680
dma_addr=0x00000000fae00000 โ physical address written to VFR BASE_ADDR
vaddr=... โ kernel virtual address used by CPU
The dma_addr value matches the VFR LAST_BUF_READ register (devmem2 0x20008144 = 0xFAE00000), confirming the VFR is actively DMA-reading the correct Linux framebuffer.
2. Objective
The goal was to write a Linux DRM/KMS kernel driver that makes the Altera VVP Video Frame Reader (VFR) IP core appear as a standard Linux display device. This allows:
- The kernel framebuffer console (
fbcon) to render the Linux text terminal on the HDMI display - Legacy applications to write pixels via
/dev/fb0 - Future Wayland compositors to use the display via the standard DRM API
The VFR is an FPGA peripheral that performs DMA reads from a Linux-allocated framebuffer in HPS DDR memory and streams the pixel data to a downstream video pipeline ending at an HDMI transmitter.
3. Hardware Pipeline
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HPS DDR @ 0xFAE00000 (CMA framebuffer, 8 MB) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ F2SDRAM AXI bridge
โ 256-bit read master, burst=16
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VFR โ Video Frame Reader (0x20008000) โ
โ Lite mode ยท 4 colour planes ยท Pixel Packing ยท 1 pix/clk โ
โ vid_clock: 200 MHz โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AXI4-S Lite ยท 32-bit tdata ยท 200 MHz
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CVO โ Clocked Video Output (0x20006000) โ
โ Lite mode ยท 4 colour planes ยท VID_IS_ASYNC=1 โ
โ vid_clock: 200 MHz ยท fr_clock: 148.5 MHz โ
โ Internal FIFO depth: 4096 entries โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AXI4-S full-raster ยท 148.5 MHz
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FR2CV โ Full-Raster to Clocked Video Converter โ
โ vid_clock: 148.5 MHz โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 24-bit parallel RGB ยท DATA[23:0]
โ HSYNC ยท VSYNC ยท DE
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TFP410PAP โ HDMI Transmitter (I2C 0x38, reg 0x08 = 0xBF) โ
โ 24-bit RGB input ยท TMDS encoding โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HDMI
โผ
Dell Monitor 1920ร1080@60Hz
Clock Domains
| Domain | Frequency | Drives |
|---|---|---|
vid_clock |
200 MHz | VFR, CVO data path, TPG |
fr_clock |
148.5 MHz | CVO VTG, FR2CV, TFP410 pixel clock |
Running vid_clock faster than fr_clock provides headroom for DDR latency. The CVO VID_IS_ASYNC=1 parameter enables the internal CDC FIFO to bridge the two clock domains safely.
Hardware Address Boundaries
The Agilex 5 HPS maps 2 GB of DDR at 0x80000000โ0xFFFFFFFF. The relevant address regions for this driver are:
Physical DDR address space (2 GB):
0x80000000 โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ATF / TF-A secure โ 32 MB (no-map reserved)
0x82000000 โโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Linux kernel + heap โ
โ Device drivers โ
โ Page tables โ
โ ... โ
0xFAA00000 โโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ CMA reserved region โ 32 MB
โ (contiguous DMA pool) โ
0xFAE00000 โ โโโโโโโโโโโโโโโโโโโโโ โ โ VFR BASE_ADDR (confirmed)
โ โ DRM framebuffer โ โ
โ โ 1920ร1080ร4 โ โ
โ โ = 8,294,400 B โ โ
0xFAFE8000 โ โโโโโโโโโโโโโโโโโโโโโ โ
โ (remainder of CMA) โ
0xFCA00000 โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SWIOTLB bounce buf โ 2 MB
0xFFFFFFFF โโโโโโโโโโโโโโโโโโโโโโโโโโโ
The VFR MMIO register block sits in the HPS-to-FPGA Lightweight AXI bridge window:
0x20000000 โโโโโโโโโโโโโโโโโโโโโโโโโโโ H2F Lightweight AXI bridge base
โ ...other peripherals...โ
0x20006000 โโโโโโโโโโโโโโโโโโโโโโโโโโโค CVO registers (0x200 bytes)
0x20008000 โโโโโโโโโโโโโโโโโโโโโโโโโโโค VFR registers (0x400 bytes)
โ ... โ
0x2FFFFFFF โโโโโโโโโโโโโโโโโโโโโโโโโโโ H2F Lightweight AXI bridge end
The CMA framebuffer at 0xFAE00000 is within the 32-bit address space (DMA_BIT_MASK(32)) accessible to the VFR AXI master via the F2SDRAM bridge.
A Linux DRM/KMS platform driver consisting of:
| File | Purpose |
|---|---|
altera_vfr_drm.c |
Main driver: probe, KMS objects, hardware control |
altera_vfr_drm.h |
Private struct and container-of helpers |
altera_vfr_regs.h |
VFR and CVO register byte offsets |
Kconfig |
Kernel config entry |
Makefile |
Kbuild rules |
altr,vfr-drm.yaml |
Device tree binding schema |
The driver creates the following DRM objects:
drm_device
โโโ drm_plane โ primary plane backed by CMA GEM buffer
โโโ drm_crtc โ represents the VFR output pipeline (no_vblank=true)
โโโ drm_encoder โ pass-through TMDS encoder
โโโ drm_connector โ always-connected HDMI-A, fixed 1080p60 mode
5. How the Driver Works
Probe Sequence
When the kernel matches compatible = "altr,vfr-drm-1.0":
- Allocate
drm_device+altera_vfrprivate state viadevm_drm_dev_alloc() - Map VFR MMIO registers from DT
reg[0](0x20008000) - Read
VID_PIDregister โ abort with-ENODEVif not0x6AF7024A - Map CVO MMIO registers from DT
reg[1](0x20006000) - Stop VFR (
RUN = STOP) - Call
altera_vfr_cvo_init()โ program CVO IMG_INFO registers - Initialise DRM KMS objects (plane, CRTC, encoder, connector)
- Call
drm_mode_config_reset()โ setsno_vblank=trueon CRTC state - Register DRM device โ creates
/dev/dri/card0 - Install fbdev emulation โ allocates CMA buffer, creates
/dev/fb0 - Initial atomic commit triggers
vfr_program_bufset()โ VFR starts
Hardware Programming
First enable โ vfr_program_bufset():
RUN = STOP
BUFSET[0].BASE_ADDR = CMA physical address (0xFAE00000)
BUFSET[0].NUM_BUFFERS = 1
BUFSET[0].INTER_LINE_OFFS = 7680 (stride = 1920 ร 4 bytes)
BUFSET[0].WIDTH = 1920
BUFSET[0].HEIGHT = 1080
BUFSET[0].INTERLACE = 0 (progressive)
BUFSET[0].COLORSPACE = 0 (RGB)
BUFSET[0].SUBSAMPLING = 0 (4:4:4)
BUFSET[0].BPS = 8
BUFSET[0].FIELD_COUNT = 0 (infinite/continuous)
NUM_BUFFER_SETS = 1
BUFFER_MODE = SINGLE_SET
STARTING_BUFSET = 0
COMMIT = 1
RUN = FREE_RUNNING
Page flip โ vfr_update_base_addr():
BUFSET[0].BASE_ADDR = new physical address
COMMIT = 1
6. Memory and Cache Coherency
The Problem
The ARM Cortex-A55 CPU writes framebuffer pixels into its L1/L2 cache. The VFR AXI DMA master reads directly from DDR via the F2SDRAM bridge, bypassing the CPU cache. Without intervention the VFR reads stale DDR data while current pixel data sits in the CPU cache โ resulting in white noise or a frozen image on the display.
Solution โ F2SDRAM Bridge with Non-Cacheable Memory
Connect the VFR AXI master to the F2SDRAM bridge and mark the framebuffer allocation as non-cacheable via dma-noncoherent in the device tree:
display-controller@20008000 {
compatible = "altr,vfr-drm-1.0";
reg = <0x20008000 0x400>,
<0x20006000 0x200>;
dma-noncoherent;
};
dma-noncoherent tells the Linux DMA framework to allocate the CMA framebuffer as non-cacheable memory. CPU writes bypass the cache and go directly to DDR via the write-combining buffer:
CPU write โ write-combining buffer (128-256 bytes) โ DDR
VFR reads DDR directly via F2SDRAM bridge โ always sees current data
Why Non-Cacheable is the Right Choice
The framebuffer is 8 MB โ approximately 4ร the size of the Agilex 5 L3 cache (2 MB). Any substantial screen update โ scrolling text, redrawing the console, updating large areas โ will thrash the L3 cache completely. The framebuffer will push out whatever working data the CPU had cached and replace it with pixel data that the CPU writes once and never reads back. This is the worst possible cache usage pattern.
For applications where the CPU's primary job is not painting graphics โ signal processing, control systems, networking, data acquisition โ the display is a convenience peripheral. There is no benefit to caching the framebuffer. Non-cacheable write-combining is the correct and efficient choice:
- No cache pollution โ the L3 cache remains fully available for real workloads
- No coherency overhead โ cacheable DMA transactions require the cluster snoop unit to check for cache hits on every transaction, adding latency and consuming bandwidth. Non-cacheable F2SDRAM reads have no such overhead
- Write-combining efficiency โ the write-combining buffer coalesces CPU pixel writes into 64-byte DDR bursts, well matched to the VFR's 32-byte AXI burst size
- Write posting โ CPU writes are still posted via the store buffer and do not stall. The write-combining buffer drains to DDR in nanoseconds โ far faster than the 16.7ms frame period
dma_sync_single_for_device()
A dma_sync_single_for_device() call was added in atomic_update as a one-time cache flush at the moment the VFR is first programmed. This ensures any CPU-cached framebuffer content written before dma-noncoherent took full effect is flushed to DDR before the VFR starts reading:
dma_sync_single_for_device(vfr->dev, paddr,
VFR_FIXED_STRIDE * VFR_FIXED_HEIGHT,
DMA_TO_DEVICE);
This is a belt-and-suspenders measure for the initial commit only โ once dma-noncoherent is active all subsequent CPU writes go directly to DDR and no explicit flush is needed.
7. CVO Initialisation in Lite Mode
Why It Is Needed
In Lite mode the VFR outputs a simplified AXI4-S stream with no embedded metadata packets. The CVO receives raw pixel data but has no way to determine frame dimensions from the stream itself.
The CVO must be explicitly told the frame width and height via its IMG_INFO registers. Without this:
IMG_INFO_WIDTH = 0at power-on- CVO does not know when to assert end-of-line
VID_SIZE_ERRfires continuously- Display is garbled or blank
In Full mode the VFR embeds image information packets in the stream containing width, height, colorspace etc. The CVO reads these automatically โ no software programming needed.
altera_vfr_cvo_init()
Called at probe before the VFR is started:
iowrite32(VFR_FIXED_WIDTH, cvo_regs + CVO_REG_IMG_INFO_WIDTH); /* 1920 */
iowrite32(VFR_FIXED_HEIGHT, cvo_regs + CVO_REG_IMG_INFO_HEIGHT); /* 1080 */
iowrite32(0, cvo_regs + CVO_REG_IMG_INFO_INTERLACE); /* progressive */
iowrite32(0, cvo_regs + CVO_REG_IMG_INFO_COLORSPACE);/* RGB */
iowrite32(0, cvo_regs + CVO_REG_IMG_INFO_SUBSAMPLING);/* 4:4:4 */
iowrite32(VFR_FIXED_BPS, cvo_regs + CVO_REG_IMG_INFO_BPS); /* 8 */
iowrite32(CVO_FALLBACK_FORCE_VID, cvo_regs + CVO_REG_FALLBACK); /* force VFR input */
Note: The CVO has no COMMIT register โ IMG_INFO writes take effect immediately. 0x0190 is REG_TPG_SIZE_ERR_COUNT (read-only diagnostic), not a commit register.
CVO Register Map (key offsets from UG-20344 Table 327, page 353)
| Offset | Register | Access |
|---|---|---|
| 0x0120 | IMG_INFO_WIDTH | Write-only in Lite mode |
| 0x0124 | IMG_INFO_HEIGHT | Write-only in Lite mode |
| 0x0128 | IMG_INFO_INTERLACE | Write-only in Lite mode |
| 0x012C | IMG_INFO_COLORSPACE | Write-only in Lite mode |
| 0x0130 | IMG_INFO_SUBSAMPLING | Write-only in Lite mode |
| 0x0138 | IMG_INFO_BPS | Write-only in Lite mode |
| 0x0140 | REG_STATUS | Read-only |
| 0x0154 | REG_FALLBACK | Read/Write |
8. No-Vblank Design
Why No Interrupt
The VFR in Lite mode does not have an accessible IRQ block. The standard DRM commit tail (drm_atomic_helper_commit_tail) calls drm_atomic_helper_wait_for_vblanks() which blocks for 10 seconds waiting for a hardware vblank interrupt that never arrives.
Solution
A custom CRTC reset function sets no_vblank=true on the CRTC state:
static void altera_vfr_crtc_reset(struct drm_crtc *crtc)
{
state = kzalloc(sizeof(*state), GFP_KERNEL);
__drm_atomic_helper_crtc_reset(crtc, state);
state->no_vblank = true; /* no hardware vblank available */
}
Flip events are delivered immediately in atomic_flush under the event spinlock:
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, new_state->event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
A custom non-blocking commit tail avoids all vblank waits:
static void altera_vfr_commit_tail(struct drm_atomic_state *state)
{
drm_atomic_helper_commit_modeset_disables(state->dev, state);
drm_atomic_helper_commit_planes(state->dev, state, 0);
drm_atomic_helper_commit_modeset_enables(state->dev, state);
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_cleanup_planes(state->dev, state);
}
9. Clock Domains
Three clock domains exist in the VVP pipeline:
| Clock | Frequency | Components |
|---|---|---|
cpu_clock / vid_clock |
200 MHz | VFR, CVO register interface, CVO data path, TPG |
fr_clock |
148.5 MHz | CVO VTG, CVO full-raster output, FR2CV, TFP410 |
| HPS AXI clock | 400 MHz | VFR AXI read master (internal to VFR, CDC inside) |
Running vid_clock (200 MHz) faster than fr_clock (148.5 MHz) gives 35% headroom for DDR latency spikes. The CVO VID_IS_ASYNC=1 compile-time parameter enables the internal CDC FIFO between the 200 MHz and 148.5 MHz domains.
With vid_clock = fr_clock = 148.5 MHz (same clock) the CVO FIFO has zero headroom โ any DDR latency stalls the VFR, drains the FIFO, and causes VID_STALL_ERR. This was the root cause of the stall errors observed during development.
10. Pixel Format
The driver uses XRGB8888 โ 32 bits per pixel, 4 bytes per pixel in memory.
Why XRGB8888
XRGB8888 divides evenly into every standard AXI bus width:
256-bit (32 bytes) รท 4 bytes = 8 pixels โ perfect alignment
128-bit (16 bytes) รท 4 bytes = 4 pixels โ perfect alignment
This ensures the VFR AXI read master always starts and ends each burst on a pixel boundary โ no fractional pixels, no byte misalignment.
Pixel Packing with 4 Colour Planes
The VFR is configured with:
- 4 colour planes (X, R, G, B)
- Pixel Packing mode
Pixel Packing rounds each pixel up to a 32-bit word boundary. With 4 planes ร 8 BPS = 32 bits, each XRGB8888 pixel occupies exactly one 32-bit word. The VFR reads 4 bytes from DDR and outputs 32-bit tdata to the AXI4-S stream.
The CVO is also configured with 4 colour planes. The FR2CV outputs 32-bit data to the TFP410. The TFP410 only uses DATA[23:0] โ the X byte on DATA[31:24] is physically unconnected on the PCB and harmlessly ignored.
Memory Layout
XRGB8888 in DDR (little-endian):
Byte +0: Blue (B)
Byte +1: Green (G)
Byte +2: Red (R)
Byte +3: X=0 (padding, ignored by TFP410)
11. Key Register Values
VFR Registers (base 0x20008000)
| Address | Register | Value | Meaning |
|---|---|---|---|
| 0x20008000 | VID_PID | 0x6AF7024A | IP confirmed โ |
| 0x20008004 | VERSION | 0x18070001 | major=24, update=7, regmap=1 |
| 0x20008008 | LITE_MODE | 0x00000001 | Lite mode active |
| 0x20008020 | COLOR_PLANES | 0x00000004 | 4 planes (XRGB) |
| 0x20008024 | PIXELS_IN_PARALLEL | 0x00000001 | 1 pixel/clock |
| 0x20008028 | PACKING | 0x00000002 | Pixel Packing |
| 0x20008140 | STATUS | 0x00000001 | Running โ |
| 0x20008144 | LAST_BUF_READ | 0xFAE00000 | DMA active โ |
| 0x200081A0 | RUN | 0x00000001 | FREE_RUNNING |
| 0x200081B0 | BUFSET[0].BASE_ADDR | 0xFAE00000 | Framebuffer โ |
| 0x200081BC | BUFSET[0].STRIDE | 0x00001E00 | 7680 bytes โ |
| 0x200081C0 | BUFSET[0].WIDTH | 0x00000780 | 1920 pixels |
| 0x200081C4 | BUFSET[0].HEIGHT | 0x00000438 | 1080 lines โ |
CVO Registers (base 0x20006000)
| Address | Register | Value | Meaning |
|---|---|---|---|
| 0x20006000 | VID_PID | 0x6AF70244 | CVO confirmed โ |
| 0x20006008 | VID_FIFO_DEPTH | 0x00001000 | 4096 entries โ |
| 0x20006020 | COLOR_PLANES | 0x00000004 | 4 planes โ |
| 0x2000601C | PIXELS_IN_PARALLEL | 0x00000001 | 1 pixel/clock โ |
| 0x20006028 | CPU_CLK_FREQ | 0x0BEBC200 | 200 MHz โ |
| 0x20006030 | VID_IS_ASYNC | 0x00000001 | Async mode โ |
| 0x20006140 | REG_STATUS | 0x00000041 | Locked, no errors โ |
| 0x20006154 | REG_FALLBACK | 0x00000008 | Force VFR input โ |
| 0x20006250 | REG_TOTALS | 0x04650898 | 2200ร1125 total raster โ |
12. FPGA Configuration
VFR Platform Designer Parameters
| Parameter | Value |
|---|---|
| Lite mode | Enabled |
| Number of colour planes | 4 |
| Bits per symbol | 8 |
| Pixels in parallel | 1 |
| Packing | Pixel |
| AXI master data width | 256-bit |
| Read burst target | 16 |
| Always burst max burst | true |
| Burst on burst boundaries | true |
| Max pending read transactions | 8 |
CVO Platform Designer Parameters
| Parameter | Value |
|---|---|
| Lite mode | Enabled |
| Number of colour planes | 4 |
| Pixels in parallel | 1 |
| VID_IS_ASYNC | Enabled |
| Input FIFO depth | 4096 |
| vid_clock | 200 MHz |
| fr_clock | 148.5 MHz |
13. Driver Source Files
altera_vfr_regs.h โ Key Constants
#define VFR_PIXELS_IN_PARALLEL 1
#define VFR_FIXED_WIDTH 1920
#define VFR_FIXED_HEIGHT 1080
#define VFR_FIXED_STRIDE (VFR_FIXED_WIDTH * 4) /* 7680 */
#define VFR_FIXED_VFR_WIDTH VFR_FIXED_WIDTH /* 1920 */
#define VFR_FIXED_BPS 8
#define VFR_VID_PID_EXPECTED 0x6AF7024A
/* CVO registers */
#define CVO_REG_IMG_INFO_WIDTH 0x0120
#define CVO_REG_IMG_INFO_HEIGHT 0x0124
#define CVO_REG_IMG_INFO_INTERLACE 0x0128
#define CVO_REG_IMG_INFO_COLORSPACE 0x012C
#define CVO_REG_IMG_INFO_SUBSAMPLING 0x0130
#define CVO_REG_IMG_INFO_BPS 0x0138
#define CVO_REG_STATUS 0x0140
#define CVO_REG_FALLBACK 0x0154
#define CVO_FALLBACK_FORCE_VID 0x8
altera_vfr_drm.h โ Private Struct
struct altera_vfr {
struct drm_device drm; /* must be first */
struct device *dev;
void __iomem *regs; /* VFR MMIO */
void __iomem *cvo_regs; /* CVO MMIO */
bool running;
struct drm_plane primary;
struct drm_crtc crtc;
struct drm_encoder encoder;
struct drm_connector connector;
};
Key Functions in altera_vfr_drm.c
| Function | Purpose |
|---|---|
vfr_program_bufset() |
Full VFR init on first atomic commit |
vfr_update_base_addr() |
Address-only update on page flip |
altera_vfr_cvo_init() |
Program CVO IMG_INFO registers at probe |
altera_vfr_plane_atomic_update() |
DRM plane callback โ main hardware interface |
altera_vfr_crtc_reset() |
Custom reset โ sets no_vblank=true |
altera_vfr_crtc_atomic_flush() |
Delivers flip events immediately |
altera_vfr_commit_tail() |
Custom non-blocking commit tail |
altera_vfr_connector_get_modes() |
Returns single fixed 1080p60 mode |
altera_vfr_probe() |
Full initialisation sequence |
14. Device Tree
display-controller@20008000 {
compatible = "altr,vfr-drm-1.0";
reg = <0x20008000 0x400>, /* VFR registers */
<0x20006000 0x200>; /* CVO registers */
dma-noncoherent;
};
Two reg entries are required:
- Index 0: VFR hardware register block
- Index 1: CVO hardware register block (needed for Lite mode IMG_INFO programming)
15. Kernel Configuration
Config Fragment
CONFIG_DRM=y
CONFIG_DRM_FBDEV_EMULATION=y
CONFIG_DRM_ALTERA_VFR=y
CONFIG_STRICT_DEVMEM=n
CONFIG_IO_STRICT_DEVMEM=n
Kconfig
config DRM_ALTERA_VFR
tristate "Altera VVP Video Frame Reader DRM driver"
depends on DRM && OF && HAS_IOMEM
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_CLIENT_LIB
select DRM_CLIENT_SELECTION
select is used instead of depends on for helper symbols because other drivers in the base config (Rockchip, VC4, Meson etc.) select DRM_GEM_DMA_HELPER=m, which prevents it being promoted to =y via depends on. Using select forces =y when our driver is enabled. DRM_CLIENT_LIB and DRM_CLIENT_SELECTION are required from kernel 6.17+ for the new client-based fbdev setup.
Kernel Build System Integration
# drivers/gpu/drm/Kconfig
source "drivers/gpu/drm/altera-vfr/Kconfig"
# drivers/gpu/drm/Makefile
obj-$(CONFIG_DRM_ALTERA_VFR) += altera-vfr/
The Makefile line is the most common casualty of a kernel branch merge โ if it is missing the driver directory is silently skipped by Kbuild and CONFIG_DRM_ALTERA_VFR will not appear in .config at all.
Kernel API Changes by Version
The driver has been ported across several kernel releases. The following table documents the changes required at each transition:
| Kernel | Change | Action |
|---|---|---|
| 6.12 | Baseline โ CMA helpers renamed CMAโDMA | Use drm_gem_dma_helper.h, drm_fb_dma_helper.h, drm_fbdev_dma.h |
| 6.12 | platform_driver.remove returns void |
Change signature accordingly |
| 6.12 | devm_drm_dev_alloc() returns private struct |
No container_of needed |
| 6.17 | .date field removed from drm_driver |
Remove .date = "20240101" |
| 6.17 | drm_fbdev_dma_setup() removed |
Replace with drm_client_setup_with_fourcc(&vfr->drm, DRM_FORMAT_XRGB8888) |
| 6.17 | fbdev_probe callback required in drm_driver |
Add .fbdev_probe = drm_fbdev_dma_driver_fbdev_probe |
| 6.17 | New include needed | Add #include <drm/clients/drm_client_setup.h> |
| 6.17 | DRM_CLIENT_LIB=m causes linker error |
Add select DRM_CLIENT_LIB and select DRM_CLIENT_SELECTION to Kconfig |
| 6.18 | mode_valid signature โ const added |
Change struct drm_display_mode *mode โ const struct drm_display_mode *mode |
Document generated from development session โ Agilex 5 AXE5-Falcon, kernel 6.12โ6.18, Quartus 26.1