A sensor that starts streaming CSI the moment it powers on breaks the assumptions behind most camera drivers. There is no I2C address to read, no register to write, no ID to check, and the frame rate can drift with inputs the kernel never sees. The kernel still needs a driver, though: the receiver and the video node are built entirely from what your code reports. This guide is for engineers wiring a fixed, free-running CSI source — a strapped image sensor, or a radar that emits raw ADC data in CSI clothing — into a Linux capture stack. You will decide which operations to implement and which to stub, describe the fixed geometry in the device tree, deliver RAW8 bytes unprocessed, and prove the stream with a byte-count test before anyone ships it.
What the kernel needs from a source it cannot control
The decision up front: probe and remove are real code, every callback that exists to configure a camera — power on and off, start and stop streaming, frame-rate setting — is a stub that returns 0, and the geometry lives in your device tree and format operations. That is the entire job, and it is smaller than a camera driver because the camera never changes state.
The driver itself is not optional. Camera device registration runs once, when the kernel initializes the pipeline or your module is inserted: probe registers the subdevice, links it to the receiver, and only then does the video node appear. The kernel's subdevice documentation recommends embedding the v4l2_subdev structure in your driver's per-device state structure, and that advice holds even when the device has no control bus. If you are working from your SoC vendor's sensor-porting example, the lifecycle is identical — the controllable-sensor example simply has real function bodies where yours will be empty.
Stubs are the honest implementation here, not a shortcut. A source that streams from power-on has nothing to power on and nothing to start. What you give up is error reporting, and that has a consequence worth designing around.
Where the lane count, format and geometry come from
V4L2 never asks your hardware. Two sets of values define the CSI signal, and both are yours to get right.
The device tree endpoint carries the physical bus description: the number of data lanes, the clocking, and the link to the receiver. Probe reads it with the framework's standard property helpers, so you do not hand-write a parser, and no runtime tool can change lanes after boot.
The driver's format operations report what the link carries: the pixel format, the frame size and the frame interval. The receiver sizes its line buffers and its DMA from exactly these values, so they must match the bytes on the wire rather than a data sheet's maximum.
Query tools read the driver, not the sensor. When v4l2-ctl asks for the format, it shows whatever your operations return, and the whole stack believes it. If your values and the wire disagree, captures come out short or scrambled, and nothing tells you which side is wrong. Keep the kernel's V4L2 sub-device API reference and its camera sensor driver guide open while you work; both describe sources connected to a main bridge, which is your topology even when the source is a radar.
Non-image data follows the same rules. V4L2 moves bytes and does not care that yours are ADC magnitudes. Report the width as the number of sampling loops per frame and the height as lines per frame, and let the application treat the capture as a matrix. If a sibling board does have a control bus and still fails, the causes live elsewhere: the MIPI sensor detected but streaming fails checklist covers power and clocks, and sensor detected but frames time out separates I2C failures from MIPI ones.
Op by op: what is real and what is a stub
| Driver operation | Real or stub | What it does in this design |
|---|---|---|
| probe | Real | Reads the endpoint, registers the subdevice and its pads, links to the receiver; no video node without it |
| remove | Real | Unregisters the subdevice so the module unloads cleanly |
| power on / power off | Stub returning 0 | The source is always streaming; there is nothing to switch |
| start / stop streaming | Stub returning 0 | The camera never starts or stops; the stub keeps the pipeline state machine moving |
| frame-rate setting | Stub returning 0 | The rate comes from the source's own inputs, not from the kernel |
| device-tree parsing | Framework helpers | Standard property helpers read the endpoint; no hand-written parser |
| format and frame-size operations | Real, fixed values | The geometry lives here: the receiver sizes buffers and DMA from these values |
probe and remove do the bookkeeping that makes the node appear and disappear; the framework supplies the file-operation plumbing such as open. The stubs matter because the receiver still runs its normal start-of-stream sequence: it begins listening, and since your source never stops, the first complete frame is already on its way when the buffers open.
Worked example: bandwidth for a 512 × 256 RAW8 radar frame at 30 fps
Inputs and where each comes from:
- width = 512 samples per line — example geometry; in the ADC case the width is the number of sampling loops per frame, and step 1 below measures yours
- height = 256 lines per frame — example value
- 1 byte per pixel — RAW8 carries one byte per pixel, the format the source emits
- 30 fps — example rate for the fixed source
- 80 Mbit/s to 2.5 Gbit/s per data lane — the range the MIPI D-PHY specification versions define, 80 Mbit/s floor and 2.5 Gbit/s ceiling
The rule: multiply width × height × bytes per pixel × frame rate, then by 8 for bits, and size the lane so the payload stays under about 80 percent of the lane's rate. The margin absorbs CSI-2 framing — about 11 bytes of headers, CRC and sync per line packet, under 3 percent of a 512-byte line.
512 × 256 = 131,072 bytes per frame. 131,072 × 30 = 3,932,160 bytes per second. 3,932,160 × 8 = 31,457,280 bit/s ≈ 31.5 Mbit/s of payload.
Decision: one data lane. At the D-PHY floor of 80 Mbit/s, the payload sits at 31.5 / 80 ≈ 39 percent of the lane, so one lane carries this stream with the framing overhead absorbed many times over. The decision flips when width × height × fps × 8 passes 0.8 × lane rate: a 1024 × 512 frame at 30 fps needs 125.8 Mbit/s of payload against a 64 Mbit/s budget on one 80 Mbit/s lane, so that geometry takes two lanes or a faster lane clock.
The build sequence
- Measure the geometry from the wire. Feed a known pattern — a counter or a test frame the source can inject — capture one frame with the receiver in raw mode, and record width, height, 1 byte per pixel and the measured frame rate. These four numbers are the acceptance test for everything after.
- Describe the bus in the device tree. Camera node, endpoint, data lane count, link to the receiver, and the pixel format the link carries. Every value matches step 1.
- Write the platform driver skeleton. State structure with the embedded v4l2_subdev, the operations table, probe and remove. Build it as a loadable module; insertion runs the same probe path as built-in registration.
- Implement probe. Read the endpoint with the framework's property helpers, register the subdevice and its pads, and create the link to the receiver.
- Stub the control callbacks. Power on and off, start and stop streaming, frame-rate setting: each returns 0.
- Implement the format operations. Report the fixed 8-bit raw format, the width and height from step 1, and the fixed frame interval. Accept only the sizes you registered.
- Load and register. Insert the module and confirm the kernel log shows the subdevice registered and the video node created.
- Capture and count. Request the raw format with v4l2-ctl or your application, then confirm each frame is exactly width × height bytes.
Verify the stream before you trust it
- Byte count. Each frame must be width × height × 1 bytes — 131,072 in the worked example. Any other count means a value on your side disagrees with the wire.
- Format query. v4l2-ctl reports what your driver declares; compare it with the step 1 measurements, not with what you intended to type.
- Timing. Measure the frame-to-frame interval over a few hundred frames. The jitter you see is the source's own input variation, which the kernel was never able to command away.
- Reload. Unload and reload the module; the node should come back. If the device stays busy, a stale process still holds it — the fix in V4L2 device busy after restart applies.
- Unprocessed bytes. Confirm no layer demosaics or converts on the way through. That failure has a name on image sensors — see Bayer decode from raw image data — and it will eat your ADC bytes just the same.
Before you ship: geometry measured from a known-pattern capture; one format statement shared by the device tree, the driver and the application; a byte-count acceptance test in the capture path; and a userspace decision on what a frameless run means. The systems topic page collects the rest of the capture-stack fixes.
Changing frame size without reloading the module
Yes — with one honest constraint: the kernel still never asks the sensor. Register every geometry the source produces as a discrete size in the format operations. The application stops the stream, selects the new registered size, restarts, and resynchronizes by discarding lines until each line's byte count matches the new width — width bytes per line for RAW8. The module stays loaded throughout.
Restarting between geometries is the reliable path. Switching formats mid-stream is where buffers and timing break; ordinary cameras hit the same wall when a pixel-format change drags linked settings along, as in changing pixel format halves your frame rate. The same no-reload pattern extends to other runtime settings, which is exactly what runtime camera exposure control without reloading the driver shows for a controllable camera.
Common mistakes and the check that catches each
- A colour format for byte data. A Bayer statement invites demosaicing, and the bytes you read come out cooked. Declare a single-component 8-bit raw format and unpack in the application.
- Width and height swapped. The frame byte count still matches, but every line is scrambled. Count the bytes in one line — width bytes for RAW8 — not just the frame.
- Lane count disagreement. If the endpoint says two lanes and the receiver is configured for one, capture runs and produces garbage or short frames. Re-read both values against the step 1 capture.
- Format depth narrower than the converter. If the ADC samples wider than 8 bits and you pack into RAW8, declare what the link actually carries or the depth is lost at the statement, not in your code. The ADC bit depth versus pixel format mismatch and the full-depth raw10 and raw12 sensor modes show both sides of that trap.
- Trusting the node. Probe succeeds with nothing connected. The acceptance test is frames of the right size, never the existence of a device file.
When a supported camera is the better answer
Write this driver when the source has no controllable twin. If the device is a standard image sensor with an I2C interface, stop here: off-the-shelf area scan cameras on GigE, USB3 or CoaXPress arrive with maintained drivers, and your hours go into the inspection instead of the kernel. The camera finder filters by interface, resolution and sensor format, and the camera configurator pairs the camera with a lens and light.
Depth and profile data have their own supported path: the 3D sensors category covers laser profilers and stereo cameras that ship with capture stacks of their own. And when a fixed-rate source can speak a supported interface instead of CSI, frame grabbers — browse the accessories finder — take CoaXPress, Camera Link and GigE streams in hardware, with no kernel module of your own.
If the source really is a free-running CSI stream, start at step 1, keep the worked example's byte counts as the acceptance test, and make every value trace back to one capture of a known pattern.
Questions
Can I leave the streaming callbacks as stubs that return 0?
Yes, for a source that streams on its own from power-on. The stubs keep the pipeline state machine moving without pretending to control hardware. The cost is a video node that registers even when nothing is connected, so an application that opens it times out instead of failing at probe. Cover this in userspace: treat a run that produces no frames of the expected width × height byte count as a capture failure, not a driver success.
Where do the CSI lane count and pixel format come from without I2C?
From your values, never from the hardware. The device tree endpoint carries the physical bus description — lane count and the link to the receiver — and the driver's format operations report the pixel format, frame size and frame interval. The receiver programs its DMA from those values, and v4l2-ctl shows whatever your driver reports. If the capture looks wrong, compare every value against a raw capture of a known pattern.
How do I keep the RAW8 data unprocessed?
Declare a single-component 8-bit raw format rather than a colour format. Colour formats invite demosaicing and conversion somewhere in the stack, and then the bytes you read are cooked, not delivered. With a raw format the bytes reach the application exactly as the link carried them, one byte per pixel and width × height bytes per frame. Read them with the plain capture calls and unpack them in your own code.
How much CSI bandwidth does a RAW8 stream need?
Multiply width × height × frame rate × 1 byte, then by 8 for bits. A 512 × 256 frame at 30 fps is 131,072 bytes per frame, 3,932,160 bytes per second, about 31.5 Mbit/s of payload. One MIPI D-PHY data lane carries 80 Mbit/s at its slowest, so this stream fits one lane with headroom for packet framing. Plan a second lane when the payload passes roughly 80 percent of the lane rate.
Can the frame size change without reloading the driver?
Yes. Register every geometry the source produces as a discrete size in the format operations, and let the application stop the stream, select the new size and restart. The module stays loaded. The sensor cannot be asked what it is sending, so the application resynchronizes by discarding lines until each line's byte count matches the new width. Switching geometry mid-stream is the path that breaks buffers and frame timing.