Есть один класс багов, который не показывает себя. Никакой ошибки компиляции, никакого сообщения в логе, никакого креша. Контроллер просто работает не с той частотой, и ты гадаешь, почему 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-карта, которая не отвечает ни на что. Сорок попыток, таблица исключённых вариантов и два оставшихся объяснения. И приглашение помочь, потому что в одиночку я тут застрял.