· · 7 min read

Adding HDMI support to the SG2000

With the working SD image preserved, trace an irreplaceable board's display path through boot files, schematics, and Linux drivers. One reversible device-tree change enables the control bus and makes the HDMI bridge's identity registers answer.

Pin down what “not working” means

My board is a small NixOS computer built around the SG2000 SoC (system on a chip). It was made as a conference badge, and I won mine in the organizers’ contest, so there is no second one to fall back on. Its makers said HDMI wasn’t working yet. The shipped image was configured to start a graphical login, but Linux created no display device for it.

I set out to get the display hardware working under Linux, one component at a time. This process is called bring-up.

The SG2000 is part of a Milk-V Duo Module 01, a compute module containing the processor and built-in storage. The organizers removed these modules from their larger evaluation boards on a hot plate and hand-soldered them onto the badges.

This SoC was never designed to communicate over HDMI. The display path runs from the SoC over MIPI DSI, a serial link carrying pixel data, to an LT8912B bridge chip. The bridge converts that signal from DSI to HDMI.

The first useful milestone was to get Linux to read the bridge’s identity registers, the locations inside the chip that identify it. That would establish a way to talk to a real component in the display path, with a result I could check directly.

Preserve the working state before changing it

I started with a read-only pass: nothing written, flashed or erased on the board or its SD card. Before the card’s first write, I saved a compressed image of the whole card to another disk and recorded its SHA-256 hash. The board’s Nix configuration could rebuild a card, but the image preserved the exact state that had already booted.

That backup covered only the SD card. The compute module also had built-in storage. The running kernel couldn’t even initialize it, so I had no image to restore it from. I kept a standing rule never to flash it.

After that first pass, the original card and a second one became working media. I saved a fresh image after each major breakthrough, preserving the last state that worked as the experiments continued.

Read the board’s own records before probing

The SD card first told me what the shipped software targeted. Its device-tree files describe the hardware to Linux, and their filenames pointed to the Duo S. Milk-V documents that this compute module can use Duo S firmware.

The SG2000 can run Linux on either its ARM core or its main RISC-V core. Comparing the default boot firmware with the supplied builds showed it was byte-identical to the ARM build. That identified the default processor choice without needing a console.

The design repository showed how the HDMI bridge was connected. DSI carries the pixels, but the bridge has a separate two-wire bus, I2C, for configuration and register reads. Getting an answer over I2C would let me check communication with the chip before tackling the software that sends it a picture.

Annotated board photograph identifying the HDMI output at upper left, the LT8912B HDMI bridge at lower left, and the shielded compute module containing the SG2000 at center.
The display corner: the HDMI socket, the LT8912B bridge and the shielded compute module holding the SoC. The first test would read the bridge's identity over its I2C control bus.

The design files placed the bridge on the SoC’s I2C2 controller, at addresses 0x48 to 0x4A. That controller wasn’t enabled in the shipped device tree. This gave me a specific gap to close: Linux needed that bus enabled and its pins assigned to I2C before it could reach the bridge’s registers.

The bus wiring also informed its speed. The design used 10 kΩ pull-up resistors, which bring the signal lines back high after a device pulls them low. Those lines take time to rise, and a faster bus leaves less time for that transition. I chose 100 kHz rather than 400 kHz for the first test.

The repository’s history also showed that this board revision fixed a known fault in the HDMI wiring, so software was the likelier blocker. The register reads would be the first direct evidence that the control connection worked.

Size the missing piece before choosing a route

Mainline is the Linux kernel’s shared codebase, which hardware vendors can build on and modify for their boards. It already had a driver for the HDMI bridge. The shipped kernel built it as a module, but the device tree had no entry describing the bridge. Adding that entry alone wouldn’t complete the display path: the driver attaches to a DSI host, the SoC’s end of the pixel link. Mainline lacked drivers for this SoC’s host and the display pipe that feeds it frames from memory.

The SoC’s DSI host wasn’t the common DesignWare design already supported by mainline, so that driver couldn’t fill the gap. Porting the vendor stack or writing a new driver would both require configuring the D-PHY, the circuitry that drives electrical signals onto the DSI wires. That setup was difficult either way.

The vendor’s Linux 5.10 display stack used fbdev, an older interface built around a memory buffer called a framebuffer. It never registered a DSI host for the existing bridge driver to attach to. I eventually chose to write the missing driver for DRM, Linux’s display subsystem, using the vendor code as a hardware reference. That would let me reuse mainline’s bridge driver.

Either route needed access to the bridge’s control registers. The first test could establish that access independently of the larger driver decision.

Make the first change one you can prove

The first display change enabled I2C2 at 100 kHz and assigned its pins to I2C. I replaced the compiled device tree, the DTB, on the card’s boot partition and kept the old file beside it. Restoring the previous configuration meant copying that file back. The kernel stayed as it was.

The new DTB came from a slightly newer kernel tree, so I compared it with the card’s DTB before putting it on the board. The offline diff showed only the intended change and renumbered phandles, the IDs device-tree nodes use to refer to each other. That check mattered because replacing the whole hardware description could otherwise carry unrelated changes into the test.

After booting with the new DTB, I found the adapter by its controller address in sysfs, Linux’s view of devices and drivers. The hardware controller called I2C2 appeared as Linux bus 0. Without a device-tree alias fixing its number, Linux numbers adapters in registration order, and this was the only one enabled.

The acceptance test was a live answer from the bridge’s chip-ID registers. Using i2c-tools, command-line utilities for accessing the bus, I read two registers at the bridge’s address 0x48:

i2cget -y 0 0x48 0x00 -> 0x12
i2cget -y 0 0x48 0x01 -> 0xb2     # live, non-0x00/0xFF => real silicon responding

From the saved record of the reads, unedited. Each arrow points to the value the read returned.

Those replies established that Linux could reach the bridge over its control bus and read its identity. They verified the bus setup and a responding chip; sending pixels over DSI still required further work. The device-tree change went to the board’s public repository as a small pull request of its own.

When recovery is uncertain, gather evidence before changing anything, preserve the working state, and choose a first change whose result you can directly observe.

More in Linux drivers and bring-up →

Share