The Surface 2 has two cameras, both on the I2C3 bus:
| Rear | Front | |
|---|---|---|
| Sensor | OmniVision OV5693, 5 MP | onsemi AR0330, 3.4 MP |
| I²C | 0x36 | 0x10 |
| Port | CSI-A, 2 lanes | CSI-B, CIL-C brick, 2 lanes, RAW10 |
| Clock | 24 MHz | 24 MHz |
At the start there was nothing: Linux had no VI/CSI driver for Tegra 114, ov5693 was ACPI-only at 19.2 MHz, and mainline had no AR0330 driver at all.
Reading the topology
The camera topology block sits right in ACPI, in \_SB.GFXC.TCBB. It gives the camera and sensor counts, and the CsiC x2 string for the front camera later became the key hint. The clock comes from vi_sensor (408/17 = 24 MHz) with gate csus, on pin cam_mclk_pcc0, function vi_alt3.
The video input driver
We started from the Tegra 20/30 series and checked registers against NVIDIA's 3.4 kernel sources and the L4T R17 userspace camera library. OV5693 gained a device tree binding and a 24 MHz PLL setup, and AR0330 was ported from the GPL-2.0 Rockchip driver to the current API.
The rear camera delivered its first frames fairly quickly, and a real image appeared the same day.
The front camera mystery
The sensor answered on I²C, initialised and turned on its test pattern, yet not one packet reached the pixel parser. We went through everything obvious:
- the
MIPI_PAD_CTRLregister: it does not exist on T114; - a soft reset and the Rockchip init sequence;
- calibration masks
0x180and0x60: calibration passes, no data; - the camera block GPIOs: one turned out to be the backlight, another an interrupt.
The answer was in NVIDIA's libnvmm_camera.so. On Tegra 114, port CSI-B is laid out differently from Tegra 30: it takes its lanes from a new CIL-C brick, not CIL-B. Here CIL-B is only the upper half of a four-lane CSI-A.
/*
* CSI-A port = PPA fed by CIL-A (brick A; x4 = CIL-A + CIL-B)
* CSI-B port = PPB fed by CIL-C (brick C; x4 = CIL-C + CIL-D) <-- not CIL-B!
* CSI-E port = PPB fed by CIL-E
* PHY_CIL_COMMAND (0x868): A [1:0], B [17:16], C [21:20], D [25:24], E [29:28]
* PHY_CILx_CONTROL0: A 0x86c, B 0x870, C 0x8f8, D 0x8fc, E 0x900
* pad config (calibration RMW): A 0x894, B 0x89c, C 0x928, D 0x930, E 0x938
*/That was not all. UEFI leaves the CIL-C pads powered down: 0x928 reads 0x00020007, where the low three bits disable two data lanes and the clock lane. With those bits cleared, frames arrived without a single error.
/*
* Bits [2:0] of the C/D/E pad config look like per-lane power-downs
* (CIL-C read 0x00020007 after UEFI = 2 data + clock lane, CIL-E 0x5 = 1 data
* + clock lane). NVIDIA x4 writes 0x928 = 0x00020000 (bits cleared).
*/
static unsigned int ppb_pad_clr = 0x7;One more trap: calibration must use mask 0x004 (CSIC only). Mask 0x00c (C and D) times out, because the CSID pads stay powered down in two-lane mode.
Orientation and colour
The image came out upside down. Flipping through sensor register 0x3820 broke the CSI stream, so we flip in VI instead: VI_OUTPUT_V_DIRECTION, bit 20, writing from the last line. A one-line sensor offset preserves BGGR order. The modules are mounted differently, so each channel has its own setting:
static bool t114_vflip(struct tegra_vi_channel *chan)
{
return chan->portnos[0] == 1 ? out_vflip_ch1 : out_vflip;
}Linux has no hardware ISP here, so libcamera's software ISP handles raw Bayer. We tuned a colour correction matrix to remove the green and blue casts.
Reaching applications
libcamera handles simple pipelines and only needs the new device declared:
static const SimplePipelineInfo supportedDevices[] = {
{ "qcom-camss", {}, true },
{ "sun6i-csi", {}, false },
{ "tegra-video", {}, true },
};The driver must run in media controller mode: tegra-video mc_api=1. On top sits a service that creates PipeWire nodes and starts a camera only on demand. Older programs use v4l2loopback:
| Device | What it is |
|---|---|
/dev/video20 | Rear camera, 720p YUYV 30 fps |
/dev/video21 | Rear camera, 1080p NV12 |
/dev/video22 | Front camera, 1152×648 |
Tested in GNOME Snapshot and Chromium. An idle camera uses no CPU and stops 3 seconds after the last client leaves.
Limits
- All debayering runs on the CPU: 1080p takes about 1.3 cores.
- Only one camera can stream at a time.
cma=384Mis required, otherwise streaming runs out of contiguous memory.- Video recording is limited because there is no hardware encoder.