cd /news/artificial-intelligence/fairphone-6-wide-camera-experimental… · home topics artificial-intelligence article
[ARTICLE · art-69160] src=nondescriptpointer.com ↗ pub= topic=artificial-intelligence verified=true sentiment=↑ positive

Fairphone 6 wide camera experimental Linux support

Experimental Linux support for the Fairphone 6 wide camera has been achieved by porting the qcom-camss driver to the Qualcomm milos (SM7635) SoC, enabling the OmniVision OV13B10 sensor to capture images for QR scanning. The work, driven by Luca Weiss and heavily assisted by LLM tools, overcomes a key blocker to making the device usable with mainline Linux and postmarketOS, though onboard audio and other cameras remain unsupported.

read11 min views1 publishedJul 22, 2026

Background #

The Fairphone 6 is an interesting target for mainline Linux and postmarketOS for a few reasons:

  • It has relatively modern hardware compared with most other supported postmarketOS devices.
  • Fairphone invests in mainline Linux support, with Luca Weiss driving much of the development.
  • Those efforts have already provided promising initial support for the hardware.
  • Fairphone deliberately allows alternative operating systems.
  • Fairphone aims to support its devices for a long time, so the phone could provide a stable base for future work.

There are still two main blockers to making the device generally usable: onboard audio and camera support. I am working on a project that needs QR scanning, so I used that as an opportunity to see whether I could get at least one camera working. Disclaimer: I heavily relied on LLM assistance to make this work.

Fairphone 6 cameras #

The Fairphone 6 has three cameras:

  • Rear: Sony IMX896
  • Front: Samsung S5KKD1
  • Wide: OmniVision OV13B10

The Sony and Samsung sensors have no mainline Linux drivers, but the OmniVision OV13B10 has a mainline driver. The ultra-wide camera is sufficient for QR scanning.

A phone camera is not a single device. On Qualcomm SoCs, the capture path is a chain:

image sensor  →  CSI-2 D-PHY  →  CSIPHY  →  CSID  →  ISP (VFE/TFE)  →  memory
   (I2C)         (MIPI lanes)   (decode)   (demux)   (write engine)     (DMA)

There are also several supporting components, including a camera clock controller (camcc), the CCI (Qualcomm's dedicated camera I2C controller), power rails, and a VCM (voice-coil motor) for autofocus.

The downstream vendor Android kernel drives all of this with Qualcomm's substantial, proprietary CAMX/cam-kernel stack. On mainline, the equivalent is the much smaller qcom-camss driver, with libcamera providing userspace integration and image processing.

To get the OmniVision camera working, I had to teach qcom-camss about the SoC's specific blocks, describe the hardware in the device tree, enable the sensor driver, and configure libcamera to turn raw Bayer frames into images that applications could use.

Initial exploration #

Before starting the implementation, I wanted to find out how much of the work was new and how much could be ported from hardware that mainline already supported. That would show whether the project was feasible without manufacturer support.

The initial research was encouraging:

  • The FP6's SoC is Qualcomm milos(SM7635). The downstream board is codenamed volcano, and postmarketOS already boots it with a mainline-based kernel fork. - The camera hardware blocks are TFE665(a thin-front-end ISP),** CSID665**, and** CSIPHY v2.2.1**. - The camcc clock controller andCCI are already present in the kernel fork. - The three cameras are: Main: Sony IMX896, with no mainline driver.Ultra-wide: OmniVisionOV13B10, with a mainline driver that supports ACPI/x86 only.** Front:**Samsung S5KKD1, with no mainline driver.

That made the target obvious: the OV13B10 ultra-wide. It is a 13 MP sensor with an existing mainline driver, and its wide field of view is suitable for QR scanning.

When I compared the downstream register headers (cam_tfe665.h

, cam_csiphy_2_2_1_hwreg.h

, and cam_tfe_bus.c

) with mainline qcom-camss

, I found that TFE665 is essentially the same IP as TFE530, which mainline already supports. The register layouts are identical. Only the base offsets of the internal blocks differ, because the write bus lives at a different address. Similarly, the CSID665 RDI registers match the supported CSID, and CSIPHY v2.2.1 belongs to the same three-phase PHY family.

This meant that I could port existing drivers instead of writing everything from scratch.

Step 1: Port the capture subsystem into the kernel #

qcom-camss

was not yet available on milos, so I had to connect the existing pieces.

TFE665 ISP driver

I added a new camss-vfe-665.c

, modelled directly on the mainline TFE530 driver (camss-vfe-340.c

). Because the register contents are identical, the driver logic is the same. The main difference is that milos places the ISP control block and write-bus block at different offsets. The bus register base moved from 0xa00

to 0x1800

, and encapsulating that offset shift accounted for most of the work.

CSID665 and CSIPHY v2.2.1

CSID665 reused the existing gen-2 CSID operations because its RDI register offsets were identical.CSIPHY v2.2.1 needed a new lane-configuration table and D-PHY tuning values for this revision. I transcribed the lane register sequence and the approximately 1.1 Gbps/lane ("500 Msps") data-rate and AFE settings from the downstreamcam_csiphy_2_2_1_hwreg.h

. The PHY's register window was at offset0x1000

.

Resources, compatible, and device tree

In camss.c

, I described milos's capture complex: four CSIPHYs, three CSIDs, three TFEs, their clocks, interconnects, and power domains. I also registered a new qcom,milos-camss

compatible. In the device tree, I added the camss@ac13000

node with its registers, IRQs, clocks, interconnects, IOMMUs, GDSC, and CSI input ports, along with the MCLK and reset pinctrl states.

Missing register-bus clock

I ran into a problem at this point. The driver bound, but reading the TFE hardware-version register returned 0x0

, and reset timed out as if the block were not powered. The fix was a clock that was not obviously an ISP clock: ** CAM_CC_SOC_AHB_CLK**. It gated the AHB register bus used by the whole camera complex, including the CCI. Without it, register accesses silently returned zero. I added that clock and the CAMNOC AXI clocks, then set the CAMNOC data-path clock rate. The ISP came alive with

hw_version = 0x30000000

.This suggests that, on Qualcomm hardware, a block that reads as zero may be missing a clock or power domain on its access path.

Step 2: Bring up the OV13B10 sensor #

The mainline ov13b10

driver exists, but it is written for x86/ACPI laptops and only matches through ACPI. I made changes in two places.

Making the driver usable on ARM and device tree

  • I added an OpenFirmware match table(ovti,ov13b10

) so the driver could probe from the device tree. - I described the sensor in milos-fairphone-fp6.dts

. It lives on the CCI I2C bus at address0x36

, needsMCLK1 at 19.2 MHz, a reset GPIO, and power rails. For this experimental bring-up, I simply forced the regulators on instead of fully sequencing them.

2 small details

Lane numbering. The downstream device tree useddata-lanes = <1 2 3 4>

, while mainline's convention iszero-indexed:<0 1 2 3>

. With the wrong numbering, CSIPHY programmed a lane mask with lane 0 missing, and the PHY never locked.Which CSIPHY? The FP6 wiring routed the ultra-wide camera toCSIPHY1. Getting this from the downstream device tree instead of guessing saved a lot of trial and error.

At this point, the sensor probed successfully, the chip ID was read correctly over I2C, CSIPHY reported lane activity, and the ISP produced frames that were completely black.

Step 3: The all-zero frames mystery #

This was the main bug. Frames arrived at the right rate and size, and buf_done

fired, but every pixel was zero. That was also true when I enabled the sensor's internal colour-bar test pattern, which was generated inside the sensor and should have appeared regardless of the scene. The data path delivered frame timing, but not pixel data.

The kernel log provided a clue: VFE0: Bad config violation

. The ISP's write engine reported a consumer/config violation once per frame because the write master's configuration did not match the incoming data.

When I dug into the downstream cam_tfe_bus.c

, the difference became clear. The RDI write-master's packer format depends on the ISP bus width:

  • TFE530 (qcm2290, the target of the mainline driver) has a 64-bit RDI bus, so it uses packer0xa

. TFE665 (milos) has a 128-bit RDI bus, so it needs packer0x0

.

The mainline driver hard-coded the 64-bit value. On milos, that was wrong, and the write engine refused to write the pixels. Changing the register value from PLAIN64 (0xa)

to 0x0

turned the all-zero frames into real images: the maximum pixel value was 255, the full colour-bar pattern appeared, and the violations stopped.

That was the point at which the camera physically worked: OV13B10 → CSIPHY → CSID → TFE665 → DDR, delivering genuine Bayer frames.

Step 4: Raw Bayer to libcamera #

Raw frames were enough for a QR script, but a usable camera needs libcamera, the userspace framework used by modern Linux camera applications. libcamera 0.7.2 already recognises the milos qcom-camss

graph through its generic simple pipeline handler. Its software ISP debayered the raw frames to RGB, with GPU acceleration on the Adreno through EGL, at approximately 30 fps out of the box. cam

and GStreamer's libcamerasrc

produced clean images immediately.

Auto-exposure was the part that did not work. Images were dark or overly bright, and the log reported Failed to create camera sensor helper for ov13b10

. libcamera's software auto-exposure and gain loop needed a small per-sensor helper to map the sensor's gain register value to a real gain multiplier. libcamera includes helpers for several sensors, but not this one.

The fix was small. The OV13B10's analogue gain is linear, with 0x80 = 1×

, or gain = code / 128

, which is the same behaviour as several other OmniVision sensors. I added a class to libcamera's sensor-helper database:

class CameraSensorHelperOv13b10 : public CameraSensorHelper {
public:
    CameraSensorHelperOv13b10() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; }
};
REGISTER_CAMERA_SENSOR_HELPER("ov13b10", CameraSensorHelperOv13b10)

The auto-exposure loop started converging immediately. I also added an entry to libcamera's sensor-properties database and backported the sensor driver's V4L2 crop and get_selection

support, which libcamera uses for pixel-array geometry. After that, GStreamer and PipeWire received a properly exposed feed. Once I installed the PipeWire libcamera plugin, the camera appeared in desktop applications as a normal video source.

The images are still somewhat overexposed in some situations, so the helper might need further work.

Step 5: Focus, orientation, and resolution issues #

I still had a few issues to fine-tune.

Autofocus

The ultra-wide camera has a voice-coil motor (VCM) for focus. The downstream device tree suggests that it is an Awinic AW86017. After powering the autofocus rail, which is controlled by a GPIO-driven regulator, and probing the CCI I2C bus, I found a device at address 0x0c

that spoke the de facto standard DW9714 10-bit DAC protocol. The mainline ** dw9714** driver drives it directly.

A focus sweep confirmed that the lens moved physically and that sharpness peaked clearly at a middle setting. libcamera associated the lens with the sensor through a lens-focus

device-tree link.

Unfortunately, libcamera's software ISP has no autofocus algorithm. Rather than leave the lens parked at infinity and produce blurry close-ups, I set a sensible fixed focus tuned for arm's-length QR scanning and documents using a small udev rule. The setting could be adjusted as needed, or the focus could be controlled from software.

Orientation

The sensor is mounted at an angle relative to the phone's portrait orientation, so previews initially appeared sideways. Setting the device-tree rotation and orientation properties allowed libcamera-aware applications to display the feed upright.

Binned modes

The OV13B10 driver advertised several resolutions, including 2×2-binned half-resolution modes. On this CAMSS path, the binned modes produced garbled, sheared output. Their readout geometry did not match the declared frame size, while full-resolution and a couple of other modes were clean.

Rather than ship a broken preview, I disabled the two broken modes so libcamera would select only working ones. The trade-off is that, because the software ISP crops instead of scaling, a 1080p preview is a centre crop of a larger mode. It is slightly zoomed in rather than showing the full ultra-wide field of view.

The result is a live, upright, auto-exposed, in-focus preview in GNOME Camera / Snapshot, and QR scanning works.

Sources #

The patches and related files needed to reproduce these results are available in this repository:

github.com/nondescriptpointer/fairphone6-wide-camera-linux

Limitations #

This is an experimental start, not production camera support.

Only the ultra-wide (OV13B10) works. The main (IMX896) and front (S5KKD1) sensors have no mainline drivers.There is no autofocus. libcamera's software ISP has no contrast-AF loop, so focus is a fixed compromise position. Proper autofocus would require an AF algorithm in the software ISP or an app-side loop to drive the VCM.The preview field of view is cropped at common resolutions because the 2×2-binned sensor modes are broken on this path and the software ISP crops instead of scaling. Fixing the binned-mode register sequences, or adding a scaler, would restore the full ultra-wide field of view.Power sequencing is simplified. Regulators are forced on instead of being sequenced, and the VCM draws a little idle current while held at a fixed focus position. This is fine for experimentation, but needs proper power management for daily use.The ISP is software-only. All debayering, auto-exposure, and auto-white-balance processing happens in software, with GPU assistance. It works well for QR scanning and casual use, but does not provide the image quality of a tuned hardware ISP pipeline.- I did not spend time optimising image quality. Further optimisation is likely needed to get better photos from the sensor.

Next steps #

  • Fix the binned modes to restore the full field of view.
  • Write proper power sequencing.
  • Prepare the parts that are ready for upstreaming.
── more in #artificial-intelligence 4 stories · sorted by recency
── more on @fairphone 6 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/fairphone-6-wide-cam…] indexed:0 read:11min 2026-07-22 ·