TL;DR. Подключил к Repka Pi 5 (RK3588) Arducam IMX519 16MP через CSI — а её ядро не поддерживает: нет ни драйвера, ни device-tree оверлея под этот сенсор. Эпопея растянулась на четыре акта и семнадцать пересборок ядра. Первый — портирование драйвера и overlay: сенсор появился в медиа-графе, но кадра не давал. Второй — интеграция с rkaiq и ложные следы (split-pipeline, rkisp-unite). Третий — kernel-отладка с
pr_infoв драйвере: шесть багов в RKCIF и imx519, каждый блокировавший свой этап (error 515→ kernel oops → EINVAL → ENOMEM → EPIPE → stream ON). Казалось, дальше — чистая физика MIPI, но четвёртый акт нашёл настоящий корень: кто-то в дереве Repka OS поменялIMX519_DEFAULT_LINK_FREQс493500000на408000000, не тронув PLL-регистры сенсора — DPHY настраивался под 816 Мбит/с, а сенсор физически гнал 987. Приёмник смотрел «не в то окно», HS-clock не ловился. После возврата честных 493.5 МГц кадр пошёл (avg≈110, 100% ненулевых пикселей) — а ещё четыре фикса (PM-runtime, single-ISP, режим сенсора 1080p) довели его до стабильного 1920×1080. Все десять багов оказались софтверными — разводка платы исправна. Под катом — весь путь с реальными командами, логами и откатом.

Задача и зачем #
Была цель — получить поток с конкретной камеры Arducam 16MP IMX519 (без автофокуса) именно на Repka Pi 5 через штатный CSI-разъём. Это не самый ходовой выбор, поэтому я сразу готовился к тому, что «из коробки» не заведётся. Но «не из коробки» — это одно, а «пересборка ядра» — совсем другое. До последнего надеялся обойтись малой кровью.
| Компонент | Модель / значение |
|---|---|
| Платформа | Repka Pi 5 (Rockchip RK3588) |
| ОС | Repka OS (Ubuntu 25.10), ядро 6.1.115.1-repka-pi5 |
| Камера | Arducam IMX519 16MP (без автофокуса, fixed-focus) |
| Интерфейс | MIPI-CSI (штатный 22-pin разъём Repka Pi 5) |
| Шлейф | AWM 20624, 22-pin, 0.5 мм pitch (под RP5/Repka Pi 5) |
| Цель | видеопоток с сенсора через V4L2 |
Словарь терминов (читай, если буквы страшные) #
Дальше будет много аббревиатур из мира встроенного Linux и камер. Собрал их в одно место. Если термин знаком — листай дальше.
💡 V4L2 (Video4Linux2). Главный фреймворк Linux для работы с видео. Камера, ТВ-тюнер, веб-камера — всё это «видеоустройства», доступные через
/dev/video*. Управление идёт ioctl'ами:VIDIOC_S_FMT(выбрать формат/разрешение),VIDIOC_STREAMON(пуск стрима),VIDIOC_DQBUF(забрать кадр из очереди). Утилитаv4l2-ctl— обёртка над этими ioctl в командной строке.💡 MIPI / CSI-2. MIPI — альянс, стандартизующий интерфейсы мобильной электроники. CSI-2 (Camera Serial Interface, версия 2) — последовательный протокол передачи данных от камеры к процессору. По сути «USB для сенсоров»: данные бегут по тонкому шлейфу. Минимальный набор — одна пара для тактового сигнала (clock lane) и одна-четыре пары для данных (data lanes).
💡 Lane (лейн, дифференциальная пара). Два провода (P — positive, N — negative), по которым сигнал идёт «в противофазе». Приёмник смотрит разность — так подавляются помехи. У 2-lane камеры:
CLK±(clock) +D0±+D1±= 6 сигнальных пинов на разъёме.💡 PHY (физический уровень). «Модем» внутри чипа, который ловит высокочастотный аналоговый сигнал с лейнов и превращает его в цифровые байты. На RK3588 два типа PHY: INNO DPHY (простой, 2+2 лейна) и Samsung DC-PHY (combo, до 4 лейнов). Разъём Repka Pi 5 подключён к INNO DPHY0.
💡 CSI-host. Контроллер протокола CSI-2 — разбирает пакеты, проверяет CRC, извлекает кадры. Сидит «выше» PHY: PHY отдаёт байты, host собирает их в строки/кадры. На RK3588 их шесть (
mipi0_csi2…mipi5_csi2). Разъём Repka Pi 5 (MIPI_CSI0_*) подключён к CSI Host 2 (не к Host 0, как подсказывает название!).💡 CIF / VICAP. Блок захвата (Video Capture) — берёт кадры из CSI-host, умеет кропать/масштабировать для RAW. На RK3588 это
rkcif@fdce0000.💡 media graph (media-ctl). Топология соединений внутри ядра — кто к кому подключён. Сенсор → DPHY → CSI-host → CIF → ISP — это цепочка «сущностей» (entities), соединённых «линками» (links). Команда
media-ctl -d /dev/media0 -pпечатает весь граф: имена, пады, форматы, статусы линков (ENABLED/DISABLED). Главное средство понять, «видит ли ядро камеру».💡 subdev (sub-device). Часть media graph, не отдающая пиксели напрямую, а обрабатывающая их: сенсор, DPHY, CSI-host, ISP-subdev. У каждого свой файл
/dev/v4l-subdevN. Формат/кроп на них задаётся отдельно от видео-ноды (--set-subdev-fmt,--set-subdev-selection), а пиксели забираются уже с видео-ноды (/dev/video11).💡 Драйвер (kernel driver). Кусок кода в ядре (.c-файл), который умеет общаться с конкретным железом — через регистры, I²C, MMIO. Может быть модулем (
.ko, грузится по требованию черезmodprobe) или built-in (вшит в образ ядраImage, всегда активен). На RK3588 разница между ними — не формальность, см. ниже.💡 rkaiq (Rockchip AI Image Quality). Пользовательский демон (
rkaiq_3A_server), который крутит петлю 3A: Auto-Exposure, Auto-White-Balance, Auto-Focus. Читает статистику из ISP, считает нужные выдержку/гейн/ББ и прописывает их обратно в сенсор через V4L2. Без него кадр выходит чёрным (сенсор стоит на минимальной экспозиции). Привязывается к сенсору по имени модуля (camera-module-name) и ищет калибровочный iqfile по шаблону<sensor>_<module-name>_default.json.💡 iqfile. JSON-файл калибровки сенсора под ISP: таблицы LSC (коррекция виньетирования линзы), AWB (баланс белого), AE-цели (целевая яркость). Один на пару «сенсор + модуль». Если rkaiq не находит iqfile — кадр выйдет тёмным/неправильного цвета.
💡 link_freq. Константа в драйвере сенсора (
IMX519_DEFAULT_LINK_FREQ), сообщающая приёмнику ожидаемую MIPI-частоту. По ней DPHY вычисляетTHS_SETTLE— окно захвата clock-lane. Должна совпадать с тем, что физически генерирует сенсор через PLL-регистры. Несовпадение = приёмник «смотрит не в то окно» = нет HS-clock = чёрный кадр. Главный баг этого поста — именно рассогласование.💡 PM-runtime (Power Management). Механизм Linux, отключающий тактирование простаивающих устройств. Устройство в
suspended— «спящее», его регистры недоступны. Чтобы поработать, нужноpm_runtime_get_sync(dev)— вызывает resume-колбэк, включающий clk. Если написать MMIO в спящее устройство — зависание на шине.💡 unite / single-ISP. RK3588 имеет два ISP-блока. Режим
rkisp_uniteсклеивает их для больших кадров (кадр делится пополам + 128 пикселей перекрытия). Режим single-ISP (rkisp0) — один блок. На этой плате unite неработоспособен (рассинхрон двух ISP), поэтому используется single-ISP с потолком по ширине ~1920.💡 stride / bytes-per-line. Сколько байт занимает одна строка кадра в памяти. Может быть больше ширины (
W), если ISP выравнивает до 64 байт: например, 2328 → stride 2336. Если читать кадр как «плотный»W×H, игнорируя stride — изображение срезает вбок. Реальный stride даётv4l2-ctl --get-fmt-video(полеBytes per Line).
Шлейфы и разъёмы: куда и как вставлять #
Прежде чем лезть в драйверы и логи, разберёмся с физикой — тут легко наступить на грабли ещё до включения. В комплекте с Arducam IMX519 идёт два шлейфа, и они для разных плат. Перепутать — и камера вообще не определится.
💡 Что такое FPC-шлейф? Это гибкий плоский кабель (Flexible Printed Circuit). На одной его стороне — медные «пятаки» (контакты), вторая сторона — гладкая изоляция. Разъём на плате прижимает шлейф контактами к своим «усикам», и от ориентации шлейфа зависит, попадёт ли сигнал на нужные пины. Вставить шлейф «не той стороной» — частая причина «камера не работает».
Два разъёма камер на Raspberry/Repka: 15-pin и 22-pin #
Стандарт MIPI-CSI на платах этого семейства существует в двух физических вариантах:
| Параметр | 15-pin (старый) | 22-pin (новый) |
|---|---|---|
| Шаг контактов (pitch) | 1.0 мм | 0.5 мм (плотнее) |
| Линии данных MIPI | 2 (Lane 0, Lane 1) | 4 (Lane 0–3) — выше пропускная способность |
| Питание камере | 3.3 В | 3.3 В |
| Где применяется | Raspberry Pi 3 / 4 / B+ | Raspberry Pi 5, Pi Zero, Compute Module IO, Repka Pi 5 |
Repka Pi 5 конструктивно повторяет разъём Raspberry Pi 5 — это 22-pin, 0.5 мм pitch. Поэтому для неё подходит только «новый» шлейф. Старый 15-pin сюда физически не воткнёшь без адаптера.
💡 Что такое pitch? Это расстояние между центрами соседних контактов. 0.5 мм у 22-pin против 1.0 мм у 15-pin — значит, шлейф с 22 контактами по ширине примерно такой же, как 15-пиновый, но контакты вдвое плотнее. Отсюда и хрупкость: 0.5-мм шлейф легче повредить и сложнее вставить ровно.
Какой шлейф для какой платы #
В комплекте с Arducam обычно два шлейфа, и их легко перепутать:
| Шлейф | Маркировка | Для какой платы |
|---|---|---|
| UC-236 | (старый, 15-pin, 1.0 мм) | Raspberry Pi 4 и раньше |
| AWM 20624 | (новый, 22-pin, 0.5 мм) | Raspberry Pi 5 / Repka Pi 5 ← наш |
⚠️ На Repka Pi 4 разъёма CSI под этот стандарт НЕТ. Шлейф UC-236 предназначен для Raspberry Pi 4 (и ранних моделей). Если у вас именно Repka Pi 5 — берите шлейф с маркировкой AWM 20624 (или любой 22-pin / 0.5 мм pitch). UC-236 туда не подойдёт ни физически, ни по назначению.

Как вставлять шлейф в разъём CSI Repka Pi 5 #
Это самый капризный момент. У шлейфа AWM 20624 контакты («оголённая часть с пятаками») только с одной стороны. Правильная ориентация для разъёма CSI на Repka Pi 5:
┌─────────────────────────────┐
процессор ← │ контакты (пятаки) ВВЕРХ/к CPU │
│ изоляция (гладкая) ВНИЗ │
└─────────────────────────────┘
▲
сторона БЕЗ контактов направлена к замку разъёма
Простыми словами:
- Оголённая часть с контактами (пятаками) смотрит в сторону процессора (внутрь платы).
- Сторона, где контактов нет (гладкая изоляция), направлена в сторону замка разъёма (наружу).
⚠️ Перевёрнутый шлейф = «камеры нет». Это самая частая аппаратная ошибка. На Raspberry Pi 5 (и на Repka Pi 5, повторяющей её разводку) «контакты вверх» — это неправильно. При перевёрнутом шлейфе сенсор не получит нужные сигналы (CLK/DATA повиснут в воздухе или уйдут не на те пины), и I²C-сканер вообще никого не найдёт. Так что если
i2cdetectпоказывает пустую шину — прежде чем винить драйвер, проверьте ориентацию шлейфа.
💡 Как вставлять. Сначала аккуратно потяните защёлку разъёма вверх (она откидывается на пару миллиметров, не дёргайте силой — 0.5-мм разъём хрупкий). Вставьте шлейф до упора, ровно, без перекоса. Затем опустите защёлку, чтобы она прижала шлейф. Если шлейф вставлен криво — часть контактов не дотянется, и поведение будет непредсказуемым.
В моём случае именно шлейф AWM 20624 оказался нужным — его и вставил в разъём CSI по ориентации «контактами к процессору». После этого I²C-скан (о нём ниже) сразу увидел сенсор, и стало ясно, что физика подключена правильно, а проблема — в софте.
Симптом: камеры нет нигде #
Подключил шлейф, загрузился. Первое, что проверяю всегда, — появились ли устройства:
ls /dev/video* /dev/media*
/dev/video-dec0
/dev/video-enc0
Только два узла, и оба — это аппаратные видео-кодировщик/декодировщик RK3588 (video-enc0, video-dec0). К камере они не имеют отношения. Ни /dev/video0, ни /dev/media0. Камера вообще не видна.
💡 Что такое
/dev/video0и/dev/media0? В Linux оборудование камеры выставляется через два слоя./dev/video0— это «видеоузел», из которого можно читать кадры (через V4L2)./dev/media0— это узел «медиа-контроллера»: он описывает весь конвейер (сенсор → PHY → CIF → ISP) как граф связей. Нет ни того, ни другого — ядро даже не знает про камеру.
Прежде чем паниковать, полез проверить физику — отвечает ли сенсор на шине I²C. Это подскажет, вставлен ли шлейф правильно:
# Сканируем шины I²C в поисках сенсора (по адресам)
sudo i2cdetect -y 6
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- 0c -- -- --
10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- --
...
50: -- UU -- -- -- -- -- -- 58 -- -- -- -- -- -- --
Отличные новости: на шине I²C-6 кто-то отвечает — 0x1a (это и есть сам IMX519, его стандартный адрес), 0x50 (EEPROM модуля Arducam). Значит, шлейф сидит правильно, питание на модуль подаётся, физика ок. Проблема — софтверная.
Диагностика: почему ядро не видит камеру #
Шаг 1. Есть ли оверлей под мою камеру? #
Проверяю, какие device-tree overlay'ы (о них ниже) идут в комплекте с Repka OS:
ls /boot/dtb/rockchip/overlay/ | grep -iE "csi|cam|imx|ov"
rk3588-csi-imx219.dtbo ← есть только под IMX219 (RPi Camera V2)
Под IMX519 оверлея нет. Только под IMX219 — это другая камера (RPi Camera V2), родственная, но не моя.
Лирическое отступление: device tree и overlay простыми словами #
Чтобы ядро узнало про оборудование на ARM-плате, оно читает device tree (DT) — скомпилированное описание всего железа: какие контроллеры есть, по каким адресам, какие пины за что отвечают, какие регуляторы питания подключены. На Repka он лежит в /boot/dtb/rockchip/rk3588-repka-pi5.dtb.
Overlay — это «патч» к device tree. Базовый DT описывает плату, а overlay добавляет к нему, например, описание камеры: «на шине I²C-6 по адресу 0x1a висит сенсор Sony IMX519, подключён к PHY, требует 24 МГц тактовый сигнал». Overlay хранится в отдельном файле .dtbo и применяется загрузчиком U-Boot при старте.
💡 MIPI-CSI — это интерфейс камер. Две линии данных (data lanes) + одна тактовая линия, по которым сенсор вываливает пиксели. На RK3588 сигнал с камеры проходит длинный путь: сенсор → DPHY (приёмник физического уровня) → CIF (захват) → ISP (обработка). Каждый узел в этом пути — отдельный драйвер в ядре, и все они должны быть описаны в DT и связаны между собой «графом связей» (endpoints).
Шаг 2. Драйвер IMX519 есть в ядре? #
# Что есть в конфиге ядра из сенсоров Sony IMX
zcat /proc/config.gz | grep -E "VIDEO_IMX"
CONFIG_VIDEO_IMX219=y ← IMX219 встроен в ядро
CONFIG_VIDEO_IMX415=y
# CONFIG_VIDEO_IMX519 — такой строки НЕТ
Драйвера IMX519 в ядре нет. Это и понятно: Arducam делает драйвер под Raspberry Pi, в основной поток ядра Linux (mainline) он не влит.
Шаг 3. Конвейер RK3588 включён в ядро? #
А вот это — хорошая новость. Проверяю, что весь путь сенсор→ISP встроен в ядро:
zcat /proc/config.gz | grep -E "ROCKCHIP_ISP|ROCKCHIP_CIF|V4L2_FWNODE|V4L2_ASYNC"
CONFIG_VIDEO_ROCKCHIP_ISP=y ← ISP встроен
CONFIG_VIDEO_ROCKCHIP_CIF=y ← CIF встроен
CONFIG_V4L2_FWNODE=y
CONFIG_V4L2_ASYNC=y ← async-привязка (важно ниже)
Вся инфраструктура MIPI-конвейера RK3588 встроена в ядро. Не хватает только драйвера самого сенсора. То есть задача сводится к: добавить драйвер IMX519 + описать его в device tree.
Сводка после диагностики #
| Проверка | Результат |
|---|---|
/dev/video0, /dev/media0 |
отсутствуют |
| Шина I²C (физика) | ✅ сенсор отвечает на 0x1a, шлейф ок |
| Overlay под IMX519 | ❌ отсутствует (есть только IMX219) |
| Драйвер IMX519 в ядре | ❌ отсутствует |
| Конвейер RK3588 (ISP/CIF/DPHY) | ✅ встроен в ядро |
Вывод: надо портировать драйвер и писать свой overlay. План — в три этапа.
Этап 1. Порт драйвера Arducam под ядро 6.1 #
Arducam держит исходники драйвера в открытом репозитории ArduCAM/IMX519_AK7375. Клонирую:
git clone https://github.com/ArduCAM/IMX519_AK7375.git
Внутри — IMX519/imx519.c (около 2000 строк) и Makefile для out-of-tree сборки. Первая проверка — собирается ли он вообще против ядра Repka:
cd IMX519_AK7375/IMX519
make KDIR=/lib/modules/6.1.115.1-repka-pi5/build
Не собирается. Но — и это главное — все ошибки одного класса: API-drift между ядрами 5.15 и 6.1. Драйвер писался под RPi-ядро ~5.15, а в 6.1 часть типов и функций переименовали. Конкретно:
| Что в драйвере (5.15) | Что стало в 6.1 |
|---|---|
v4l2_subdev_pad_config |
v4l2_subdev_state |
v4l2_async_register_subdev_sensor_common |
v4l2_async_register_subdev_sensor |
imx519_remove() возвращает int |
должен быть void |
MEDIA_BUS_FMT_SENSOR_DATA |
MEDIA_BUS_FMT_METADATA_FIXED |
fh->pad |
fh->state |
Всё это механические правки — переименования, без изменения логики. Аккуратно правлю сигнатуры функций (их штук пять) и пересобираю:
make KDIR=/lib/modules/6.1.115.1-repka-pi5/build
# ...
LD [M] imx519.ko
imx519.ko собрался. Самый рискованный момент — совместимость драйвера с чужим ядром — снят. Это чистый V4L2-драйвер, без завязок на Raspberry Pi.
💡 Out-of-tree vs built-in. Можно грузить драйвер как внешний модуль
.ko(out-of-tree) — он подгружается во время работы системы. А можно «встроить» в ядро ещё на этапе компиляции (CONFIG_VIDEO_IMX519=y), и тогда он едет внутри самогоImage. Как выяснится дальше, для камер на RK3588 эта разница критична.
Этап 2. Device-tree overlay под конвейер RK3588 #
Драйвер есть, но ядро о нём не знает, пока нет описания в DT. Нужно написать свой overlay. Удобно, что в комплекте Repka есть рабочий rk3588-csi-imx219.dtbo под родственную камеру — его декомпилирую и беру за образец:
dtc -I dtb -O dts /boot/dtb/rockchip/overlay/rk3588-csi-imx219.dtbo
Этот overlay описывает всю цепочку CSI→DPHY→CIF→ISP и активирует каждый узел. Топология для IMX519 идентична (тот же разъём, та же разводка Repka), поэтому копирую её 1-в-1. Меняю только фрагмент сенсора под требования драйвера IMX519 (выясненные чтением imx519.c):
compatible = "sony,imx519",reg = <0x1a>(I²C-адрес);- тактовый сигнал 24 МГц (без него сенсор молчит);
- три обязательных питания:
VANA(2.8 В),VDIG(1.05 В),VDDL(1.8 В); - на endpoint — строго
data-lanes = <1 2>иlink-frequencies = /bits/ 64 <493500000>(драйвер это валидирует).
Собираю overlay и кладу рядом с другими:
dtc -@ -I dts -O dtb -o rk3588-csi-imx519.dtbo rk3588-csi-imx519.dts
sudo cp rk3588-csi-imx519.dtbo /boot/dtb/rockchip/overlay/
Включаю его в /boot/repkaEnv.txt (бэкап оригинала — обязательно):
sudo cp /boot/repkaEnv.txt /boot/repkaEnv.txt.bak
# правим строку overlays:
# overlays=panthor-gpu csi-imx519
Перезагрузка — и…
Этап 3. Сенсор опознался, но графа нет (главный сюрприз) #
После ребута проверяю логи:
dmesg | grep -i imx519
imx519 6-001a: Device found is imx519 ← СЕНСОР ОПОЗНАН!
Chip ID прочитан (регистр 0x0016 вернул 0x0519), питание, тактовый сигнал и link-frequency — корректны. Но…
media-ctl -d /dev/media0 -p | grep imx519
# (пусто — imx519 в медиа-графе отсутствует)
Сенсор опознан, а в медиа-графе его нет. RKCIF (захват) при этом непрерывно ругается:
rkcif-mipi-lvds2: rkcif_update_sensor_info: stream[0] get remote terminal sensor failed!
get_remote_sensor: video pad[0] is null
Копаю глубже через debugfs:
cat /sys/kernel/debug/v4l2-async/pending_async_subdevices
imx519 6-001a:
rockchip-csi2-dphy0:
rkisp0-vir0:
rockchip-mipi-csi2:
rkcif-mipi-lvds2:
Все пять узлов конвейера висят в pending — каждый зарегистрировался как async-subdev, но ни один notifier их не «связал». Это ключевое открытие.
Лирическое отступление: v4l2-async и race condition #
Камерный конвейер состоит из независимых драйверов (сенсор, PHY, CIF, ISP), которые probe'ятся в непредсказуемом порядке. Чтобы они «нашли» друг друга, в V4L2 есть механизм async-binding: каждый узел регистрируется как «кандидат» (subdev), и хотя бы один из них создаёт «слушателя» (notifier), который связывает кандидатов по графу связей в device tree.
Проблема в том, что notifier не ждёт вечно. Если на момент завершения notifier'а нужный сенсор ещё не зарегистрировался — связывание не происходит, и граф остаётся разорванным. Сенсор потом подгрузится, но его уже некому «подобрать».
Так и случилось. Таймлайн из dmesg:
12.63s — rkcif notifier COMPLETED (решил, что сенсоров больше не будет)
13.17s — imx519 наконец зарегистрировался (опоздал на полсекунды)
Ложные следы, на которые я чуть не повёлся #
След 1: «Догрузить модуль раньше через initramfs». Добавил imx519 в /etc/initramfs-tools/modules, пересобрал initramfs. Модуль действительно стал грузиться раньше (с 17.9 с до 13.1 с) — но gap всё равно остался, notifier закрывался чуть раньше. Помогло, но не до конца.
След 2: «Перепривязать RKCIF через sysfs». В /sys/bus/platform/drivers/rkcif/ есть bind/unbind. Попробовал отбиндить и забиндить RKCIF заново, чтобы он переслушал notifier, когда сенсор уже на месте. Результат — kernel oops (крах RKCIF). Медиа-граф не рассчитан на повторный probe при существующих сущностях. Этот путь отпал.
После этих граблей стало ясно: лечить надо корень — порядок probe — а не симптомы.
Этап 4. Пересборка ядра с встроенным IMX519 #
Единственное надёжное решение: сделать драйвер built-in (CONFIG_VIDEO_IMX519=y), а не модулем. Тогда probe сенсора пойдёт синхронно с RKCIF в одном цикле, и async-binding сработает штатно. Но для этого нужно пересобрать ядро целиком.
Шаг 1. Исходники нужной версии #
Полные исходники лежат у производителя на GitFlic. Нужен именно репозиторий под RK3588 — npo_rbs/repka-os_linux-rockchip (не путать с repka-os_kernel, это ядро под Repka Pi 4 на Allwinner). Проверяю версию:
git clone --depth 1 --branch master \
https://gitflic.ru/project/npo_rbs/repka-os_linux-rockchip.git
cd repka-os_linux-rockchip
grep -E "^(VERSION|PATCHLEVEL|SUBLEVEL|EXTRAVERSION)" Makefile
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 115
EXTRAVERSION = .1
Точное совпадение с установленным ядром 6.1.115.1-repka-pi5.
Шаг 2. Встраиваю драйвер в дерево #
Три правки:
# 1. Исходник — в дерево
cp imx519.c drivers/media/i2c/imx519.c
# 2. Запись в Kconfig (рядом с imx219)
cat >> drivers/media/i2c/Kconfig <<'EOF'
config VIDEO_IMX519
tristate "Sony IMX519 sensor support"
depends on I2C && VIDEO_DEV
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select V4L2_FWNODE
help
Sony IMX519 camera (Arducam 16MP).
EOF
# 3. Запись в Makefile
echo 'obj-$(CONFIG_VIDEO_IMX519) += imx519.o' >> drivers/media/i2c/Makefile
Шаг 3. Конфиг от текущей системы #
Беру готовый .config от установленного ядра — чтобы собрать с теми же опциями, что работают сейчас:
cp /boot/config-6.1.115.1-repka-pi5 .config
# Включаю IMX519 как built-in
sed -i '/^CONFIG_VIDEO_IMX219=y$/a CONFIG_VIDEO_IMX519=y' .config
make ARCH=arm64 oldconfig # нормализовать конфиг
Шаг 4. Сборка #
make ARCH=arm64 -j8 Image dtbs modules
⚠️ Место и время. Полное дерево ядра — ~2 ГБ, сборка съедает ещё ~4 ГБ. На 8 ядрах RK3588 сборка идёт ~15–20 минут. На 16 ГБ microSD я не уместился — пришлось перенести систему на 64 ГБ eMMC. Учитывайте объём носителя заранее.
⚠️ Охлаждение обязательно. При сборке грузятся все 8 ядер на 100% подряд, минут 15. С активным охлаждением (вентилятор) температура процессора доходит до ~70 °C — терпимо. А вот без вентилятора RK3588 быстро уходит в троттлинг: сбрасывает частоты, сборка растягивается в разы, а при перегреве ядра плата может и зависнуть на ровном месте. Так что собирайте ядро только с обдувом — это не рекомендация, а необходимое условие. Если у платы только радиатор без вентилятора — снимите корпус, поставьте внешний кулер, или собирайте короткими заходами с паузами на остывание.
Сборка падала дважды, и оба раза по моей вине:
certs/extract-cert.c: openssl/bio.h: Нет такого файла— не хваталоlibssl-dev. Лечитсяapt install libssl-dev.fixdep: error opening file ... No such file— гонка при параллельной сборке: я запустил две пересекающиеся фоновые сборки в одном дереве, и они повредили.o-файлы друг друга. Лечитсяmake cleanи одной чистой сборкой черезsetsid.
Проверка, что драйвер действительно встроен:
grep imx519 drivers/media/i2c/built-in.a # imx519.o присутствует → built-in ✓
ls -lh arch/arm64/boot/Image # ядро собрано
Этап 5. Установка ядра (с откатом) #
Это самая рискованная часть — есть шанс, что новое ядро не загрузится. Поэтому сначала бэкапы.
⚠️ Перед установкой ядра — обязательно снимите резервные копии. Если новое ядро не встанет, плата может не загрузиться, и откатить можно будет только с другой системы (сняв eMMC/SD и подмонтировав на ПК) или через serial-консоль. Ниже — что именно бэкапить и как возвращать.
# Бэкап ядра и DTB
sudo cp /boot/vmlinuz-6.1.115.1-repka-pi5 \
/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig
sudo cp /boot/dtb/rockchip/rk3588-repka-pi5.dtb \
/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig
# Бэкап модулей
sudo mv /lib/modules/6.1.115.1-repka-pi5 \
/lib/modules/6.1.115.1-repka-pi5.bak-orig
Установка. Замечу: текущее ядро у Repka — gzip-сжатый Image, поэтому наше (изначально несжатое) сжимаем так же:
gzip -9 -c arch/arm64/boot/Image > /boot/vmlinuz-6.1.115.1-repka-pi5
sudo cp arch/arm64/boot/dts/rockchip/rk3588-repka-pi5.dtb \
/boot/dtb/rockchip/rk3588-repka-pi5.dtb
sudo make ARCH=arm64 modules_install
sudo depmod -a 6.1.115.1-repka-pi5
Финальная проверка, что imx519 теперь в modules.builtin (значит, встроен в ядро, а не отдельный файл):
grep imx519 /lib/modules/6.1.115.1-repka-pi5/modules.builtin
# kernel/drivers/media/i2c/imx519.ko ← встроен ✓
После перезагрузки: кажется, заработало #
Самый волнительный момент всей истории — перезагрузка с пересобранным ядром. Запускаю проверку по списку:
uname -r
# 6.1.115.1-repka-pi5 ← наше ядро грузится ✓
dmesg | grep imx519
[ 12.255] platform csi2-dphy0: Fixed dependency cycle(s) with /i2c@fec80000/camera-imx519@1a
[ 12.267] imx519 6-001a: Looking up VANA-supply from device tree
[ 12.267] imx519 6-001a: Looking up VDIG-supply from device tree
[ 12.267] imx519 6-001a: Looking up VDDL-supply from device tree
[ 12.277] imx519 6-001a: Device found is imx519 ← сенсор опознан ✓
Главный признак успеха — теперь смотрю, перестал ли RKCIF ругаться на сенсор:
dmesg | grep -c "get remote terminal sensor failed"
# 0 ← НОЛЬ строк! Раньше их были десятки
Ноль. Async-binding прошёл — сенсор привязался к конвейеру. Это решило проблему порядка загрузки, над которой я бился. Но, как выяснится ниже, это была лишь первая из стен.
Камера в медиа-графе #
Смотрю топологию:
media-ctl -d /dev/media0 -p | grep -i imx519
- entity 63: imx519 6-001a (2 pads, 1 link, 0 routes)
pad0: Source
[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:srgb ...]
-> "rockchip-mipi-csi2":0 [ENABLED] ← линк активен
Сенсор появился как полноправная сущность графа, линк к CSI2 приёмнику ENABLED. Формат SRGGB10_1X10 (10-битный байеровский RAW) propagated через весь путь сенсор→DPHY→CSI2→CIF. А в crop-границах сенсора виден и полный 16-мегапиксельный режим:
crop.bounds:(8,48)/4656x3496 ← доступен полный кадр IMX519
Сенсор живой и управляемый #
Проверяю, что камера не просто «висит» в графе, а реально управляется:
v4l2-ctl -d /dev/v4l-subdev2 --list-ctrls-menus | head
User Controls
exposure 0x00980911 (int) : min=20 max=1147 step=1 default=1000 value=1000
horizontal_flip (bool) : default=0 value=0
vertical_flip (bool) : default=0 value=0
Image Source Controls
analogue_gain (int) : min=0 max=960 step=1 default=0 value=0
red_pixel_value (int) : min=0 max=4095 ... ← BLC (коррекция чёрного)
Exposure, gain, повороты, BLC — все controls отвечают. Камера полностью интегрирована в V4L2.
💡 Почему я обрадовался. Из всей эпопеи самым сложным оказалось не «написать драйвер» и не «собрать ядро», а заставить сенсор привязаться к медиа-графу. На RK3588 это требует синхронного порядка probe, который достижим только при built-in драйвере. И вот — сенсор в графе, линки ENABLED, controls живые. Казалось бы, победа. Но, как выяснилось, я праздновал слишком рано.
Ложная победа: камера в графе, но кадра нет #
Обрадовавшись, что сенсор привязался к конвейеру, я пошёл за финальным призом — первым кадром. Ведь есть /dev/video0, /dev/video11 (ISP mainpath), камера в графе... Должно же заработать? Как бы не так.
v4l2-ctl -d /dev/video11 --set-fmt-video=width=1920,height=1080,pixelformat=NV12
v4l2-ctl -d /dev/video11 --stream-mmap --stream-count=1 --stream-to=test.nv12
VIDIOC_STREAMON returned -1 (Operation not permitted)
И пустой файл test.nv12 в 0 байт. Попытки через CIF-узлы (/dev/video0) — error 515. Камера «есть», а картинку не отдаёт. Тут я понял: «сенсор в медиа-графе» — это ещё не «камера работает». Между этими двумя состояниями — целая пропасть, которую я в азарте перепрыгнул мысленно, но не фактически.
💡 В чём была ошибка. Я принял промежуточную веху (async-binding прошёл, сенсор виден в графе) за финальную (поток идёт, кадр ловится). Это классическая ловушка отладки: «всё зеленое в чек-листе» создаёт ложное чувство завершённости. Реальный критерий успеха — не
media-ctlпоказывает сенсор, а файл с пикселями ненулевого размера.
Почему кадра нет: новый слой проблем #
Стал разбираться, и вскрылся совершенно отдельный мир проблем — экосистема Rockchip ISP. Оказалось, что на RK3588 между RAW-сенсором и готовым RGB/YUV стоит ISP (Image Signal Processor — аппаратный процессор обработки изображений), и путь через него не «автоматический», а требует целой связки:
rkaiq_3A_server— сервер автоэкспозиции/автофокуса/баланса белого (3A). Без него ISP не знает, как обрабатывать кадр.- IQ-файл (
/etc/iqfiles/*.json) — калибровка ISP под конкретный сенсор (BLC, AWB, vignetting, цветовая матрица). Без него rkaiq не запустится. - Тонкая настройка мульти-device медиа-графа — CIF живёт на
/dev/media0, а ISP на/dev/media1, и их надо связать через rawrd-мост.
Пробую запустить rkaiq и сразу натыкаюсь на стену:
rkaiq_3A_server
CAMHW:E:248:parse sensor entity name imx519 6-001a error at 0, please check sensor driver!
CAMHW:E:@get_sensor_caps /dev/v4l-subdev2: Get sensor module info failed
CAMHW:E:get isp or ispp info fail, something gos wrong!
parse sensor entity name imx519 6-001a error — rkaiq не может разобрать имя сенсора! И это при том, что сам сенсор в графе прекрасно виден. Оказывается, rkaiq парсит имя сущности по строго определённому формату m<индекс>_<сторона>_<сенсор>, а наш драйвер оставил стандартное V4L2-имя imx519 6-001a. Плюс rkaiq дёргает специальный ioctl RKMODULE_GET_MODULE_INFO, чтобы получить имя модуля и подобрать IQ-файл — а наш Arducam-драйвер этот ioctl не реализует.
Что выяснилось о рабочем примере #
Чтобы понять, чего не хватает, разобрал рабочий imx219.c из дерева ядра Repka — для него есть и IQ-файл, и rkaiq его привязывает. И увидел три вещи, которых нет в Arducam-версии imx519:
#include <linux/rk-camera-module.h>— заголовок с Rockchip-специфичными структурами.- Чтение
rockchip,camera-module-*из device tree (index, facing, name, lens) — это те самые свойства, что я прописал в overlay, но драйвер их не читал. - Формирование имени в формате Rockchip:
snprintf(sd->name, ..., "m%02d_%c_%s %s", ...)— даётm00_b_imx219 6-0010, а неimx219 6-0010. - Обработка ioctl
RKMODULE_GET_MODULE_INFO+RKMODULE_GET_HDR_CFG+ compat-обёртка для 32-бит.
То есть «работающий» imx219 от Rockchip — это не тот же imx219, что в mainline. Это модифицированная версия с интеграцией в проприетарный стек rkaiq. Наш Arducam imx519 этой интеграции лишён — он «чистый» V4L2-драйвер.
⚠️ Ловушка «у меня есть драйвер». Когда находишь драйвер под свой сенсор (особенно от производителя камеры), легко решить, что этого достаточно. Но на RK3588 драйвер сенсора — только первый слой. Над ним ещё Rockchip-специфичный «клей» (module info, rkaiq-ioctl, IQ-файл), и без него сенсор хоть и виден, но бесполезен для картинки.
Итог ложной победы #
| Думал, что достиг | На самом деле |
|---|---|
| «Камера работает» | Сенсор виден в графе, но кадр не получается |
| Драйвер «готов» | Драйвер V4L2-готов, но не интегрирован в rkaiq |
| Конвейер «настроен» | Конвейер сенсор→CIF есть, но CIF↔ISP мост не активен |
| Осталось «только снять кадр» | Нужен целый второй акт: доработка драйвера + IQ-файл + rkaiq |
Так «финальная» глава превратилась в пролог ко второму акту — интеграции с Rockchip ISP. И вот этот второй акт разворачивается прямо здесь, потому что он оказался не короче первого.
Второй акт: интеграция с rkaiq и стена split-pipeline #
Раз поняв, чего не хватает, я засучил рукава и доработал драйвер по образцу imx219: добавил #include <linux/rk-camera-module.h>, чтение rockchip,camera-module-* из device tree, формирование имени в формате m00_b_imx519 и обработку ioctl RKMODULE_GET_MODULE_INFO. Заодно создал IQ-файл imx519_arducam-imx519_default.json, скопировав его с ближайшего родственника — imx219 (калибровка будет неточной по цветам, но rkaiq должен запуститься).
Пересобрал ядро заново, перезагрузился и проверяю имя сенсора:
media-ctl -d /dev/media0 -p | grep -A1 "CAM_SENSOR\|imx519"
- entity 63: m00_b_imx519 6-001a (2 pads, 1 link, 0 routes)
type V4L2 subdev subtype Sensor flags 0
Имя изменилось на m00_b_imx519 — rockchip-формат на месте. Запускаю rkaiq с надеждой:
rkaiq_3A_server
Cound not find rkisp dev names, skipped /dev/media0
ERR: Bad media topology for: /dev/media0
CAMHW:E:get isp or ispp info fail, something gos wrong!
Старая ошибка parse sensor entity name error исчезла — rkaiq теперь распознаёт сенсор. Но на её месте выросла новая: Bad media topology. Прогресс есть, но стена просто отодвинулась.
Диагноз: split-pipeline #
Стал разбираться, в чём дело. На RK3588 медиа-инфраструктура оказалась разрезана надвое:
/dev/media0 (rkcif): сенсор → DPHY → CSI2 → CIF stream-узлы
/dev/media1 (rkisp0): rkisp-isp-subdev ← rawrd0_m ← ... → mainpath
Сенсор и CIF-захват живут на одном media-устройстве, а ISP (процессор обработки) — на другом. rkaiq же перебирает /dev/media%d и для каждого ищет сенсор и ISP вместе. На media0 есть сенсор, но нет ISP; на media1 — наоборот. Ни одно устройство не подходит, отсюда Bad media topology для всех.
При этом сам rkaiq_3A_server за всю историю платы не отработал успешно ни разу — 2102 «Bad topology» и 0 «engine succeed» в логах. То есть это не специфика моего imx519: камерный стек сломан здесь для любого сенсора.
💡 Что такое split-pipeline. На RK3588 есть две аппаратные единицы: CIF (Camera Interface — захват сырого потока с MIPI) и ISP (Image Signal Processor — обработка: дебайеризация, баланс белого, гамма). В простом режиме они работают как отдельные подсистемы и создают два независимых media-device. Связь между ними — через DDR: CIF пишет туда RAW-кадр, а ISP забирает его через узлы
rawrd*. Но rkaiq хочет видеть весь путь как единый граф на одном media-устройстве — и в split-режиме этого не получает.
Перепроверил гипотезы (и отбросил две) #
Прежде чем лезть в device tree, проверил версии и попытался обойти rkaiq вручную.
Гипотеза 1: несовпадение версий rkaiq ↔ ядро. Сравнил: rkaiq 5.0x4.1-rk3588 пишет ISP HW ver: 30, в конфиге ядра CONFIG_VIDEO_ROCKCHIP_ISP_VERSION_V30=y, rkisp-драйвер v02.09.00. Всё совпадает. Версии — не причина.
Гипотеза 2: получить кадр в обход rkaiq. На Neardi Wiki нашёл рабочий пример захвата с mainpath через v4l2-ctl --stream-mmap=3 --stream-skip=3. Попробовал то же у себя: активировал линк rkisp_rawrd0_m → rkisp-isp-subdev, выставил форматы, запустил захват. Результат — select timeout, файл 0 байт. CIF-узел /dev/video0 и вовсе не открывается (error 515 / EOPNOTSUPP).
Замкнутый круг: ISP (rawrd на media1) читает кадр из DDR, куда его должен записать CIF (на media0). Но CIF не запускается без «terminal subdev» синхронизации с ISP, а ISP не запускается без данных от CIF. Каждая сторона ждёт другую — и никто не начинает.
Решение в исходниках: rkisp-unite #
Интернет по Bad media topology давал мало: issue #254 на ubuntu-rockchip закрыт без решения (автор ушёл на Arch). Зато в исходниках самого ядра Repka нашёл подсказку — в rk3588s.dtsi узел rkisp0_vir0 содержит закомментированную альтернативу:
rkisp0_vir0: rkisp0-vir0 {
rockchip,hw = <&rkisp0>; /* одиночный ISP0 → split */
/*
* dual isp process image case
* other rkisp hw and virtual nodes should disabled
* rockchip,hw = <&rkisp_unite>; ← объединённый режим */
*/
};
А в rk3588s-tablet-single.dtsi этот режим включён явно: rkisp_unite + rkisp_unite_mmu в okay, rkisp0_vir0 указывает на rkisp_unite, а одиночный rkisp0 — выключен. Это и есть dual-ISP unite mode, при котором два ISP-блока объединяются в единый media-graph. И судя по документации Firefly, топология overlay у меня верная — не хватает только перевести её в unite-режим.
Что меняю в overlay #
Добавил в свой rk3588-csi-imx519.dts три изменения:
| Узел | Было (split) | Стало (unite) |
|---|---|---|
rkisp0 (одиночный ISP0) |
status = "okay" |
status = "disabled" |
rkisp0_vir0 |
rockchip,hw = <&rkisp0> |
rockchip,hw = <&rkisp_unite> |
rkisp_unite |
disabled |
status = "okay" |
rkisp_unite_mmu |
disabled |
status = "okay" |
После перекомпиляции overlay проверил, что все метки фиксапов (включая новые rkisp_unite, rkisp_unite_mmu) присутствуют в базовом DTB — иначе overlay не применится. Применились, ок.
Ожидание: после перезагрузки rkaiq должен найти сенсор и ISP на одном media-устройстве, Bad media topology исчезнет, и кадр пойдёт. Но это пока гипотеза — проверить можно только ребутом.
💡 Почему я не уверен. Unite-режим официально описан для планшетных конфигураций RK3588 (где он работает). На Repka Pi 5 никто его, судя по всему, не включал — отсюда и сломанный rkaiq «из коробки». Шанс, что unite поднимет единый граф — высокий, но не 100%. Если не поможет — останется последний рычаг: стриминг через
libv4l-rkmpp/ GStreamer rk-плагин в обход rkaiq. Но это уже третья стена, и о ней — если до неё дойдёт.
Третий акт: kernel-отладка, или шесть багов в чужом драйвере #
Unite оказался ложным следом: он объединяет два ISP-блока для удвоения ширины кадра, а не сливает media-устройства. Сенсор по-прежнему на media0, ISP — на media1, rkaiq так же падает. Тогда я перестал гадать и пошёл по жёсткому пути — добавлять отладочный вывод прямо в ядро и собирать его после каждого изменения. Этот третий акт состоял из шестнадцати пересборок ядра и шести найденных багов, каждый из которых блокировал свой этап. Рассказываю по порядку — это полезно даже если у вас другой сенсор.
Баг №1 и №2: g_frame_interval без фильтра ENOIOCTLCMD #
Сначала добавил pr_info в rkcif_fh_open (open-handler CIF) и увидел точное место отказа:
RKCIF_OPEN: ENTER stream[0]
RKCIF_OPEN: attach_hw ok
RKCIF_OPEN: update_sensor_info FAIL ret=-515
515 = ENOIOCTLCMD — «ioctl не реализован». Оказалось, в rkcif_update_sensor_info (capture.c) CIF вызывает у сенсора g_frame_interval — необязательный video-subdev-op, который наш imx519 не реализует. Соседние проверки корректно фильтруют этот код:
// Правильно (соседи):
ret = v4l2_subdev_call(..., get_mbus_config, ...);
if (ret && ret != -ENOIOCTLCMD) { return ret; }
// Баг (наш случай):
ret = v4l2_subdev_call(..., g_frame_interval, ...);
if (ret) { return ret; } ← не фильтрует ENOIOCTLCMD → open падает с -515
Починил — добавил фильтр. open() заработал. Но тут же вскрылся второй экземпляр того же бага — в rkcif_set_fmt. Там g_frame_interval тоже без фильтра возвращал -515, из-за чего cif_fmt_out оставался NULL, а потом VIDIOC_REQBUFS делал null-deref → kernel oops → зависание платы (watchdog тормозил перезагрузку). Тот же фикс, и crash ушёл.
💡 Урок. Когда находишь баг «отсутствует фильтр ENOIOCTLCMD» — проверь все вызовы
g_frame_intervalв файле. У RKCIF их восемь, и два были сломаны одинаково. Лечится одним паттерном.
Баг №3: field = INTERLACED вместо NONE #
После фиксов open и set_fmt заработали, но STREAMON падал с EINVAL в rkcif_sanity_check_fmt. Добавил отладку в rkcif_get_input_fmt и увидел:
RKCIF_FMT: sensor code=0x300f field=1 size=1920x1080
code=0x300f (SRGGB10_1X10) — правильно. Но field=1 (INTERLACED) — неправильно, должно быть 0 (NONE). В таблице in_fmts для SRGGB10_1X10 только field=NONE, поэтому совпадения нет → NULL → EINVAL.
Наш драйвер в imx519_update_image_pad_format устанавливал width/height, но не трогал field — и ядро V4L2 оставляло там мусор (1). Фикс — одна строка:
fmt->format.field = V4L2_FIELD_NONE;
Баг №4: crop bounds больше input #
Sanity прошла, но новая ошибка: crop size is bigger than input. Сенсор в get_selection(CROP_BOUNDS) возвращал полный pixel array (8, 48, 4656, 3496), а CIF копировал это в свой crop и проверял crop.left + crop.width <= input.width. При left=8, width=4656 сумма 4664 > 4656 — превышение на 8 пикселей. На Raspberry Pi unicam это прощает, RKCIF — нет.
Фикс — в get_selection для CROP_BOUNDS возвращать размер текущего mode, а не весь pixel array:
case V4L2_SEL_TGT_CROP_BOUNDS:
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = imx519->mode->width;
sel->r.height = imx519->mode->height;
Баг №5: dummy buffer размера 0 #
Crop прошёл, но STREAMON упал с ENOMEM. Лог: Failed to allocate the memory for dummy buffer, size 0. CIF пытался создать dummy-буфер (для режима без реального стрима), но размер вычислялся через enum_frame_interval, который наш сенсор не реализует → размер оставался 0.
Обход — выключить dummy через sysfs:
echo 0 > /sys/devices/platform/rkcif-mipi-lvds2/is_use_dummybuf
Баг №6: LINK_FREQ без control-флагов (финальный софтверный) #
После dummybuf ошибка сменилась на EPIPE, а в логах:
rockchip-csi2-dphy0: No pixel rate control in subdev
DPHY ищет у сенсора control V4L2_CID_LINK_FREQ (сообщение про «pixel rate» вводит в заблуждение). Я добавил регистрацию LINK_FREQ в imx519_init_controls, но с ошибкой — передал &imx519_ctrl_ops вместо NULL. Из-за этого V4L2 считал control изменяемым, вызывал s_ctrl, который не обрабатывает LINK_FREQ → ctrl(id:0x9f0901,val:0x0) is not handled → stream off.
// Баг:
v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_LINK_FREQ, ...);
// Правильно (как в imx219):
v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, ...);
После фикса not handled исчез, и DPHY наконец включился:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 987
csi2_dphy_s_stream stream on:1, dphy1, ret 0
Прогресс симптомов — вся картина #
Шесть багов, каждый блокировал свой этап. Симптомы эволюционировали так:
| Этап | Симптом | Баг | Фикс |
|---|---|---|---|
open() |
error 515 |
g_frame_interval без фильтра | ret != -ENOIOCTLCMD |
set_fmt |
kernel oops → hang | то же в set_fmt → null deref | тот же фикс |
sanity_check |
EINVAL |
field=INTERLACED | field = V4L2_FIELD_NONE |
sanity_check |
EINVAL crop |
bounds > input | bounds = mode size |
STREAMON |
ENOMEM |
dummy buffer size 0 | is_use_dummybuf=0 |
STREAMON |
EPIPE |
нет LINK_FREQ control | v4l2_ctrl_new_int_menu(..., NULL, ...) |
STREAMON |
stream ON, кадра нет | физический слой MIPI | (открытая задача) |
Где мы сейчас: сенсор стримит, кадра нет #
После шести фиксов pipeline полностью активируется — но кадр всё ещё 0 байт. Казалось бы, опять стена. Но чтобы понять, где именно обрывается поток, я добавил отладку в сам сенсорный драйвер — в imx519_set_stream и imx519_start_streaming. И получил ответ, который всё расставил по местам:
dmesg | grep -E "SET_STREAM|START_STREAMING"
imx519 6-001a: SET_STREAM: enable=1 streaming=0
imx519 6-001a: SET_STREAM: pm_runtime_get_sync ret=0
imx519 6-001a: START_STREAMING: mode=1920x1080
imx519 6-001a: START_STREAMING: writing common regs (272)
imx519 6-001a: START_STREAMING: common regs OK
imx519 6-001a: START_STREAMING: writing mode regs (73)
imx519 6-001a: START_STREAMING: mode regs OK
imx519 6-001a: START_STREAMING: ctrl setup OK
imx519 6-001a: START_STREAMING: writing MODE_SELECT=STREAMING
imx519 6-001a: START_STREAMING: MODE_SELECT ret=0 (DONE)
imx519 6-001a: SET_STREAM: start_streaming ret=0
Каждый шаг — ret=0. Сенсор полностью инициализируется: 272 общих регистра + 73 регистра режима записаны по I²C без единой ошибки, controls применены, регистр MODE_SELECT переведён в режим стриминга. Команда s_stream(1) дошла до сенсора, и он подтвердил готовность. Софтверная инициализация — идеальна.
DPHY тоже включается корректно:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 987
csi2_dphy_s_stream stream on:1, dphy1, ret 0
987 Мбит/с = 493.5 МГц × 2 (DDR) — это ровно та link frequency, которую мы задали в overlay. DPHY вычислил тайминги из LINK_FREQ control, который мы добавили в баге №6. Всё сходится.
Последняя софтверная попытка: continuous clock #
Я попробовал убрать clock-noncontinuous из device-tree overlay — многие сенсоры требуют именно continuous clock (DPHY постоянно генерирует тактовый сигнал, а не только в момент передачи данных). Перекомпилировал overlay, перезагрузился — без изменений, кадр по-прежнему 0 байт.
Проверка register-tables: может, сенсор настроен неправильно? #
Раз continuous clock не помог, возникло подозрение: а вдруг register-tables драйвера (писавшиеся Arducam для Raspberry Pi) настраивают MIPI-выход сенсора в режим, несовместимый с RK3588 DPHY? Например, неправильное число lanes, или PLL выдаёт не ту частоту.
Проверил. В driver-таблицах для всех режимов:
{0x0114, 0x01}, // CSI_LANE_MODE = 2-lane — правильно для RK3588
{0x0301, 0x06}, // PLL VTPXCK_DIV
{0x0303, 0x04}, // PLL VTSYCK_DIV
{0x0307, 0x40}, // PLL_VT_MPY
// ... и так далее
Регистр 0x0114 (CSI lane mode) = 0x01 — это 2-lane режим, ровно то, что наш overlay и заявляет (data-lanes = <1 2>). PLL-настройки тоже есть и записываются без ошибок (видели в логе START_STREAMING). Поиск по интернету показал: IMX519 datasheet — под NDA Sony, и единственный источник register-tables — это сам Arducam-драйвер. Альтернативных RK3588-специфичных таблиц не существует. При этом те же таблицы работают на Raspberry Pi — значит регистры сами по себе корректны, и MIPI-выход сенсора настроен правильно.
Register-tables не виноваты.
Сравнение с Raspberry Pi 5: камера жива #
Чтобы исключить битую камеру, сняли ту же Arducam IMX519 на Raspberry Pi 5 — и получили полноценный кадр 4656×3496 JPEG, 1.15 МБ. Камера, шлейф и MIPI-выход сенсора — исправны.
Сравнение параметров выявило критичное расхождение:
| Параметр | RPi 5 (работает) | Repka Pi 5 (кадра нет) |
|---|---|---|
| link-frequency | 408 МГц | |
| data_rate_mbps | 816 | |
| clock-noncontinuous | да | |
| pixel_rate | 426.67 МГц | то же |
| 0x0114 (lane mode) | 0x01 (2-lane) | то же |
Драйвер Arducam содержал IMX519_DEFAULT_LINK_FREQ = 493500000 — неправильное значение. Реальная link frequency сенсора — 408 МГц (подтверждено V4L2 controls на RPi5). После исправления DPHY переключился на правильные 816 Мбит/с:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 816
csi2_dphy_s_stream stream on:1, dphy1, ret 0
816 Мбит/с — ровно то, что и на работающем RPi5. Но кадр по-прежнему не доходит.
Итог: граница софтвера #
После исправления link-frequency и восстановления clock-noncontinuous — все софтверные параметры совпадают с RPi5. Все доказательства говорят одно:
- ✅ Камера жива (кадр получен на RPi5, 1.15 МБ)
- ✅ Сенсор полностью инициализируется (345 регистров, все
ret=0) - ✅ DPHY включён с правильным таймингом (816 Мбит/с, как на RPi5)
- ✅ link-frequency, clock-noncontinuous, lane mode — всё совпадает с RPi5
- ❌ MIPI-данные всё равно не доходят до CIF на Repka
При этом I²C работает (chip-ID читается, регистры пишутся), а MIPI high-speed lanes — нет. Это возможно, потому что I²C и MIPI — разные пары проводов в шлейфе: I²C (SCL/SDA) подключён правильно, а MIPI lanes (CLK±, D0±, D1±) — могут не быть. Несмотря на то, что разъём 22-pin «совместим» с RPi5, фактическая разводка MIPI lanes на плате Repka Pi 5 может отличаться, или RK3588 DPHY требует иной lane-mapping, чем RPi5 rp1-cfe.
Дальнейшая диагностика требует физического доступа — осциллографа или логического анализатора на MIPI-линиях, чтобы увидеть, выходит ли реально high-speed сигнал с сенсора и доходит ли он до DPHY RK3588. Это уже не та задача, которую решает правка кода — софтверный фундамент полностью готов.
💡 Главный урок третьего акта. Я трижды ошибался с гипотезами (metadata-pad, rkisp-unite, register-tables), пока не перестал гадать и не начал инструментировать ядро напрямую.
pr_infoв нужной функции за одну итерацию даёт больше, чем часы чтения кода и гугления. Когда упёрся в стену — добавь отладочный вывод в каждую проверку функции, пересобери, и последняя напечатанная строка покажет точное место отказа. Это дороже (цикл сборки), но вернее любых догадок. А когда каждый шаг в логе показываетret=0, и параметры совпадают с работающей платформой — пора признать, что проблема не в коде, а в физике подключения.
Четвёртый акт: настоящий баг — link_freq #
И вот тут — поворот, ради которого стоило всё предыдущее. Вывод про «физика MIPI, нужен осциллограф» в конце третьего акта оказался неверным. Софт там был небезупречен, просто я ошибочно пришёл к выводу, что все софтверные гайки закручены. На самом деле одна критическая гайка сидела криво, и именно она изображала «аппаратную неисправность». Этот четвёртый акт добавил к шести багам ещё четыре и привёл, наконец, к реальному кадру.
Баг №7: PM-runtime в DPHY-hw (коротко) #
Прежде чем перейти к главному, — один промежуточный баг. На этапе, когда начал трассировать MIPI-приёмник, вскрылось, что функция csi2_dphy_hw_stream_on намертво зависала на первой же MMIO-записи. Причина: PHY-hw устройство (fedc0000.csi2-dphy0-hw) оставалось в runtime suspended без resume-колбэка, и первая попытка записать в его регистры вставала в ожидании шины:
cat /sys/devices/platform/fedc0000.csi2-dphy0-hw/power/runtime_status
# suspended ← блок спит, MMIO недоступен
Фикс — pm_runtime_get_sync(hw->dev) перед MMIO-доступом и балансный pm_runtime_put в stream_off. После этого функция дошла до конца, и THS_SETTLE=0x16 корректно пишется (readback OK). Важно другое: даже после этого фикса DPHY честно программировался, сенсор честно стримил, а PHY_STATE bit0 (HS-clock активен) оставался нулём. Кадра по-прежнему не было. И вот тогда в дело вступил проверочный сенсор.
💡 Что такое
PHY_STATE bit0? У Synopsys DWC CSI-2 host есть регистр статуса приёмника.bit0означает «clock lane в режиме High-Speed» — то есть приёмник физически ловит высокочастотный тактовый сигнал от сенсора. Еслиbit0 = 0, данных нет в принципе: без HS-clock приёмник не может привязать биты ко времени. Этот бит был главным «измерителем здоровья» MIPI-физики весь четвёртый акт.
Эксперимент, который всё перевернул: OV5647 на той же разводке #
Гипотеза «виновата разводка Repka» требовала проверки, а осциллографа под рукой не было. Тогда подключил вместо IMX519 простую OV5647 (от AlphaBot 2) — она работает на низкой скорости ~350 Мбит/с. И кадр пошёл. На той же разводке, через тот же шлейф, через тот же софт-путь (INNO DPHY0 → CSI Host 2), на том же bit0, который наконец стал единицей. Подробно история заведения OV5647 (xclk 25 МГц, привязка iqfile, типы чёрных кадров) описана в отдельном посте — OV5647 на Repka Pi 5: первый кадр.
Это был момент истины: разводка исправна. Если OV5647 на 350 Мбит/с идёт, а IMX519 на 816 Мбит/с — нет, то виновата не плата. Оставалось понять, чем именно различаются эти два случая на уровне MIPI-частоты. Первая, ленивая мысль — «высокоскоростной MIPI просто не проходит по геометрии дорожек Repka». И она оказалась ложной, как выяснилось через час.
Баг №8 (ГЛАВНЫЙ): рассогласование link_freq #
Раз OV5647 работает, а IMX519 — нет, я полез сравнивать сам драйвер IMX519. Скачал официальный репозиторий Arducam (github.com/ArduCAM/IMX519_AK7375), взял оттуда imx519.c и сделал diff с тем, что лежит в дереве Repka OS. Результат меня буквально ошарашил — расхождение ровно в одной строке:
// Оригинал Arducam (GitHub):
#define IMX519_DEFAULT_LINK_FREQ 493500000 // = 987 Мбит/с (DDR ×2)
// Дерево Repka OS:
#define IMX519_DEFAULT_LINK_FREQ 408000000 // = 816 Мбит/с (DDR ×2)
Кто-то в дереве Repka OS поменял только этот #define — и больше ничего. Но #define сам по себе частоту не задаёт! Реальную MIPI-частоту формируют регистры сенсора 0x030x/0x040x (PLL и делители MIPI-выхода). А они в обеих версиях драйвера абсолютно идентичны — diff по регистрам показал ноль различий. Значит, сенсор физически всегда выдаёт 987 Мбит/с, что бы ни писал #define. Константа в драйвере — это лишь то, что он сообщает остальной системе (через control V4L2_CID_LINK_FREQ), а не то, что сенсор физически генерирует.
💡 Что такое
link_freqи почему константа должна совпадать с реальностью? MIPI-приёмник (DPHY) не «автоматически» подстраивается под приходящий сигнал — его нужно запрограммировать под ожидаемую частоту. Конкретно он вычисляет тайминги захвата, самый критичный из которых — THS_SETTLE: окно времени, в течение которого приёмник ожидает перехода clock-lane в режим High-Speed после старта передачи. Если THS_SETTLE рассчитан под одну частоту, а сенсор выдаёт другую — приёмник «смотрит не в то окно» и просто не замечает HS-clock. КонстантаIMX519_DEFAULT_LINK_FREQ— это и есть то значение, под которое DPHY настраивает THS_SETTLE. И она обязана совпадать с тем, что реально выходит с сенсора.
Получилось классическое рассогласование:
- DPHY-приёмник читал
link_freq = 408 МГци настраивалTHS_SETTLEпод 816 Мбит/с. - Сенсор физически гнал 987 Мбит/с (PLL-регистры оригинальные, нетронутые).
- Приёмник открывал окно «не в то время» → HS-clock не детектировался →
PHY_STATE bit0 = 0→ кадра нет.
И вот теперь самое досадное: в третьем акте я сам же видел в логе data_rate_mbps 816 и радовался — «ага, совпадает с RPi5». Но на RPi5 приёмник rp1-cfe устроен иначе, и рассогласование там не критично, а на RK3588 DPHY оно убивает на корню. Симптом «приёмник не видит сигнал» выглядел стопроцентно аппаратным — а первопричиной был один-единственный неправильный #define в драйвере.
Фикс: вернуть честные 493.5 МГц #
Две правки — в драйвере и в overlay, чтобы значение не «разъехалось» между ними:
// imx519.c
#define IMX519_DEFAULT_LINK_FREQ 493500000 // было 408000000
/* rk3588-csi-imx519.dts, в endpoint сенсора (валидация в драйвере это проверяет) */
link-frequencies = /bits/ 64 <493500000>; /* было 408000000 */
Пересборка ядра (драйвер built-in, см. третий акт), ребут — и в логе теперь честные 987 Мбит/с:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 987 ← совпадает с физикой сенсора
Запускаю стрим и читаю кадр. Сначала было даже страшно смотреть — но:
кадр 0: avg=110.7 ненулевых=100% ← РЕАЛЬНОЕ ИЗОБРАЖЕНИЕ ✓
кадр 8: avg=110.7 ненулевых=100% ← стабильно!
avg=110 (средняя яркость по кадру), 100% пикселей ненулевые — это не чёрный экран, не шум, не синий кадр. Это нормальная картинка. После четырёх актов, десяти багов и семнадцати пересборок ядра — кадр пошёл. Этот #define и был финальным корнем всей эпопеи.
💡 Урок бага №8. Константа в драйвере и физическая реальность сенсора — две разные вещи.
#define LINK_FREQописывает, что драйвер думает о частоте; регистры0x030x/0x040xопределяют, что сенсор реально выдаёт. Менять одно без другого — гарантированно сломать приёмник. И самый коварный момент: симптом («приёмник не ловит сигнал») выглядит аппаратным, и можно потратить дни на осциллограф и разводку, прежде чем догадаешься проверить одну константу в драйвере на совпадение с оригиналом.
Баг №9: unite (dual-ISP) затемняет правую половину #
Обрадовавшись кадру, я присмотрелся и обнаружил, что он битый. Левая половина — нормальное изображение, правая — затемнённый градиент. Симптом не зависит от разрешения (одинаково на 640×480 и 1920×1080). Гистограмма по половинам:
| Зона | avg | что видно |
|---|---|---|
| Левая половина (X 0–960) | 152.6 | реальное изображение (std=113) |
| Правая половина (X 960–1920) | 66.9 | затемнённый градиент (std=59) |
Граница ровно посередине кадра — и это не совпадение. Это multi-ISP split. В overlay включён rkisp_unite (двойной ISP): кадр делится пополам, левую половину обрабатывает ISP0, правую — ISP1. И на этом стенде правая половина выходит кривой. В логе rkaiq это видно как status params of left isp and right isp are different — ISP0 и ISP1 рассинхронизируются, а в момент останова стрима — rkisp_stream_stop id:0 timeout.
💡 Что такое rkisp_unite? У RK3588 два независимых ISP-блока (
rkisp0@fdcb0000,rkisp1@fdcc0000). Режимrkisp_uniteсклеивает их в один «двойной» ISP, чтобы тянуть большие кадры (вплоть до 16 МП) — каждый блок берёт свою половину ширины плюс 128 пикселей перекрытия (RKMOUDLE_UNITE_EXTEND_PIXEL). В коде CIF это выглядит какwidth /= 2; width += RKMOUDLE_UNITE_EXTEND_PIXEL. Для маленьких кадров это избыточно, и если стыковка двух ISP идёт вкривь — правая половина темнеет.
Корень конкретно на узких кадрах — в логике unite-перекрытия: при right_crop_y < RKMOUDLE_UNITE_EXTEND_PIXEL (128) коррекция overlap ломает захват правой половины. Для 1080p (высота 1080) это условие формально не должно срабатывать по crop_y, но рассинхрон ISP0/ISP1 всё равно делает правую половину непригодной. Лечить сам движок unite на этом стенде бессмысленно — проще обойти.
Фикс: single-ISP #
В overlay поменять статусы зеркально — включить одиночный rkisp0+isp0_mmu, выключить rkisp_unite+rkisp_unite_mmu, и убрать свойство rockchip,hw = <&rkisp_unite> из rkisp0_vir0:
/* было (unite): */
fragment@9 { target = <&isp0_mmu>; __overlay__ { status = "disabled"; }; };
fragment@10 { target = <&rkisp0>; __overlay__ { status = "disabled"; }; };
fragment@11 { target = <&rkisp0_vir0>; __overlay__ { status = "okay"; rockchip,hw = <&rkisp_unite>; }; };
fragment@12 { target = <&rkisp_unite>; __overlay__ { status = "okay"; }; };
fragment@13 { target = <&rkisp_unite_mmu>; __overlay__ { status = "okay"; }; };
/* стало (single-ISP): */
fragment@9 { target = <&isp0_mmu>; __overlay__ { status = "okay"; }; };
fragment@10 { target = <&rkisp0>; __overlay__ { status = "okay"; }; };
fragment@11 { target = <&rkisp0_vir0>; __overlay__ { status = "okay"; /* без rockchip,hw */ }; };
fragment@12 { target = <&rkisp_unite>; __overlay__ { status = "disabled"; }; };
fragment@13 { target = <&rkisp_unite_mmu>; __overlay__ { status = "disabled"; }; };
⚠️
rkisp0/isp0_mmuиrkisp_unite/rkisp_unite_mmuсмотрят на одни и те же регистры (0xfdcb0000,0xfdcb7f00). Включать их одновременно нельзя (EBUSY). Поэтому это именно зеркальная перестановка: одни включаем, другие гасим.
Меняется только device tree — ядро пересобирать не нужно. Перекомпиляция overlay + ребут, и:
| Левая ½ | Правая ½ | соотношение | таймауты | |
|---|---|---|---|---|
| unite (было) | 152.6 | 66.9 | 0.44 ✗ | stream_stop timeout |
| single-ISP (стало) | 161.2 | 150.8 | 0.94 ✓ | нет |
Полосы исчезли, баланс белого выровнялся, таймауты ISP пропали. IMX519 выдаёт равномерное изображение 1920×1080.
💡 Заметка на полях: тот же баг ловит и OV5647. Когда «завели» OV5647 на unite, я обрадовался общему avg=121 и не проверил половины. А правая половина у неё тоже была тёмной (right/left = 0.56). Просто более низкое разрешение и более яркая сцена камуфлировали дефект. Так что single-ISP — правильный выбор для любой камеры на этом стенде, не только IMX519.
Баг №10: single-ISP не даунскейлит full-res → чёрные строки #
Кадр пошёл равномерный, но гистограмма оказалась бимодальной: из 1080 строк 623 полностью чёрные (яркость ≈ 2.0), и идут они характерными группами примерно по 93 строки. Причина быстро нашлась: single-ISP не умеет даунскейлить full-res 4656×3496 в 1920×1080. Сенсор отдавал полный 16-мегапиксельный кадр, а ISP ломался при попытке ужать его почти втрое — отсюда вываливающиеся чёрные полосы.
Фикс — не заставлять ISP делать даунскейл, а переключить режим самого сенсора в 1920×1080 через subdev, чтобы сенсор делал binning аппаратно и отдавал уже готовый 1080p:
# Сенсор сам делает binning до 1080p — ISP получает готовый размер, даунскейл не нужен
v4l2-ctl -d /dev/v4l-subdev2 --set-subdev-fmt pad=0,width=1920,height=1080
Тогда ISP достаётся уже 1920×1080, никакой даунскейл не требуется, кадр становится чистым — гистограмма одновершинная, битых строк ноль. Именно это и делает готовый скрипт захвата (см. шпаргалку в конце).
Ограничения по разрешениям #
После всех десяти фиксов проверил, какие разрешения реально живут на single-ISP + rkaiq:
| Режим сенсора | single-ISP | Что происходит |
|---|---|---|
| 1920×1080 | ✅ работает чисто | rkaiq сам настраивает ISP crop, всё согласовано; avg≈130, без полос, 0 битых строк |
| 2328×1748 (8 МП) | ⚠️ нужен ручной crop ISP | → ломает rkaiq (AWB stats invalid), кадр чёрный/с полосами; плюс stride-padding 2336 vs 2328 (ISP выравнивает до 64 байт) — без учёта stride изображение срезает вбок |
| 3840×2160 (4K) | ⚠️ нестабильно | иногда select timeout |
| 4656×3496 (16 МП) | ✗ select timeout |
single-ISP физически не тянет; для этого и нужен unite, а unite на этой плате мёртв |
Корень ограничений выше 1080p: single-ISP оставляет crop на ISP subdev равным 1920×1080 (видно в media-ctl: crop:(0,0)/1920x1080). Чтобы получить полный кадр, нужно вручную выставить --set-subdev-selection target=crop. Но как только трогаешь ISP crop вручную — rkaiq ломается: в логе AWB stats invalid, ignore, экспозиция не сводится (avg≈2 на всех кадрах). rkaiq — это не пассивный обработчик, а петля управления 3A, которая сама пишет окно измерений (завязанное на crop); вмешавшись, рвёшь петлю.
💡 Почему rkaiq такой чувствительный. Движок 3A (AE/AWB/AF) читает статистику из ISP, считает экспозицию/ББ/гейн и пишет их обратно в ISP-регистры — включая окно измерения. Если извне поменять crop — статистика начинает приходить из «не того» окна, rkaiq её отбрасывает, петля рвётся. На 1080p crop по умолчанию уже правильный, поэтому всё работает; на 8/16 МП он не совпадает — и любое ручное вмешательство только вредит.
Итог по разрешениям: на single-ISP стабильно и чисто работает только 1920×1080. Это и есть рабочая конфигурация IMX519 на Repka Pi 5. Full-res 16 МП формально требует unite, а unite на этой плате мёртв (рассинхрон ISP0/ISP1). Возможно, 8/16 МП удалось бы завести, разобравшись, как корректно скармливать rkaiq нестандартный crop (через его собственный API, а не через v4l2) — но это уже отдельная большая задача.
Ирония финала: железный симптом, софтверная причина #
Главный урок всей эпопеи — не в технических деталях, а в классе багов. Симптом «PHY_STATE bit0 = 0, HS-clock не ловится» выглядел аппаратным: приёмник физически не видит сигнал сенсора. И он действительно аппаратный — DPHY физически не ловит clock. Но первопричиной был софтверный баг: DPHY настраивался под неправильную частоту, потому что одна константа в драйвере не совпадала с реальностью. Уберите баг — и то же железо, та же разводка, тот же сенсор начинают работать.
Это самый коварный класс ошибок: аппаратный симптом + софтверная причина. Причём «доказательства», что виновато железо, были убедительными: все ret=0 в логе, параметры совпадают с RPi5, I²C работает. Просто один из «совпадающих» параметров (link_freq) совпадал с ошибочным эталоном — мы сравнивали с RPi5, где рассогласование некритично, и радовались совпадению. Я даже успел написать вывод «нужен осциллограф и схема платы», прежде чем один-единственный diff с оригинальным драйвером всё перевернул.
Разводка платы Repka Pi 5 оказалась исправной — это доказано OV5647, которая работает на той же разводке, через тот же шлейф, через тот же софт-путь. Все десять багов оказались софтверными:
| № | Баг | Симптом | Слой |
|---|---|---|---|
| 1 | g_frame_interval без фильтра ENOIOCTLCMD | error 515 |
RKCIF |
| 2 | то же в set_fmt → null-deref | kernel oops → hang | RKCIF |
| 3 | field = INTERLACED |
EINVAL |
imx519 |
| 4 | crop bounds > input | EINVAL crop |
imx519 |
| 5 | dummy buffer size 0 | ENOMEM |
RKCIF |
| 6 | LINK_FREQ без control-флагов | EPIPE |
imx519 |
| 7 | PM-runtime в DPHY-hw | зависание на MMIO | DPHY |
| 8 | link_freq 408 vs 493.5 МГц | PHY_STATE bit0 = 0 | imx519 (главный) |
| 9 | unite (dual-ISP) рассинхрон | полосы на правой половине | rkisp_unite |
| 10 | full-res + даунскейл single-ISP | бимодальные чёрные строки | rkisp |
💡 Мораль четвёртого акта. Даже когда кажется «виновато железо», проверь, правильно ли софт вообще говорит железу, что делать. Симптом «приёмник не видит сигнал» не означает, что проблема в приёмнике или проводе — она может быть в том, что софт настроил приёмник на чужую частоту. Аппаратный симптом без софтверной первопричины — редкость; аппаратный симптом с софтверной первопричиной — классика, на которую легко потерять дни.
Как запустить у себя: тянем из репозитория #
Все патчи, оверлей, скрипт захвата, собранные кадры и оба поста лежат в репозитории: https://gitflic.ru/project/beeugene/imx519-fo-repka-pi-patch, рабочая ветка — imx519-working.
⚠️ Предупреждение. Действия ниже затрагивают загрузчик и ядро. Делайте только при наличии резервной SD-карты для восстановления (или возможности перепрошить). Ядро и оверлей — для Repka Pi 5 (RK3588), на других платах не проверялись. Прежде чем перезаписывать — сохраните оригиналы (команды ниже).
Что в репозитории #
drivers/ изменённые исходники ядра (PM-фикс + честный link_freq)
media/i2c/imx519.c ← IMX519_DEFAULT_LINK_FREQ 493500000
phy/rockchip/phy-rockchip-csi2-dphy-hw.c ← pm_runtime_get_sync в stream_on
dtb/rockchip/overlay/ готовый .dtbo + читаемый .dts оверлей
rk3588-csi-imx519.{dts,dtbo} ← путь Host 2 + 493.5 МГц + single-ISP
tools/
cam_imx519.sh ← захват кадра (1920×1080, single-ISP, rkaiq)
images/ кадры-результаты (JPG)
blog/ оба поста-расследования
Скомпилированное ядро в репо не лежит (привязано к версии, ~13 МБ). Ниже два пути: либо пересобрать из патчей (надёжно), либо — если ваше ядро уже содержит исправления — поставить только overlay.
Вариант A. Полная установка с пересборкой ядра #
Этот путь воспроизводит всю историю четвёртого акта: накладываем исправления на дерево linux-rockchip и пересобираем образ. Нужно дерево исходников ядра Repka OS (через apt source или архив с GitFlic) и около часа на самой Repka.
# 1. Клонируем репо патчей
git clone https://gitflic.ru/project/beeugene/imx519-fo-repka-pi-patch.git
cd imx519-fo-repka-pi-patch
git checkout imx519-working
# 2. Накладываем исправления поверх дерева linux-rockchip
# (предполагаем, что дерево лежит в /root/linux-rockchip и той же версии)
cp drivers/media/i2c/imx519.c /root/linux-rockchip/drivers/media/i2c/
cp drivers/phy/rockchip/phy-rockchip-csi2-dphy-hw.c \
/root/linux-rockchip/drivers/phy/rockchip/
# 3. Проверяем ключевые правки
grep IMX519_DEFAULT_LINK_FREQ /root/linux-rockchip/drivers/media/i2c/imx519.c
# должно быть 493500000 ← главный фикс
grep pm_runtime_get_sync /root/linux-rockchip/drivers/phy/rockchip/phy-rockchip-csi2-dphy-hw.c
# должно найтись в csi2_dphy_hw_stream_on
# 4. Сборка ядра (на самой Repka Pi 5, ~1 час)
cd /root/linux-rockchip
make ARCH=arm64 -j8 Image
# 5. УСТАНОВКА — с обязательным БЭКАПОМ старого ядра!
cp /boot/vmlinuz-6.1.115.1-repka-pi5 /boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig
gzip -9c arch/arm64/boot/Image > /boot/vmlinuz-6.1.115.1-repka-pi5
# 6. Ставим overlay (см. Вариант B, шаги 2–4 ниже)
sudo reboot
Вариант B. Только overlay (если ядро уже исправное) #
Если ваше ядро уже содержит PM-фикс и честный link_freq (или вам хватает OV5647-пути без правки ядра), достаточно поставить overlay:
# 1. Клонируем репо
git clone https://gitflic.ru/project/beeugene/imx519-fo-repka-pi-patch.git
cd imx519-fo-repka-pi-patch && git checkout imx519-working
# 2. БЭКАП оригинального окружения (обязательно!)
cp /boot/repkaEnv.txt /boot/repkaEnv.txt.bak-mystart
# 3. Кладём overlay туда, где его ищет u-boot
cp dtb/rockchip/overlay/rk3588-csi-imx519.dtbo \
/boot/dtb/rockchip/overlay/
# 4. Прописываем overlay в repkaEnv.txt (отредактируйте строку overlays=)
# overlays=panthor-gpu csi-imx519
# 5. Проверяем, что iqfile для IMX519 на месте (rkaiq ищет его по имени модуля)
ls /etc/iqfiles/ | grep imx519
# должно быть: imx519_arducam-imx519_default.json
# (имя модуля в overlay жёстко прописано под этот файл)
sudo reboot
После загрузки проверяем, что сенсор привязался:
i2cdetect -y 6 | awk '/^1[0-9a-f]:/{print}' # 1a = UU (занят драйвером)
dmesg | awk '/imx519/{print}' | tail -5 # строки про Device found / stream
Проверка: один кадр #
Финальная проверка, что всё работает — захватить кадр готовым скриптом:
cp tools/cam_imx519.sh /root/ && chmod +x /root/cam_imx519.sh
/root/cam_imx519.sh 12 /root/imx519.jpg
# Ожидаемый вывод:
# ✓ /root/imx519.jpg (1920x1080) avg≈130 лев/прав≈130/130 [ок] тёмных строк 0/1080
Если avg≈100-150 и тёмных строк 0 — всё работает. Если avg=0 или avg=2 во всех кадрах — смотрите раздел про «типы чёрных кадров» (нужно дать rkaiq 5+ кадров на сходимость, либо проверьте, что демон rkaiq_3A активен: systemctl status rkaiq_3A).
Если новое ядро не загрузилось: откат #
Если после перезагрузки плата не поднимается (не отвечает по сети, нет картинки на HDMI) — скорее всего, ядро встало криво. Откат делается с другой системы: вынимаете накопитель (SD/eMMC), подмонтируете его на ПК или на второй Repka, и возвращаете файлы из бэкапов:
# На другой системе, с подмонтированным загрузочным разделом платы (например, /mnt)
cp /mnt/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig \
/mnt/boot/vmlinuz-6.1.115.1-repka-pi5
cp /mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig \
/mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb
# Вернуть модули
rm -rf /mnt/lib/modules/6.1.115.1-repka-pi5
mv /mnt/lib/modules/6.1.115.1-repka-pi5.bak-orig \
/mnt/lib/modules/6.1.115.1-repka-pi5
# (опционально) выключить overlay, если он мешает
sed -i 's/^overlays=.*/overlays=panthor-gpu/' /mnt/boot/repkaEnv.txt
После возврата файлов плата грузится со старым ядром, как ни в чём не бывало.
💡 Если плата грузится, но камера всё ещё не работает — это уже не «ядро не встало», а софтверная отладка: смотрите
dmesg | grep imx519, проверяйте, что overlay применился (ls /proc/device-tree/i2c@fec80000/camera-imx519@1a/), и крутите параметры overlay (регуляторы, тактовый сигнал). На этом этапе откат не нужен — ядро рабочее, просто камера требует доводки.
Если грузится, но отвалилось Wi-Fi/сеть: битый modules.dep #
Это самый коварный сценарий: плата грузится, вы радуетесь, а потом обнаруживаете, что отвалился Wi-Fi, пропал графический логин, или не работает звук. Ядро-то новое, а вот модули — недоустановлены.
Как это происходит #
При пересборке ядра make modules_install копирует сотни .ko-файлов и генерирует modules.dep — индекс, по которому modprobe находит модули. Если в момент установки происходит перезагрузка (watchdog, зависание, случайный ребут) — modules_install обрывается на середине. Файлы .ko скопированы частично, а modules.dep остаётся пустым или обрезанным (153 байта вместо нормальных ~50 КБ).
Результат: modprobe aic8800_fdrv (драйвер Wi-Fi) не находит модуль, потому что depmod не зарегистрировал его в индексе. Wi-Fi не поднимается. То же — с любым другим модулем: звуком, GPU, Bluetooth.
Как диагностировать #
# Размер modules.dep — если около 150 байт, он сломан
ls -l /lib/modules/$(uname -r)/modules.dep
# Должно быть ~50000 байт; если 150 — пересобирайте
# Проверить, знает ли modprobe о нужном модуле
modprobe -c | grep aic8800 # пусто = проблема
# Модуль физически есть, но не находится
find /lib/modules/$(uname -r) -name "aic8800*"
Как починить #
Если плата доступна (Ethernet работает, или через HDMI/клавиатуру) — перегенерируйте индекс и переустановите модули:
# Способ 1: быстрый — только пересобрать индекс (если .ko файлы на месте)
cd /root/linux-rockchip # там, где дерево исходников
make ARCH=arm64 modules_install # полная переустановка модулей
depmod -a $(uname -r) # принудительно пересобрать индекс
# Способ 2: если дерево исходников недоступно, только depmod
depmod -a $(uname -r)
ls -l /lib/modules/$(uname -r)/modules.dep # проверить размер
# Проверить, что модуль находится теперь
modprobe -c | grep aic8800
# Загрузить вручную
modprobe aic8800_fdrv
ip link show wlan0 # должен появиться интерфейс
Если Ethernet не работает (модуль dwmac тоже отвалился) — подключите монитор и USB-клавиатуру, или выпишите накопитель и сделайте depmod с другой системы, подмонтировав раздел.
⚠️ Мораль. Никогда не перезагружайтесь, пока
make modules_installполностью не завершён. Проверяйте, что последняя строка вывода —DEPMOD /lib/modules/..., а размерmodules.dep— порядка 50 КБ. Если сомневаетесь — запуститеdepmod -a $(uname -r)вручную после установки. Это спасёт вас от «отвалившегося Wi-Fi после пересборки ядра».
Что вообще может «отвалиться» #
Модульная подсистема RK3588 на Repka включает десятки драйверов, которые грузятся через modprobe:
| Что | Модуль | Симптом пропажи |
|---|---|---|
| Wi-Fi | aic8800_fdrv, aic8800_bsp |
нет wlan0 |
| Звук (HDMI) | snd-hdmi |
нет звука |
| GPU | panthor |
нет графики |
| Видео-кодеки | mpp_* |
не работает аппаратное видео |
| Камера (если модуль) | imx519 |
нет /dev/video* |
Если что-то из этого пропало после пересборки — сначала проверьте modules.dep, а не вините ядро.
Итоги #
| Параметр | Значение |
|---|---|
| Камера | Arducam IMX519 16MP (fixed-focus) |
| Платформа | Repka Pi 5 (RK3588), ядро 6.1.115.1-repka-pi5 |
| Путь | пересборка ядра + 10 баг-фиксов в RKCIF/imx519/DPHY/rkisp (через kernel-отладку) |
| Драйвер | портирован 5.15→6.1 + RKMODULE ioctl + rockchip-имя + link_freq 493.5 + crop/field фиксы |
| Overlay | собственный rk3588-csi-imx519.dtbo (Host 2, single-ISP) |
| Физика | ✅ сенсор отвечает на I²C, шлейф корректен, разводка исправна (доказано OV5647) |
| Ядро после ребута | ✅ грузится, chip ID 0x0519 прочитан |
| Камера в медиа-графе | ✅ entity m00_b_imx519, линки ENABLED |
open() / set_fmt / sanity |
✅ работают (после фиксов g_frame_interval, field, crop) |
| Pipeline активация | ✅ stream ON, DPHY включён (987 Мбит/с, честная link_freq) |
| Сенсор инициализация | ✅ 345 регистров записаны, MODE_SELECT=STREAMING ret=0 |
| Готовый кадр | ✅ 1920×1080, avg≈130, без полос (single-ISP + rkaiq) |
| Баг-фиксов в чужом коде | 10 (6 в RKCIF/imx519 + PM-runtime + link_freq + single-ISP + режим сенсора) |
| Пересборок ядра | 17 |
| Бэкапы | ядро, DTB, модули, overlay — всё сохранено для отката |
Главный неочевидный вывод всей истории — их три, и все болезненные.
Первый: на RK3588 разница между out-of-tree модулем и built-in драйвером — не формальность. Модуль грузится позже завершения async-notifier'а RKCIF, и сенсор «теряется». Лишь встроив драйвер в ядро, удаётся выставить правильный порядок probe.
Второй: «сенсор в медиа-графе» — тоже ещё не победа, и даже pipeline, который активируется — не победа. Каждый снятый софтверный баг открывал следующий — в общей сложности десять, от error 515 до чёрных строк в кадре. И только когда все десять закрылись, картинка стала чистой.
Третий, главный: аппаратный симптом + софтверная причина — самый коварный класс багов. Вывод третьего акта «физика MIPI, нужен осциллограф» оказался ошибочным — настоящим корнем был один-единственный неправильный #define LINK_FREQ в драйвере. Симптом «приёмник не ловит clock» выглядел стопроцентно аппаратным, а лечился одной строкой кода. И годы чтения кода не дали бы этого без diff'а с оригинальным драйвером Arducam.
Уроки, которые я вынес #
- Сначала физика, потом софт. Проверка I²C-сканером за минуту подтвердила, что шлейф и питание ок, и проблема — в драйверах/DT, а не в «битой камере». Не полезет ничего — начинай с самого нижнего уровня.
- Out-of-tree vs built-in — не одно и то же на RK3588. Модуль технически работает (chip ID читается), но не привязывается к медиа-графу из-за race condition с notifier'ом. Если «всё собралось, но графа нет» — копай в сторону порядка probe, и не трать время на перепривязку через sysfs (крахнет RKCIF).
- Не запускай параллельные сборки в одном дереве. Две пересекающиеся
makeв одном дереве объектов дают «гонку» с потерянными.d-файлами и непонятными ошибкамиfixdep. Одна чистая сборка черезsetsid— и всё соберётся. - Празднуй только на финальной вехе. Я отметил победу, когда сенсор появился в медиа-графе с ENABLED-линком — и ошибся. На RK3588 «драйвер собрался» → «chip ID прочитался» → «сенсор в графе» → «файл с пикселями ненулевого размера» — это четыре разных уровня, и только последний настоящая победа. Всё остальное — промежуточные ступени, любая из которых может оказаться ложным финишем.
- Проверяй константы драйвера на совпадение с оригиналом. Главный баг всей эпопеи (№8, link_freq) нашёлся одним
diffс GitHub-оригиналом Arducam. Один неправильный#defineизображал «аппаратную неисправность» четыре акта подряд. Прежде чем винить железо — сверь драйвер с эталоном. - Не трогай ISP crop при работающем rkaiq. rkaiq — петля управления, которая сама пишет окно измерений в ISP. Ручной crop вне 1080p рвёт эту петлю (
AWB stats invalid), и кадр уходит в чёрное. Если нужен не-1080p режим — ищи API rkaiq, не лезь в v4l2-selection.
Шпаргалка команд #
# Камера видна на I²C?
sudo i2cdetect -y 6
# Какие overlay камер есть в комплекте
ls /boot/dtb/rockchip/overlay/ | grep -iE "csi|cam|imx"
# Сборка out-of-tree модуля (проверка совместимости)
make KDIR=/lib/modules/$(uname -r)/build
# Компиляция overlay
dtc -@ -I dts -O dtb -o rk3588-csi-imx519.dtbo rk3588-csi-imx519.dts
# Сборка ядра (built-in imx519)
make ARCH=arm64 -j8 Image dtbs modules
# Проверка, что драйвер встроен
grep imx519 drivers/media/i2c/built-in.a
# Логи камеры после загрузки
dmesg | grep -i imx519
# Ключевая проверка — привязался ли сенсор к CIF:
dmesg | grep -c "get remote terminal sensor failed" # 0 = успех
# Async-привязка (кто ждёт кого; пусто = все связаны)
cat /sys/kernel/debug/v4l2-async/pending_async_subdevices
# Топология медиа-графа + проверка, что imx519 в нём с ENABLED-линком
media-ctl -d /dev/media0 -p | grep -iA2 imx519
# Формат и доступные размеры сенсора
v4l2-ctl -d /dev/v4l-subdev2 --get-subdev-fmt pad=0,stream=0
# Controls сенсора (exposure, gain, flip) — живые?
v4l2-ctl -d /dev/v4l-subdev2 --list-ctrls-menus
# Захват кадра готовым скриптом (1920x1080, single-ISP, rkaiq сводит 3A)
# cam_imx519.sh сам переключит режим сенсора и сконвертирует в JPG.
./cam_imx519.sh 12 /root/imx519.jpg
# Переключение режима сенсора вручную (1080p — сенсор делает binning)
v4l2-ctl -d /dev/v4l-subdev2 --set-subdev-fmt pad=0,width=1920,height=1080
# Честная link_freq в драйвере (главный фикс! 408 → 493.5 МГц)
grep IMX519_DEFAULT_LINK_FREQ drivers/media/i2c/imx519.c
# должно быть 493500000
# Проверка data_rate в логе (должно совпадать с физикой сенсора)
dmesg | grep data_rate_mbps # 987 — честная, 816 — баг
# Откат ядра (с другой системы, на подмонтированном разделе /mnt)
cp /mnt/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig \
/mnt/boot/vmlinuz-6.1.115.1-repka-pi5
cp /mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig \
/mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb
Ссылки #
- Репозиторий с патчами, оверлеем и кадрами (ветка
imx519-working) — рабочая конфигурация IMX519 на Repka Pi 5 - Repka Pi — официальный сайт
- Исходники ядра Repka Pi 5 (RK3588) на GitFlic
- ArduCAM/IMX519_AK7375 — исходники драйвера (эталон для сверки link_freq)
- 16MP IMX519 — Arducam Wiki
- Rockchip BSP kernel — rockchip-linux/kernel
- Соседний пост: OV5647 на Repka Pi 5 — первый кадр после долгой диагностики MIPI-стека — как проверочная OV5647 доказала исправность разводки