Yocto Project Configuration
The most modern and NXP-recommended method for developing with the LS1028A is the Layerscape Linux Distribution POC (LLDP) based on Yocto. Unlike the LSDK (which uses Flexbuild), LLDP uses Yocto/Bitbake and is the long-term supported path.
1 Host Requirements
Requirement
Details
Operating System
Ubuntu 20.04 LTS (Focal) — official recommendation
RAM
Minimum 8 GB (16 GB+ recommended)
Disk Space
Minimum 100 GB free
CPU
Minimum 4 cores (more cores = faster builds)
Tools
git, repo, python3, wget, curl, build-essential
Install dependencies on Ubuntu:
sudo apt-get update && sudo apt-get install -y gawk wget git diffstat unzip texinfo gcc-multilib \
build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping \
python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev pylint xterm rsync curl locales zstd liblz4-tool \
repo ca-certificates
# Configure git (required by repo)
git config --global user.name "Your Name"
git config --global user.email "
[email protected]"
2 Download the LLDP Repository (Yocto for LS1028A)
# Create working directory
mkdir ~/lldp-ls1028a && cd ~/lldp-ls1028a
# Initialize repo with the LLDP manifest (Kirkstone, kernel 5.15)
repo init -u https://github.com/nxp-qoriq/yocto-sdk.git \
-b kirkstone \
-m ls-5.15.71-2.2.0_distro.xml
# Sync all repositories (may take 30-60 minutes)
repo sync
Note: For the latest version (LLDP L6.1.1, kernel 6.1), check the updated manifest at: https://github.com/nxp-qoriq/yocto-sdk
3 Set Up the Build Environment for LS1028A
# Initialize Yocto environment for the LS1028A
# (run from the lldp-ls1028a/ directory)
DISTRO=fsl-qoriq-distro MACHINE=ls1028ardb source distro-setup-env
# This automatically creates and enters the build directory
# You are now in: ~/lldp-ls1028a/build_ls1028ardb/
4 local.conf File — GPU Configuration
The conf/local.conf file inside the build directory controls compilation options. For the LS1028A with GPU, verify or add:
# Edit the configuration file
nano conf/local.conf
Key parameters for GPU and desktop:
# Target machine
MACHINE = "ls1028ardb"
# Distribution with GPU and Wayland support
DISTRO = "fsl-qoriq-distro"
# Enable display and GPU features
DISTRO_FEATURES:append = " wayland opengl"
# GPU driver: use Etnaviv (open-source) for LS1028A
# DO NOT use imx-gpu-viv (i.MX only, requires ARCH_MXC)
PREFERRED_PROVIDER_virtual/libgl = "mesa"
PREFERRED_PROVIDER_virtual/libgles1 = "mesa"
PREFERRED_PROVIDER_virtual/libgles2 = "mesa"
PREFERRED_PROVIDER_virtual/egl = "mesa"
# Enable OpenCL support via Vivante GPU
IMAGE_INSTALL:append = " clinfo"
# Accept NXP proprietary licenses (required for GPU firmware)
LICENSE_FLAGS_ACCEPTED = "nxp-proprietary"
# Parallel build threads (adjust to your host PC)
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j8"
# (Optional) Use ccache to speed up recompilations
INHERIT += "ccache"
5 Required Yocto Layers (bblayers.conf)
Verify that conf/bblayers.conf includes these layers:
cat conf/bblayers.conf
It must contain at least:
BBLAYERS ?= " \
${BSPDIR}/sources/poky/meta \
${BSPDIR}/sources/poky/meta-poky \
${BSPDIR}/sources/meta-openembedded/meta-oe \
${BSPDIR}/sources/meta-openembedded/meta-multimedia \
${BSPDIR}/sources/meta-openembedded/meta-python \
${BSPDIR}/sources/meta-openembedded/meta-networking \
${BSPDIR}/sources/meta-freescale \
${BSPDIR}/sources/meta-qoriq \
${BSPDIR}/sources/meta-nxp-desktop \
"
The meta-nxp-desktop layer is what provides GPU support for the LS1028A with the ls-image-desktop image.
6 Build the Image with GPU Support
# Recommended: Desktop image with full GPU support (LS1028A only)
# Includes: GNOME desktop, Weston/Wayland, Vivante GPU drivers, OpenCL
bitbake ls-image-desktop
# Alternative: download all packages first before building
# (useful for catching network errors before the long build)
bitbake ls-image-desktop --runall fetch
bitbake ls-image-desktop
# Minimal: main image without desktop (no GPU by default)
bitbake ls-image-main
# Lite: minimal image
bitbake ls-image-lite
Estimated build time: Between 4 and 8 hours on the first build (depending on host hardware). Incremental builds are much faster.
7 Install the Image to the SD Card
After compilation, output files are located in: tmp/deploy/images/ls1028ardb/
# Identify the SD card (verify with lsblk)
lsblk
# Install image using flex-installer (included in the SDK)
flex-installer \
-b tmp/deploy/images/ls1028ardb/boot_ls1028ardb.tgz \
-f tmp/deploy/images/ls1028ardb/firmware_ls1028ardb_sdboot.img \
-r tmp/deploy/images/ls1028ardb/ls-image-desktop-ls1028ardb.tar.zst \
-d /dev/sdX # replace with your SD device
Practical Example: OpenCL Application on LS1028A
This example shows how to compile and run a program that uses the GC7000UL GPU to add two vectors with OpenCL.
8.1 Source Code: vector_add.cl (OpenCL Kernel)
Create the kernel file on the LS1028A board:
# On the LS1028A (via serial or SSH)
cat > /home/root/vector_add.cl << 'EOF'
__kernel void vector_add(
__global const float* a,
__global const float* b,
__global float* c,
const int n)
{
int gid = get_global_id(0);
if (gid < n) {
c[gid] = a[gid] + b[gid];
}
}
8.2 Source Code: vector_add.c (OpenCL Host)
cat > /home/root/vector_add.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <CL/cl.h>
#define VECTOR_SIZE 1024
int main() {
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue queue;
cl_program program;
cl_kernel kernel;
cl_mem buf_a, buf_b, buf_c;
cl_int err;
// 1. Get Vivante GPU platform and device
err = clGetPlatformIDs(1, &platform, NULL);
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
// Print device name
char device_name[128];
clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_name), device_name, NULL);
printf("GPU detected: %s\n", device_name);
// 2. Create context and command queue
context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
queue = clCreateCommandQueue(context, device, 0, &err);
// 3. Read kernel source from file
FILE* f = fopen("vector_add.cl", "r");
fseek(f, 0, SEEK_END);
size_t src_size = ftell(f);
rewind(f);
char* src=(char*)malloc(src_size + 1);
fread(src, 1, src_size, f);
src[src_size] = '\0';
fclose(f);
// 4. Compile OpenCL program
program = clCreateProgramWithSource(context, 1, (const char**)&src, &src_size, &err);
err = clBuildProgram(program, 1, &device, NULL, NULL, NULL);
if (err != CL_SUCCESS) {
char log[2048];
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
sizeof(log), log, NULL);
printf("Compilation error:\n%s\n", log);
return 1;
}
kernel = clCreateKernel(program, "vector_add", &err);
// 5. Prepare input data
float* h_a = (float*)malloc(VECTOR_SIZE * sizeof(float));
float* h_b = (float*)malloc(VECTOR_SIZE * sizeof(float));
float* h_c = (float*)malloc(VECTOR_SIZE * sizeof(float));
for (int i = 0; i < VECTOR_SIZE; i++) {
h_a[i] = (float)i;
h_b[i] = (float)(VECTOR_SIZE - i);
}
// 6. Create GPU buffers
buf_a = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
VECTOR_SIZE * sizeof(float), h_a, &err);
buf_b = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
VECTOR_SIZE * sizeof(float), h_b, &err);
buf_c = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &err);
// 7. Set kernel arguments and execute on GPU
int n = VECTOR_SIZE;
clSetKernelArg(kernel, 0, sizeof(cl_mem), &buf_a);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &buf_b);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &buf_c);
clSetKernelArg(kernel, 3, sizeof(int), &n);
size_t global_size = VECTOR_SIZE;
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
&global_size, NULL, 0, NULL, NULL);
clFinish(queue);
// 8. Read result
clEnqueueReadBuffer(queue, buf_c, CL_TRUE, 0,
VECTOR_SIZE * sizeof(float), h_c, 0, NULL, NULL);
// 9. Verify result (each element should equal VECTOR_SIZE = 1024)
int ok = 1;
for (int i = 0; i < VECTOR_SIZE; i++) {
if (h_c[i] != (float)VECTOR_SIZE) { ok = 0; break; }
}
printf("Result: %s\n", ok ? "CORRECT - GPU works!" : "CALCULATION ERROR");
printf("Example: a[0]=%.0f + b[0]=%.0f = c[0]=%.0f\n",
h_a[0], h_b[0], h_c[0]);
// Free resources
clReleaseMemObject(buf_a); clReleaseMemObject(buf_b); clReleaseMemObject(buf_c);
clReleaseKernel(kernel); clReleaseProgram(program);
clReleaseCommandQueue(queue); clReleaseContext(context);
free(h_a); free(h_b); free(h_c); free(src);
return 0;
}
8.3 Compile and Run on the LS1028A
On the LS1028A board (connected via serial or SSH):
# Install build tools and OpenCL headers
apt-get install -y gcc clinfo ocl-icd-libopencl1 opencl-headers
# Compile
gcc -o vector_add vector_add.c -lOpenCL -I/usr/include
# Run
./vector_add
Expected output:
GPU detected: Vivante OpenCL Device GC7000UL.6202.0000
Result: CORRECT - GPU works!
Example: a[0]=0 + b[0]=1024 = c[0]=1024
12.4 Yocto Recipe to Include the Example in the Image
To include the example directly in the Yocto image, create a recipe in your custom layer:
mkdir -p ~/lldp-ls1028a/sources/meta-my-layer/recipes-examples/opencl-vector/files
cp vector_add.c vector_add.cl \
~/lldp-ls1028a/sources/meta-my-layer/recipes-examples/opencl-vector/files/
Recipe file opencl-vector_1.0.bb:
SUMMARY = "OpenCL vector addition example for LS1028A GPU"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://vector_add.c \
file://vector_add.cl"
DEPENDS = "virtual/opencl-icd opencl-headers"
S = "${WORKDIR}"
do_compile() {
${CC} ${CFLAGS} -o vector_add vector_add.c -lOpenCL ${LDFLAGS}
}
do_install() {
install -d ${D}${bindir}
install -m 0755 vector_add ${D}${bindir}/
install -d ${D}/home/root
install -m 0644 vector_add.cl ${D}/home/root/
}
FILES:${PN} += "/home/root/vector_add.cl"
Add to local.conf and rebuild:
IMAGE_INSTALL:append = " opencl-vector"
bitbake ls-image-desktop
Practical Example: OpenGL ES Rendering with Wayland/Weston
This example shows how to render an animated triangle using OpenGL ES 2.0 with EGL on the Weston (Wayland) compositor — the standard "Hello World" of embedded graphics on the GC7000UL GPU.
9.1 Graphics Stack Diagram
C Application
↓
OpenGL ES 2.0 (libGLESv2.so — Vivante GC7000UL)
↓
EGL 1.4 (libEGL.so — interface between GLES and Wayland)
↓
Wayland Client (libwayland-client, libwayland-egl)
↓
Weston Compositor (DRM/KMS + Mali-DP500)
↓
DisplayPort → 4K Monitor
9.2 Install Dependencies
On the LS1028A (with ls-image-desktop):
apt-get install -y libgles2-mesa-dev libegl1-mesa-dev libwayland-dev libwayland-egl-backend-dev gcc pkg-config
9.3 Source Code: triangle_gles.c
cat > /home/root/triangle_gles.c << 'EOF'
/**
* OpenGL ES 2.0 + EGL + Wayland example
* Renders a colored spinning triangle on the Vivante GC7000UL GPU of the LS1028A
* Compile: gcc -o triangle_gles triangle_gles.c -lwayland-client -lwayland-egl -lEGL -lGLESv2 -lm
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#define WIDTH 800
#define HEIGHT 600
static struct wl_display *wl_display = NULL;
static struct wl_compositor *wl_compositor = NULL;
static struct wl_shell *wl_shell = NULL;
static struct wl_surface *wl_surface = NULL;
static struct wl_shell_surface *shell_surface = NULL;
static struct wl_egl_window *egl_window = NULL;
static EGLDisplay egl_display;
static EGLContext egl_context;
static EGLSurface egl_surface;
static const char *vertex_shader_src=
"attribute vec2 a_position; \n"
"attribute vec3 a_color; \n"
"varying vec3 v_color; \n"
"uniform float u_angle; \n"
"void main() { \n"
" float c = cos(u_angle); \n"
" float s = sin(u_angle); \n"
" vec2 rot = vec2( \n"
" a_position.x*c - a_position.y*s,\n"
" a_position.x*s + a_position.y*c \n"
" ); \n"
" gl_Position = vec4(rot, 0.0, 1.0);\n"
" v_color = a_color; \n"
"} \n";
static const char *fragment_shader_src=
"precision mediump float; \n"
"varying vec3 v_color; \n"
"void main() { \n"
" gl_FragColor = vec4(v_color, 1.0);\n"
"} \n";
/* Vertices: position (x,y) + color (r,g,b) */
static const float vertices[] = {
0.0f, 0.8f, 1.0f, 0.0f, 0.0f, /* Top - Red */
-0.7f, -0.5f, 0.0f, 1.0f, 0.0f, /* Left - Green */
0.7f, -0.5f, 0.0f, 0.0f, 1.0f, /* Right - Blue */
};
static void registry_global(void *data, struct wl_registry *reg,
uint32_t name, const char *iface, uint32_t ver) {
if (strcmp(iface, "wl_compositor") == 0)
wl_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, 1);
else if (strcmp(iface, "wl_shell") == 0)
wl_shell = wl_registry_bind(reg, name, &wl_shell_interface, 1);
}
static void registry_global_remove(void *d, struct wl_registry *r, uint32_t n) {}
static const struct wl_registry_listener registry_listener = {
registry_global, registry_global_remove
};
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512]; glGetShaderInfoLog(shader, 512, NULL, log);
printf("Shader error: %s\n", log); exit(1);
}
return shader;
}
int main() {
wl_display = wl_display_connect(NULL);
if (!wl_display) { printf("Error: could not connect to Wayland\n"); return 1; }
struct wl_registry *registry = wl_display_get_registry(wl_display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_dispatch(wl_display);
wl_display_roundtrip(wl_display);
wl_surface = wl_compositor_create_surface(wl_compositor);
shell_surface = wl_shell_get_shell_surface(wl_shell, wl_surface);
wl_shell_surface_set_toplevel(shell_surface);
egl_display = eglGetDisplay((EGLNativeDisplayType)wl_display);
eglInitialize(egl_display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 16, EGL_NONE
};
EGLConfig egl_config; EGLint num_configs;
eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_configs);
EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, ctx_attribs);
egl_window = wl_egl_window_create(wl_surface, WIDTH, HEIGHT);
egl_surface = eglCreateWindowSurface(egl_display, egl_config,
(EGLNativeWindowType)egl_window, NULL);
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
printf("GPU: %s\n", glGetString(GL_RENDERER));
printf("OpenGL ES Version: %s\n", glGetString(GL_VERSION));
GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader_src);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_src);
GLuint program = glCreateProgram();
glAttachShader(program, vs); glAttachShader(program, fs);
glLinkProgram(program); glUseProgram(program);
GLint pos_loc = glGetAttribLocation(program, "a_position");
GLint color_loc = glGetAttribLocation(program, "a_color");
GLint angle_loc = glGetUniformLocation(program, "u_angle");
glViewport(0, 0, WIDTH, HEIGHT);
float angle = 0.0f;
int frames = 0;
printf("Rendering spinning triangle (Ctrl+C to exit)...\n");
while (1) {
wl_display_dispatch_pending(wl_display);
glClearColor(0.1f, 0.1f, 0.15f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1f(angle_loc, angle);
glEnableVertexAttribArray(pos_loc);
glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), vertices);
glEnableVertexAttribArray(color_loc);
glVertexAttribPointer(color_loc, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), vertices + 2);
glDrawArrays(GL_TRIANGLES, 0, 3);
eglSwapBuffers(egl_display, egl_surface);
angle += 0.02f;
if (angle > 6.2832f) angle -= 6.2832f;
frames++;
if (frames % 300 == 0)
printf("Frame %d — angle: %.2f rad\n", frames, angle);
}
eglDestroyContext(egl_display, egl_context);
eglDestroySurface(egl_display, egl_surface);
eglTerminate(egl_display);
wl_display_disconnect(wl_display);
return 0;
}
9.4 Compile and Run
# Compile
gcc -o triangle_gles triangle_gles.c \
-lwayland-client -lwayland-egl \
-lEGL -lGLESv2 -lm
# Make sure Weston is running and set Wayland environment
export XDG_RUNTIME_DIR=/run/user/0
export WAYLAND_DISPLAY=wayland-0
# Run
./triangle_gles
Expected terminal output:
GPU: Vivante GC7000UL
OpenGL ES Version: OpenGL ES 3.1 V6.4.3.p4.398061
Rendering spinning triangle (Ctrl+C to exit)...
Frame 300 — angle: 6.00 rad
Frame 600 — angle: 5.68 rad
A RGB triangle spinning on a dark background will appear on screen, rendered by the GC7000UL GPU.
Bio_TICFSL_0-1789407894776.png
9.5 Yocto Recipe
SUMMARY = "OpenGL ES 2.0 spinning triangle example — LS1028A"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://triangle_gles.c"
DEPENDS = "virtual/libgles2 virtual/egl wayland"
S = "${WORKDIR}"
do_compile() {
${CC} ${CFLAGS} -o triangle_gles triangle_gles.c \
-lwayland-client -lwayland-egl -lEGL -lGLESv2 -lm ${LDFLAGS}
}
do_install() {
install -d ${D}${bindir}
install -m 0755 triangle_gles ${D}${bindir}/
}
9.6 Yocto Recipe for OpenCV with GPU
Add to local.conf:
IMAGE_INSTALL:append = " opencv python3-opencv"
PACKAGECONFIG:append:pn-opencv = " opencl"
bitbake ls-image-desktop
Regards