Драйвер, которого не было: часы MediaTek MT6789 / A driver that was never there: MT6789 clocks

Есть один класс багов, который не показывает себя. Никакой ошибки компиляции, никакого сообщения в логе, никакого креша. Контроллер просто работает не с той частотой, и ты гадаешь, почему SD-карта молчит, а экран пустой. В моём проекте это заняло больше всего времени — и началось с того, что файл, который должен был работать, физически не собирался.

Речь про драйвер часов MediaTek MT6789.

Файл, которого нет

Смотрю на форк и вижу всё на месте. drivers/clk/mediatek/clk-mt6789.c — файл есть. Три драйвера объявлены: mtk_clk_apmixedsys, mtk_clk_topckgen, mtk_clk_infracfg. В DTS есть узлы с совместимыми строками. Всё как надо.

Собираю. И драйвер часов в сборке нет.

Смотрю Makefile того же форка:

# drivers/clk/mediatek/Makefile
//obj-$(CONFIG_TARGET_MT6789) += clk-mt6789.o

Вот оно. Строка «закомментирована» двойным слэшем. В Makefile комментарий — это #. Никакого // в Makefile не существует, это синтаксис C++. Плюс CONFIG_TARGET_MT6789, которого не существует ни в одном конфиге.

Файл не собирался. Никогда. С того момента, как его написали.

Вторая ошибка, в том же файле

Ладно, раскомментировал строку. Пробую собрать. Ошибка компиляции:

static int mt6789-infracfg_ao_probe(struct udevice *dev)

Дефис в имени функции. В C так нельзя, это не идентификатор. Файл не скомпилировался бы, даже если бы строка в Makefile была живая. Три ошибки, и ни одна не проявляется снаружи.

Третья проблема: чужие таблицы

Файл открывается, компилируется после исправления имени. Открываю содержимое таблиц и вижу: 222 уникальных CLK_* ID и 13 PLL.

CLK_APMIXED_ARMPLL     ← такого на MT6789 нет
CLK_APMIXED_CCIPLL     ← и такого нет
CLK_APMIXED_MPLL       ← нет

Это смесь таблиц от разных чипов. ARMPLL, CCIPLL, MPLL на MediaTek MT6789 не существуют — я проверял по живому устройству, эти блоки в Android даже не тактируются.

Итог: 222 ID в форке против 145, которые нужны реально. Файл с таблицами от соседних SoC.

Самая дорогая ошибка — молчаливая

Всё это было подготовкой. Главное — в том, как драйвер индексирует свои массивы.

Внутри clk-mtk.c три массива идут друг за другом:

fclks[]   фиксированные частоты (F26M, CLKRTC)
fdivs[]   делители (MAINPLL_D4, UNIVPLL_D5, …)
muxes[]   мультиплексоры (AXI_SEL, SPM_SEL, …)

Массивы разделены смещениями fdivs_offs и muxes_offs. ID часов — это индекс в этих массивах. Значит, ID обязаны идти подряд: сначала все fclks, потом все fdivs, потом все muxes. Без пропусков.

В форке пропуски были — ID 31–36 и 54–55 не использовались. Дальше считаем:

Симптом Что происходит
CLK_TOP_MSDC30_1_SEL = 116,
muxes_offs = 95
индекс 21 при 5 муксах в массиве → читаем мусор за пределами массива
CLK_TOP_F26M = 63 падает в диапазон «делители» → fdivs[62] при 54 делителях → опять за границу

Что такое «читать мусор за пределами массива»? Это взять случайное значение из соседней памяти и использовать его как частоту. Без ошибки. Без сообщения. Контроллер получает неправильную частоту и просто не работает.

Вот это и было одной из причин, почему SD-карта не определялась — а я лез в совсем другие места: в pinctrl, в питание PMIC, в частоту MSDC. Часы. Конечно, часы.

В v161 перенумеровал все ID подряд: fclks 0–1, fdivs 2–55, muxes 56–60. Восемь строк в mt6789-clk.h — и контроллер стал тактироваться правильно. Просто карта всё равно не ответила, но это уже другая история.

MSDC30_1_SEL: мукс, который не похож на остальные

Ещё одна вещь, на которую ушёл день. Почти все мультиплексоры topckgen пишутся одним регистром. MSDC30_1_SEL — исключение:

{
        .id          = CLK_TOP_MSDC30_1_SEL,
        .mux_reg     = 0x80,
        .mux_set_reg = 0x84,     /* отдельный SET  */
        .mux_clr_reg = 0x88,     /* отдельный CLR  */
        .upd_reg     = 0x04,     /* регистр обновления */
        .upd_shift   = 29,
        .mux_shift   = 8,
        .flags       = CLK_MUX_SETCLR_UPD,
}

Запись идёт не прямо в регистр мукса, а через пару set/clear, и потом обязательно требуется апдейт — записать бит 29 в регистре 0x04. Без апдейта мукс просто не переключится.

В форке это было обычным MUX_GATE(..., 0x88, 8, 3, 15) — один регион, без set/clr/upd. Отсюда неверная частота на SD-контроллере. Исправил в v190.

Где лежат гейты

Отдельный слой — гейты инфраструктуры, infracfg_ao. Шесть групп регистров в базе 0x10001000:

IFRAO0   SET 0x80   CLR 0x84   STA 0x90   GCE, UART0-3, PWM, I2C
IFRAO1   SET 0x88   CLR 0x8C   STA 0x94   MSDC0, MSDC1, SPI0, AUXADC
IFRAO2   SET 0xA4   CLR 0xA8   STA 0xAC   SSUSB, UFS, SPI1-5
IFRAO3   SET 0xC0   CLR 0xC4   STA 0xC8   AP_MSDC0, CCIF2, SPI6-7

Если контроллер молчит, смотрю, какой гейт ему нужен. Для дисплея это IFRAO0 бит 8 (CLK_IFRAO_GCE) и бит 27 (GCE_26M). Для SD — IFRAO1 бит 4 (CLK_IFRAO_MSDC1) и бит 16 (MSDC1_SRC). Для USB — IFRAO2 бит 1 (SSUSB).

Тут есть отдельная грабля. В драйвере кнопок я гейты включаю через SET. В драйвере SD — тоже через SET, но в некоторых местах пришлось делать наоборот, через CLR, потому что запись в CLR-регистр где-то в infra_ao1 вешает шину. Один и тот же регистр, два противоположных поведения в зависимости от региона. Неочевидно, но проверено на живом железе.

Что получилось

Файл переписан: 709 строк → 191. Плюс пересобрал заголовочный файл с определениями ID — 169 строк удалено, 78 добавлено.

Было Стало
не собирается собирается
дефис в имени функции исправлено
222 ID, 13 PLL (от чужих чипов) 145 ID, 10 PLL (реальные)
пропуски в нумерации, OOB-доступ нумерация подряд
MSDC30_1_SEL одним регистром set/clear + апдейт

Итог: 10 PLL, 54 делителя, 5 мультиплексоров, 68 гейтов.

Побочный эффект оказался приятным: clk dump в U-Boot теперь показывает полную картину. Когда я полгода ломал голову над SD-картой, у меня не было способа посмотреть, что контроллер вообще тактируется правильно. Теперь есть, и половина вопросов отпадает сама.


Код: drivers/clk/mediatek/clk-mt6789.c ·
разбор: docs/hardware/clocks.md ·
общее состояние проекта: README

Дальше — последняя статья. Про то, что не работает: SD-карта, которая не отвечает ни на что. Сорок попыток, таблица исключённых вариантов и два оставшихся объяснения. И приглашение помочь, потому что в одиночку я тут застрял.