Custom GMSL2 camera driver for Jetson: V4L2 subdev
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:
- 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)
- A modified SerDes driver because their hardware uses a different register initialization sequence than NVIDIA’s reference design
- 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:
- Sensor probes (
dmesg | grep sensor_nameshows probe success) i2cdetecton the camera bus shows the sensor addressv4l2-ctl --list-devicesshows the camera entrynvarguscamerasrc sensor-id=0 ! fakesinkexits without errornvarguscamerasrc sensor-id=0 ! nvvidconv ! xvimagesinkproduces 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.
Adding V4L2 controls for exposure and gain
Argus uses V4L2 controls to read and write exposure and gain during auto-exposure operation. Without these controls, Argus falls back to fixed values and AE does not converge.
static int sensor_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct sensor_priv *priv = container_of(ctrl->handler,
struct sensor_priv, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_EXPOSURE:
/* Convert microseconds to sensor line count */
/* line_time_us = line_length / pix_clk_hz * 1e6 */
return sensor_set_exposure_lines(priv->client,
ctrl->val / priv->line_time_us);
case V4L2_CID_ANALOGUE_GAIN:
/* Convert V4L2 gain value to sensor's dB or linear register format */
return sensor_set_gain_reg(priv->client, ctrl->val);
}
return -EINVAL;
}
static int sensor_ctrls_init(struct sensor_priv *priv)
{
struct v4l2_ctrl_handler *hdl = &priv->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 4);
priv->exposure = v4l2_ctrl_new_std(hdl, &sensor_ctrl_ops,
V4L2_CID_EXPOSURE, SENSOR_MIN_EXP_US, SENSOR_MAX_EXP_US,
1, SENSOR_DEFAULT_EXP_US);
priv->gain = v4l2_ctrl_new_std(hdl, &sensor_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN, SENSOR_MIN_GAIN, SENSOR_MAX_GAIN,
1, SENSOR_DEFAULT_GAIN);
return hdl->error;
}
Get SENSOR_MIN_EXP_US and SENSOR_MAX_EXP_US from the datasheet’s exposure register description. The min is typically determined by sensor readout constraints (cannot be less than one line time); the max is one frame period minus a few lines of blanking.
Troubleshooting custom GMSL2 driver failures
Sensor doesn’t appear on i2cdetect after SerDes probe
The GMSL2 link is not locked, or the address reassignment failed. Check dmesg | grep -i max929, the SerDes driver logs address assignment steps. If the deserializer probe succeeded but the serializer never appeared, read MAX9296A register 0x0013 (bit 3 = link locked). Link lock failure means a hardware problem (power, cable, or GPIO).
Driver probes but v4l2-ctl --list-devices shows no camera
The tegra-camera-platform module entry is missing or the proc-device-tree path in the DTS is wrong. Verify by checking /proc/device-tree/i2c@*/max9296*/ to confirm the sensor node exists at the exact path your DTS module definition declares.
Streaming starts but frames are all black
The sensor’s streaming enable register wrote correctly, but the analog section didn’t initialize. Check that your register table includes the full sensor startup sequence, not just the streaming enable. Some sensors require a specific sequence: standby → load mode registers → enable analog path → enable digital path → enable output. Missing the analog enable step produces black frames.
Green or purple frames
Wrong Bayer pattern. The pixel_phase in the mode table (rggb, bggr, grbg, gbrg) doesn’t match the sensor’s actual Bayer output for this mode. Check the sensor datasheet for the pixel array layout. Also check readout_orientation, a 180° rotation also inverts the Bayer phase.
Frames present but uncorr_err in nvcsi logs
MIPI data rate mismatch. Recalculate pix_clk_hz from the sensor’s PLL configuration registers for this mode: pix_clk_hz = line_length × (active_h + vblank) × fps. If link_freq_hz is set separately, it must equal half the physical MIPI bit rate per lane: link_freq_hz = (pix_clk_hz × bit_depth) / (2 × num_lanes).
For custom GMSL2 driver development on unusual sensors or non-standard serializer configurations, the GMSL camera driver development service covers scoped engagements. 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 bring-up on Jetson Orin.
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/.
Relevant Services
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.
My GMSL2 sensor driver probes but streaming produces no frames. What should I check first?
Check in this order: 1) The GMSL2 link is still locked after driver probe (i2cget 0x48 0x13, bit 3 = 1). 2) The sensor streaming enable register write succeeded (add a pr_debug after the write and check dmesg). 3) The mode table pix_clk_hz and link_freq_hz match the sensor's actual PLL output for the configured register table. 4) Check dmesg for nvcsi uncorr_err, if present, the MIPI timing in the mode table is wrong.
What is the difference between the sensor driver and the SerDes driver in a GMSL2 system?
The SerDes driver (MAX9295A/MAX9296A) manages the physical GMSL2 link: link training, I2C tunnel, address reassignment, and MIPI output configuration. The sensor driver manages the image sensor itself: power sequence, operating mode register tables, streaming, and V4L2 controls. The two drivers communicate through the camera_common infrastructure, your sensor driver calls gmsl_dser_setup() from the SerDes driver to trigger serializer configuration, but does not directly write SerDes registers.
Can I use the same sensor driver source for both direct CSI and GMSL2 camera configurations?
Yes, with conditional compilation or runtime detection. The tegra-camera-platform sensor ops are identical for both configurations, the sensor driver does not need to know it is behind a GMSL2 SerDes vs. directly connected. The difference is in the DTS: the GMSL2 configuration adds the serializer and deserializer nodes as parents, and the sensor node gets the nvidia,gmsl-dser-device property. If you structure the driver to configure only what the DTS provides, the same .c file works for both.
What happens if my sensor's initialization register sequence is incomplete or wrong?
The sensor may start outputting garbage frames, a black frame, or no frames at all depending on which registers are missing. Common symptoms: green or purple frames (wrong pixel format or Bayer pattern register), correct resolution but wrong frame rate (PLL registers incomplete), blank frames only (streaming enable register correct but sensor analog section not initialized). Compare your register table against the vendor's reference initialization script, any deviation will cause one of these symptoms.
Written by
Andrés CamposCo-Founder & CTO · ProventusNova
8 years deep in embedded systems, from underwater ROVs to edge AI. Andrés leads every technical delivery personally.
Connect on LinkedInRelated Articles
How to write a V4L2 MIPI camera driver for Jetson
Step-by-step guide to writing a custom V4L2 MIPI camera driver for NVIDIA Jetson using the tegra-camera-platform framework. Sensor ops, mode tables, DTS.
GMSL2 camera driver on Linux: V4L2, MAX9296 kernel driver, device tree
How to set up a GMSL2 camera driver on Linux for Jetson. MAX9296A deserializer kernel driver, V4L2 subdev chain, device tree structure, and common probe.
Best Companies for Custom Camera Driver Development on NVIDIA Jetson
Comparing the top options for V4L2, MIPI CSI, and GMSL2 camera driver development on NVIDIA Jetson — specialists, hardware vendors, and freelancers.
GMSL2 camera bring-up on Jetson Orin: MAX9295/MAX9296 setup
Step-by-step GMSL2 camera bring-up on Jetson Orin with MAX9295A and MAX9296A. Link lock, I2C tunnel, device tree, and first frame verification.