메인라인 리눅스와 postmarketOS 환경에서 페어폰 6의 초광각 카메라 센서(OmniVision OV13B10)를 작동시키기 위한 실험적 개발이 진행되었습니다. 개발자는 기존 리눅스 커널 드라이버(qcom-camss)를 포팅하여, 제조사의 독자적인 지원 없이도 QR 코드 스캔 등의 기본적인 카메라 기능이 작동함을 확인했습니다. 이는 수리 가능하고 오래 지원되는 페어폰이 대체 운영체제에서도 확장성을 가질 수 있음을 보여줍니다.
번역된 본문
배경
Fairphone(페어폰) 6은 몇 가지 이유로 메인라인 Linux 및 postmarketOS의 매력적인 타겟입니다. 이 기기는 지원되는 대부분의 다른 postmarketOS 기기들에 비해 비교적 최신 하드웨어를 탑재하고 있습니다. Fairphone은 메인라인 Linux 지원에 투자하고 있으며, Luca Weiss가 이 개발을 주도하고 있습니다. 이러한 노력 덕분에 이미 하드웨어에 대한 유망한 초기 지원이 제공되었습니다. 또한 Fairphone은 대체 운영체제의 설치를 의도적으로 허용합니다. Fairphone은 기기를 장기간 지원하는 것을 목표로 하므로, 이 폰은 향후 작업을 위한 안정적인 기반이 될 수 있습니다. 하지만 이 기기를 일반적으로 사용 가능하게 만드는 데에는 여전히 두 가지 주요 장애물이 있습니다. 바로 내장 오디오와 카메라 지원입니다. 필자는 QR 스캔이 필요한 프로젝트를 진행 중이었기 때문에, 이것을 기회 삼아 최소한 하나의 카메라라도 작동시킬 수 있는지 알아보기로 했습니다.
면책 조항: 이 작업을 성공시키기 위해 LLM(대형 언어 모델)의 도움을 크게 받았습니다.
Fairphone 6 카메라
Fairphone 6에는 세 개의 카메라가 있습니다:
후면: Sony IMX896
전면: Samsung S5KKD1
와이드(광각): OmniVision OV13B10
Sony 및 Samsung 센서는 메인라인 Linux 드라이버가 없지만, OmniVision OV13B10은 메인라인 드라이버가 존재합니다. 이 초광각 카메라는 QR 스캔 용도로는 충분합니다.
휴대폰 카메라는 단일 장치가 아닙니다. Qualcomm SoC에서 캡처 경로는 하나의 사슬(Chain)과 같습니다: 이미지 센서 → CSI-2 D-PHY → CSIPHY → CSID → ISP (VFE/TFE) → 메모리 (I2C) (MIPI lanes) (decode) (demux) (write engine) (DMA)
또한 카메라 클럭 컨트롤러(camcc), CCI(Qualcomm의 전용 카메라 I2C 컨트롤러), 전원 레일(파워 레일), 그리고 자동 초점을 위한 VCM(보이스 코일 모터) 등 여러 지원 구성 요소가 있습니다. 다운스트림 공급업체인 Android 커널은 Qualcomm의 방대한 독점 CAMX/cam-kernel 스택을 통해 이 모든 것을 구동합니다. 메인라인에서 그에 해당하는 것은 훨씬 작은 qcom-camss 드라이버이며, libcamera가 사용자 공간 통합 및 이미지 처리를 제공합니다.
OmniVision 카메라를 작동시키기 위해, 필자는 qcom-camss에 SoC의 특정 블록이 어떤 것인지 알려주고, 디바이스 트리에 하드웨어를 설명하며, 센서 드라이버를 활성화하고, libcamera를 구성하여 원시 Bayer 프레임을 애플리케이션이 사용할 수 있는 이미지로 변환하도록 해야 했습니다.
초기 탐색
구현을 시작하기 전에, 이 작업 중 얼마나 많은 부분이 새로운 작업이고, 얼마나 많은 부분을 메인라인에서 이미 지원하는 하드웨어로부터 포팅할 수 있는지 알고 싶었습니다. 그것은 제조사의 지원 없이도 프로젝트가 실현 가능한지를 보여줄 것입니다.
초기 연구는 고무적이었습니다.
FP6의 SoC는 Qualcomm milos(SM7635)입니다.
다운스트림 보드의 코드명은 volcano이며, postmarketOS는 이미 메인라인 기반 커널 포크를 사용하여 이를 부팅합니다.
카메라 하드웨어 블록은 TFE665(thin-front-end ISP), CSID665, CSIPHY v2.2.1입니다.
camcc 클럭 컨트롤러와 CCI는 이미 커널 포크에 존재합니다.
세 가지 카메라는 다음과 같습니다:
메인: Sony IMX896, 메인라인 드라이버 없음.
초광각: OmniVision OV13B10, ACPI/x86만 지원하는 메인라인 드라이버가 존재함.
전면: Samsung S5KKD1, 메인라인 드라이버 없음.
이를 통해 타겟이 분명해졌습니다. 바로 OV13B10 초광각 카메라입니다. 이는 기존 메인라인 드라이버가 있는 13MP 센서이며, 넓은 시야각은 QR 스캔에 적합합니다.
다운스트림 레지스터 헤더(cam_tfe665.h, cam_csiphy_2_2_1_hwreg.h, cam_tfe_bus.c)를 메인라인 qcom-camss와 비교했을 때, TFE665가 메인라인에서 이미 지원하는 TFE530과 본질적으로 동일한 IP라는 것을 발견했습니다. 레지스터 레이아웃은 동일합니다. 쓰기 버스가 다른 주소에 있기 때문에 내부 블록의 기본 오프셋만 다릅니다. 이와 유사하게 CSID665 RDI 레지스터는 지원되는 CSID와 일치하며, CSIPHY v2.2.1은 동일한 3상 PHY 제품군에 속합니다. 이것은 필자가 모든 것을 처음부터 작성하는 대신 기존 드라이버를 포팅할 수 있음을 의미했습니다.
1단계: 캡처 하위 시스템을 커널로 포팅
qcom-camss는 아직 milos에서 사용할 수 없었기 때문에, 기존 조각들을 연결해야 했습니다.
TFE665 ISP 드라이버
메인라인 TFE530 드라이버(camss-vfe-340.c)를 직접 모델로 삼아 새로운 camss-vfe-665.c를 추가했습니다. 레지스터 내용이 동일하기 때문에 드라이버 로직도 동일합니다. 주요 차이점은 mil...
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 and CCI are already present in the kernel fork. The three cameras are: Main: Sony IMX896, with no mainline driver. Ultra-wide: OmniVision OV13B10 , 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 downstream cam_csiphy_2_2_1_hwreg.h . The PHY's register window was at offset 0x1000 . 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 address 0x36 , needs MCLK1 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 used data-lanes = <1 2 3 4> , while mainline's convention is zero-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 to CSIPHY1 . 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 packer 0xa . TFE665 (milos) has a 128-bit RDI bus, so it needs packer 0x0 . 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