beeugene
103 просмотров0 комментариев+1

IMX519 на Repka Pi 5: камера работает. Пересобираем ядро «по-жёсткому»

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_csi2mipi5_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 туда не подойдёт ни физически, ни по назначению.

Шлейф UC236 и AWM 20624

Как вставлять шлейф в разъём 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:

  1. #include <linux/rk-camera-module.h> — заголовок с Rockchip-специфичными структурами.
  2. Чтение rockchip,camera-module-* из device tree (index, facing, name, lens) — это те самые свойства, что я прописал в overlay, но драйвер их не читал.
  3. Формирование имени в формате Rockchip: snprintf(sd->name, ..., "m%02d_%c_%s %s", ...) — даёт m00_b_imx219 6-0010, а не imx219 6-0010.
  4. Обработка 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

После 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 МГц 493.5 МГц → исправили на 408 МГц
data_rate_mbps 816 987 → теперь 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, и параметры совпадают с работающей платформой — пора признать, что проблема не в коде, а в физике подключения.

И вот тут — поворот, ради которого стоило всё предыдущее. Вывод про «физика 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». И она оказалась ложной, как выяснилось через час.

Раз 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.

Уроки, которые я вынес #

  1. Сначала физика, потом софт. Проверка I²C-сканером за минуту подтвердила, что шлейф и питание ок, и проблема — в драйверах/DT, а не в «битой камере». Не полезет ничего — начинай с самого нижнего уровня.
  2. Out-of-tree vs built-in — не одно и то же на RK3588. Модуль технически работает (chip ID читается), но не привязывается к медиа-графу из-за race condition с notifier'ом. Если «всё собралось, но графа нет» — копай в сторону порядка probe, и не трать время на перепривязку через sysfs (крахнет RKCIF).
  3. Не запускай параллельные сборки в одном дереве. Две пересекающиеся make в одном дереве объектов дают «гонку» с потерянными .d-файлами и непонятными ошибками fixdep. Одна чистая сборка через setsid — и всё соберётся.
  4. Празднуй только на финальной вехе. Я отметил победу, когда сенсор появился в медиа-графе с ENABLED-линком — и ошибся. На RK3588 «драйвер собрался» → «chip ID прочитался» → «сенсор в графе» → «файл с пикселями ненулевого размера» — это четыре разных уровня, и только последний настоящая победа. Всё остальное — промежуточные ступени, любая из которых может оказаться ложным финишем.
  5. Проверяй константы драйвера на совпадение с оригиналом. Главный баг всей эпопеи (№8, link_freq) нашёлся одним diff с GitHub-оригиналом Arducam. Один неправильный #define изображал «аппаратную неисправность» четыре акта подряд. Прежде чем винить железо — сверь драйвер с эталоном.
  6. Не трогай 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

Ссылки #


+1

Комментарии (0)

Для участия в обсуждении Вы должны быть авторизованным пользователем

Еще посты по теме

Наиболее интересные по мнению читателей



Темы

Навигация

ВойтиРегистрация