Fairphone 6 초광각 카메라의 실험적인 Linux 지원
요약
Fairphone 6 기기에서 메인라인 Linux 및 postmarketOS를 위한 초광각 카메라(OmniVision OV13B10) 지원 실험 과정을 다룹니다. Qualcomm SoC 환경에서 libcamera와 qcom-camss 드라이버를 활용해 카메라 하드웨어를 활성화하는 기술적 과정을 설명합니다.
핵심 포인트
- Fairphone 6는 메인라인 Linux 지원에 우호적인 하드웨어 구조를 가짐
- OmniVision OV13B10 센서는 메인라인 드라이버가 존재하여 구현 가능
- Qualcomm SoC의 복잡한 카메라 캡처 경로(CSI-2, ISP 등) 이해 필요
- libcamera와 qcom-camss 드라이버를 통한 사용자 공간 통합 과정 기술
배경 (Background)
Fairphone 6는 몇 가지 이유로 메인라인 Linux (mainline Linux) 및 postmarketOS를 위한 흥미로운 대상입니다:
- 지원되는 대부분의 다른 postmarketOS 기기들과 비교했을 때 상대적으로 현대적인 하드웨어를 갖추고 있습니다.
- Fairphone은 메인라인 Linux 지원에 투자하고 있으며, Luca Weiss가 개발의 상당 부분을 주도하고 있습니다.
- 이러한 노력은 이미 하드웨어에 대한 유망한 초기 지원을 제공했습니다.
- Fairphone은 의도적으로 대안 운영체제를 허용합니다.
- Fairphone은 자사 기기를 오랫동안 지원하는 것을 목표로 하므로, 이 전화기는 향후 작업을 위한 안정적인 기반을 제공할 수 있습니다.
이 기기를 일반적으로 사용 가능하게 만드는 데에는 여전히 두 가지 주요 장애물이 있습니다: 온보드 오디오 및 카메라 지원입니다. 저는 QR 스캔이 필요한 프로젝트를 진행 중이어서, 이를 계기로 최소한 카메라 하나라도 작동시킬 수 있는지 확인해 보았습니다. 면책 조항: 저는 이를 구현하기 위해 LLM (Large Language Model)의 도움에 크게 의존했습니다.
Fairphone 6 카메라
Fairphone 6에는 세 개의 카메라가 있습니다:
- 후면: Sony IMX896
- 전면: Samsung S5KKD1
- 초광각 (Wide): OmniVision OV13B10
Sony 및 Samsung 센서는 메인라인 Linux 드라이버가 없지만, OmniVision OV13B10은 메인라인 드라이버를 가지고 있습니다. 초광각 카메라는 QR 스캔을 하기에 충분합니다.
휴대폰 카메라는 단일 장치가 아닙니다. Qualcomm SoC에서는 캡처 경로가 다음과 같은 체인 형태로 구성됩니다:
이미지 센서 (image sensor) → CSI-2 D-PHY → CSIPHY → CSID → ISP (VFE/TFE) → 메모리 (memory)
(I2C) (MIPI lanes) (decode) (demux) (write engine) (DMA)
또한 카메라 클록 컨트롤러 (camcc), CCI (Qualcomm의 전용 카메라 I2C 컨트롤러), 전원 레일 (power rails), 그리고 오토포커스를 위한 VCM (voice-coil motor)을 포함한 여러 지원 구성 요소가 있습니다.
다운스트림 벤더 Android 커널은 Qualcomm의 방대한 독점 CAMX/cam-kernel 스택을 사용하여 이 모든 것을 구동합니다. 메인라인에서는 이에 상응하는 훨씬 작은 규모의 qcom-camss 드라이버가 있으며, libcamera가 사용자 공간 (userspace) 통합 및 이미지 프로세싱을 제공합니다.
OmniVision 카메라를 작동시키기 위해, 저는 qcom-camss에 SoC의 특정 블록들을 알려주고, 디바이스 트리 (device tree)에 하드웨어를 기술하며, 센서 드라이버를 활성화하고, libcamera가 raw Bayer 프레임을 애플리케이션에서 사용할 수 있는 이미지로 변환하도록 구성해야 했습니다.
초기 탐색
구현을 시작하기 전에, 작업량 중 어느 정도가 새로운 작업이고 어느 정도가 메인라인 (mainline)에서 이미 지원하는 하드웨어로부터 포팅 (port)될 수 있는지 확인하고 싶었습니다. 이를 통해 제조사의 지원 없이도 프로젝트가 실현 가능한지 판단할 수 있기 때문입니다.
초기 조사 결과는 고무적이었습니다:
- FP6의 SoC는 Qualcomm milos (SM7635)입니다. 다운스트림 (downstream) 보드의 코드네임은 volcano이며, postmarketOS는 이미 메인라인 기반의 커널 포크 (kernel fork)를 통해 이를 부팅합니다. - 카메라 하드웨어 블록은 TFE665 (thin-front-end ISP), CSID665, 그리고 CSIPHY v2.2.1입니다. - **camcc 클록 컨트롤러 (clock controller)**와 CCI는 이미 커널 포크에 존재합니다. - 세 개의 카메라는 다음과 같습니다: 메인 (Main): Sony IMX896, 메인라인 드라이버 없음. 초광각 (Ultra-wide): OmniVision OV13B10, ACPI/x86만 지원하는 메인라인 드라이버 있음. 전면 (Front): Samsung S5KKD1, 메인라인 드라이버 없음.
이로 인해 목표가 명확해졌습니다: 바로 OV13B10 초광각 카메라입니다. 이는 기존 메인라인 드라이버가 존재하는 13 MP 센서이며, 넓은 시야각을 가지고 있어 QR 스캔에 적합합니다.
다운스트림 레지스터 헤더 (cam_tfe665.h, cam_csiphy_2_2_1_hwreg.h, cam_tfe_bus.c)를 메인라인 qcom-camss와 비교했을 때, TFE665는 본질적으로 메인라인이 이미 지원하는 TFE530과 동일한 IP라는 것을 발견했습니다. 레지스터 레이아웃 (register layouts)은 동일합니다. 쓰기 버스 (write bus)가 다른 주소에 위치하기 때문에 내부 블록의 베이스 오프셋 (base offsets)만 다를 뿐입니다. 마찬가지로, CSID665 RDI 레지스터는 지원되는 CSID와 일치하며, CSIPHY v2.2.1은 동일한 3상 PHY (three-phase PHY) 제품군에 속합니다.
이는 모든 것을 처음부터 쓰는 대신 기존 드라이버를 포팅할 수 있음을 의미했습니다.
1단계: 커널로 캡처 서브시스템 (capture subsystem) 포팅하기
qcom-camss
milos에서는 아직 사용할 수 없었기에, 기존의 조각들을 직접 연결해야 했습니다.
TFE665 ISP 드라이버
메인라인 TFE530 드라이버(camss-vfe-340.c)를 직접 모델로 삼아 새로운 camss-vfe-665.c를 추가했습니다. 레지스터 내용이 동일하기 때문에 드라이버 로직도 같습니다. 주요 차이점은 milos가 ISP 제어 블록(control block)과 쓰기 버스 블록(write-bus block)을 서로 다른 오프셋(offset)에 배치한다는 점입니다. 버스 레지스터 베이스가 0xa00에서 0x1800으로 이동했으며, 이 오프셋 이동을 캡슐화하는 것이 작업의 대부분을 차지했습니다.
CSID665 및 CSIPHY v2.2.1
CSID665는 RDI 레지스터 오프셋이 동일했기 때문에 기존의 gen-2 CSID 연산(operations)을 재사용했습니다. CSIPHY v2.2.1은 이 리비전에 맞는 새로운 레인 구성 테이블(lane-configuration table)과 D-PHY 튜닝 값이 필요했습니다. 저는 하위 레벨의 cam_csiphy_2_2_1_hwreg.h로부터 레인 레지스터 시퀀스와 약 1.1 Gbps/lane ("500 Msps") 데이터 속도(data-rate) 및 AFE 설정을 필사했습니다. PHY의 레지스터 윈도우(register window)는 오프셋 0x1000에 위치했습니다.
Resources, compatible, 및 device tree
camss.c에서 milos의 캡처 복합체(capture complex)를 기술했습니다: 4개의 CSIPHY, 3개의 CSID, 3개의 TFE, 그리고 이들의 클록(clocks), 인터커넥트(interconnects), 전원 도메인(power domains)을 포함합니다. 또한 새로운 qcom,milos-camss 호환성(compatible) 항목을 등록했습니다. 디바이스 트리(device tree)에는 레지스터, IRQ, 클록, 인터커넥트, IOMMU, GDSC, CSI 입력 포트와 함께 MCLK 및 리셋 핀컨트롤(pinctrl) 상태를 포함하는 camss@ac13000 노드를 추가했습니다.
누락된 레지스터-버스 클록
이 시점에서 문제에 부딪혔습니다. 드라이버는 바인딩(bound)되었지만, TFE 하드웨어 버전 레지스터를 읽으면 0x0이 반환되었고, 블록에 전원이 공급되지 않은 것처럼 리셋 타임아웃(reset timed out)이 발생했습니다. 해결책은 ISP 클록처럼 명확하게 보이지 않는 클록인 CAM_CC_SOC_AHB_CLK였습니다. 이 클록은 CCI를 포함한 전체 카메라 복합체에서 사용되는 AHB 레지스터 버스를 게이팅(gated)하고 있었습니다. 이 클록이 없으면 레지스터 액세스는 조용히 0을 반환했습니다. 저는 해당 클록과 CAMNOC AXI 클록을 추가한 다음, CAMNOC 데이터 경로(data-path) 클록 속도를 설정했습니다. ISP는 hw_version = 0x30000000과 함께 활성화되었습니다.
이는 Qualcomm 하드웨어에서 0으로 읽히는 블록의 경우, 액세스 경로(access path) 상의 클록(clock) 또는 전원 도메인(power domain)이 누락되었을 수 있음을 시사합니다.
2단계: OV13B10 센서 구동 (Bring up)
메인라인(mainline) ov13b10 드라이버가 존재하지만, 이는 x86/ACPI 노트북용으로 작성되어 ACPI를 통해서만 매칭됩니다. 저는 두 곳을 수정했습니다.
ARM 및 디바이스 트리(device tree)에서 드라이버 사용 가능하게 만들기
- 드라이버가 디바이스 트리로부터 프로브(probe)될 수 있도록 OpenFirmware 매치 테이블(
ovti,ov13b10)을 추가했습니다. milos-fairphone-fp6.dts에 센서를 기술했습니다. 이 센서는 CCI I2C 버스의0x36주소에 위치하며, 19.2 MHz의 MCLK1, 리셋 GPIO, 그리고 전원 레일(power rails)이 필요합니다. 이 실험적인 구동(bring-up)을 위해, 전원 레귤레이터(regulators)를 완전히 순차적으로 제어하는 대신 단순히 강제로 켰습니다.
2가지 작은 세부 사항
레인 번호 매기기(Lane numbering). 다운스트림(downstream) 디바이스 트리는 data-lanes = <1 2 3 4>를 사용한 반면, 메인라인의 관례는 **0부터 시작하는 인덱스(zero-indexed)**인 <0 1 2 3>입니다. 번호 매기기가 잘못되면 CSIPHY는 레인 0이 누락된 레인 마스크(lane mask)를 프로그래밍하게 되고, PHY는 결코 잠금(lock) 상태가 되지 않습니다.
어떤 CSIPHY인가? FP6 배선은 초광각 카메라를 CSIPHY1로 라우팅했습니다. 추측하는 대신 다운스트림 디바이스 트리에서 이 정보를 얻어낸 덕분에 많은 시행착오를 줄일 수 있었습니다.
이 시점에서 센서는 성공적으로 프로브되었고, 칩 ID는 I2C를 통해 올바르게 읽혔으며, CSIPHY는 레인 활동(lane activity)을 보고했고, ISP는 완전한 검은색 프레임을 생성했습니다.
3단계: 모든 픽셀이 0인 프레임의 미스터리
이것이 주요 버그였습니다. 프레임은 올바른 속도와 크기로 도착했고 buf_done도 발생했지만, 모든 픽셀이 0이었습니다. 이는 센서 내부에서 생성되어 장면과 관계없이 나타나야 하는 센서 내부 컬러 바 테스트 패턴(internal colour-bar test pattern)을 활성화했을 때도 마찬가지였습니다. 데이터 경로(data path)는 프레임 타이밍은 전달했지만, 픽셀 데이터는 전달하지 못했습니다.
커널 로그가 단서를 제공했습니다: VFE0: Bad config violation.
ISP의 쓰기 엔진(write engine)은 쓰기 마스터(write master)의 설정이 들어오는 데이터와 일치하지 않았기 때문에, 프레임당 한 번씩 **컨슈머/설정 위반(consumer/config violation)**을 보고했습니다.
하위 스트림인 cam_tfe_bus.c를 자세히 조사했을 때, 그 차이가 명확해졌습니다. RDI 쓰기 마스터(write-master)의 **패커 포맷 (packer format)**은 ISP 버스 폭(bus width)에 따라 달라집니다:
- TFE530 (메인라인 드라이버의 대상인 qcm2290)은 64비트 (64-bit) RDI 버스를 가지므로 패커
0xa를 사용합니다. - TFE665 (milos)는 128비트 RDI 버스를 가지므로 패커
0x0이 필요합니다.
메인라인 드라이버는 64비트 값을 하드코딩했습니다. milos에서는 이것이 잘못되었고, 쓰기 엔진(write engine)이 픽셀 쓰기를 거부했습니다. 레지스터 값을 PLAIN64 (0xa)에서 0x0으로 변경하자, 모두 0이었던 프레임이 실제 이미지로 변했습니다. 최대 픽셀 값이 255가 되었고, 전체 컬러바 패턴이 나타났으며, 위반(violations) 보고가 중단되었습니다.
그 시점이 카메라가 물리적으로 작동하기 시작한 지점이었습니다: OV13B10 → CSIPHY → CSID → TFE665 → DDR 경로를 통해 실제 Bayer 프레임을 전달하게 된 것입니다.
4단계: Raw Bayer에서 libcamera로
Raw 프레임은 QR 스크립트를 실행하기에는 충분했지만, 실제로 사용할 수 있는 카메라가 되려면 현대적인 Linux 카메라 애플리케이션에서 사용되는 유저스페이스 프레임워크인 libcamera가 필요합니다. libcamera 0.7.2는 이미 범용 simple 파이프라인 핸들러를 통해 milos의 qcom-camss 그래프를 인식합니다. libcamera의 **소프트웨어 ISP (software ISP)**는 EGL을 통한 Adreno GPU 가속을 사용하여 Raw 프레임을 RGB로 디베이어링(debayer)했으며, 별도의 설정 없이도 약 30fps를 구현했습니다. cam과 GStreamer의 libcamerasrc는 즉시 깨끗한 이미지를 생성했습니다.
자동 노출 (Auto-exposure) 부분이 작동하지 않았습니다. 이미지가 너무 어둡거나 지나치게 밝았으며, 로그에는 Failed to create camera sensor helper for ov13b10라고 보고되었습니다. libcamera의 소프트웨어 자동 노출 및 게인 루프(gain loop)는 센서의 게인 레지스터 값을 실제 게인 승수(gain multiplier)로 매핑하기 위한 센서별 작은 헬퍼(helper)가 필요했습니다. libcamera에는 여러 센서에 대한 헬퍼가 포함되어 있지만, 이 센서는 포함되어 있지 않았습니다.
해결 방법은 간단했습니다. OV13B10의 아날로그 게인은 선형적이며, 0x80 = 1×, 즉 gain = code / 128입니다. 이는 다른 여러 OmniVision 센서와 동일한 동작 방식입니다. 저는 libcamera의 센서 헬퍼 데이터베이스에 클래스를 추가했습니다:
;
자동 노출 (auto-exposure) 루프가 즉시 수렴하기 시작했습니다. 또한 libcamera의 센서 속성 (sensor-properties) 데이터베이스에 항목을 추가하고, libcamera가 픽셀 어레이 기하학 (pixel-array geometry)에 사용하는 센서 드라이버의 V4L2 크롭 (crop) 및 get_selection 지원 기능을 백포트 (backport)했습니다. 그 후, GStreamer와 PipeWire가 적절하게 노출된 피드 (feed)를 수신하게 되었습니다. PipeWire libcamera 플러그인을 설치하자, 카메라는 데스크톱 애플리케이션에서 일반적인 비디오 소스로 나타났습니다.
일부 상황에서는 이미지가 여전히 다소 과다 노출 (overexposed)되므로, 헬퍼 (helper)에 대한 추가 작업이 필요할 수 있습니다.
5단계: 초점, 방향 및 해상도 문제
미세 조정이 필요한 몇 가지 문제가 여전히 남아 있었습니다.
오토포커스 (Autofocus)
초광각 카메라는 초점을 위해 **보이스 코일 모터 (VCM)**를 사용합니다. 하위 디바이스 트리 (device tree)에 따르면 이는 Awinic AW86017입니다. GPIO 구동 레귤레이터 (regulator)로 제어되는 오토포커스 레일 (autofocus rail)에 전원을 공급하고 CCI I2C 버스를 프로빙 (probing)한 결과, 사실상의 표준인 DW9714 10비트 DAC 프로토콜을 사용하는 0x0c 주소의 장치를 발견했습니다. 메인라인(mainline) dw9714 드라이버가 이를 직접 구동합니다.
초점 스윕 (focus sweep)을 통해 렌즈가 물리적으로 움직이고 선명도가 중간 설정에서 명확하게 정점에 도달함을 확인했습니다. libcamera는 lens-focus 디바이스 트리 링크를 통해 렌즈를 센서와 연결했습니다.
불행히도, libcamera의 소프트웨어 ISP에는 오토포커스 알고리즘이 없습니다. 렌즈를 무한대 초점에 고정해 두고 흐릿한 근접 촬영 결과물을 만드는 대신, 작은 udev 규칙을 사용하여 팔 길이 정도의 거리에서 QR 코드 스캔 및 문서 확인에 적합하도록 조정된 합리적인 고정 초점 (fixed focus)을 설정했습니다. 이 설정은 필요에 따라 조정하거나 소프트웨어에서 초점을 제어할 수 있습니다.
방향 (Orientation)
센서가 휴대폰의 세로 방향에 대해 비스듬한 각도로 장착되어 있어, 초기 프리뷰는 옆으로 누워 보였습니다. 디바이스 트리 회전 (rotation) 및 방향 (orientation) 속성을 설정함으로써 libcamera를 인식하는 애플리케이션들이 피드를 똑바로 표시할 수 있게 되었습니다.
빈 모드 (Binned modes)
OV13B10 드라이버는 2×2 빈닝 (binned) 절반 해상도 모드를 포함하여 여러 해상도를 광고했습니다. 이 CAMSS 경로에서 빈닝 모드(binned modes)는 깨지고 기울어진 출력을 생성했습니다. 전체 해상도 및 다른 몇몇 모드들은 깨끗했던 반면, 이들의 판독 기하 구조 (readout geometry)는 선언된 프레임 크기와 일치하지 않았습니다.
작동하지 않는 프리뷰를 제공하는 대신, 저는 libcamera가 작동하는 모드만 선택할 수 있도록 문제가 있는 두 가지 모드를 비활성화했습니다. 이로 인한 트레이드오프 (trade-off)는 소프트웨어 ISP가 스케일링 (scaling) 대신 크롭 (crop)을 수행하기 때문에, 1080p 프리뷰가 더 큰 모드의 중앙 크롭이 된다는 점입니다. 이는 전체 초광각 시야 (field of view)를 보여주는 대신 약간 확대된 상태로 보여줍니다.
그 결과 GNOME Camera / Snapshot에서 라이브, 정방향, 자동 노출, 초점이 맞은 프리뷰를 볼 수 있으며, QR 스캐닝도 작동합니다.
Sources
이 결과들을 재현하는 데 필요한 패치와 관련 파일들은 이 리포지토리 (repository)에서 확인할 수 있습니다:
Limitations
이것은 실험적인 시작이며, 상용 수준의 카메라 지원 (production camera support)은 아닙니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN AI Posts의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기