Engineer writing a custom GMSL2 camera driver for Jetson — kernel source code and V4L2 subdev integration
gmsl2camera driverv4l2kernel driverjetson orinmax9296subdev

Writing a custom GMSL2 camera driver for Jetson: subdev and V4L2

Andres Campos ·

Writing a custom GMSL2 camera driver for Jetson goes beyond what the sensor driver documentation covers. The GMSL2 transport layer adds serializer and deserializer drivers to the subdev chain, virtual channel configuration, and link training sequences that need to complete before the sensor sees any I2C traffic. This post covers the full custom driver structure.

Key Insights

  • Custom GMSL2 sensor drivers for Jetson use the tegra-camera-platform framework — not raw V4L2 subdev ops
  • You almost never write SerDes drivers from scratch — the MAX9295A/MAX9296A drivers already exist in L4T
  • The sensor mode table in the DTS is as important as the driver code — wrong mode table means wrong MIPI timing
  • The minimum viable driver is: power on/off, one hard-coded mode, streaming start/stop
  • Test the SerDes chain first with an existing reference sensor before swapping in your custom sensor

What “custom GMSL2 driver” actually means

When teams say they need to write a custom GMSL2 driver, they usually mean one of three things:

  1. A new sensor driver for a camera sensor that NVIDIA does not have in L4T (non-reference IMX variant, custom ISP, OmniVision sensor not in NVIDIA’s supported list)
  2. A modified SerDes driver because their hardware uses a different register initialization sequence than NVIDIA’s reference design
  3. A combined driver for a vertically integrated camera module where sensor and serializer are a single unit

In most cases it is option 1. The MAX9295A and MAX9296A drivers that NVIDIA ships in L4T handle the SerDes side correctly for standard configurations. The custom work is the sensor driver — the code that programs the specific image sensor registers, manages the sensor power sequence, and describes the sensor’s operating modes.

The tegra-camera-platform sensor driver framework

Jetson sensor drivers do not implement raw V4L2 subdev ops. They implement tegra_cam_platform_sensor_ops which wraps V4L2 and adds Jetson-specific extensions.

The minimum required ops:

static struct tegra_cam_platform_sensor_ops sensor_ops = {
    .numfmts       = ARRAY_SIZE(sensor_fmts),
    .fmts          = sensor_fmts,
    .numframerates = ARRAY_SIZE(sensor_framerates),
    .framerates    = sensor_framerates,
    .s_power       = sensor_s_power,
    .s_stream      = sensor_s_stream,
    .g_frame_interval = sensor_g_frame_interval,
    .s_frame_interval = sensor_s_frame_interval,
    .set_mode      = sensor_set_mode,
    .g_sensor_mode_id = sensor_g_mode_id,
};

The framework calls set_mode before s_stream. Your set_mode implementation writes the sensor register table for the selected mode. Your s_stream implementation sends the streaming enable/disable command to the sensor.

The sensor mode table

The mode table is where MIPI timing is defined. It lives in the device tree, not in the driver code. The framework reads it during probe and makes it available through V4L2 controls.

sensor_modes {
    mode0 {    /* 1920x1080 @ 30fps, 4-lane MIPI */
        mclk_khz            = "37125";
        num_lanes           = "4";
        tegra_sinterface    = "serial_a";
        vc_id               = "0";
        discontinuous_clk   = "no";
        dpcm_enable         = "false";
        cil_settletime      = "0";
        active_w            = "1920";
        active_h            = "1080";
        mode_type           = "bayer";
        pixel_phase         = "rggb";
        csi_pixel_bit_depth = "12";
        readout_orientation = "0";
        line_length         = "2200";
        inherent_gain       = "1";
        mclk_multiplier     = "20.25";
        pix_clk_hz          = "750750000";
        min_gain_val        = "1";
        max_gain_val        = "16";
        min_hdr_ratio       = "1";
        max_hdr_ratio       = "64";
        min_framerate       = "1.462151";
        max_framerate       = "30";
        min_exp_time        = "13";
        max_exp_time        = "683709";
        embedded_metadata_height = "1";
    };
};

Getting the pix_clk_hz, line_length, and mclk_multiplier values wrong produces subtle failures — the sensor streams but at the wrong frame rate, or the MIPI timing is slightly off causing intermittent uncorr_err. Cross-reference these values against your sensor’s datasheet register tables for the specific operating mode.

The sensor driver structure

A minimal custom sensor driver for a GMSL2 camera:

static int sensor_s_power(struct v4l2_subdev *sd, int on)
{
    struct camera_common_data *s_data = to_camera_common_data(sd->dev);
    /* Toggle VDD, VDDIO, VANA rails via regulator API */
    /* Toggle XCLR (reset) GPIO */
    /* Wait for PLL lock time (check datasheet — typically 1-5ms) */
    return 0;
}

static int sensor_set_mode(struct v4l2_subdev *sd, u32 val)
{
    /* Write register table for mode 'val' */
    /* Tables are typically in a separate sensor_regs.h file */
    /* Each entry: {address, value} pairs in 16-bit or 8-bit format */
    return sensor_write_table(sd, sensor_mode_table[val]);
}

static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    if (enable)
        return sensor_write_reg(sd, SENSOR_STREAM_ENABLE_REG, 0x01);
    else
        return sensor_write_reg(sd, SENSOR_STREAM_ENABLE_REG, 0x00);
}

The register tables are typically long arrays of address/value pairs derived directly from the sensor vendor’s initialization script. For IMX sensors, Sony publishes these through their partner portal. For OmniVision sensors, they are in the OV design guide.

Testing before full implementation

Start with the minimum viable driver: power on, hard-code a single mode, send the streaming enable register. Do not worry about V4L2 controls (gain, exposure) until you have frames.

Test progression:

  1. Sensor probes (dmesg | grep sensor_name shows probe success)
  2. i2cdetect on the camera bus shows the sensor address
  3. v4l2-ctl --list-devices shows the camera entry
  4. nvarguscamerasrc sensor-id=0 ! fakesink exits without error
  5. nvarguscamerasrc sensor-id=0 ! nvvidconv ! xvimagesink produces visible frames

If step 4 passes but step 5 shows a green or purple frame, the pixel format or Bayer pattern in the mode table is wrong — not a driver bug.

For the supporting infrastructure these drivers rely on, see GMSL2 camera driver on Linux: V4L2, MAX9296 kernel driver, device tree. For debugging the GMSL2 chain before writing your sensor driver, see GMSL2 camera not working on Jetson: 5 failure modes.

NVIDIA’s sensor driver framework documentation is in the Jetson Linux Sensor Driver Programming Guide. Reference sensor drivers (IMX390, IMX219) are in the L4T kernel source at kernel/nvidia/drivers/media/i2c/.


NVIDIA Jetson Expert Support

Stuck on a Jetson bring-up?

We've debugged this failure mode before. BSP, device tree, camera pipelines, OTA — most blockers clear in the first session. No long retainers. No guessing.

Frequently Asked Questions

What kernel framework should I use to write a GMSL2 camera driver for Jetson?

Use the tegra-camera-platform sensor driver framework, which extends the Linux V4L2 subdev API with Jetson-specific extensions for sensor modes, NVCSI configuration, and Argus integration. The framework is in drivers/media/platform/tegra/camera/ in the L4T kernel source. Your sensor driver implements the tegra_cam_platform_sensor_ops ops table rather than raw V4L2 subdev ops.

Do I need to write drivers for both the sensor and the MAX9295A/MAX9296A?

Usually not from scratch. NVIDIA provides MAX9295A and MAX9296A drivers in the L4T kernel source that handle SerDes initialization, link lock, and I2C tunnel management. Your custom work is typically a new sensor driver (for your specific IMX, OV, or custom sensor) that sits on top of the existing SerDes drivers. You modify the SerDes drivers only if your hardware uses non-standard register programming.

What is the minimum a GMSL2 sensor driver must implement?

The tegra-camera-platform framework requires: s_power (power on/off sequence), s_stream (start/stop streaming), set_mode (load sensor register table for a given mode), g_frame_interval and s_frame_interval, and the sensor_mode table in the DTS. The mode table defines resolution, frame rate, MIPI lanes, link frequency, and pixel format for each supported operating mode.

How do I handle the GMSL2 I2C address reassignment in a custom driver?

The MAX9296A deserializer driver handles address reassignment through the nvidia,gmsl-dser-device DTS property and the gmsl_dser_setup() function. Your sensor driver calls into the deserializer driver via the camera_common_data infrastructure. You do not implement address reassignment in the sensor driver — the SerDes driver owns that logic.

How do I test a custom GMSL2 sensor driver before writing the full mode table?

Write a minimal driver that only implements s_power and a single mode in s_stream. Hard-code the link frequency and lane count to match your sensor's datasheet default mode. If you can get one frame of any kind (even with wrong format or resolution) through the pipeline, the SerDes chain is working and you can iterate on the mode table and format configuration.

Andrés Campos, Co-Founder & CTO at ProventusNova

Written by

Andrés Campos

Co-Founder & CTO · ProventusNova

8 years deep in embedded systems — from underwater ROVs to edge AI. Andrés leads every technical delivery personally.

Connect on LinkedIn