A camera link that streams cleanly in D-PHY and dies the moment the same hardware switches to C-PHY is one of the most repeatable failures in embedded vision bring-up. The capture driver resets the channel every 2.5 seconds, the trace floods with PHY error interrupts, and the sensor is provably streaming the whole time. This guide is for engineers bringing up a MIPI CSI-2 camera over a SerDes link on an embedded SoC. It gives you the failure fingerprint, the per-trio polarity fix, the trio-mapping checks and the bandwidth arithmetic that decides how many trios the mode really needs.
What the failure looks like
You request a stream and no frame ever arrives. The usual bench command is v4l2-ctl --set-fmt-video=width=3840,height=2162,pixelformat=YUYV --set-ctrl bypass_mode=0 --stream-mmap -d /dev/video0. The kernel log then loops on a fixed cadence: uncorr_err: request timed out after 2500 ms, followed by err_rec: attempting to reset the capture channel, repeating every 2.5 seconds for as long as the stream stays open. On the trace side the receiver's interrupt source keeps firing: rtcpu_nvcsi_intr ... type:PHY_INTR0 phy:0 cil:1 st:0 vc:0 status:0x00000080, alternating between cil:0 and cil:1.
Three facts together pin the fault to the receiver's PHY configuration. The I2C probe found the sensor. An oscilloscope on the wires between the SerDes receiver and the SoC shows a healthy C-PHY waveform, so symbols are being driven. And PHY_INTR0 repeats at status 0x00000080 while that waveform is present — the receiver is being offered symbols it cannot turn into packets. That combination rules out the sensor, its I2C bus and the cable; go to the guide on separating I2C probe failures from MIPI link failures only if the probe itself had failed. The rest of the MIPI triage guides live on the systems topic page.
If the scope shows no waveform at all, stop here: the transmitter side is dead, and that is the territory of the guide on power, clock and device-tree causes when a sensor streams nothing.
Why D-PHY success proves little about C-PHY
CSI-2 sits above the physical layer, so packets, virtual channels and pixel formats are identical in both modes. What changes is the wire. A D-PHY lane is a differential pair with a separate clock channel. A C-PHY trio is three wires with no clock line at all: every symbol transition changes all three wires, and the receiver recovers timing from the data itself.
That design has a direct consequence. The receiver must know the wire order of every trio, because a rotation (ABC versus BCA) that does not match the board routing and the SerDes output map leaves the PHY unable to lock or shifts symbols onto the wrong bit positions. The polarity setting that made D-PHY work does not carry over: the D-PHY correction is a per-lane inversion bit, while the C-PHY correction is a three-wire permutation chosen per trio. Lane groups are trios too, so the receiver accepts a fixed set of trio counts, which is the second failure mode below.
Run the bandwidth check before touching a register
The check comes first because it decides which configurations are even worth trying. C-PHY throughput has one fixed exchange rate — the MIPI standard's 2.28 bit per symbol, per trio:
Worked example, from an 8 MP mode on a real bring-up: 3840 × 2162 active pixels, YUYV output (16 bit per pixel), 30 fps, line length 4400, symbol rate 1.5 Gsym/s per trio.
- Pixels per frame: 3840 × 2162 = 8,302,080
- Pixel data per frame: 8,302,080 px × 16 bit/px = 132,833,280 bit ≈ 132.8 Mbit
- Payload: 132,833,280 bit × 30 fps = 3,984,998,400 bit/s ≈ 3.98 Gbit/s
- On the wire, with blanking: 3.98 Gbit/s × (4400 / 3840) = 3.98 × 1.146 ≈ 4.57 Gbit/s (packet headers add about 0.08 % at a 7680-byte line payload)
- Capacity of one trio: 2.28 bit/symbol × 1.5 Gsym/s = 3.42 Gbit/s
| Trios | Capacity at 1.5 Gsym/s | Measured against 4.57 Gbit/s | Verdict |
|---|---|---|---|
| 1 | 3.42 Gbit/s | 0.75× | cannot carry this mode |
| 2 | 6.84 Gbit/s | 1.5× | carries it with margin |
| 4 | 13.68 Gbit/s | 3.0× | the configured link, ample headroom |
Two decisions fall straight out of that table. The four-trio link is not undersized — a capacity shortage produces short frames and dropped lines, not a total timeout, which is the signature covered in the guide on short-frame errors caused by a mode that does not match the sensor. And a single trio is arithmetically impossible here: it would need at least 4.57 / 2.28 ≈ 2.0 Gsym/s. The same payload arithmetic is how area scan cameras get matched to any interface, CSI-2 included.
Cause 1: the per-trio polarity rotation
On this class of receiver, each trio interface — the CIL blocks named in the trace — carries a polarity-swizzle control, with register names of the form NVCSI_PHY_0_NVCSI_CIL_A_POLARITY_SWIZZLE_CTRL_0. In D-PHY that field inverts a differential pair. In C-PHY it selects the wire order of each trio, and the constants this platform defines are NVCSI_CPHY_POLARITY_ABC and NVCSI_CPHY_POLARITY_BCA.
The correct value is a property of your wiring, not of the sensor. It follows the path from the SerDes output trio through the connector and the PCB traces to the receiver pins, so read it off the schematic and the SerDes mapping table. On this bring-up the mapping alternated down the link — trio 0 ABC, trio 1 BCA, trio 2 ABC, trio 3 BCA — written into brick_config.lane_polarity[0..3] before the PHY started, in the platform's CSI driver source file csi5_fops.c. The same driver derived D-PHY polarity as a per-lane bit from a bitmask; that loop has no meaning for trios, which is why the C-PHY branch sets each rotation explicitly. A two-trio link uses only the first two entries.
The test is quick. Program the rotations, open the stream, and watch the trace. If the PHY_INTR0 events stop and frames arrive, the rotation was the fault. If the registers read back your values and the 0x00000080 interrupts continue, the rotation is right and the trio mapping is the next suspect.
Cause 2: the receiver cannot map your lane count to trios
A trio count the transmitter supports is not automatically one the receiver can map. On the same bring-up, the sensor could reduce its output, but the SerDes receiver had no single-trio configuration in its C-PHY map — the supported arrangement was the four-trio one. That constraint is set by the receiver's design, not by a register you can argue with, and a mismatch produces exactly the timeout loop above: the receiver waits for trios that never arrive where it expects them.
So the check is documentary. Read the SerDes user guide's C-PHY section for the supported trio configurations and the placement rules. Receivers of this class commonly fix the configuration statically, allow trio locations to be swapped within one PHY, and expose per-trio polarity inversion. Program it once, before the first stream — a link reconfigured mid-run has nothing to fall back on.
Then match the transmitter. The device tree carried num_lanes = "4", phy_mode = "CPHY" and bus-width = <4> on the endpoint; changing the trio count means changing that lane description together with the sensor's own output mode, then re-running the bandwidth table, because capacity and mapping move together.
Cause 3: the wrong register block
The register dump in this case read 0x15a04738, expected 0x1000 in the polarity field and got 0x00000000; a write of 0x1000 to the same address did not change the read-back, and a neighbouring offset returned a bus error. The explanation was mundane: the address was wrong. The swizzle controls sit at per-instance offsets — 0x15a11ce0, 0x15a21ce0, 0x15a31ce0 and 0x15a41ce0 for CIL A of PHY 0 through 3, with the matching 0x15a11de0 through 0x15a41de0 addresses for CIL B.
Treat both symptoms as address errors. A zero read-back where a programmed value should be, a write that will not stick, or a bus error on a nearby offset means the memory map does not agree with you — go back to the per-instance map rather than concluding the driver dropped your configuration. Read the registers back after the driver's configuration step, with a stream open, so you are looking at the applied state rather than reset defaults.
Bring-up checklist
Run the checks in this order; each one either clears a cause or localizes it.
Before power-on
- Draw the trio map — SerDes trio number to connector pins to receiver CIL instance to rotation constant — and keep it with the schematic. It is the reference for every polarity setting.
- Compute the wire rate and trio capacity from the formula above and pick a configuration with at least 1.5× margin.
First stream
- Confirm the I2C probe, then scope the C-PHY waveform at the receiver-side connector, before reading any register.
- Capture the trace while the stream is open and read the PHY_INTR0 status lines.
- Program the per-trio rotation from the map, start the stream, and confirm the interrupts stop.
After any mode change
- Re-run the bandwidth table, because fps, line count or bits-per-pixel changes move the wire rate.
- Read the swizzle registers back at their per-instance addresses, after the driver has configured the link.
When to skip the MIPI link entirely
If the deliverable is inspection images rather than a custom CSI link, the cleanest fix removes the PHY layer from the project. A board-level camera that bridges its sensor to a standard interface hands you frames with no trio polarity, no SerDes mapping and no lane counts to configure. The LS-BCM-0003 pairs a 3.1 MP global-shutter sensor (1/1.8") with a native USB3 interface and streams at 120 fps, so the same embedded host reads it as an ordinary USB3 camera.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Interface
- USB3
- Resolution
- 5 MP
The decision
Commit to these calls:
- Waveform present at the receiver, PHY_INTR0 firing at status 0x00000080, D-PHY working on identical hardware: program the per-trio ABC/BCA rotation from your SerDes map before the stream starts. On this bring-up that change alone moved the link from a 2.5-second reset loop to live frames.
- Tempted to simplify to one trio at 1.5 Gsym/s: not for this mode. One trio carries 3.42 Gbit/s against the 4.57 Gbit/s needed; use two trios (6.84 Gbit/s, 1.5× margin) or raise the single-trio symbol rate to at least 2.0 Gsym/s.
- Zero read-backs and bus errors in the register dump: wrong block, not wrong driver. Use the per-instance offsets and read back while the link runs.
If you are selecting parts for a new link rather than rescuing one, the camera finder filters by interface and resolution and the camera configurator walks the chain from sensor to cable; for a specific sensor-plus-SerDes integration, contact us with the lane mode and the bandwidth numbers.
Questions
Why does the same camera work in D-PHY but time out in C-PHY?
The CSI-2 packet layer is identical, but the physical layer is not. D-PHY sends each lane as a differential pair with a separate clock; C-PHY sends symbols on three-wire trios and recovers the clock from the data itself. The receiver therefore needs a per-trio wire rotation and a trio mapping that match the board and the SerDes, and those settings are independent of the D-PHY polarity bits that made the first mode work.
How much data can one C-PHY trio carry?
Multiply the symbol rate by 2.28, the bits per symbol the MIPI C-PHY standard defines. At 1.5 Gsym/s a trio carries 2.28 × 1.5 = 3.42 Gbit/s; two trios carry 6.84 Gbit/s and four carry 13.68 Gbit/s. Compare that with your payload — pixels per frame × bits per pixel × frame rate, plus blanking — and keep roughly 1.5× margin before committing to a trio count.
What does PHY_INTR0 status 0x00000080 tell me?
It marks a PHY-level error interrupt on the named CIL while the link should be streaming. The meaning of the individual bit is defined in the SoC's technical reference manual rather than in the trace, so do not read a generic meaning into the hex value alone. In bring-up practice, a repeating 0x00000080 with a confirmed C-PHY waveform at the receiver input means the receiver cannot lock or decode the trios — polarity rotation or trio mapping, not the sensor.
Can I change trio polarity while the stream is running?
Treat the C-PHY configuration as static. Camera SerDes receivers of this class state in their user guides that dynamic configuration is not supported, while trio locations may be swapped within the same PHY and polarity inversion is programmable — once, before streaming. Program the rotation and the mapping, start the link, then confirm the registers read back the programmed values while frames flow.
Is the ABC, BCA, ABC, BCA rotation universal for four-trio links?
No. The rotation constants encode how the SerDes output map crosses the PCB to the receiver pins, so they belong to that wiring, not to four-trio links in general. The case that produced the numbers in this guide alternated ABC and BCA down the four trios; a different SerDes part, a different connector pinout or a pair-swapped layout produces a different sequence. Derive yours from the schematic and the SerDes mapping table, then confirm it on the bench.
