Первое, что я хотел от нового загрузчика, — чтобы он показывал что-нибудь на экране телефона. Не логотип производителя, а нормальный текст: меню, список разделов, приглашение ввести команду. В U-Boot для этого есть подсистема video, есть драйверы панелей, есть vidconsole, который рисует символы. Всё должно работать из коробки.
Не работает. И вот тут выяснилось, что в форке U-Boot под MediaTek нет вообще ничего, что умеет показывать текст на экране этого телефона. Ни DRM-драйвера для MT6789, ни драйвера панели, ни встроенного фреймбуфера, который заработал бы. Ноль.
Писать с нуля? Я не верил. Нашёл обходной путь, который в итоге и стал основным.
Главный трюк: панель уже работает
Вот что важно понять про загрузку на MediaTek. Порядок такой:
Preloader → BL2_EXT → LK → U-Boot
К моменту, когда начинает грузиться U-Boot, панель уже инициализирована — её подняли BL2_EXT и LK. DSI-контроллер тактируется, тайминги выставлены, панели отправлены команды инициализации, она горит.
Это меняет всё. Мне не нужно поднимать панель. Нужно только перенастроить конвейер, который передаёт в неё картинку.
У MediaTek это OVL0 → RDMA0 → DITHER0 → DSI0:
framebuffer в DRAM
│
▼
OVL0 слой 0: адрес, размер, формат пикселей
│
▼
RDMA0 читает пиксели из памяти
│
▼
DITHER0 приводит к формату панели
│
▼
DSI0 уже настроен LK, шлёт команды
│
▼
ILI7835F AMOLED 1080x2400
Формально это DRM-пайплайн. Но драйвера DRM в U-Boot для MediaTek нет, и тащить его из Linux — это месяц работы. Написать свой, который умеет только инициализировать четыре блока и показывать кадр, — это 453 строки.
drivers/video/mediatek/mtk_disp_g99.c. Написал с нуля.
Первый способ: вообще не прикалываться
Самое очевидное решение — не трогать ничего, просто дать U-Boot кадр по адресу simple-framebuffer из device tree. Адрес там есть, 0xfe0c8000, прямо из DT телефона.
Телефон с этого момента не грузится. Synchronous Abort, всё.
Причина тупая, если знать: этот адрес не отображён в MMU U-Boot. Ядро Android отображает, а загрузчик — нет. Любое обращение к кадру, даже из драйвера, уходит в никуда и вызывает исключение синхронной абстракции.
Узел simple-framebuffer из DTS выпилил. Саму область памяти оставил в reserved-memory, но без флага no-map — чтобы U-Boot к ней мог добраться. Это отдельная тонкость: no-map означает «не маппить в MMU», то есть гарантированно недоступно.
Адрес кадра: два числа, не путать
Тут я сам путался какое-то время. В DT резерве кадр начинается с 0xfe0c8000. А в коде драйвера по умолчанию стоит 0xfeabe000. Это не ошибка — это два разных адреса:
| Адрес | Что это |
|---|---|
0xfe0c8000 |
начало зарезервированной области, размер 31 МБ |
0xfeabe000 |
адрес, который реально поставил LK в OVL0_L0_ADDR |
0xfeabe000 лежит внутри того резерва. Драйвер читает OVL0_L0_ADDR и берёт то, что реально настроил LK; если там пусто или 0xffffffff — откатывается на 0xfeabe000. Плюс проверка диапазона: вне 0x40000000…0x2fffffffff — тоже откат.
Это важный принцип: адрес берём у того, кто поднял панель. LK знает, куда оно положило кадр. Мы — нет.
Кэш, который стоил мне полдня
Экран загорался пустым. Текст рисуется в память, vidconsole доволен, а панель показывает старое. Классика.
Проблема в том, что mw.l пишет только в кэш CPU. А OVL читает кадр из DRAM. Данные лежат в кэше, DRAM про них ничего не знает, и на панель уходит мусор — точнее, ничего.
Решение — dc cvac после записи в кадр: сбросить кэш до точки монолитности. В драйвере это делает flush_dcache_range() автоматически. Но если пишешь в кадр руками из консоли U-Boot, нужно сделать самому:
dc cvac 0xfeabe000 0xa3e000
Забыл про это на полдня, потому что драйвер vidconsole обычно сам об этом не напоминает.
Смещение 0xd00, из-за которого чуть не сошёл с ума
Вот это стоит отдельного рассказа, потому что выглядит как глупость, а стоит недели.
У MediaTek DSI-команды лежат в очереди (CMDQ). В большинстве чипов этот буфер по смещению 0x200 от базы DSI. Я так и написал сначала. Экран не заработал.
Открыл дизассемблированный LK, начал искать отправку команд. Нашёл функцию, которая формирует код команды для DSI и кладёт её в очередь. Смотрел на смещения — и увидел 0xd00.
У MT6789 очередь команд по 0xd00. Не 0x200.
Код, который шлёт кадр на панель (DCS Write Memory Start), выглядит так:
#define DSI0_CMDQ (DSI0_BASE + 0xd00) /* MT6789: НЕ 0x200 */
#define DSI_CMDQ_WRITE_MEM_START 0x2c3909
Один байт в адресе. Неделя на то, чтобы догадаться, что 0x200 пришёл из головы от другого чипа.
Что ещё пришлось поправить
Ещё три вещи, которые вылезли при отладке:
OVL0 SRC_CON = 0x3. LK оставил активными два слоя. Надо оставить как есть, иначе логотип поверх текста.
OVL0 L0_CON = 0x000001ff. Было 0x1001020 — не подходит для ARGB8888. Меняю на 32 бита и разрешение 1080×2400.
CFG_UPDATE после мультиплексоров. MMSYS пишет через теневые регистры: записал в MMSYS_OVL0_MOUT — а реальное значение сменится только после CFG_UPDATE. Без этого артефакты и двоение текста.
Отдельно: гейты часов в MMSYS снимаются через регистр CLR, а не SET. Это не опечатка в моём коде — так у MediaTek.
Как проверять
После probe() драйвер пишет блок значений в память прямо за кадром — по адресу 0xff4a1400. Читается уже из консоли U-Boot:
md.l 0xff4a1400 20
| Смещение | Что |
|---|---|
+0x00 |
magic 0xDEADBEEF |
+0x04 |
uc_priv->fb |
+0x08 |
plat->base |
+0x0c |
uc_priv->fb_size |
Если magic не тот — драйвер не дошёл до конца probe(), и в консоли будет написано, где остановился. Эта мелочь сэкономила мне несколько часов: не надо гадать, что сломалось.
Что получилось
Текст U-Boot на AMOLED 1080×2400. Меню загрузки на экране телефона, точки, логотип сборки — всё рисуется собственным драйвером на 453 строки. Заработало с v183, окончательно — с v187.
Только текст. Ни картинок, ни графики — vidconsole в UCLASS_VIDEO умеет ровно это.
Код: drivers/video/mediatek/mtk_disp_g99.c ·
разбор дисплея: docs/hardware/display.md ·
общее состояние проекта: README
Дальше — про USB: почему fastboot, консоль и UMS пришлось делать на PIO без DMA, и почему ПК не видел устройство два дня подряд.