Surface Go 2 rear camera detected but black on FydeOS v22.1 – IPU3 CSI-2 timeout

:pushpin:Question Description

**Have you searched the community for Have you searched the community for related issues?

Yes. I found previous Surface Go 2 camera reports, but my issue is not caused by the camera being disabled in UEFI.

Description

The front camera works on my Microsoft Surface Go 2, but switching to the rear camera results in a completely black screen.

After selecting the rear camera:

  • The Camera app shows a black image.
  • No frames are received.
  • I cannot switch back to the front camera.
  • Restarting the cros-camera service restores the front camera.
  • Selecting the rear camera again reproduces the problem.

The rear camera is enabled in Surface UEFI.

The same rear camera produced a visible image when I previously tested Mobian Linux, although camera support there was not fully polished. This suggests the rear camera hardware is functional.

Hardware and system information

Device: Microsoft Surface Go 2 LTE
Processor: Intel Core m3-8100Y
RAM: 8 GB
Storage: 128 GB
FydeOS product: FydeOS for You – Surface Go 2
FydeOS version: v22.1 stable
Build: 16503.20.22.7
Board: surface-go2-io
Kernel: 6.6.99-09011-gfdc62122de5f-dirty

Expected behaviour

The Camera app should switch between the working front OV5693 camera and rear OV8865 camera.

Actual behaviour

The rear camera is detected by the kernel and ChromeOS camera HAL, but opening it produces a black screen and no camera frames.

Hardware detection

The kernel detects all three Surface camera sensors:

ipu3-cio2 0000:00:14.3: Found supported sensor INT33BE:00
ipu3-cio2 0000:00:14.3: Found supported sensor INT347A:00
ipu3-cio2 0000:00:14.3: Found supported sensor INT347E:00
ipu3-cio2 0000:00:14.3: Connected 3 cameras

The required drivers are loaded:

ipu3_cio2
ipu3_imgu
ov5693
ov8865
intel_skl_int3472_discrete

The camera service also detects both normal cameras:

ProbeMediaController(): Probing sensor ‘ov5693 4-0036’
ProbeMediaController(): Probing sensor ‘ov8865 2-0010’

StartOnThread(): Camera module “libcamera camera HALv3 module” has 2 built-in camera(s)
StartOnThread(): SuperHAL started with 2 modules, 2 built-in cameras

Reproduction steps

  1. Boot FydeOS v22.1 on the Surface Go 2.
  2. Open the standard Camera app.
  3. Confirm that the front camera displays an image.
  4. Press the switch-camera button.
  5. The rear camera opens to a black screen.
  6. The application then cannot switch back to the front camera.

Relevant logs when rear camera is opened

OpenDeviceImpl(): CHROME, camera_id = 0, camera_module = libcamera camera HALv3 module, internal_camera_id = 0

ConfigureStreams(): id = 0, type = CAMERA3_STREAM_OUTPUT, size = 1600x1200, format = HAL_PIXEL_FORMAT_YCbCr_420_888

ConfigureStreams(): id = 1, type = CAMERA3_STREAM_OUTPUT, size = 1600x1200, format = HAL_PIXEL_FORMAT_BLOB

ipu3-cio2 0000:00:14.3: CSI-2 receiver port 0: escape mode ultra-low power state exit for clock lane

MonitorTimeoutOnThread(): No results for more than 2 s

MonitorTimeoutOnThread(): No requests for more than 2 s

Earlier attempts also produced repeated errors on CSI-2 receiver port 0:

multiple packet header errors detected
payload checksum (CRC) error
DPHY synchronization error

Android camera access also fails, including with a 640x480 preview, so this is not limited to the ChromeOS Camera application.

The following warning appears but may be unrelated because both cameras are still detected:

OnOptionsUpdated(): Cannot find feature profile as dict for device model “surface-go2”

Restarting the service temporarily restores the front camera:

restart cros-camera

Request

Please investigate the Surface Go 2 OV8865 rear-camera pipeline in the FydeOS v22.1 build.

The problem appears to be in the OV8865 / IPU3 CIO2 / libcamera pipeline because:

  • The rear sensor is detected.
  • The driver is loaded.
  • The camera HAL exposes it.
  • Opening it produces CSI-2 receiver errors.
  • No frames reach cros-camera.
  • The front camera works through the same camera service.
  • The rear camera previously produced an image under Mobian Linux.

I can provide the complete /var/log/messages output if required. issues?**

  • No similar issues found.

Please describe the problem you’ve encountered in detail:

  • Description:

:computer:System & Device Information

  • Hardware & System Configuration:

    • Processor:
    • Graphics Card(Please specify if there’s a discrete graphics card):
    • RAM:
    • FydeOS Version:
  • Relevant System Logs:

    • Open the Chromium browser and enter /var/log/messages to retrieve logs.
    • Log Details:

Note: If you’ve lost your FydeOS subscription, please refer to this FAQ for assistance.


The rear OV8865 probes and begins streaming, but CIO2 reports repeated CSI-2 packet-header, payload-CRC and D-PHY synchronisation errors, including at 640×480 from the Android camera stack. This suggests a sensor/CIO2 link configuration problem rather than an IPU3 tuning-file issue.
Linux 6.6 contains OV8865 PLL configurations for both 19.2MHz and 24MHz input clocks. Could you confirm which external clock rate the FydeOS build exposes to ov8865, which PLL table the driver selects, and whether the sensor link frequency and lane configuration match CIO2 receiver 0?
Separately, after the rear-camera stream times out, the camera HAL remains stuck and cannot recover or switch back to the front camera until restart. That appears to be an independent HAL recovery bug.

Thanks for the very detailed report — the symptom description is accurate and helpful.

Confirmed on our side: this is a real bug in our v22.1 Surface Go 2 build, not a configuration mistake or a UEFI issue. Your reasoning is right that the hardware is fine — the sensor probes, powers up and starts transmitting; the CSI-2 receiver just can’t decode what it sends.

Two separate problems here:

  1. The rear-camera CSI-2 errors. The DPHY-sync / packet-header / CRC / “ULPS exit for clock lane” combination points at a D-PHY timing mismatch — the receiver is timed for one bit rate while the sensor transmits at another. On this platform the sensor’s master clock is not programmed by the kernel; the INT3472 driver only gates it on and off and takes the frequency on trust from an ACPI table. If that table’s value doesn’t match the real oscillator, nothing in the kernel can detect it, and this is what you’d see. That’s my leading theory; a second possibility is the CIO2 capture mode we select on Surface boards.

  2. The stuck HAL. Independent and already confirmed from source: our camera pipeline has no timeout or recovery path, so when frames stop the session is never torn down and the front camera stays blocked. You found the right workaround — restart cros-camera. I’m fixing that separately, and it’ll land regardless of the rear-camera outcome.

There are two things you can run that would let me pick the right fix and both are read-only. In dev mode: Ctrl+Alt+Tshellsudo su.

(a) Please post the output of these — this is the most valuable thing:

dmesg | grep -iE 'ov8865|int3472|no external clock|clock rate'
cat /sys/kernel/debug/clk/clk_summary | grep -iE 'int3472|clk_name|rate'

The first tells me whether the driver found a clock at all; the second tells me the frequency it believes in and whether it’s actually enabled. If those two disagree with each other, that’s the bug.

(b) An A/B test that tells me whether the capture mode is involved:

stop cros-camera
cam -l                    # note the index of the ov8865 / back camera

dmesg -C
cam -c <N> -C30 -s role=raw,width=3264,height=2448 -s role=viewfinder,width=1600,height=1200
dmesg | grep ipu3-cio2

dmesg -C
cam -c <N> -C30 -s role=raw,width=1632,height=1224 -s role=viewfinder,width=640,height=480
dmesg | grep ipu3-cio2

start cros-camera

The second command takes a different path through our pipeline and puts the sensor in a lower-rate binned mode. If it captures frames cleanly while the first one errors, the fix is on our side and is small. If both error identically, it confirms the clock theory and I’ll go after that instead.

Your Mobian observation is genuinely useful, by the way — if you still have that install and can run dmesg | grep -i ov8865 there, the clock frequency it reports would settle this immediately.

I completed the requested A/B tests.

Rear camera is camera 1: Internal back camera (\_SB_.PCI0.LNK0).

Full-resolution mode configured as:

3264x2448-SBGGR10_IPU3 + 640x480-NV12

It produced no frames and remained stuck at Capture 30 frames until Ctrl+C. No ipu3-cio2 lines appeared afterward.

Binned mode configured as:

1632x1224-SBGGR10_IPU3 + 640x480-NV12

It also produced no frames and remained stuck until Ctrl+C. The only CIO2 line was:

ipu3-cio2 0000:00:14.3: CSI-2 receiver port 0: escape mode ultra-low power state exit for clock lane

After reboot, the OV8865 probes successfully, DW9719 is instantiated, and the TPS68470 clock rate is confirmed as 19200000.

I also attempted the requested media-ctl topology command, but this FydeOS v22.1 host image does not include media-ctl:

sudo: media-ctl: command not found

/dev/media0 and /dev/media1 are both present.

Therefore, both full-resolution and binned modes stall before delivering the first frame, and I cannot collect the requested media topology without a supplied media-ctl binary or an alternative command available in this build.

Thank you — the A/B test did exactly what I hoped and ruled out my main theory. Since both the full-resolution and binned modes fail the same way, the capture mode we select is not the problem, and I can stop looking there.

The 19200000 reading is also useful: it tells me the kernel and the camera PMIC agree on the sensor clock. If the firmware had asked for 24 MHz, the driver would have programmed 24 MHz and you’d have seen that number instead. So the clock is almost certainly fine too.

What your test did show is more interesting than either. Previously, through the Camera app, we saw a storm of corrupted-packet errors. Your cam runs show the opposite — the receiver is nearly silent, with just one “clock lane woke up” event and no data at all. That means the sensor powers on and brings up its MIPI clock, then never sends a frame. That’s a different and more tractable problem, so this round of testing genuinely moved things forward.

One more round and I think I’ll have it. Please run these and attach the two log files (they’ll be large — that’s expected):

stop cros-camera
cam -l                                  # confirm the indices; you reported rear = 1

# broken path
LIBCAMERA_LOG_LEVELS=*:DEBUG cam -c1 -C5 \
  -s role=raw,width=1632,height=1224 -s role=viewfinder,width=640,height=480 \
  > /tmp/cam-rear.log 2>&1
# let it sit ~10 s, then Ctrl+C

# working path, same code, for comparison
LIBCAMERA_LOG_LEVELS=*:DEBUG cam -c2 -C5 \
  -s role=viewfinder,width=640,height=480 \
  > /tmp/cam-front.log 2>&1

start cros-camera

The front-camera log matters as much as the rear one — the front sensor works through identical code, so diffing the two will show me exactly where the rear path diverges. These logs also contain the full media topology, so the missing media-ctl is no longer a blocker.

Also please post:

dmesg | grep -iE 'ov8865|dw9719|no external clock|clock rate|int3472'

I specifically want to know whether the line no external clock found, continuing... appears — that’s the last remaining way the clock could be wrong despite the correct rate reading.

cam-rear.log (225.5 KB)
cam-front.log (46.2 KB) > I completed the requested rear/front camera comparison and have attached both full debug logs.

  • cam-rear.log — 226 KB
  • cam-front.log — 47 KB

Both cam commands completed and returned to the prompt without needing Ctrl+C.

I also ran:

dmesg | grep -iE 'ov8865|dw9719|no external clock|clock rate|int3472'

It returned no output. In particular, there was no no external clock found, continuing... message.

The detected cameras were:

1: Internal back camera (\_SB_.PCI0.LNK0)

2: Internal front camera (_SB_.PCI0.LNK1)


































`cam -l` also reported that both `ov8865.yaml` and `ov5693.yaml` were missing.

These logs answered it — The software side of the rear camera is completely healthy. Buffers allocate, streaming starts, the pipeline runs end to end, and there isn’t a single error in 1,785 lines apart from a missing tuning file that the working front camera is also missing. The problem is one layer lower.

Comparing your two logs: the front camera reports 6 frame-start events over the capture. The rear camera reports 987 of them in 26 milliseconds. The receiver isn’t seeing frames — it’s seeing noise and mistaking it for the start of a frame, tens of thousands of times a second. That’s why the app reports 200 fps instead of 30, why the raw frame counter never advances past zero, and why the auto-exposure logic pushes gain to 100,000,000× and still measures nothing: every pixel arriving is black.

So the sensor is powered, configured and transmitting, but the receiver can’t lock onto the signal. Your original “black screen” and the packet/CRC errors you saw earlier are both symptoms of that single problem.

I produced a real image on this exact Surface Go 2 under Mobian using an earlier linux-surface kernel. It was extremely buggy and unreliable, but I definitely saw output from the rear camera.
That makes a completely failed camera module or disconnected MIPI link unlikely and suggests a configuration or regression difference between the earlier linux-surface camera stack and the FydeOS 6.6 build.
One distinction may still be useful: the current signature could mean either:
the OV8865 is genuinely streaming but CIO2 cannot lock onto its signal; or
the sensor never exits standby and CIO2 is interpreting floating-line noise as thousands of frame-start events.
Could we confirm the OV8865 mode-select register while a rear capture is running? I have seen this suggested:
i2ctransfer -y -f 2 w2@0x10 0x01 0x00 r1
but I do not want to force access while the kernel driver owns the sensor without first confirming that bus 2 and address 0x10 are correct for this build.
I can also attach a complete ACPI dump so you can inspect the INT347A SSDB data from this exact unit. Please let me know whether you would prefer:
acpidump -b
or particular tables/files from /sys/firmware/acpi/tables/.

Yes please, acpidump -b is the right form. One caveat so you know what to expect — the specific values I most want (lane count and clock rate) are filled in at runtime by firmware, so they’ll read as zero in a static dump. It’s still useful because your unit is the Core m3 LTE model and ours is the Pentium version, so the tables may differ. But the register reads above are what will actually settle it.

Neither acpidump nor the i2c-tools utilities are included in this FydeOS host image.

I copied the raw ACPI tables directly from:

/sys/firmware/acpi/tables

including its contents and dynamic tables, and have attached them as:

surface-go2-m3-lte-acpi-sysfs.tar.gz

Please confirm whether this raw sysfs archive is sufficient in place of acpidump -b.

I also checked for the register-reading tools:

i2ctransfer — not installed
i2cget      — not installed
i2cset      — not installed
i2cdetect   — not installed

I have not attempted to install anything into the FydeOS host system. Could you provide a compatible i2ctransfer binary or an alternative command available in this build so I can read OV8865 register 0x0100 while capture is active?
surface-go2-m3-lte-acpi-sysfs.tar.gz (64.0 KB)

Attached: fydeos-camera-debug.tar.gz (732 KB). It contains i2ctransfer, i2cget, i2cdetect, media-ctl, v4l2-ctl, yavta and acpidump.
fydeos-camera-debug.tar.gz (731.6 KB)

ov8865-capture.log (954 Bytes)
ov8865-probe-output.txt (6.4 KB)
I completed the supplied OV8865 register probe.

The supplied script initially could not locate the sensor because this build exposes it through the i2c-INT347A:00 ACPI path, so I adjusted only the bus-detection section; it resolved correctly to bus 2, address 0x10.

Results:

  • Correct chip ID: 00 88 65
  • Idle MODE SELECT 0x0100: 0x00
  • Capturing at both t+4s and t+7s: 0x01
  • MIPI configuration 0x3018: 0x72 — four lanes
  • PLL registers: 0x0300=0x02, 0x0302=0x4b — 19.2 MHz table
  • CIO2 still reports only: escape mode ultra-low power state exit for clock lane
  • No valid frames are delivered

Therefore the OV8865 genuinely exits standby with the expected 19.2 MHz PLL configuration, but CIO2 receiver 0 still cannot decode its CSI-2 stream.

I have attached the complete probe output and /tmp/ov8865-capture.log.

That’s the answer to the question you raised, and it rules out your second hypothesis cleanly: the sensor is genuinely streaming. Mode select goes 0x00 when idle and 0x01 during capture, exactly as it should; four lanes are programmed; the PLL is on the 19.2 MHz table. Everything the driver controls is correct. So the receiver is failing to decode a real signal, not misreading a dead bus.

Thanks also for fixing the bus detection — you were right, and I’ve folded your change into the attached version.

Your results pointed me somewhere specific. I went back through the power and clock wiring for both cameras and found that they don’t share a clock source. The front camera, which works, gets its clock from one controller; the rear camera gets its clock from a separate power-management chip (the TPS68470). That chip is the only piece of hardware unique to the camera that’s failing.

And there’s something questionable about how the kernel drives it. It calculates the sensor’s clock from a crystal frequency that is assumed, hard-coded, and never checked against the actual board. If that assumption is wrong for the Surface Go 2, the sensor would run at a slightly wrong speed — which the receiver cannot tolerate, and which would produce exactly what you’re seeing, while leaving the front camera untouched.

One more probe, and it’s read-only and quick — attached, tps68470-probe.sh:


sudo tar xzf fydeos-camera-debug.tar.gz -C /usr/local # overwrites the old copy

cd /usr/local/fydeos-camera-debug

sudo chmod +x *.sh bin/*

sudo ./tps68470-probe.sh

The two lines to watch during capture are CLKCFG1 and PLLCTL. If CLKCFG1 reads 0x00 or PLLCTL bit 0 is clear while capturing, the sensor is getting no clock at all and we have our answer. If they read 0x0a and 0xd1 with the expected dividers, the chip is doing its job, and the remaining suspects are the crystal itself and the physical lane wiring — neither of which software can read, so that’s where I’d move to a test kernel.

If you ran gnvs-camera-read.sh from the previous package, its output would be useful too.
fydeos-camera-debug.tar.gz (736.0 KB)

The TPS68470 probe had the same ACPI sysfs naming issue as the OV8865 probe.

I changed only the detection section to use:

/sys/bus/i2c/devices/i2c-INT3472:05

This resolved correctly to I²C bus 2, and the device is bound to the int3472-tps68470 driver.

The probe then identified:

TPS68470 at i2c bus 2, address 0x4c

However, every register read returned:

Error: Sending messages failed: Remote I/O error

This happened both while idle and during rear-camera capture, so I could not obtain valid CLKCFG1, PLLCTL or divider values.

I have attached the complete probe output. Does this FydeOS kernel require a different read method, such as accessing the driver’s regmap/debug interface rather than raw i2ctransfer?
tps68470-probe-output.txt (3.1 KB)

Yes — that’s exactly the right call, and the method you suggested works on this build.

The chip’s driver keeps a register interface exposed under debugfs, and on this kernel it’s configured without caching, so reading it gives the real hardware state rather than a copy of what the driver last wrote. That’s actually better evidence than the raw I2C read would have been.

Attached, tps68470-regmap.sh:


sudo tar xzf fydeos-camera-debug.tar.gz -C /usr/local # overwrites the old copy

cd /usr/local/fydeos-camera-debug

sudo chmod +x *.sh bin/*

sudo ./tps68470-regmap.sh

Same registers as before, same idle-vs-capturing comparison, plus a full dump and a listing of every I2C device on the system. Ignore the old tps68470-probe.sh.

The two values that matter, in the “while capturing” section:

  • CLKCFG1 — 0x0a means the clock outputs are being driven; 0x00 means they’re tri-stated and the sensor is getting no clock at all.

  • PLLCTL — bit 0 set (0xd1) means the PLL is running; 0xd0 means it isn’t.

REVID should read 0x21; if it does, we know the read path is trustworthy.

On your question about why the raw read failed — I don’t have a definitive answer yet. The sensor answered on the same bus, so the bus is fine and the PMIC specifically isn’t responding at the address I assumed. Either the address is different from what the firmware tables suggest, or something else owns it.
fydeos-camera-debug.tar.gz (737.4 KB)

tps68470-regmap-output.txt (4.4 KB)
The debugfs regmap probe completed successfully.

During capture:

  • REVID = 0x21
  • CLKCFG1 = 0x0a
  • PLLCTL = 0xd7 — bit 0 set
  • All clock dividers match the expected values:
    POSTDIV2=01, BOOSTDIV=03, BUCKDIV=02, PLLSWR=03, XTALDIV=aa, PLLDIV=20, POSTDIV=01, CLKCFG2=05

The values are identical while idle and capturing. This appears to confirm that the TPS68470 PLL is enabled and its clock outputs are being driven correctly.

I have attached the full regmap output. It looks like the next useful step is the test kernel you mentioned.

sudo tar xzf fydeos-camera-debug-modules.tar.gz -C /usr/local
cd /usr/local/fydeos-camera-debug-modules

sudo ./install-debug-modules.sh          # test 1
sudo reboot
# open Camera, switch to the rear camera, then:
dmesg | grep -i DEBUG

Then please run test 2 — it’s the decisive one:

sudo ./install-debug-modules.sh testpattern
sudo reboot

This makes the sensor generate colour bars internally, bypassing the lens and exposure entirely. If they come through, the data path works and the problem is much narrower than we think. If they don’t, the link genuinely can’t carry data, and tests 3 and 4 (lanes 2, plldiv 40) tell us why.
fydeos-camera-debug-modules.tar.gz (60 KB)

Test 2 completed.

I installed the sensor internal colour-bar test pattern, rebooted, opened the Camera app and switched to the rear camera. The rear camera remained completely black; no colour bars appeared.

The debug log confirms that the test pattern was enabled successfully:

ov8865 i2c-INT347A:00: DEBUG: forced test pattern 2 (ret 0)

It also shows the rear camera starting with:

lanes=4 link_freq=360000000 pixel_rate=288000000

I have attached camera-debug-test2.txt.

I have left the debug modules installed and am ready for the next test
camera-debug-test2.txt (1.9 KB)
camera-debug-test1.txt (1.9 KB)

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.

Interim update — reproduced outside FydeOS, rear sensor can produce a correct image

I’ve spent today testing this on a completely separate Mobian + linux-surface installation from USB. The results have changed the diagnosis quite a lot, so I wanted to post the confirmed findings and correct a couple of things from my earlier testing.

1. The same failure mode reproduces outside FydeOS

Mobian’s stock kernel (7.1.3+deb14-amd64) does not enumerate the cameras on this Surface Go 2, so I installed linux-surface:

6.18.7-surface-1
#1 SMP PREEMPT_DYNAMIC Mon Jan 26 00:16:20 UTC 2026

Userspace is libcamera 0.7.2 with v4l-utils.

The front OV5693 works normally.

The rear OV8865 can reproduce the same CSI-2 errors seen on FydeOS, on receiver port 0 only:

ipu3-cio2 0000:00:14.3: CSI-2 receiver port 0: multiple packet header errors detected
ipu3-cio2 0000:00:14.3: CSI-2 receiver port 0: frame sync error
ipu3-cio2 0000:00:14.3: CSI-2 receiver port 0: DPHY synchronization error

During bad 1280x720 starts I also get effectively empty output buffers: 1,382,400-byte NV12 frames gzip down to about 1,406 bytes, exactly like the empty frames I saw under FydeOS.

So the failure mode itself is not unique to FydeOS.

2. The important result: the rear camera can produce a completely normal image

On the same linux-surface boot, one 1280x720 rear capture came up normally at about 30 fps.

Nine of ten saved frames contained substantial image data rather than zeros. I decoded one of those NV12 buffers with ffmpeg and it was a completely normal, recognisable rear-camera photograph.

Immediately afterwards I ran ten independent captures using the same command. All ten came up bad again, at an impossible ~118–136 fps, and every saved frame was effectively empty.

So on this kernel the same nominal rear configuration can start in at least two states:

  • ~30 fps + genuine correct image
  • ~130 fps + empty buffers

No reboot or kernel/module reload was involved between those tests.

This is the strongest evidence I have that the rear camera hardware is not simply dead. The fault is intermittent/state-dependent between stream starts.

For clarity: I have still never obtained a real rear image under FydeOS itself.

3. Full-resolution mode is correctly programmed, but processed output can be corrupted

Requesting:

cam -c 1 --stream width=1920,height=1080,pixelformat=NV12

makes libcamera select the OV8865’s full 3264x2448 sensor mode.

During a live capture I checked the entire advertised path:

OV8865 sensor pad:
3264x2448 SBGGR10

ipu3-csi2 0 sink:
3264x2448 SBGGR10

ipu3-csi2 0 source:
3264x2448 SBGGR10

CIO2 raw node:
3264x2448
ip3b (10-bit Bayer BGGR IPU3 packed)
Bytes per Line: 4224
Size Image: 10340352

So there is no visible sensor/CSI-2 format mismatch.

I also read the actual OV8865 hardware registers while it was streaming:

0x3808-0x380f:
0x0c 0xc0 0x09 0x90 0x0f 0x30 0x09 0xa6

which decodes to:

output width = 3264
output height = 2448
HTS = 3888
VTS = 2470

X increment:
0x01 0x01

Y increment:
0x01 0x01

format/binning:
0x00 0x40

So the sensor itself really is programmed for native 3264x2448 operation. Stale 1632x1224 binning/subsampling inside the OV8865 is ruled out.

The ~15 fps rate in this mode is also normal. I initially thought this represented a half-rate failure, but that was incorrect: with the native-mode SCLK/HTS/VTS values, ~15 fps is the expected sensor cadence.

Despite all of that, some processed 1920x1080 NV12 frames contain a recognisable scene duplicated side-by-side. One copy appears red-tinted and the other blue-tinted. Decoding only the Y/luma plane still shows the side-by-side duplication in monochrome, so this is not simply an NV12 chroma interpretation problem.

I do not yet know where that duplication is introduced.

4. Direct CIO2 RAW capture also succeeds

I bypassed libcamera/ImgU output and streamed directly from /dev/video10.

CIO2 produced four complete 3264x2448 ip3b RAW frames:

10340352 bytes per frame

with timestamps separated by:

66.690 ms
66.690 ms
66.690 ms

which is again about 15 fps.

The combined file size was exactly:

41361408 bytes

So CIO2 is at least delivering complete full-sized RAW buffers consistently in this state.

I attempted to decode one of those IPU3-packed RAW frames with a small homemade unpacker, but the resulting image was black. I do NOT consider that diagnostic yet because the IPU3 RAW10 packing is unusual and my decoder has not been independently validated. I therefore cannot yet say whether the side-by-side corruption exists in the CIO2 RAW data or is introduced later by ImgU/libcamera.

That is the next boundary I need to establish.

5. Your D-PHY timing instrumentation was accurate

The linux-surface kernel has dynamic debug available, so I enabled the existing CIO2 timing debug output rather than using modified modules.

The live values were:

REAR:
freq ct 0
freq cs 1343
freq dt 0
freq ds 1316

FRONT:
freq ct 0
freq cs 1368
freq dt 0
freq ds 1322

Those exactly match the values your FydeOS instrumented module reported.

So your timing calculations/instrumentation were correct and the linux-surface kernel is configuring the receiver with the same values.

6. Correction regarding the earlier test-pattern test

I also tried setting:

test_pattern=2 (Color bars)

with v4l2-ctl before starting a capture.

The control read back as “Color bars”, but the resulting saved frame was still a real photograph rather than colour bars.

Therefore that particular userspace test did not prove that the sensor was actually outputting its internal pattern.

I don’t want to overstate why yet because the OV8865 runtime-PM/control replay path complicates the interpretation. It also does not necessarily invalidate your custom forced-pattern test if your module wrote the hardware register directly.

For now I am withdrawing my earlier suggestion that the pixel array/optics had definitely been excluded by my userspace test-pattern attempt.

Current conclusion

At this point I think the most defensible conclusions are:

  • the same rear CSI-2 failure mode reproduces on linux-surface 6.18.7;
  • the OV8865 on this physical tablet is capable of producing a completely normal rear image;
  • the failure can change between stream starts without a reboot;
  • full-resolution sensor programming is correct in both the kernel’s media graph and the sensor’s actual registers;
  • the D-PHY timing values match FydeOS exactly;
  • direct CIO2 capture produces complete full-sized RAW buffers at the expected ~15 fps;
  • I have not yet established whether the full-resolution side-by-side corruption already exists in CIO2 RAW or is introduced by ImgU/libcamera.

So I don’t think you need to spend more time building FydeOS-specific debug modules at this stage. The Mobian/linux-surface environment gives me much easier access to dynamic debug, I2C and raw V4L2 capture, so I’ll continue narrowing it there.

If I can determine whether the corruption appears before or after ImgU, or identify a reproducible bad→good transition, I’ll update the issue again.

Thanks again for all the work you’ve already done on this. Several of the measurements from your FydeOS debug build have now been independently reproduced on linux-surface.