مینو
په Microsoft Surface 2 کې لینکس

په Tegra 114 کې کمرې

A VI/CSI driver from scratch, the AR0330 port and why the front camera sent nothing.

د مقالې بشپړ متن اوس مهال په انګلیسي شته.

The Surface 2 has two cameras, both on the I2C3 bus:

RearFront
SensorOmniVision OV5693, 5 MPonsemi AR0330, 3.4 MP
I²C0x360x10
PortCSI-A, 2 lanesCSI-B, CIL-C brick, 2 lanes, RAW10
Clock24 MHz24 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_CTRL register: it does not exist on T114;
  • a soft reset and the Rockchip init sequence;
  • calibration masks 0x180 and 0x60: 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:

DeviceWhat it is
/dev/video20Rear camera, 720p YUYV 30 fps
/dev/video21Rear camera, 1080p NV12
/dev/video22Front 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=384M is required, otherwise streaming runs out of contiguous memory.
  • Video recording is limited because there is no hardware encoder.