Short version (EN): a complete, measurement-backed map of what blocks the Nintendo Switch OLED panel (Samsung AMS699VC01, ID 0x2050) above ~64 Hz. Everything here was verified by reading hardware back, not inferred. Includes the DSI timing algorithm used by the Switch display driver, the location of the mode structure inside nvservices, a hard-coded field that silently forces 60 Hz timings, and twelve refuted hypotheses. We are looking for the register map of this panel's DDIC — see the last section.
Панель Switch OLED — Samsung AMS699VC01, идентификатор 0x2050 (сырой 50 9B 20).
Геометрия: строка 72+72+720+136 = 1000, кадр 1+9+1280+10 = 1300, пиксельный клок 78 МГц = ровно 60.000 Гц.
Здесь — карта того, что проверено измерением на пути к частоте выше 64 Гц: что работает, что не работает, и где именно стоит стена.
Отрицательные результаты приведены наравне с положительными. Их тут больше, и они ценнее: каждый закрывает направление, в которое иначе полезет следующий.
Достигается патчем пиксельного клока в образе nvservices (exefs-патч Atmosphere).
файловое смещение 0x000F29FC, 4 байта -> pclk в кГц
78000 = 60 Гц (сток)
83200 = 64 Гц
Смещение в файле включает 0x100 байт заголовка NSO; патчер вычитает их сам,
отображённый адрес — 0xF28FC. Работает end-to-end, проверено.
Выше 64 Гц тот же патч даёт зелень и замирание.
Драйвер Switch считает тайминги DSI тем же алгоритмом, что ядро L4T
(drivers/video/tegra/dc/dsi.h). Его константы найдены в образе nvservices по
смещениям 0x3A090..0x3A17C.
период_бита(пс) = 1e12 / (пиксельный_клок * бит_на_пиксель / линий)
tbyte = 8 * период_бита
поле = округл(значение_пс / tbyte) - аппаратный_инкремент
Значения по умолчанию, пикосекунды:
| поле | значение | инкремент |
|---|---|---|
| HS-EXIT | 120000 | 1 |
| HS-TRAIL | 3 + conv(max(8*бит, 60000 + 4*бит)) |
0 |
| HS-ZERO | 145000 + 5*бит | 3 |
| HS-PREPARE | 65000 + 5*бит | 1 |
| CLK-TRAIL | 80000 | 1 |
| CLK-POST | 70000 + 52*бит | 1 |
| CLK-ZERO | 260000 | 1 |
| TLPX | 60000 | 1 |
| CLK-PREPARE | 65000 | 1 |
| WAKEUP | 1970000 (из дерева устройства) | 1 |
Проверка. Расчёт воспроизводит регистры живого железа побайтово на двух независимых частотах:
60 Гц (468 Мбит/с на линию, бит 2137 пс) -> T0=06070603 T1=040A0E03
64 Гц (499 Мбит/с, бит 2003 пс) -> T0=06070704 T1=040A0F03
Раскладка слов:
PHY_TIMING_0 = HS-EXIT<<24 | HS-TRAIL<<16 | HS-ZERO<<8 | HS-PREPARE
PHY_TIMING_1 = CLK-TRAIL<<24 | CLK-POST<<16 | CLK-ZERO<<8 | TLPX
PHY_TIMING_2 = CLK-PREPARE<<16 | CLK-PRE<<8 | WAKEUP
BTA_TIMING = TAGET<<16 | TASURE<<8 | TAGO (из наносекунд дерева: 450/180/360)
Значения для 90 и 120 Гц:
90 Гц (702 Мбит/с, бит 1425 пс): T0=0A090A05 T1=060C1604 T2=0501AC BTA=260F1F
120 Гц (936 Мбит/с, бит 1068 пс): T0=0D0B0F07 T1=080E1D06 T2=0701E6 BTA=341429
Считается скриптом src/phy_timings.py.
Отображённое смещение 0xF2A08 (файловое 0xF2B08), рядом с пиксельным клоком.
Область записываемых данных. Регистры D-PHY собираются из её полей кодом по +0x39FD4:
поле +0 -> HS-EXIT поле +16 -> CLK-TRAIL
поле +4 -> HS-TRAIL поле +20 -> CLK-POST
поле +8 -> HS-ZERO поле +24 -> CLK-ZERO
поле +12 -> HS-PREPARE поле +32 -> CLK-PREPARE
поле +36 -> CLK-PRE
Сток: 6 7 6 3 | 4 10 14 | 1 3 1.
Рядом, по +0xF28DC, лежит вся геометрия режима — это таблица режимов, её нулевая
запись:
720 1280 72 1 136 10 72 9 <pclk>
Следом идёт телевизионный режим 480p (720 480 ... 27027).
HS-PREPARE не вычисляется от клока — оно прибито константой:
39f58: mov w24, #0x3
39f70: csinc w8, w24, wzr, eq ; ==2 -> 1, иначе 3
39f74: str w8, [x9, #12] ; запись HS-PREPARE в структуруФормула с константами 65000/120000 лежит в соседней ветке (0x3A084), которая
при обычной конфигурации не выполняется.
Следствие: патч данных не держится — структуру перезаписывает эта ветка при каждой настройке режима. Патч самой инструкции работает:
файловое 0x0003A058: 52800078 -> 528000B8 (mov w24,#3 -> mov w24,#5)
Проверено чтением памяти: поле становится 5, все десять полей структуры совпадают с расчётом.
Тот же w24 задаёт и TLPX через csel по +0x39FAC — одна константа на две
величины, а нужны разные.
Функция по +0x3A2E0 в образе nvservices:
ldr w10,[x8,#60] ; DSI_HOST_CONTROL -> сохранить
ldr w11,[x8,#64] ; DSI_CONTROL -> сохранить
and w10,w10,#0xffffcfff ; сбросить TX_TRIG
orr w10,w10,#0x2000 ; TX_TRIG = HOST
mov w9,#0xfffffff4 ; маска ~(бит0|бит1|бит3)
and w9,w11,w9 ; сбросить HOST_ENABLE, VIDEO_ENABLE, DCS_ENABLE
orr w9,w9,#0x1 ; поднять HOST_ENABLE
str w10,[x8,#60] ; str w9,[x8,#64]
ожидание очистки DSI_TRIGGER, шаг 10 мкс
длинный пакет: (длина<<8) | (канал<<6) | тип -> DSI_WR_DATA, затем данные по 4 байта
короткий: тип | канал<<6 | данные0<<8 | данные1<<16
str #2,[x8,#76] ; DSI_TRIGGER = HOST
обе сохранённые величины возвращаютсяКлючевое: драйвер гасит VIDEO_ENABLE на время пакета. Без этого host-команды
под HOS не уходят — именно поэтому распространённое мнение «под HOS в панель не
записать» неверно.
Замок 0xE2 — вендорское расширение, пять 16-битных групп, 5A открыто,
A5 закрыто. Снятие третьего уровня требует семибайтного пакета
(заголовок 0x739), hekate шлёт пятибайтный и достаёт только до второго.
Подробности и дамп сорока регистров:
https://github.com/Dimasick-git/switch-oled-panel-lvl3
Канал записи подтверждён обратным чтением. Идиома B0 (смещение) + B1 (байт),
в загрузчике:
B1[0x27] до записи: 05 -> после: 04 -> восстановлено: 05
Линия сброса панели: порт V, вывод 2 (в дереве устройства nvidia,panel-rst-gpio,
вывод 0xAA = 170). Полный переподъём панели под HOS работает: сброс, повторная
отправка заводской последовательности из дерева, возврат видеорежима — 9 команд
из 9, картинка возвращается.
Таблица развёртки: регистр B1, смещения 0x27..0x3A — десять подряд 16-битных
значений 0x0514 = 1300, то есть вертикальный итог. Правится, панель слушается:
уменьшение вдвое заставляет её прогонять 650 строк вместо 1280, кадр обрывается.
Это усечение развёртки, не ускорение.
| направление | итог |
|---|---|
| подъём пиксельного клока | 65 Гц срыв в полосы, 66 замирание |
| пересчёт таймингов D-PHY | все параметры в допуске, срыв остаётся |
| преэмфазис 1 -> 3 из 3 | эффект есть, стену не двигает |
| сила драйверов площадок 0x77777 -> 0xFFFFF | без изменений |
| ужимание горизонтального гашения | тот же срыв при стоковой скорости линии |
| холодный старт на 90 / 120 из загрузчика | чёрный экран |
последовательность Cooler3D F0 5A5A / F2 01 / 60 00 / F7 0F |
уходит доказуемо, эффекта нет |
кодировки S6E3FC3 (F2 00/60 08) и S6E3HC4 (F2 01/60 00) |
не работают |
MAUCCTR (F0 5A5A) |
дамп с ним и без него побайтово одинаков |
| таблица развёртки B1 | усекает кадр, частоту не меняет |
| правка полей E0 (счётчики строк и мелкие поля) | все записи уходят, картина не меняется |
| удержание регистров против драйвера | не выигрывается: 7 захватов генератора и 6 подмен таймингов за 30 с |
Отдельно: рабочая сборка L4T на 90 Гц, о которой ходят слухи, при разборе оказалась
обычной. Её загрузчик (HekateUnlocked) содержит стоковые тайминги панели
байт в байт, а в дереве устройства nvidia,dsi-refresh-rate = <0x3C> = 60.
Драйвер панели в ядре L4T (panel-s-720p-7-0.c) не содержит команд частоты вообще —
только яркость 0x51 и цветовой режим 0xA0.
Единственная величина, которая росла при всех способах — строк в секунду:
60 Гц: 78 000 строк/с -> 12.82 мкс на строку чисто
64 Гц: 83 200 строк/с -> 12.02 мкс чисто
65 Гц: 84 500 строк/с -> 11.83 мкс полосы
66 Гц: 85 800 строк/с -> 11.65 мкс замирание
1280 активных строк по 12.82 мкс — это 16.4 мс даже при нулевом гашении, то есть 61 Гц физического потолка. Запас около восьми процентов выбирается на 64.
Для 120 Гц строка должна идти 6.4 мкс — вдвое быстрее. Это не подстройка, это другой режим работы строчного драйвера.
Карта регистров DDIC панели AMS699VC01 (идентификатор 0x2050, сырой 209B50).
Публичных данных нет: поиск по всему открытому коду на идиому замка
0xE2, 0x5A, 0x5A даёт ноль совпадений. Ни одно известное семейство Samsung
её не использует — у S6E3FC3, S6E3HC4, S6E3HA8 ключ F0/FC. Наша панель по
способу доступа к регистрам не совпадает ни с чем опубликованным.
Всё остальное готово: канал записи подтверждён, измеритель частоты по импульсам кадра честный, инструмент проб работает без пересборок, переподъём панели отлажен, алгоритм таймингов сверен с железом.
Если у вас есть эта карта регистров или вы знаете, какой DDIC стоит в этой панели — откройте issue. Одного документа хватит, чтобы превратить перебор в работу.
Всё здесь проверено чтением из железа или измерением, а не выведено рассуждением. Частота меряется счётом импульсов вертикальной синхронизации (опорный замер на стоке: 59.971 Гц при заявленных 60). Запросы режима у драйвера измерением не считаются — на них мы однажды потеряли неделю: строка сообщала 90 Гц, пока панель шла на 60.
Отдельно про ошибки: они тут тоже перечислены. Молчащий кламп, который месяцами срезал всё выше 64 Гц из-за неотображённой апертуры MIPI_CAL. Замер, который измерял запрос режима вместо частоты. Приписывание эффекта не тому регистру, когда в одном прогоне менялись два места. Каждая из них стоила недели.