Полный порт mainline Linux (postmarketOS + phosh) на планшет Samsung SM-T385 (Qualcomm MSM8917 / Snapdragon 425). Здесь всё, что нужно, чтобы повторить: загрузчик, патчи ядра, пакет устройства, инструменты и документация всех раскопок.
Wi-Fi-версия SM-T380 электрически почти идентична (см. порт sapphicdisaster/t380-linux, от которого отпочковался этот) — отличия: модем, SoC ID и ревизия платы.
| Подсистема | Статус | Примечания |
|---|---|---|
| Загрузка (lk2nd → extlinux) | ✅ | сплэш postmarketOS, без отладочных полос |
| Экран + тач | ✅ | панель BOE TV080WXM, тач FTS8CD56 (свой инит + пинок после загрузки) |
| GPU (Adreno 306) | ✅ | freedreno, phosh ускорен |
| Wi-Fi / Bluetooth | ✅ | wcn36xx |
| LTE-модем: интернет + SMS | ✅ | bam-dmux + QMI; тумблер «мобильные данные» в phosh |
| Звук | играет разговорный динамик у экрана — через него идёт всё, включая музыку | |
| Громкоговоритель | ❌ | тракт до усилителя NXP TFA9890 работает; оборвана катушка самого динамика (доказано током) |
| Микрофон | ✅ | нужен был отдельный вывод питания смещения (в mainline его не было) |
| Зарядка + батарея | ✅ | свои драйверы sm5703 (зарядник + топливомер) |
| Обе камеры | ✅ | фронт S5K5E3 5Мп, тыл S5K4H5 8Мп — свои mainline-драйверы; видны приложениям через libcamera |
| Автофокус тыловой камеры | ✅ | dw9807-vcm, v4l2 focus_absolute |
| Вспышка / фонарик | ✅ | в шторке phosh, ток регулируется (в драйвере зарядника) |
| Датчик чехла | ✅ | закрыл — экран гаснет, открыл — проснулся |
| Поворот экрана | ✅ | lis2hh12 + iio-sensor-proxy |
| Карта microSD | ✅ | слот включён в device tree (был выключен с ранних версий) |
| Видеодекодер (venus) | ✅ | |
| Сон (suspend) | ✅ | s2idle; засыпает по чехлу, будят кнопка и открытие чехла |
| Голосовые вызовы | ✅ | звук идёт в обе стороны; кнопки микрофона и режима — своя служба вместо callaudiod |
Всё ниже — на ваш страх и риск. KNOX-гарантия слетает необратимо.
- Разблокировать загрузчик (OEM unlock в настройках Android + Download mode).
- Прошить lk2nd в раздел
bootчерез Odin/Heimdall:lk2nd/lk2nd-gta2slte.img(наша сборка — с фиксом Samsung XPU для smc, см.lk2nd/0001-samsung-xpu-smc-stub.patch). После этого планшет умеет fastboot (громкость вниз при старте). - Собрать и установить postmarketOS через pmbootstrap:
- взять свежие pmaports и наложить наши пакеты:
kernel/→device/testing/linux-postmarketos-qcom-msm89x7/(патчи 0001-0011, конфиг, DTS),device/→device/testing/device-samsung-gta2slte/; pmbootstrap init(устройство samsung-gta2slte, UI phosh, systemd);pmbootstrap install, затемpmbootstrap flasher flash_rootfsчерез fastboot lk2nd.
- взять свежие pmaports и наложить наши пакеты:
- Первая загрузка — и дальше всё обновляется обычным
apk.
kernel/— патчи ядра msm89x7 (0001-0011), конфиг, DTS устройства. Ключевое: инит тача FTS8CD56 (0001), зарядник+топливомер+вспышка sm5703 (0002), панель TV080WXM (0003), звук wcd-analog LINEOUT+bypass (0005), модем bam-dmux legacy-open (0006-0007), камеры s5k5e3/s5k4h5 (0009), vdd-supply для dw9807 (0011).device/— пакет устройства: deviceinfo (cmdline с clk_ignore_unused — без него нет сплэша), UCM-профили звука, сервисы-сторожа (звук, тач, экран-по-чехлу, DPM модема).lk2nd/— device tree для lk2nd, патч обхода Samsung XPU (без него smc из области загрузчика запрещён и lk2nd умирает), готовый образ.tools/— отладочные инструменты: битбанг-I2C через /dev/mem, GPIO-поке TLMM, съёмка камерой, свип автофокуса, декодер RAW10.camera-ref/— извлечённые из стоковых прошивок регистровые таблицы сенсоров (проприетарные библиотеки НЕ публикуются — см. docs/RAZBOR.md, откуда их взять).docs/RAZBOR.md— главный документ: полная история порта со всеми граблями, отладочными методиками и объяснениями каждого решения.
- DTB собирался не тем ядром — номера клоков в 6.17 и 7.1.3 разные; месяцы «отдельных» багов (eMMC, BAM, I2C, USB) были одной ошибкой.
- Звук: аппаратный буст динамика сгорел (массовая болезнь модели) — лечится режимом BYPASS_MODE кодека, но ставить его можно только один раз при probe.
- Тыловая камера давала идеально чёрный кадр: MTK-таблицы «того же» сенсора не подходят — настоящая init-таблица добыта из бинарной vendor-библиотеки стоковой прошивки T385.
- CCI (камерный I2C) без power-domain VFE0_GDSC глух; подтяжки CCI1 питаются от камерного VIO.
- Сплэш при загрузке: ядро гасит «ничейные» клоки дисплея —
clk_ignore_unused pd_ignore_unusedобязательны. - smc на Samsung SBOOT: XPU запрещает smc из памяти загрузчика — стаб копируется в HLOS-память (патч lk2nd).
- sapphicdisaster/t380-linux — фундамент порта (T380);
- msm8916-mainline / msm89x7-mainline — ядро и lk2nd;
- postmarketOS — за всё остальное.
Порт сделан в паре человек + Claude (Anthropic) — вся отладка велась по SSH на живом устройстве.
- Надпись «Unknown (FIXME!)» в меню загрузчика: SBOOT выбирает
стоковый DTB (он приклеен к образу как совместимый), а в нём нет узла
/lk2nd, по которому lk2nd опознаёт устройство. Косметика. - Звук поднимается ~1.5–2 мин после загрузки (ждёт звуковой сопроцессор). До этого регулировка громкости не отзывается — норма.
lk2nd-gta2slte.img — рабочий образ, проверенный на живом планшете.
Он получен не прямой сборкой из исходников: пересборка этого дерева
текущим окружением даёт бинарник, который не стартует
(HALT: spinning forever), причина пока не найдена. Поэтому в образе
сохранён проверенный код, а отладочные «пиксельные маркеры» (они рисовали
цветные полосы при старте) обезврежены точечной правкой готового
бинарника — скриптом patch-stripes.py:
python3 lk2nd/patch-stripes.py lk2nd-gta2slte-debug-stripes.img lk2nd-gta2slte.imgСкрипт находит маркерные циклы и заменяет в них инструкцию записи
str rZ, [rX], #4 на add rX, rX, #4: указатель по-прежнему двигается,
цикл завершается, но в кадровый буфер ничего не пишется. Длина кода не
меняется, поэтому раскладка и тайминги остаются как в проверенном образе.
Два тупика, на которые ушло время (чтобы не повторять):
- просто «занопить» запись нельзя — пост-инкремент указателя живёт
внутри
str, без него цикл становится бесконечным; - перенаправить запись за пределы кадрового буфера тоже нельзя — область за ним закрыта аппаратной защитой (XPU), и планшет виснет на логотипе Samsung без единого сообщения.
Исходный отладочный образ оставлен как lk2nd-gta2slte-debug-stripes.img:
цветные полосы в нём показывают, до какого этапа дошёл загрузчик — это
до сих пор лучший инструмент диагностики, если lk2nd перестанет
стартовать.
- Сон. Работает s2idle: закрыл чехол — планшет заснул, открыл или
нажал питание — проснулся. Мешал наш же драйвер автофокуса: его
обработчик засыпания шёл к актуатору по I2C, когда шина CCI уже
обесточена, отваливался по таймауту и ядро отменяло весь suspend
(патч 0011). Тачскрин после пробуждения глохнет — лечится
перепривязкой драйвера, служба
t385-touch-resume. - Карта microSD. Слот стоял выключенным в device tree с раннего
этапа порта (тогда ядро умирало сразу после инициализации карты —
на самом деле из-за неверных номеров тактовых сигналов). Включён:
карта определяется и монтируется. Учтите: нумерация
mmcblkпри этом сдвигается, eMMC становитсяmmcblk1. - Загрузка ускорена: 42 → 34 секунды. Наша служба реинита тача
была
Type=oneshotс паузой и держалаmulti-user.targetлишних 12 секунд. Сообщения ядра с экрана убраны черезloglevel=0иvt.global_cursor_default=0. - Звонки: половина пути. Разговорный тракт живёт внутри звукового
сопроцессора: модем и кодек соединяются там напрямую, а поднимает
тракт сам сопроцессор в момент, когда кто-то открывает голосовой
поток
VoiceMMode1(в драйвере q6voice этоstartupу DAI). Никто этого не делал: ModemManager отвечает за сигнализацию, callaudiod — только за переключение профилей. Службаt385-callaudioследит за вызовами и держит поток открытым; в UCM добавлены голосовые точки маршрутизации. Приём звука заработал, микрофон — пока нет. - Вспышка перестала мигать сама. feedbackd брал единственный
светодиод системы (а это вспышка камеры) под индикацию пропущенных
уведомлений. Своя схема откликов без светодиода:
device/feedbackd-gta2slte.json.
Собеседника слышно, а нас — нет. Рабочая гипотеза: PulseAudio у нас
настроен никогда не отпускать звуковую карту (иначе глохнет тракт
динамика), поэтому порт TERT_MI2S_TX постоянно занят на 44,1 кГц, а
голосовой сессии он нужен в своём режиме. Попытка отпускать источник на
время звонка (pactl suspend-source) убила и приём — откачено.
Следующие шаги: пробовать другие TX-порты в
VoiceMMode1 Capture Mixer и ловить момент освобождения порта.
Не работают также громкая связь и удержание вызова в интерфейсе.
-
Микрофон заработал. Он не работал вообще — ни в записи, ни в звонках. Кодек при этом честно включал вход, АЦП и своё внутреннее смещение, а сигнала не было. Причина: аналоговым микрофонам нужно отдельное питание смещения, которое в стоке подаётся с ножек GPIO68 (основной) и GPIO99 (второй), а в mainline-дереве их не было вовсе. Добавлены как
regulator-fixed. Замер до и после: уровень сигнала вырос с ~7 (шум) до ~150. -
Кнопки в интерфейсе звонка ожили. Громкая связь и отключение микрофона не нажимались, потому что в звуковом профиле не было режима «Voice Call» — служба callaudiod работает только с ним. Профиль добавлен (
device/ucm-VoiceCall.conf).Важная тонкость, стоившая отдельного круга отладки: в этом профиле потоки должны указывать на обычные PCM (
hw:X,0иhw:X,1), а не на голосовойhw:X,6. Голосовой поток данных не принимает — он лишь переключатель для звукового сопроцессора; если указать его, система звука считает выход неисправным («card has no usable sink») и кнопки остаются серыми.
- Микрофон:
tools/— проиграть шум в динамик и замерить уровень записи; рабочий микрофон даёт рост в 4–5 раз. - Управление вызовом:
busctl --user call org.mobian_project.CallAudio /org/mobian_project/CallAudio org.mobian_project.CallAudio SelectMode u 1(а такжеEnableSpeaker b true,MuteMic b true) — все три должны вернутьtrue.
Если pmbootstrap падает с urlopen error, а apk add на планшете
подвисает — это не поломка порта, а недоступный HTTP (HTTPS при этом
работает). Собирать с --offline, ставить с --no-network.
Раздел boot всегда можно вернуть из TWRP:
adb push boot_backup.img /tmp/
adb shell dd if=/tmp/boot_backup.img of=/dev/block/bootdevice/by-name/boot bs=1048576
adb rebootСнимайте бэкап до каждой прошивки загрузчика:
adb shell dd if=/dev/block/bootdevice/by-name/boot of=/tmp/boot_backup.img bs=1M count=8
adb pull /tmp/boot_backup.imgСобеседник долго не слышал ничего, и причина оказалась не в железе.
В системе есть штатная служба разговорного тракта q6voiced, и общий
пакет soc-qcom-msm89x7-modem задаёт ей голосовой поток номером 4:
/usr/share/q6voiced/q6voiced.conf: q6voice_device=4
А на этой карте VoiceMMode1 — устройство 6, четвёртого нет вовсе.
Тракт передачи не открывался никогда, и журнал говорил об этом прямо:
q6voiced: Failed to open tx: No such file or directory
Лечится переопределением службы (device/q6voiced-t385.conf). Туда же
вынесено включение голосовых микшеров: пока они сброшены, голосовой
поток не открывается вообще — arecord -D hw:0,6 отвечает
«недопустимый аргумент».
Стандартный callaudiod на этом железе неработоспособен: ему нужен
отдельный режим звуковой карты для разговора, а такой режим здесь
невозможен — система звука либо молча отбрасывает его, либо делает из
него негодный выход («card has no usable sink»). Написана замена
device/t385-callaudiod.py, занимающая то же имя на шине.
Две тонкости, на которых легко потерять день:
- у
dbus-pythonнужно держать ссылку на занятое имя, иначе сборщик мусора его отпускает и на каждый вызов запускается новая копия службы — состояние кнопок теряется; - стандартное описание объекта не перечисляет свойства, его пришлось дописать вручную, иначе состояние не читается извне.
Чинить нечего у кнопок «удержание» и «добавить звонок»: в самой
оболочке (libcall-ui, cui-call-display.ui) они объявлены
выключенными — <property name="sensitive">False</property>.
Раньше здесь было написано, что у динамика сгорел повышающий преобразователь, а звук идёт через LINEOUT. Это опровергнуто опытом. Ровный тон с поочерёдным отключением излучателей показал: при выключенном разговорном динамике тишина при любом состоянии LINEOUT, а при выключенном LINEOUT звук есть. То есть LINEOUT молчит тоже, и единственный живой излучатель — разговорный динамик у экрана.
Разгадка нашлась в стоковом дереве Samsung: динамик подключён не к
кодеку, а к отдельному усилителю. Там есть шина, подписанная
«HW I2C for AMP» (BLSP1 QUP5, 0x7af6000, ножки GPIO22/23), включены
линии MI2S (GPIO12, 13, 94, 95), а в qcom,audio-routing динамика нет
вообще — только микрофоны.
Живое сканирование этой шины дало NXP TFA9890 по адресу 0x34
(регистр ревизии 0x03 = 0x0080), который поддерживает штатный
mainline-драйвер tfa989x — без фирменной прошивки. Драйвер к нему
привязывается и при проигрывании выводит его из спящего режима
(регистр управления 0x8201 → 0x8208).
Заодно убран фиктивный регулятор tlmm94_vreg на GPIO94 — наследие
порта T380, без единого потребителя. GPIO94 входит в группу линий MI2S
к этому усилителю, и держать её как обычный выход было ошибкой.
Чем закончилось: динамик заработал. Причин оказалось две, и обе в ядре.
Первая — описка в mainline. Функция выбора тактового сигнала выдаёт для четверичного порта идентификатор третичного:
case AFE_PORT_ID_QUATERNARY_MI2S_RX:
case AFE_PORT_ID_QUATERNARY_MI2S_TX:
return Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT; /* должен быть QUAD */Константа Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT в ядре есть и не
используется нигде. Сопроцессор на такую просьбу отвечает отказом
(cmd 0x100f3 -> error 0x2), и порт остаётся без такта. Стоковый
драйвер Qualcomm возвращает здесь правильный идентификатор.
Вторая — недостающий бит мультиплексора у msm8917. Сток пишет в
регистр mic_iomux значение 0x02020002, а mainline только
0x00020002 — не хватает BIT(25). Без него сигнал до ножек не
доходит.
Обе правки в kernel/0012-quat-mi2s-clk-id.patch. После них линии MI2S
ожили, а усилитель доложил о себе сам:
ножка 94: [0, 1] ДРЕБЕЗЖИТ
усилитель: состояние=0xd85f ФАПЧ=1 выходной каскад работает=1
Регистр состояния из мёртвого 0x0bdd стал 0xd85f, и микросхема
начала измерять напряжение батареи и собственную температуру (до этого
там намертво стояли значения по умолчанию). Динамик подключён к
звуковому профилю: разговорный оставлен включённым вторым излучателем.
Чем закончилось на самом деле. Тракт до усилителя удалось починить полностью, а динамик всё равно молчит.
Что проверено объективно, без ушей:
- линии MI2S ожили — чтение уровней TLMM показывает дребезг;
- ножка данных найдена: на цифровой тишине её уровень 0 %, на тоне 25 % — значит звук до усилителя доходит;
- усилитель захватывает ФАПЧ и включает выходной каскад (регистр
состояния
0x0bdd->0xd85f), начинает измерять напряжение батареи и собственную температуру; - пробовали и на максимальной громкости, и с включённым повышающим преобразователем усилителя.
Звука нет ни при одном сочетании. Похоже, оборван сам динамик — это
согласуется с тем, что он замолчал ещё в августе. В звуковой профиль он
поэтому не подключён; строки для попытки оставлены в комментарии
device/ucm-HiFi.conf.
Приложение «Камера» писало «камера не найдена», хотя снимки напрямую
получались. Причина — в наших драйверах датчиков не был реализован
.enum_frame_size:
VIDIOC_SUBDEV_ENUM_MBUS_CODE -> 0x300a: SGRBG10_1X10 есть
VIDIOC_SUBDEV_ENUM_FRAME_SIZE -> пусто нет
libcamera собирает список форматов как «код плюс доступные размеры».
Размеров нет — значит нет и форматов: No image format found, и дальше
камера не появляется нигде, потому что через libcamera их получает и
PipeWire, и все приложения.
Режим у обоих датчиков один — полный кадр (3264x2448 и 2576x1932), так
что обработчик тривиален; он в kernel/0013-sensors-enum-frame-size.patch.
После него обе камеры видны и отдают кадры на 30 к/с.
Окно VLC жило в XWayland, а всплывающие меню Qt — это отдельные окна
поверх; phoc при каждом ругался xwayland/xwm.c: xcb error: op 2:0, code 3 (BadWindow). Касание до окна тоже не доходило.
Виноват не Qt. Обычная программа на Qt5 (qmlscene) слушается
QT_QPA_PLATFORM безупречно даже при выставленном DISPLAY: просят
offscreen — получает offscreen, просят wayland — wayland. А VLC
при DISPLAY=:0 берёт libqxcb.so при любом значении переменной,
даже когда просят minimal или offscreen, к X11 отношения не
имеющие.
Причина в самом VLC. В libqt_plugin.so есть строки -platform и
unknown Qt platform: %s: модуль не читает переменную окружения, а сам
передаёт Qt аргумент командной строки -platform, а аргумент старше
переменной. Выбирает VLC по наличию DISPLAY — модуль собран с X11
(libQt5X11Extras, libX11, libX11-xcb) и при доступном X-сервере
предпочитает его.
Лечится тем, что DISPLAY убирается из окружения только для VLC —
тогда выбрать X11 ему нечем:
Exec=env -u DISPLAY /usr/bin/vlc --started-from-file %U
Проверено: с DISPLAY=:0 грузится libqxcb.so, с env -u DISPLAY —
libqwayland-generic.so. Файл device/vlc-override.desktop кладётся в
/usr/local/share/applications/ — этот каталог система просматривает
раньше /usr/share, поэтому запись в меню остаётся одна, а обновление
пакета VLC правку не затирает.
Попутно: окно первого запуска про доступ в сеть на планшете
неработоспособно (закрыть его нечем), отключается в
~/.config/vlc/vlcrc:
[core]
metadata-network-access=0
[qt]
qt-privacy-ask=0
Другого места для этой настройки нет: в libvlccore зашиты только пути
~/.config/vlc/vlcrc и ~/.vlc/vlcrc, общесистемного файла у сборки
не предусмотрено.
Жалобы выглядели как разные: «приложение тормозит», «камера не отвечает», «задняя камера не работает», «не удалось воспроизвести потоковые данные». Причина у всех одна: с дампом падал WirePlumber. Вместе с ним исчезали узлы камер в PipeWire, и приложение теряло изображение. Задняя камера при этом исправна — служба умирала на остановке предыдущей камеры, то есть ровно при переключении.
Проверять так:
systemctl --user show wireplumber -p NRestarts --valueУбивали её две ошибки libcamera, одна за другой.
Первая. FATAL pipeline_handler.cpp:398 assertion "data->queuedRequests_.empty()" failed in stop()
SoftwareIsp::stop() сначала глушит модуль автоматики, и лишь потом
возвращает отменённые буферы. Завершение запроса упирается в проверку:
if (info->metadataRequired && !info->metadataProcessed)
return;Метаданных эти кадры уже не получат — автоматика мертва. Запрос
навсегда остаётся в очереди. Штатная уборка clearIncompleteRequests()
их не ловит: она чистит только очередь на обработку, а кадры, отданные
обработчику, оттуда уже сняты. В журнале рядом видно
WARN FCQueue Obtained an uninitialised FrameContext.
Лечится патчем 0005-complete-requests-on-stop.patch: требование
метаданных снимается со всех кадров в работе до остановки обработчика.
Вторая открылась сразу за первой: FATAL egl.cpp:474 assertion "tid_ == Thread::currentId()" failed in makeCurrent() из
DebayerEGL::debayerGPU. По умолчанию цвет собирает видеоядро, а
контекст EGL создан в одном потоке и используется в другом.
Лечится настройкой в /etc/libcamera/configuration.yaml (файл входит
в пакет устройства):
---
version: 1
configuration:
software_isp:
mode: cpuПосле обеих правок: десять запусков и остановок вперемежку обеими камерами, ни одного падения.
Неожиданный итог: процессор быстрее видеоядра. Кадров в секунду, задняя камера:
| размер кадра | видеоядро | процессор |
|---|---|---|
| 3264x2448 (полный) | 5,6 | 9,3 |
| 2048x1536 | ~25 | 28,5 |
| 1632x1224 | 21,2 | 28,5 |
Полный кадр не тянется всё равно, и каждый такой кадр занимает 32 МБ —
при гигабайте памяти поток из них её вычерпывал, отчего система
вставала намертво и уходила в сброс по сторожевому таймеру. Поэтому
размер обработанного потока ограничен до 2048x1536 патчем
0004-cap-softisp-size.patch: 24-28 кадров в секунду и 12,6 МБ на
кадр. Необработанный поток не тронут.
Полезный признак при разборе таких зависаний: pstore пуст и записи об
ошибке нет, журнал просто обрывается. Это отличает жёсткий зависон от
паники ядра — искать ошибку ядра там незачем.
Сразу после того, как заработала автоэкспозиция, планшет начал падать в
панику при запуске камеры. Причина оказалась в патче режимов, а не в
libcamera: поле «текущий режим» задавалось только в set_fmt, а
обработчик управляющих элементов обращается к нему раньше:
int max = s5k->mode->height + ctrl->val - ...; /* mode == NULL */Пока автоматика не управляла усилением, до этой строки почти не доходило — ошибка тихо ждала своего часа. Как только AGC заработала и начала трогать элементы при запуске потока, ядро упало по пустому указателю. Лечится заданием режима по умолчанию в probe, до создания элементов.
Урок общий: указатель, который заполняется «когда-нибудь позже» в колбэке, надо инициализировать при создании устройства.
libcamera писала в журнал:
WARN CameraSensorProperties: No static properties available for 's5k4h5'
WARN IPASoft: Failed to create camera sensor helper for s5k4h5
Второе означало, что автоматика не умеет управлять усилением вообще и может менять только выдержку. В темноте это тупик: выдержку уже некуда растягивать, а усиление поднять нечем. Первое — что неизвестны размер пикселя и задержки датчика, из-за чего экспозиция «рыскает» при смене освещения: libcamera не знает, через сколько кадров подействует её команда.
Формула пересчёта усиления вшита в саму библиотеку, файлом её не
добавить, поэтому libcamera пересобрана с патчем
libcamera/0003-add-t385-sensors.patch. У обоих датчиков усиление
линейное — регистр 0x0204, база 32 соответствует однократному, потолок
512 = 16x:
gain_ = AnalogueGainLinear{ 1, 0, 0, 32 };
blackLevel_ = 4096; /* пьедестал 64 при 10 битах */Плюс запись в базе свойств: размер пикселя 1.12 мкм и задержки датчика. После пересборки жалоб не осталось.
Чего это не даёт: цветовой калибровки. Для неё нужны замеры по эталонным
мишеням, поэтому приложение по-прежнему берёт общий профиль
uncalibrated.yaml — баланс белого и цветопередача остаются
приблизительными.
Сборка libcamera требует двух оговорок: ускоритель кросс-компиляции на
ней ломается (cannot execute cc1), нужен pmbootstrap --no-cross; и
при шестнадцати потоках сборщику не хватает памяти на python-обвязке
(Killed signal terminated program cc1plus) — помогает pmbootstrap config jobs 3.
Приложение «Камера» находило камеры, но показ шёл на 5–6 кадрах в секунду и утягивал за собой весь планшет — вплоть до перезагрузки. При этом в журнале ядра не было ни одной ошибки камеры, зато оставался процесс вывода изображения: захлёбывалось видеоядро.
Причина простая: драйвер отдавал единственный режим — полный кадр 3264x2448. После обработки это 32 МБ на кадр, и всё это считает процессор, а потом заливает в видеоядро. На планшете с 1.3 ГБ памяти такого не выдерживает никто.
В стоковых библиотеках нашлись готовые таблицы уменьшенных режимов: у
задней камеры 816x460 (прореживание 1 к 4), у фронтальной 640x480
(биннинг 4x4). Полезная подробность: тактовые настройки у всех режимов
одинаковы, различается только число строк в кадре, так что частоты
менять не пришлось.
Фронтальная камера до этого не запускалась вовсе — её единственный
режим 2576x1932 не проходил согласование формата (not-negotiated).
| было | стало | |
|---|---|---|
| размер кадра | 32 МБ | 1.2 МБ |
| скорость | 5–6 к/с | 28.5 к/с |
Патч — kernel/0015-s5k4h5-preview-mode.patch (обе камеры); полный
кадр остался для съёмки, уменьшенный выбирается по запрошенному
размеру. После правки обе камеры проходят весь тракт до приложения:
по десять кадров за 0.6 с каждая.
Как искать такие таблицы: записи в библиотеке идут восьмёрками байт {адрес (2), значение (2), задержка (4)}; разрешение режима читается из регистров 0x034c и 0x034e, тайминги — из 0x0342 и 0x0340.
Переключение между камерами подвешивало приложение:
Failed to setup link 'msm_csiphy1'[1] -> 'msm_csid0'[0]: Resource busy
Драйвер CAMSS связывает каждый приёмопередатчик со всеми приёмниками, и
libcamera при обходе графа выдавала обеим камерам первый попавшийся —
один и тот же csid0. Вторая тогда не запускалась, пока работает
первая. Стоковая настройка Qualcomm разводит их один к одному
(qcom,csid-sd-index: задняя 0, фронтальная 1).
Патч kernel/0016-camss-one-csid-per-csiphy.patch делает так же:
csiphy0 -> csid0, csiphy1 -> csid1, а приёмники сверх числа
передатчиков остаются доступны всем. После него обе камеры работают
одновременно — вторая запускается поверх первой без ошибок.
Слуховые проверки давали тишину на всех пяти путях, но слух — довод слабый. Решающим стало измерение тока батареи: живая катушка потребляет, оборванная — нет.
Мерили парами «тишина / громкий тон», три круга подряд, чтобы вычесть дрейф; экран пригашен, поэтому фон низкий. Обязательно от батареи — с подключённой зарядкой датчик нагрузки не показывает вовсе (все состояния дают одинаковые −7.8 мА).
| Излучатель | Прирост тока на громком тоне |
|---|---|
| Разговорный динамик (заведомо живой) | +38.7 мА |
| Громкоговоритель через усилитель, максимум громкости + буст | +8.1 мА |
Крошечный разговорный динамик потребляет впятеро больше, чем большой громкоговоритель на полной громкости. Восемь миллиампер — это собственное потребление усилителя вхолостую. Ток в катушку не идёт.
Вместе с тем, что усилитель при этом рапортует полную исправность (захват ФАПЧ, работающий выходной каскад, живые показания батареи и температуры), вывод однозначен: неисправен сам динамик, а не порт.
Прошивку скачали и распотрошили, не устанавливая (samloader-rs,
SM-T385/SER, версия T385DXS4CUD3).
Порт сделан правильно. В стоковых настройках звука путь speaker
у кодека пуст:
<path name="speaker">
</path>а звук к динамику направляется ровно так же, как у нас:
QUAT_MI2S_RX Audio Mixer MultiMedia1 = 1. То есть и порт, и линия
данных, и сам усилитель выбраны верно.
Единственное отличие — сток загружает в усилитель контейнер
vendor/etc/Tfa9890.cnt (4400 байт: заплатка, настройки, модель
динамика) силами libtfa98xx.so, а mainline-драйвер не грузит туда
ничего и работает в упрощённом режиме. Но с тем же усилителем в
mainline работают Shift6mq и OnePlus — без всякого контейнера, — так
что тишину это не объясняет. Загрузка контейнера пригодилась бы разве
что ради встроенного измерения сопротивления динамика; после замера
тока в этом уже нет нужды.
Добавляя ножки MI2S, я объявил у звуковой карты свой pinctrl-0 — и тем
самым вытеснил уже объявленный в msm8937-qdsp6.dtsi:
&sound {
pinctrl-0 = <&cdc_pdm_lines_default>;
pinctrl-1 = <&cdc_pdm_lines_sleep>;
};
Кодек остался без ножек, и звук пропал весь — при внешне безупречной
картине: все узлы включены, поток идёт, регистры верные. Нашлось это
сравнением состояния всех 134 ножек процессора между рабочим и сломанным
ядром: у линий кодека 69–74 настройка 0x000000c9 превратилась в
0x00000001. Правильно — перечислять оба набора:
pinctrl-0 = <&cdc_pdm_lines_default &quat_mi2s_default>;
Хуже того, эта поломка держалась несколько сборок подряд, и весь разбор с усилителем шёл на планшете, где звука не было вообще. Все выводы вида «динамик не звучит», сделанные в это время, оказались бессмысленными. Отсюда правило: после каждой правки звука — контрольное прослушивание, и только потом следующий шаг.
Микрофон ноутбука как «уши» здесь не работает: контрольный опыт на заведомо рабочем излучателе тоже дал «тишину». Микрофон самого планшета через звуковой сервер отдаёт ровные нули. Проверять звук можно только ушами человека — все выводы «тишина», сделанные приборами, недостоверны.
Передача файлов на планшет: scp и ssh "cat > файл" при большом
объёме молча ничего не пишут (код возврата 0, файла нет). Работает
только скачивание самим планшетом — поднять HTTP-раздачу в WSL и
забрать wget.