Apologies for the long post — this covers tests 3 and 4 plus several follow-up investigations I ran while waiting. The short version is that every software-side hypothesis we’ve had is now closed, including some of my own, and I think the remaining questions are narrow enough to be answered cheaply.
Test 3 — force 2 lanes
The override loaded and applied:
options ipu-bridge force_lanes=2
INT33BE:00 lanes=2 (acpi=2)
INT347A:00 lanes=2 (acpi=4) # rear OV8865
INT347E:00 lanes=2 (acpi=1)
Note that it is global — it also pushed INT347E from its ACPI 1 lane to 2. The ChromeOS Camera app became completely unusable in this configuration and I could not switch cameras at all, but I would attribute that to the one-lane IR sensor being misconfigured during HAL enumeration rather than to anything about the rear sensor. It should not be read as “2 lanes is worse”.
With cros-camera stopped, direct libcamera still enumerated both cameras, and:
cam -c 1 --capture=30
returned all 30 rear buffers at roughly 15 fps rather than hanging at frame zero as it did in the original 4-lane test. CIO2 still produced a heavy error storm, but the signature shifted:
multiple packet header errors detected
frame sync error
DPHY synchronization error
versus the packet-header + payload-CRC + DPHY combination seen previously. I did not content-check those buffers, so “30 buffers returned” from test 3 remains unqualified.
Test 4 — plldiv 40
Active at boot, with lanes back to normal:
options clk-tps68470 dbg_plldiv=40
tps68470-clk: DEBUG xtaldiv=170 plldiv=40 postdiv=1
INT33BE:00 lanes=2 (acpi=2)
INT347A:00 lanes=4 (acpi=4)
INT347E:00 lanes=1 (acpi=1)
Front camera normal, rear camera still completely black, same port-0 error storm.
cam -c1 -C30 again returned all 30 rear buffers, at 15–16 fps against the front’s 28–30. A direct raw-role request at 1632x1224 was rejected by this libcamera/IPU3 build (“Failed to get default stream configuration”), so I captured the working 1280x720 NV12 stream from both cameras instead and checked whether the completed rear frames actually contained pixel data.
REAR: 5 × 1,382,400-byte buffers
each gzip → 1,406 bytes (0.10%), identical across all five
FRONT: same buffer size
gzip → 51.23% / 34.36% / 32.44% / 33.94%
The front also produced one effectively blank first buffer, so a single low-entropy frame proves nothing on its own. The significant part is that all five rear values were identical. A real frame from a covered lens still carries dark-current noise and would compress to tens of kilobytes. Nothing varying reached those buffers at all.
Conclusion: plldiv 40 does not help, and the rear “frames” are constant/zero-filled rather than dark images.
Live instrumentation — the rear timing line does exist
I previously reported that no rear CIO2 timing line ever appeared. That was wrong, and the explanation is mundane: ring-buffer eviction. The rear error storm overruns the kernel log faster than I could read it. After a rear capture the earliest surviving dmesg line was timestamp 1418.107157, while the rear timing line had been emitted at 1417.384387.
Capturing with dmesg -w streaming to a file instead of reading afterwards gives:
REAR (ipu3-cio2):
link_freq=360000000 bpp=10 lanes=4
clk_termen=0 clk_settle=1343
dat_termen=0 dat_settle=1316
REAR (ov8865):
stream-on extclk=19200000
lanes=4 link_freq=360000000 pixel_rate=288000000
mode=3264x2448 hts=3888 vts=2470 binning=0
FRONT (ipu3-cio2), reproducible across two consecutive runs:
link_freq=419200000 bpp=10 lanes=2
clk_settle=1368 dat_settle=1322
Both sets of values are exactly what upstream v6.6 cio2_rx_timing() computes for those link frequencies — 1343/1316 at 360 MHz and 1368/1322 at 419.2 MHz. So:
- rear CIO2 initialisation definitely runs;
- the receiver is programmed for 4 lanes at 360 MHz;
- the sensor reports 4 lanes at 360 MHz;
- the computed D-PHY timing matches upstream exactly.
Receiver-side lane-count and link-frequency mismatch are both ruled out.
Media topology
Using the bundled media-ctl, the rear graph exists and its links are enabled end to end:
ipu3-csi2 0
sink: SBGGR10_1X10/3264x2448
<- "ov8865 2-0010":0 [ENABLED]
source: -> "ipu3-cio2 0":0 [ENABLED,IMMUTABLE]
Front is equivalently OV5693 → CSI2 1 → CIO2 1, enabled.
One cosmetic difference: the rear reports @1/0 where the front reports @1/30. I believe this is a harmless upstream bug rather than a symptom. ov8865_g_frame_interval() computes fps from sensor->ctrls.pixel_rate->val — the 32-bit val field of what is a 64-bit control set via __v4l2_ctrl_s_ctrl_int64(), so that read returns 0 and the fps rounds to zero. ov5693_g_frame_interval() uses a compile-time constant instead and so is unaffected. Worth fixing upstream eventually, but not related to this fault.
Regulators
regulator_summary is not exposed in this build, but /sys/class/regulator is. The TPS68470 regulators are registered from i2c-INT3472:05 and are at the expected board-data voltages with active users:
CORE enabled 1200000 µV num_users=1
ANA enabled 2815200 µV num_users=1
VCM enabled 2815200 µV
VSIO 1800600 µV
VIO 1800600 µV
and sysfs explicitly contains:
/sys/class/regulator/regulator.2/
consumer:i2c:i2c-INT347A:00/
consumer/uevent:DRIVER=ov8865
with the supplier hierarchy pointing back into the real int3472-tps68470 device. So the rear sensor’s supplies are genuine TPS68470 rails, not dummies, and they match the upstream tps68470_board_data.c values for this board.
Interestingly, the preserved boot logs show the opposite for the working camera:
ov5693 i2c-INT33BE:00: supply dovdd not found, using dummy regulator
ov5693 i2c-INT33BE:00: supply dvdd not found, using dummy regulator
No equivalent warnings exist for OV8865. That is expected — the front is on the discrete int3472 GPIO path and the board-data table only defines int347a_* consumer supplies — but it does mean the camera on dummy regulators is the one that works.
Rear power delivery is ruled out as far as Linux can see it.
Live OV8865 register read during active streaming
Read while a rear capture was running (the stream stayed alive for all 90 buffers at ~15 fps):
0x0100 MODE_SELECT = 0x01
0x3018 MIPI_SC_CTRL0 = 0x72 (4 lanes)
0x4837 PCLK_PERIOD = 0x16
0x4850 LANE_SEL01 = 0x10
0x4851 LANE_SEL23 = 0x32
All exactly the upstream configuration. In particular 0x4837 = 0x16 confirms the upstream transmission-error workaround is present and correct for a 360 MHz link, and the lane-select registers show the expected identity mapping across all four lanes.
Where that leaves us
Ruled out by measurement:
- wrong lane count — no (sensor and receiver both 4)
- wrong link frequency — no (both 360 MHz)
- wrong CIO2 D-PHY timing calculation — no (matches upstream exactly)
- rear sensor on dummy regulators — no (real rails, correct voltages)
- wrong supply voltages as Linux sees them — no
- sensor not entering streaming mode — no (
0x0100 = 0x01 under load)
- stale/default
0x4837 — no
- wrong four-lane mapping — no
- capture mode / binning — no (ruled out by your earlier A/B)
- sensor master clock — no (19.2 MHz, PLL on the matching table)
- TPS68470 clock output — no (
CLKCFG1 = 0x0a, PLLCTL bit 0 set, REVID = 0x21)
- pixel array / exposure / optics — no, already excluded by test 2: the sensor’s internal colour-bar pattern was accepted (
forced test pattern 2 (ret 0)) and still produced black
What remains is confined to the CSI transmitter → physical link → CIO2 receiver path, and none of it is visible from userspace. One further observation that may or may not be a clue: the rear delivers buffers at almost exactly half the mode’s ~30 fps, which is what you would expect if the receiver discards every alternate frame after a sync error and only completes a buffer on the ones that survive framing.
What would help most now
1. Per-lane D-PHY status. CSIRX_STATUS_DLANE_HS (CSIRX_BASE + 0x1c, mask 0xff) and CSIRX_STATUS_DLANE_LP (+0x20, mask 0xffffff) report the HS/LP state of each data lane individually. The driver writes both at init to clear them and never reads them back, so nobody has ever seen this. Reading them during a rear capture, with the front as a control, would show directly whether all four lanes enter high-speed mode. If lanes 2 and 3 never do, that answers the whole thread.
2. The MIPI lane test pattern at 0x484d (and the clock-lane pattern at 0x484f). These are distinct from the image test pattern already tried in test 2 — they drive a fixed pattern onto the D-PHY itself, bypassing both the pixel array and the packet framing. If the lane pattern decodes while the image pattern does not, the fault is in framing; if neither decodes, it is the PHY or the physical link.
3. Which specific error bits are setting. I only see three rendered strings. Knowing whether it is “DPHY not recoverable” versus “ECC not recoverable” versus “PKT2SHORT” would narrow this considerably.
4. Possibly cheaper than a test kernel: could you build just ov8865.ko and ipu3-cio2.ko from pristine upstream v6.6 sources and let me load them? The linux-surface project lists the Surface Go 2’s rear OV8865 as working, and their 6.6 camera patch series touches neither driver — it is only ACPI _DEP ordering, IOMMU passthrough for Intel IPUs, the int3472 I²C daisy chain, and TPS68470 LED plumbing. That suggests stock v6.6 drives this sensor on this board unmodified. The module here reports a -dirty vermagic, so there is some local delta, and loading upstream builds would settle whether it matters in a single boot.
Environment notes
- This build exposes no dynamic-debug control file — neither
/proc/dynamic_debug/control nor the debugfs path — and has no strings, so I cannot enable or verify upstream dev_dbg callsites myself. Anything at debug level has to come from you as a built module.
- On your open question about the raw PMIC read failing at 0x4c while the sensor answered at 0x10: the TPS68470’s address is claimed by an ACPI OperationRegion, and
i2c-dev refuses those unless the kernel boots with acpi_enforce_resources=lax. Your regmap route was the correct one.
- The HAL recovery issue is unchanged — after a rear timeout the session is never torn down and the front stays blocked until
restart cros-camera. I understand that is being fixed separately.
Happy to run anything else. Everything above was read-only apart from the debug modules you supplied.
edit
- dw9719 VCM unbound (the 6.19 regression that stalls the async notifier) — no,
/sys/bus/i2c/devices/i2c-INT347A:00-VCM/driver resolves to /sys/bus/i2c/drivers/dw9719, and the media graph is complete
And after the regulator section:
As a cross-check, a recent linux-surface writeup for the Surface Pro 5 lists supply avdd/dvdd not found, using dummy regulator for all three sensors as normal on a machine where both cameras work. So dummy supplies are not diagnostic either way, and on this unit the rear has real rails regardless.