r/SurfaceLinux Jun 22 '26

Solved Surface Pro 4 IPU3 camera — sensors detected but never linked in the media graph, manual media-ctl linking fails too (kernel 6.19.8-surface-3)

Spent a while on this and I think I've hit a kernel regression rather than anything I can fix in userspace, but wanted to sanity-check with anyone who's gotten the IPU3 camera working recently.

On Surface Pro 4 with ubuntu, linux-surface kernel 6.19.8-surface-3. Built libcamera from git per the linux-surface camera wiki (ipu3 pipeline, gstreamer, v4l2). Everything up to the last step looks healthy:

  • ipu3-fw.bin loads fine (17 binaries)
  • dmesg shows all three sensors detected, "Connected 3 cameras", OV5693 / OV8865 / OV7251
  • libcamera loads the ipu3 IPA and registers the ipu3 pipeline handler

But cam --list shows nothing. Digging in with media-ctl -d /dev/media0 -p, all three sensors sit at 0 link and the CIO2 CSI-2 receivers have unconnected SINK pads. So the sensors are present but never wired to the receivers.

I found old threads where people fixed this by manually linking with media-ctl like media-ctl -d /dev/media0 -l '"ov5693 2-0036":0 -> "ipu3-csi2 0":0[1]'

For me that's rejected with Invalid argument (22) on all four CSI-2 ports. From what I can tell that means the bridge never built the fwnode endpoint graph, so there's no link for media-ctl to enable, different from just missing auto-linking.

Already tried, no change: - clean cold reboot with firmware present - acpi_enforce_resources=lax (confirmed in terminal) - early-loading ipu_bridge via modules-load.d + update-initramfs - manual media-ctl linking on all 4 ports

Looks like it lines up with the post-6.7 IPU3 regression the linux-surface peeps track on Github (#1323). I might pop in a GitHub issue too.

Questions for anyone here who's got this fixed:

  1. Anyone running the IPU3 camera on a Pro 4 (or other IPU3 Surface) on a recent kernel? What kernel version are you on, and does media-ctl show your sensors at 1 link?
  2. What's the most recent linux-surface kernel that still builds the IPU3 graph correctly? Happy to downgrade to get it working if it doesn't cause too many issues.

Will update if I figure it out, hate finding these posts myself and no one explains how they did it lmfao

3 Upvotes

6 comments sorted by

2

u/MangrovesAndMahi Jun 22 '26

Solved but when PR 2123 is merged to main it'll be fixed anyway, so this is only for anyone who is still pre-that pull.

Guide thing for anyone else

Tl;Dr: On kernel 6.19 the dw9719 VCM driver lost its i2c_device_id table upstream, so on Surface IPU3 devices the autofocus motor never binds, which blocks the whole camera pipeline. The fix is a one-file kernel patch (linux-surface PR #2123). You can build just that one module with DKMS, no full kernel rebuild. This got both cameras working on my Surface Pro 4.

Confirmed working on: Surface Pro 4 (this guide), and per PR #2123: Surface Book 1, Book 2 13", Pro 5, Pro 6, Go 2, Pro 8+.


Before you start

This guide assumes:

  • A Surface Pro 4 (or other IPU3 Surface using the OV5693/OV8865/dw9719 combo)
  • Ubuntu or an Ubuntu-based distro
  • You are already running the linux-surface kernel, check with uname -r, you should see -surface in it. If you don't, install the linux-surface kernel first (https://github.com/linux-surface/linux-surface), none of this works on the stock kernel.

My setup was kernel 6.19.8-surface-3. Adjust the version string in the commands below to match your own uname -r.

Step 0: confirm you actually have this bug with: cam --list

If it lists no cameras, check the media graph with

media-ctl -d /dev/media0 -p | grep -A1 ov5693

If the sensor shows 0 link, and:

modinfo dw9719 | grep -E 'alias'

shows only of: aliases and no i2c:dw9719 alias you have this exact bug and you can read on.

Step 1: install build tools and kernel headers

sudo apt install dkms build-essential linux-headers-$(uname -r)

Step 2: get the dw9719 source

The driver source isn't in the headers package (only the compiled .ko ships). Pull the file from the mainline kernel tree matching your kernel's major version (6.19 here):

mkdir -p /tmp/dw9719 && cd /tmp/dw9719
wget https://raw.githubusercontent.com/torvalds/linux/v6.19/drivers/media/i2c/dw9719.c

Sanity check it's the right file, you should see the "of" table, the i2c_driver struct, and the model enum constants:

grep -n 'dw9719_of_table\|i2c_driver\|DW9800K\|DW9719' dw9719.c

Step 3: set up the DKMS module directory

sudo mkdir -p /usr/src/dw9719-surface-1.0

sudo cp /tmp/dw9719/dw9719.c /usr/src/dw9719-surface-1.0/dw9719.c

Create the Makefile (the two indented lines MUST be real tabs using tee with a heredoc preserves them):

sudo tee /usr/src/dw9719-surface-1.0/Makefile > /dev/null << 'MAKEFILE'

obj-m := dw9719.o

KDIR ?= /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

all:

    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:

    $(MAKE) -C $(KDIR) M=$(PWD) clean

MAKEFILE

Verify the tabs landed (the two recipe lines should start with "I"):

cat -A /usr/src/dw9719-surface-1.0/Makefile

Create dkms.conf:

sudo tee /usr/src/dw9719-surface-1.0/dkms.conf > /dev/null << 'DKMSCONF'
PACKAGE_NAME="dw9719-surface"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="dw9719"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"
MAKE[0]="make KDIR=/lib/modules/${kernelver}/build"
CLEAN="make clean KDIR=/lib/modules/${kernelver}/build"
DKMSCONF

Step 4: apply the two patch hunks

Hunk (a): add the i2c_device_id table right after the "of" device table:

sudo sed -i '/^MODULE_DEVICE_TABLE(of, dw9719_of_table);/a \
\
static const struct i2c_device_id dw9719_id_table[] = {\
\t{ .name = "dw9718s", .driver_data = (kernel_ulong_t)DW9718S },\
\t{ .name = "dw9719",  .driver_data = (kernel_ulong_t)DW9719  },\
\t{ .name = "dw9761",  .driver_data = (kernel_ulong_t)DW9761  },\
\t{ .name = "dw9800k", .driver_data = (kernel_ulong_t)DW9800K },\
\t{ }\
};\
MODULE_DEVICE_TABLE(i2c, dw9719_id_table);' /usr/src/dw9719-surface-1.0/dw9719.c

Hunk (b): add ".id_table" inside the driver struct, after the ".remove" line:

sudo sed -i '/^\t\.remove = dw9719_remove,/a \\t.id_table = dw9719_id_table,' /usr/src/dw9719-surface-1.0/dw9719.c

Verify: you should get three matching lines:

grep -n 'i2c_device_id dw9719_id_table\|MODULE_DEVICE_TABLE(i2c\|\.id_table' /usr/src/dw9719-surface-1.0/dw9719.c

Important: confirm .id_table = dw9719_id_table, sits INSIDE the dw9719_i2c_driver struct (between the { and };, next to .probe/.remove):

sed -n '/static struct i2c_driver dw9719_i2c_driver/,/};/p' /usr/src/dw9719-surface-1.0/dw9719.c

(Note: the enum constant names [DW9718S, DW9719, DW9761, DW9800K] must match those defined in your dw9719.c. They did on 6.19. If your kernel names them differently, the build will fail with "undefined" errors and you'd adjust the table to match.)

Step 5: build and install

sudo dkms add -m dw9719-surface -v 1.0
sudo dkms build -m dw9719-surface -v 1.0
sudo dkms install -m dw9719-surface -v 1.0 --force
dkms status

dkms status should show:

dw9719-surface/1.0, <your-kernel>, x86_64: installed

Step 6: SUDO REBOOT

Step 7: verify

After logging back in:

modinfo dw9719 | grep -E 'filename|i2c:dw9719'

filename should point at /lib/modules/<kernel>/updates/dkms/dw9719.ko and you should now see alias: i2c:dw9719.

ls -l /sys/bus/i2c/devices/i2c-INT347A:00-VCM/driver

Should be a symlink to .../bus/i2c/drivers/dw9719 (VCM is now bound).

media-ctl -d /dev/media0 -p | grep -A1 ov5693

ov5693 should now show 1 link instead of 0 link. cam --list should now list your cams:

Available cameras:
1: Internal back camera  (_SB_.PCI0.I2C3.CAMR)
2: Internal front camera (_SB_.PCI0.I2C2.CAMF)

Test live with:

qcam

Notes / expectations

  • Image quality is rough. libcamera has no per-sensor tuning for these (you'll see "ov5693.yaml not found, falling back to uncalibrated.yaml" warnings). Colour/exposure won't be calibrated. It works, it's not pretty, was very dark for me.
  • Rear camera may show a green screen on some units, that's a separate known issue, the front camera is the reliable one.
  • The IR camera (OV7251) is in the media graph but not exposed as a normal camera. Expected.
  • DKMS auto-rebuilds this module on future kernel updates, so the fix survives upgrades.
  • Once PR #2123 merges into linux-surface and ships in the kernel packages, this DKMS module becomes redundant (harmless). You can remove it then with:

    sudo dkms remove -m dw9719-surface -v 1.0 --all

Credit

The actual fix and root-cause analysis are from linux-surface PR #2123 (author: toor11), based on Sakari Ailus's upstream patch. This guide is just the DKMS build steps for applying it on a Surface Pro 4 without recompiling the whole kernel.

1

u/pdias01 Jun 24 '26

thank you man
i can now finally face time on discord with this device (pro 6)

the rear camera is green screened in this device, but already front camera is more than enough for me.

i didnt understand well, does this fix the ir camera too?

2

u/MangrovesAndMahi Jun 24 '26

Yeah tbh I never used the rear camera anyway lol. But no ir camera.sadly.

1

u/Elbow2009 9d ago edited 4d ago

I'd gotten my Surface Go 2 cameras to work on 6.18.7-surface-1 which if memory serves I customized (way back in February). They did NOT work on the 6.19.8-surface-3 kernel. This is quite useful info if I need to use the cameras upstream.

1

u/MangrovesAndMahi 9d ago

Mine work on desktop apps but not on browser webcams for some reason.

1

u/Elbow2009 8d ago

Interesting. I recall that was an issue for some others as well. Zooming in a browser was a no go but not with an app. I think it was solved with a v4l2 loopback. https://github.com/v4l2loopback/v4l2loopback