Compare commits

...
10 Commits
Author SHA1 Message Date
snlavaiseandClaude Opus 5.5 c028f63183 device-motorola-rtwo: initramfs debug shell, panel revive, power key
- deviceinfo / modules-initfs: boot through the U-Boot chainload, modules
  needed in the initramfs for the display, USB and the DSPs.
- initfs hooks: dropbear/telnet debug shell over USB networking.
- rtwo-panel-revive.service: blank/unblank the NT37705 if it comes up
  dead from the ABL handoff.
- logind-power-key.conf: ignore the power key (handled by the dashboard).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnHwDU5yL8L6qgPEN5CV5m
2026-09-26 20:02:56 -03:00
snlavaiseandClaude Opus 5.5 8a87204d48 linux-postmarketos-qcom-sm8550: motorola-rtwo display, DP, WiFi, audio DSPs
Bring-up of the Moto Edge 40 Pro (rtwo) on the SM8550 kernel:

- dts: NT37705 DSI panel with DSC, Goodix GT9916P touch, ADSP/CDSP with
  pmic-glink (battery, UCSI), Adreno 740, WiFi (WCN7850/ath12k), physical
  keys, and USB-C DisplayPort (pmic-glink altmode -> PS5169 redriver ->
  FSA4480 -> QMP combo PHY -> mdss_dp0) following the sm8550-qrd layout.
- DP link capped at HBR (link-frequencies on &mdss_dp0_out): the DP->HDMI
  converter of a common USB-C hub trains fine at HBR2 but its HDMI output
  flickers and drops HPD; HBR x2 still covers 1080p60.
- new drivers: typec-mux-ps5169.c (Parade PS5169 redriver as typec
  switch/retimer), panel-novatek-nt37705.c.
- patches: msm DP invalid LTTPR caps + no push_idle with the link down
  (hardware reset on unplug), msm DSI keeps the panel DSC config, ath12k
  regd fixes and MHI keep-awake, ICC keepalive for the MDP fetch path.
- rtwo.config / config-motorola-rtwo.aarch64: options for all of the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnHwDU5yL8L6qgPEN5CV5m
2026-09-26 20:02:55 -03:00
snlavaiseandClaude Sonnet 5 f3fac3a449 motorola-rtwo: TLMM gpio-reserved-ranges, UTMI pipe-clk, LZ4 initramfs
Exhaustive comparison with the mainline Samsung Galaxy Tab S9 (SM8550)
port's development log turned up the concrete cause of the "<2s reboot,
no console" (TZBSP_ERR_FATAL_NOC_ERROR) and the reason USB would not
have come up afterwards:

1. gpio-reserved-ranges: the stock DT hands 17 TLMM pins to the
   hypervisor / secure VMs (qcom,tlmm-vm-gpio-list: 13, 24-25, 44-48,
   56-59, 86-87, 91, 133, 137). The mainline sm8550-tlmm probe walks
   every GPIO's config registers; reading a hyp-owned one faults the
   NoC -> TrustZone resets the SoC with no console. On the Samsung port
   this was THE trigger and gpio-reserved-ranges was the fix.

2. qcom,select-utmi-as-pipe-clk on &usb_1: mandatory once the SuperSpeed
   QMP PHY is removed from the dwc3 node - otherwise the core never
   releases DCTL.CSFTRST (-ETIMEDOUT) and no usb0 appears. Confirmed by
   the Qualcomm dwc3 glue binding and the Samsung port (its v0.15).

3. deviceinfo_initfs_compression=lz4:legacy: stock LineageOS packs both
   Android-v4 ramdisks with LZ4 legacy (verified: init_boot magic
   02214c18). A gzip stream concatenated after an LZ4-legacy one makes
   the kernel unpacker fail with "invalid magic" -> panic
   unknown-block(0,0).

Also: stock eUSB2 repeater peripheral-mode tuning (qcom,param-override-seq).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 01:52:15 -03:00
snlavaiseandClaude Sonnet 5 14ae05a5c2 motorola-rtwo: add SM8550 ABL + hypervisor requirements to the .dts
Deeper comparison of our mainline DTB against the live LineageOS DTB and
the mainline Samsung Galaxy Tab S9 (SM8550) port turned up several
things the Motorola SM8550 firmware needs that mainline sm8550.dtsi does
not provide and the ABL does not reconstruct:

1. reserved-memory: chipinfo @ 0x81cf4000 and llcc_lpi @ 0xff800000 are
   no-map in the stock DT but absent from mainline. Both fall inside a
   HLOS DRAM bank, so Linux maps and uses firmware/hardware-owned memory
   -> external abort -> SoC resets with no console (the <2s reboot). The
   Samsung port hit the exact same "NoC error" from missing chipinfo /
   LLCC carveouts.

2. __symbols__: build the DTB with dtc -@ (DTC_FLAGS_sm8550-motorola-rtwo
   in the qcom Makefile). The SM8550 ABL runs libufdt against the base
   DTB; without symbols it drops to the bootloader menu.

3. qcom_tzlog + arch_timer labels: same ABL dtbo-apply path requires
   these two labels to exist (LKML "arm64: dts: qcom: sm8550: Fix DTBO
   boot failure").

4. compatible: keep qcom,kalama-mtp / qcom,kalama / qcom,mtp next to
   qcom,sm8550 - the ABL matches the board on these strings plus
   qcom,board-id before launching the kernel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 01:36:30 -03:00
snlavaiseandClaude Sonnet 5 832651c2ca device-motorola-rtwo: fix eUSB2 PHY module name in initramfs
modules-initfs listed 'phy-qcom-snps-eusb2', which does not exist in
kernel 7.2 - the SNPS eUSB2 PHY driver is 'phy-snps-eusb2'
(CONFIG_PHY_SNPS_EUSB2=m). pmbootstrap silently skipped the unknown
name, so the built initramfs shipped only phy-qcom-eusb2-repeater.

Without phy-snps-eusb2, phy@88e3000 (qcom,sm8550-snps-eusb2-phy) never
probes -> the built-in dwc3 at usb@a600000 stays on -EPROBE_DEFER ->
no UDC -> the initramfs can't bring up the USB gadget -> no telnet on
172.16.42.1. Not a panic, but it blocks the whole USB-networking goal.

dwc3 and dwc3-qcom are built-in (modules.builtin), pstore/ramoops too
(rtwo.config's CONFIG_PSTORE_RAM=y stuck).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 01:21:37 -03:00
snlavaiseandClaude Sonnet 5 caac069a36 motorola-rtwo: add qcom,msm-id + qcom,board-id + ramoops to the .dts
The Sony-modelled minimal DT was electrically correct (verified node by
node against the DTB LineageOS 23.2 actually boots: debug UART SE7,
eUSB2 phy+repeater, hsphy supplies pm8550vs_2_l1/l3 all match exactly)
but missing two things the stock firmware relies on:

 - qcom,msm-id = <0x218 0x20000 0x207 0x20000 0x207 0x10000 0x218 0x10000>
   qcom,board-id = <0x241 0xb1b0>
   The Motorola ABL selects a DTB out of a multi-FDT vendor_boot; without
   a matching SoC/board table it may reject our blob (-> no DT -> instant
   reset, which matches the <2s black-screen loop).

 - ramoops @ 0xae000000 (0x40000 console + 0x20000 record), copied from
   the stock DT. rtwo.config already builds CONFIG_PSTORE_RAM; with the
   node in place a failed boot's console can be read back from a later
   pmOS boot. No 'no-map' (arm64 ramoops needs the linear map).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 01:09:17 -03:00
snlavaiseandClaude Sonnet 5 187b52aa12 motorola-rtwo: back to GKI v4, replace every ramdisk source with pmOS
rtwo is Android 13: the generic first-stage ramdisk lives in init_boot,
not boot.img, and the bootloader always assembles the initramfs from
init_boot + vendor_boot regardless of what boot.img contains. So the
dubai-style monolithic v2 boot.img cannot work here - keep header v4 and
replace boot (kernel), vendor_boot (dtb+cmdline+initramfs) and init_boot
(generic ramdisk) all with pmOS content.

Combine everything learned:
 - header v4, append_dtb off (dtb in vendor_boot)
 - earlycon on the cmdline (Motorola ABL requirement, from dubai)
 - raw Image kernel (stock LineageOS rtwo evidence)
 - base 0 + explicit offsets + os_version/os_patch_level
 - 24 MB zero dtbo (dubai trick)
 - the Sony-Xperia-1-V-modelled minimal .dts (no /memory, no msm-id, no
   speculative regulators)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 00:45:30 -03:00
snlavaiseandClaude Sonnet 5 c9eadef4d5 motorola-rtwo: switch to the device-motorola-dubai boot recipe
device-motorola-dubai (Motorola Edge 30, SM7325) is the closest working
Motorola mainline pmOS port. Match it:
 - header_version 2 + append_dtb=true: one monolithic boot.img with the
   DTB glued to the kernel, flashed to 'boot'. No vendor_boot / init_boot
   / GKI ramdisk layering.
 - 'earlycon' back on the cmdline - dubai's kernel-cmdline.conf says the
   Motorola ABL reboots the phone immediately WITHOUT it. This matches
   our exact <2s symptom; removing it earlier was the wrong call.
 - explicit flash_offset_* (base 0, dtb 0x1f00000, ...) and
   --os_patch_level, as dubai sets them.
 - dtbo.img = 24 MB of zeros (full partition), dubai's trick to make the
   bootloader skip overlay application.
 - kernel: gzip'd Image again (dubai uses gz, not raw).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 00:41:35 -03:00
snlavaiseandClaude Sonnet 5 35df152310 motorola-rtwo: rewrite .dts modelled on mainline Sony Xperia 1 V
Same SoC, working mainline port. Strip everything speculative:
 - no /memory node (Sony relies on the bootloader to inject it)
 - no qcom,msm-id / qcom,board-id (Sony's Qualcomm bootloader doesn't
   need them)
 - no reserved-memory / ramoops / framebuffer overrides
 - regulator tree copied verbatim from Sony, including the rails it
   deliberately omits with 'the board seems to crash on access' - our
   MTP-derived tree (with vdd-*-supply chains + bob2 + L4) may have been
   voting an RPMh rail that resets the SoC early.
Only the debug UART and USB HS-peripheral are enabled - enough for the
initramfs + USB networking. cmdline: console=ttyMSM0 (no earlycon).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 00:35:59 -03:00
snlavaiseandClaude Sonnet 5 988dd10251 motorola-rtwo: no earlycon, clean /chosen, framebuffer at root
New hypotheses from re-comparing with the stock boot:
- 'earlycon' (bare) brings up uart7 in setup_arch(), before the clock
  framework. Stock uses console=null and almost certainly leaves the
  QUP0 clock gated -> touching uart7 MMIO faults before any console.
  Drop earlycon + the serial console; keep console=tty0 only.
- Also drop clk_ignore_unused / pd_ignore_unused (can break qcom power
  domain init) and ignore_loglevel stays for the fb console.
- Move the simple-framebuffer out of /chosen and drop the
  #address-cells override there, so the bootloader's /memory + initrd
  patching into /chosen is not disturbed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QF5HebjUsRaxbW4cBX6HJP
2026-09-09 00:10:58 -03:00
24 changed files with 3311 additions and 252 deletions
@@ -0,0 +1 @@
/usr/sbin/dropbear
+94 -6
View File
@@ -2,13 +2,21 @@
maintainer="snlavaise <snlavaise@gmail.com>"
pkgname=device-motorola-rtwo
pkgver=1
pkgrel=9
pkgrel=36
pkgdesc="Motorola Moto Edge 40 Pro"
url="https://postmarketos.org"
arch="aarch64"
license="MIT"
depends="
android-tools
busybox-extras
dropbear
firmware-motorola-rtwo-adsp
firmware-motorola-rtwo-cdsp
firmware-motorola-rtwo-gpu
linux-firmware-ath12k
linux-firmware-qcom
linux-firmware-rtw88
linux-postmarketos-qcom-sm8550
postmarketos-base
soc-qcom
@@ -17,13 +25,47 @@ makedepends="devicepkg-dev"
source="
deviceinfo
modules-initfs
initfs-hook.sh
initfs-hook-cleanup.sh
30-dropbear.files
debug-telnetd.service
logind-power-key.conf
rtwo-panel-revive.sh
rtwo-panel-revive.service
"
options="!archcheck !check"
# TODO (as hardware comes up):
# - firmware-motorola-rtwo (WiFi/BT/modem/GPU blobs, generate from the
# stock image with 'pmbootstrap ...' or extract manually)
# - firmware-qcom-adreno-* for the Adreno 740 (GPU / mesa-vulkan-freedreno)
# - WiFi (WCN7850/ath12k via pcie0): links up, enumerates fine, and the real
# Motorola calibration (bdwlan.elf.rtwo.epa) is fully extracted and
# verified byte-perfect -- but it ALWAYS fails at the QMI board-data
# transfer itself (qmi failed to load board data file:-110, ETIMEDOUT),
# confirmed a dead end after exhaustive investigation (re-verified
# 2026-09-22, no format/corruption issue found). Workaround: a generic
# Qualcomm board-id=44 blob as the LEGACY board.bin (not board-2.bin) at
# ath12k/WCN7850/hw2.0/board.bin gets wlan0 to appear, but hits a
# separate, deeper WMI/HTC credit-exhaustion bug in ath12k_mac_start()
# (PMF_QOS never gets a second tx credit) -- 7 hypotheses ruled out with
# evidence, cross-referenced against a real LKML thread with the exact
# same symptom on unrelated hardware. Looks like a genuine unfixed
# upstream kernel bug, not something fixable from this device's side.
# Draft bug report at ~/pmos/ath12k-bug-report-wmi-credit-exhaustion.md,
# not sent (needs explicit go-ahead before any public post).
# - USB WiFi dongle (RTL8723DU) as an alternative to onboard WCN7850:
# CONFIG_RTW88_8723DU=m already enabled, needs linux-firmware-rtw88
# (added below) for rtw88/rtw8723d_fw.bin. Requires &usb_1 in host mode
# (now the default, see the .dts) + a self/externally-powered USB-C hub.
# - Bluetooth (qcom,wcn7850-bt) not wired in the .dts yet, same PMU node.
# - firmware-motorola-rtwo: ADSP/CDSP (remoteproc, pmic-glink battmgr/UCSI
# run on the ADSP) and the GPU zap shader are pulled in as dependencies.
# -hexagonfs (FastRPC libs for hexagonrpcd, e.g. sensors) is left out
# until hexagonrpcd is actually set up. Modem blobs still TODO.
# - GPU (Adreno 740): WORKING 2026-09-25 (freedreno FD740, OpenGL 4.6/GLES).
# a740_sqe.fw + gmu_gen70200.bin come from linux-firmware-qcom; the zap
# shader MUST be Motorola's signed one (firmware-motorola-rtwo-gpu,
# qcom/sm8550/motorola/rtwo/a740_zap.mbn) -- the generic
# qcom/sm8550/a740_zap.mbn in linux-firmware-qcom is QRD-signed and is
# rejected on a retail device.
# - make-dynpart-mappings once super partitions are mapped
# - soc-qcom-modem, qbootctl (A/B slot handling) once booting
@@ -33,9 +75,55 @@ build() {
package() {
devicepkg_package $startdir $pkgname
# devicepkg_package handles initfs-hook.sh natively (installs it to
# usr/share/mkinitfs/hooks/00-$pkgname.sh), but has no generic handler
# for extra ".files" manifests (unlike modules-initfs) -- install this
# one by hand, same mechanism postmarketos-mkinitfs-hook-debug-shell
# uses for telnetd's own 20-debug-shell.files.
install -Dm644 "$srcdir"/30-dropbear.files \
"$pkgdir"/usr/share/mkinitfs/files/30-dropbear.files
# Mata el dropbear del debug-shell antes de switch_root -- si no, sigue
# vivo pegado al puerto 22 y sshd.service de la rootfs real nunca puede
# bindear (ver comentario grande en el propio initfs-hook-cleanup.sh).
install -Dm755 "$srcdir"/initfs-hook-cleanup.sh \
-t "$pkgdir"/usr/share/mkinitfs/hooks-cleanup/
# Debug telnet shell de la rootfs REAL (no del initramfs -- ver el
# comentario grande en el propio .service). Se habilita a mano con el
# symlink porque esto no es un paquete "de verdad" con postinstall,
# es un hack de bring-up.
install -Dm644 "$srcdir"/debug-telnetd.service \
"$pkgdir"/usr/lib/systemd/system/debug-telnetd.service
install -d "$pkgdir"/usr/lib/systemd/system/sysinit.target.wants
ln -s ../debug-telnetd.service \
"$pkgdir"/usr/lib/systemd/system/sysinit.target.wants/debug-telnetd.service
# Boton de encendido: ignorar la pulsacion corta (ver el .conf).
install -Dm644 "$srcdir"/logind-power-key.conf \
"$pkgdir"/usr/lib/systemd/logind.conf.d/50-rtwo-power-key.conf
# Panel negro en frio: blank/unblank desde el rootfs si el panel salio
# muerto (el initfs-hook no alcanza cuando msm engancha el display
# despues del switch_root). Ver rtwo-panel-revive.sh.
install -Dm755 "$srcdir"/rtwo-panel-revive.sh \
"$pkgdir"/usr/libexec/rtwo-panel-revive
install -Dm644 "$srcdir"/rtwo-panel-revive.service \
"$pkgdir"/usr/lib/systemd/system/rtwo-panel-revive.service
install -d "$pkgdir"/usr/lib/systemd/system/multi-user.target.wants
ln -s ../rtwo-panel-revive.service \
"$pkgdir"/usr/lib/systemd/system/multi-user.target.wants/rtwo-panel-revive.service
}
sha512sums="
e1edb54b515de674ded1a0443901f41a37655d79da8273a504bca8a23e7c789b13ddad9ddef99e5eed8ce352502339b14e55b20eacfcf734334daf19edc7b876 deviceinfo
c572e21cb0624b554a22a218b0c4faab9b6412654bed6bfef530f3068d076b21c573bd7f4b3ca65f510f8756f636a861191cce925ef49587db7d68d09558826c modules-initfs
bc4d018a852d900fd9932bf881e81b1eae49f416dbafe212a4fa6e26c05f812cef64ca19b8adb69225331468ac1535d568f547e2e9f4ea296ed37a8c6340e4ad deviceinfo
298e836000c92db16ee4fe3ce4899eb13e9099983ee0fabff1e98c494e8a1883cef327a3ddce5c1ef98e0043b804aae8506438630b3bb144f92ded89c129668b modules-initfs
eccadc2bf5713ff9df16234c0d9daf1364bdcd88fb9709df012035719e2d5e44d658ccaf87a9bb1a76d433861c37162cdfef999fbe1093d6d0d32eb849dbdfe8 initfs-hook.sh
cb8ea3e0da0219fdf77f6def6d7a77157a4ef152cc434e3d282457647784b05e7152cf1028d00a5534ae721a38d3c24047edd1dfbaa6fd34290c3823d4cf54c0 initfs-hook-cleanup.sh
07ceb61f64d99fdbc815795beae9014dc22177d5622d015db79849faed5c14225b280d83dbac174957089344d2e857595b046683cee033d4acd76fdbf7114eab 30-dropbear.files
3511918144a3130d9ac64df78cfd9a02e3ed5ee11f45f0b3b872f2e42504434b3c3197b47797fad2510ff1e7ae7822813a8710eb4830f324b5cb101742fde062 debug-telnetd.service
bab2fbd6078d9ac342708a1e85b7f5d5cff5e1105b5bd633517bf27f9a61499b9903c9aa17d0142c82f8cd874cd9ac03d1ddb33b8c66e40094a7271dd54c0502 logind-power-key.conf
59db456c9ba57352aa8bff0e8ca73bd5b1c5c7f6f9c57e382e5bdd68791c9324d705210e77aa4e945e3869ab11dc1066e1a290c3750a27772c89f83731d28609 rtwo-panel-revive.sh
8e374a7304f8648d33d9087149e53efc5b09ffe1f7bc07b8b10521b7b1163e4787b56ecf3d2871629b1cfcc0037c1c15d084b514412e5e0a90ff66fd8e83b088 rtwo-panel-revive.service
"
@@ -0,0 +1,21 @@
[Unit]
# Bring-up diagnostic (2026-09-18): shell sin ninguna dependencia de
# cripto/entropia, para no quedar a ciegas si sshdgenkeys.service (ssh-keygen
# -A, bloqueado en getrandom() si el CRNG nunca termina de inicializar) o
# cualquier otra cosa de multi-user.target se cuelga antes de que sshd
# arranque. DefaultDependencies=no + WantedBy=sysinit.target: arranca lo mas
# temprano posible, en paralelo con todo lo demas, sin esperar a basic.target
# ni a nada que dependa de el. Misma IP/puerto que el debug-shell del
# initramfs (pmos.debug-shell), a proposito, para no tener que recordar dos
# direcciones distintas.
Description=Debug telnet shell (bring-up, sin dependencias de cripto)
DefaultDependencies=no
[Service]
Type=simple
ExecStart=/usr/sbin/telnetd -b 172.16.42.1:23 -F -l /bin/sh
Restart=always
RestartSec=1
[Install]
WantedBy=sysinit.target
+216 -15
View File
@@ -17,30 +17,231 @@ deviceinfo_external_storage="true"
deviceinfo_screen_width="1080"
deviceinfo_screen_height="2400"
deviceinfo_getty="ttyMSM0;115200"
# The DTS ships a simple-framebuffer pointing at the bootloader's continuous
# splash buffer, so there should be an early console on the panel. If the
# screen stays black, set this to "true" (and fix the framebuffer@ node).
# There is no DRM/mdss yet, so no mode setting / backlight control.
deviceinfo_no_framebuffer="true"
# Split the initramfs: a small base archive (fits in init_boot, 8 MB) plus a
# separate initramfs-extra loaded from the rootfs later.
deviceinfo_create_initfs_extra="true"
# UN SOLO initramfs, sin initramfs-extra. Es lo que hace que
# 'pmos.debug-shell' sirva de algo: init_2nd.sh (y con el, la rama
# if [ "$debug_shell" = "y" ]; then debug_shell; fi
# ) vive en el paquete de ficheros -extra, o sea DENTRO del rootfs, que en
# este equipo no podemos flashear. Con extra separado, jump_init_2nd() del
# stage 1 es un no-op, la bandera de la cmdline no la lee nadie y el unico
# camino a la shell de depuracion es agotar los 30 s de wait_boot_partition
# -> fail_halt_boot. Fusionandolo, init.sh salta a init_2nd.sh de inmediato,
# que hace setup_udev + setup_usb_network + start_unudhcpd y ENTONCES entra
# en debug_shell: telnet en 172.16.42.1:23 y getty en /dev/ttyGS0 en cuanto
# el USB enumera, sin depender de ninguna particion.
# El coste (initramfs de 3 MiB -> ~15 MiB) ya no importa: con cabecera v2 el
# ramdisk va DENTRO de boot.img, no en init_boot, y boot_a admite 96 MiB.
deviceinfo_create_initfs_extra="false"
# The stock LineageOS boot chain packs both Android-v4 ramdisks with LZ4
# legacy. When the bootloader concatenates our vendor_ramdisk with the
# init_boot ramdisk, a gzip stream after an LZ4-legacy one makes the
# kernel's unpacker choke ("invalid magic" -> panic unknown-block(0,0)).
# Match the stock format.
deviceinfo_initfs_compression="lz4:legacy"
# Bootloader related
# Bootloader: Android 13 GKI. The kernel goes in boot, the DTB + cmdline +
# initramfs in vendor_boot. init_boot holds the generic (first-stage)
# ramdisk - it MUST be replaced with pmOS's, or the stock AOSP /init runs.
# Lessons folded in from device-motorola-dubai (the closest working
# Motorola mainline port) and the stock LineageOS images.
deviceinfo_flash_method="fastboot"
# Keep flashing dtbo, and keep it EMPTY. The ABL overlays whatever sits in
# the dtbo partition on top of the DTB it took from vendor_boot, and a stock
# overlay applied to a mainline DTB mangles it - the documented advice for
# mainline on Qualcomm is to erase the dtbo partition or program NULL to it.
# The dtbo.img pmbootstrap generates here is 24 MiB of zeroes (no DTBO magic,
# no entries), so flashing it IS that "program NULL" step: it wipes the
# LineageOS/stock table (32 entries) that a restore-to-stock puts back.
# Do not drop this line expecting the ABL to skip overlays - dropping it
# leaves the stock table in place.
deviceinfo_flash_fastboot_partition_dtbo="dtbo"
deviceinfo_flash_fastboot_partition_vbmeta="vbmeta"
deviceinfo_kernel_cmdline="console=tty0 console=ttyMSM0,115200n8 earlycon clk_ignore_unused pd_ignore_unused ignore_loglevel"
# CONSOLA REAL POR UART (2026-09-13, sin framebuffer en el .dts): console=
# apunta ahora a ttyMSM0 (uart7, GENI QUP0/SE7), no a tty0. Ya no hace falta
# el 'ignore_loglevel'+panic=5-como-sustituto-de-consola: si el UART vive,
# vemos todo en tiempo real.
#
# 'earlycon' AGREGADO DE NUEVO (2026-09-13, segunda pasada). Se habia sacado
# a proposito por el riesgo real y documentado de fragilidad en el handover
# clk/earlycon (serie LKML "clk: help OF platforms with their stdout
# (earlycon) clocks during early boot", aun en v6) combinado con la sospecha
# de que DisableHypUartUsageForLogging() (SMC que el ABL llama antes de
# saltar al kernel, ver rtwo-abl-vs-abl-de-referencia) deje el UART en un
# estado raro para un acceso CRUDO y tempranisimo. Pero: 'console=ttyMSM0'
# SOLO (sin earlycon) no muestra nada hasta que el driver real de
# qcom_geni_serial prueba, lo cual exige que GCC y el pinctrl ya hayan
# probado antes - y CADA cuelgue que vimos en este bring-up paso muchisimo
# antes de eso (reset en 1-2s, antes de fbcon). Si el problema sigue siendo
# pre-driver, sin earlycon no veriamos nada, igual que en todos los intentos
# anteriores con framebuffer. Sin argumento usa /chosen/stdout-path (serial0
# -> &uart7, qcom,geni-debug-uart tiene OF_EARLYCON_DECLARE).
#
# 'panic=5': con consola real ya no es indispensable, pero se deja: si
# panickea en vez de colgarse, lo vemos en pantalla Y el registro queda en
# pstore/ramoops para la proxima vez.
# 'pmos.debug-shell': shell de depuracion por USB (telnet 172.16.42.1) con
# dmesg y pstore montados, si se llega al stage 2 del initramfs.
# 'allow_mismatched_32bit_el0' AGREGADO (2026-09-14): AYN Thor (mismo SoC,
# SD8 Gen 2 = SM8550, arranca ABL-directo sin U-Boot igual que nosotros) lo
# lleva en su receta de cmdline. Sin el, en el peor caso el kernel solo
# restringe apps de 32 bits en runtime (no bloquea el arranque segun el
# codigo fuente) - pero es gratis agregarlo: solo relaja una verificacion,
# nunca puede empeorar nada.
# 'console=tty0 ignore_loglevel' AGREGADO (2026-09-17): consola visual sobre
# el framebuffer@b8000000 reagregado al .dts del kernel, ver ese archivo.
# 'console=ttyMSM0,115200n8' SACADO (2026-09-18): no hay UART real
# enganchado en este bring-up, la consola que se usa de verdad es la de
# framebuffer (tty0, ver foto). Se deja 'earlycon' (bare, sin argumento)
# porque igual sale por /chosen/stdout-path sin bloquear nada aunque nadie
# lo lea.
# 'printk.devkmsg=on' AGREGADO (2026-09-21): con systemd.log_target=kmsg
# (ver qcom-phone.env), TODO el log visible en la consola de framebuffer
# sale de que systemd escribe activamente a /dev/kmsg -- inicialmente se
# penso que el kernel (printk.devkmsg=ratelimit por default) era el que
# cortaba esas escrituras con un rate-limit agresivo, disparando "Too many
# messages being logged to kmsg, ignoring" a los ~2-3s de systemd. FALSO,
# confirmado en hardware: con SOLO este flag el freeze sigue exactamente
# igual, misma linea. Se deja de todos modos (no molesta), ver el flag de
# abajo para el fix real.
# 'systemd.log_ratelimit_kmsg=false' AGREGADO (2026-09-21, segundo
# intento): el rate-limit real es del propio systemd, no del kernel --
# write_to_kmsg() en systemd tiene su PROPIO limitador interno para
# escrituras a /dev/kmsg, totalmente independiente de printk.devkmsg.
# Documentado en systemd(1)/kernel-command-line(7) como
# $SYSTEMD_LOG_RATELIMIT_KMSG / systemd.log_ratelimit_kmsg= (boolean,
# default true). CONFIRMADO EN HARDWARE QUE ESTE FLAG TAMPOCO ALCANZA: el
# usuario reportó que después del freeze NO aparece ningún mensaje nuevo,
# ni siquiera los timeouts de WMI del kernel que se repiten cada ~13s
# (esos SÍ se siguen generando en el ring buffer real, confirmado por
# SSH). Descarta cualquier teoría de "se generan menos mensajes" -- el
# framebuffer literalmente deja de refrescarse, sin relación con volumen
# de logging.
# 'cpufreq.default_governor=performance' AGREGADO (2026-09-21, tercer
# intento, DIAGNÓSTICO): el .dts del kernel
# (sm8550-motorola-rtwo.dts líneas 721-754) ya documenta que dispcc
# habilitado por defecto glitchea disp_cc_mdss_mdp_clk_src (el reloj de
# píxeles del MDP que el ABL/U-Boot deja corriendo para seguir escaneando
# el framebuffer heredado), fix aplicado ahí ('&dispcc { status =
# "disabled"; }', confirmado vigente) -- pero el freeze persiste con ese
# fix puesto, así que algo MÁS sigue tocando ese mismo reloj heredado por
# una vía distinta. Hipótesis: GCC comparte una PLL padre con ese reloj de
# píxeles, y el escalado dinámico de cpufreq (que arranca justo cuando el
# boot termina y baja la carga -- la misma ventana donde aparece el
# freeze) la glitchea indirectamente. Este flag fija el governor a
# 'performance' para probar si evita el glitch. CONFIRMADO EN HARDWARE QUE
# TAMPOCO ALCANZA: freeze idéntico con el governor fijo -- descarta
# cpufreq DVFS.
#
# Con 4 hipótesis descartadas con evidencia real (rate limit kernel, rate
# limit systemd, cpufreq, y el mecanismo de dispcc vía otro controlador --
# dmesg | grep WARNING/clk_rcg2/update_config/genpd no muestra nada salvo
# "PM: genpd: Not disabling unused power domains", y el usuario confirmó
# que el freeze pasaba INCLUSO ANTES de que existieran los nodos de WiFi
# en el .dts, descartando PCIe/ath12k), toca dejar de adivinar QUÉ driver
# específico es y aislar CUÁNDO pasa.
# 'systemd.mask=systemd-udev-trigger.service' PROBADO (2026-09-21, cuarto
# intento): CONFIRMADO EN HARDWARE que con esto el freeze DESAPARECE
# (pantalla mostró "emergency mode" en vez de congelarse) -- prueba
# definitiva de que es un driver/módulo cargado durante el coldplug de
# udev, no un timeout de hardware. Pero bloquear el coldplug entero rompe
# el boot normal (cae a emergency mode sin red, probablemente porque el
# descubrimiento de particiones/interfaz USB de red también depende de
# udev). SACADO, reemplazado por el flag de abajo para bisectar sin
# romper el boot.
# 'module_blacklist=gpucc_sm8550,videocc_sm8550,camcc_sm8550' AGREGADO
# (2026-09-21, quinto intento) -- CONFIRMADO EN HARDWARE QUE ES EL FIX:
# con estos 3 bloqueados, boot completo normal (SSH/red ok) y la pantalla
# llega al login prompt sin congelarse. NO se aisló cuál de los tres en
# particular es el culpable real -- el bring-up actual no necesita
# GPU/video-codec/cámara, así que no vale la pena bisectar más por ahora.
# Si en el futuro hace falta reactivar alguno (ej. GPU para aceleración
# gráfica), sacarlo de esta lista de a uno y probar en hardware; si
# individualmente reproduce el freeze, deshabilitarlo a nivel .dts
# (status = "disabled", mismo patrón que dispcc) en vez de blacklistear
# el módulo. FIX VIGENTE.
# 'gpucc_sm8550' SACADO de la lista (2026-09-21) para empezar el
# bisect de a uno, siguiendo el plan de arriba -- es el primer candidato
# porque es el unico de los tres con un caso de uso real a corto plazo
# (aceleracion grafica). El .dts no tiene NINGUN nodo de GPU (no hay
# Adreno/zap-shader, se verifico: sm8550-motorola-rtwo.dts no menciona
# "adreno"/"gpu@" en ningun lado), asi que gpucc queda habilitado por
# defecto desde sm8550.dtsi (como dispcc, ver comentario de &dispcc mas
# abajo) sin ningun consumidor todavia. Si el freeze vuelve, la sospecha
# recae en gpucc_sm8550 y el fix pasa a &gpucc { status = "disabled"; }
# en el .dts en vez del blacklist. videocc_sm8550/camcc_sm8550 SIGUEN
# blacklisteados -- no bisectar los tres a la vez.
# 'videocc_sm8550' SACADO de la lista (2026-09-21, mismo dia, mas tarde):
# reset random confirmado en hardware CON gpucc_sm8550 ya reactivado (asi
# que gpucc queda descartado como causa unica del reset). Leyendo el
# fuente real de gdsc_init() (drivers/clk/qcom/gdsc.c) contra los GDSC de
# cada controlador: video_cc_mvs0_gdsc/mvs1_gdsc tienen la flag
# HW_CTRL_TRIGGER (control autonomo por hardware del colapso de energia),
# la MISMA clase de flag que mdss_gdsc/mdss_int2_gdsc de dispcc (HW_CTRL) --
# dispcc tambien cambio de habilitado a disabled en el mismo fix del
# freeze, asi que es sospechoso que comparta este patron con videocc.
# camcc_sm8550 NO tiene ningun GDSC con HW_CTRL/HW_CTRL_TRIGGER/VOTABLE
# (todos "planos", solo POLL_CFG_GDSCR|RETAIN_FF_ENABLE) -- por eso queda
# BLACKLISTEADO todavia, es el candidato menos sospechoso de los tres.
# CONFIRMADO EN HARDWARE 2026-09-22: el reset random no volvio a pasar con
# videocc_sm8550 reactivado (junto con cpuidle.off=1 y CONFIG_GUNYAH_WATCHDOG,
# ver comentarios de abajo) -- no se aislo cual de los tres exactamente era
# necesario, pero con los tres juntos el reset desaparecio. No bisectar mas
# a menos que vuelva a aparecer.
# NOTA: esta linea documenta la cmdline "canonica" del dispositivo, pero el
# flujo de arranque VIGENTE es el chainload de U-Boot (ver
# rtwo-uboot-chainload en las notas del proyecto), que ignora esto y usa en
# cambio pmos_bootargs de ~/pmos/uboot-chainload/qcom-phone.env (mantenida
# en sincronia a mano con esta linea).
# 'ath12k.debug_mask=0x66' QUITADO 2026-09-22 (sincronizado con deviceinfo):
# el WiFi ya funciona y ese mask inundaba dmesg con cada header HTC RX y cada
# mensaje WMI (bits 0x2|0x4|0x20|0x40, solo presentes con CONFIG_ATH12K_DEBUG=y).
# Si vuelve a hacer falta diagnostico, re-agregar exactamente asi: ath12k.debug_mask=0x66
deviceinfo_kernel_cmdline="earlycon console=tty0 quiet splash panic=5 allow_mismatched_32bit_el0 random.trust_cpu=on printk.devkmsg=on systemd.log_ratelimit_kmsg=false cpufreq.default_governor=performance cpuidle.off=1 module_blacklist=camcc_sm8550 msm.separate_gpu_kms=1 deferred_probe_timeout=30"
# 'cpuidle.off=1' AGREGADO (2026-09-21): prueba para aislar si el reset
# random (siempre dentro de los primeros 5 min, nunca mientras corre algo)
# es un cuelgue al entrar/resumir de un C-state profundo via PSCI
# CPU_SUSPEND -- no hay overrides de idle-states/cpuidle en este .dts,
# heredamos lo que trae sm8550.dtsi sin validar en este hardware. Con esto
# las CPU solo usan WFI simple, nunca los estados profundos.
# CONFIRMADO EN HARDWARE 2026-09-22: el reset random no volvio a aparecer
# con esto puesto (junto con videocc_sm8550 reactivado y
# CONFIG_GUNYAH_WATCHDOG=y, ver arriba) -- FIX VIGENTE, no sacar. No se
# aislo cual de los tres cambios era el necesario especificamente; si algun
# dia hace falta reactivar cpuidle (ej. por consumo de bateria), bisectar
# de a uno en vez de sacar los tres juntos.
# CONFIG_GUNYAH_WATCHDOG=y (drivers/watchdog/gunyah_wdt.c, protocolo SMC con
# techo de ~32s) tambien esta activo -- no encajaba bien con la
# variabilidad de 0-5min del reset visto originalmente, pero se dejo activo
# sin costo real (el driver apaga el watchdog al hacer probe() si nadie
# abre /dev/watchdog) y forma parte del combo que resolvio el reset.
# CABECERA v2, NO v4 - y esta es la razon de fondo:
# DTBImgCheckAndAppendDT() coge el DTB de sitios distintos segun la version.
# v3/v4: ImageBuffer = vendor_boot, DtbOffset = dentro de vendor_boot. Si el
# match de qcom,msm-id/board-id falla, el fallback de "single appended
# DTB" calcula ImageBuffer(=BOOT.IMG) + DtbOffset(=de vendor_boot), o
# sea apunta DENTRO DEL KERNEL -> fdt_check_header falla ->
# EFI_NOT_FOUND -> LoadImageAndAuth falla -> el ABL de Motorola hace
# "Rebooting to retry" -> bootloop de 7 y a fastboot.
# v2: ImageBuffer y DtbOffset son AMBOS del boot.img, asi que el fallback
# funciona y acepta nuestro DTB SIN comparar ningun ID.
# O sea: con v4 dependemos de acertar msm-id/board-id exactos; con v2 no
# dependemos de nada. device-motorola-dubai (mismo ABL de Motorola, puerto
# mainline que SI arranca) usa v2 por esto mismo.
deviceinfo_generate_bootimg="true"
# The Motorola bootloader places the DTB / kernel at the addresses in the
# vendor_boot header. mkbootimg defaults to base 0x10000000 (dtb ends up at
# 0x11f00000, outside RAM) - stock uses base 0. Force the stock layout.
deviceinfo_bootimg_custom_args="--base 0x00000000 --kernel_offset 0x00008000 --ramdisk_offset 0x01000000 --tags_offset 0x00000100 --dtb_offset 0x01f00000 --os_version 16.0.0 --os_patch_level 2026-08"
deviceinfo_header_version="2"
deviceinfo_append_dtb="true"
deviceinfo_flash_offset_base="0x00000000"
deviceinfo_flash_offset_kernel="0x00008000"
deviceinfo_flash_offset_ramdisk="0x01000000"
deviceinfo_flash_offset_second="0x00000000"
deviceinfo_flash_offset_tags="0x00000100"
deviceinfo_flash_offset_dtb="0x01f00000"
deviceinfo_bootimg_qcdt="false"
deviceinfo_header_version="4"
deviceinfo_flash_pagesize="4096"
# Stock uses base 0 (mkbootimg defaults to 0x10000000, dtb ends up outside
# RAM). os_* to satisfy anti-rollback on the boot image.
deviceinfo_bootimg_custom_args="--base 0x00000000 --kernel_offset 0x00008000 --ramdisk_offset 0x01000000 --tags_offset 0x00000100 --dtb_offset 0x01f00000 --os_version 16.0.0 --os_patch_level 2026-08-01"
deviceinfo_flash_sparse="true"
# UFS storage
@@ -0,0 +1,15 @@
#!/bin/sh
# Corre en la etapa /hooks-cleanup de init_2nd.sh, justo antes de
# "Switching root" (el ultimo punto de control antes de pasarle el arbol
# de procesos al systemd de la rootfs real).
#
# El dropbear que arranca initfs-hook.sh para el debug-shell (-p 22, en
# background con '&') NO muere solo en switch_root -- los procesos
# existentes sobreviven el pivote de root, solo cambia el filesystem raiz
# para los que arrancan DESPUES. Confirmado en hardware 2026-09-19: quedaba
# vivo (PID bajo, ej. 235) pegado al puerto 22 para siempre, y
# sshd.service de la rootfs real nunca podia bindear ("Address in use",
# reintentaba 5 veces y se rendia con start-limit-hit) -- el sistema
# arrancaba perfecto por debajo (Multi-User System, Graphical Interface
# alcanzados en ~48s), pero SSH quedaba inutilizable siempre.
pkill dropbear 2>/dev/null || true
@@ -0,0 +1,70 @@
#!/bin/sh
# Arranca dropbear (SSH) en el debug-shell, ademas del telnetd que ya trae
# postmarketos-mkinitfs-hook-debug-shell. Ningun paquete oficial de
# postmarketOS declara dropbear para el initramfs (solo para el rootfs real,
# via postmarketos-base-downstream, que es otra cosa) -- se agrega aca a
# mano porque hace falta poder scriptear el debug-shell (heredocs, comandos
# no interactivos por SSH), cosa que telnet no permite bien.
#
# Corre via run_hooks() en init_2nd.sh, DESPUES de setup_usb_network +
# start_unudhcpd (la red ya esta arriba en este punto) y ANTES de
# debug_shell(). Ver init_functions.sh:run_hooks (invoca cada hook con
# 'sh "$hook"', no hace falta que sea ejecutable).
# Panel NT37705 (2026-09-25): el ABL deja el subsistema de display encendido
# (mdss_gdsc on) y el primer encendido de msm sobre ese estado no logra
# arrancar el panel (sin TE, lecturas DCS -61). Probado en hardware: ni un
# ciclo de alimentacion del panel ni uno del host DSI/PHY alcanzan; lo unico
# que lo arranca es apagar mdss_gdsc (reinicia todo el hardware de display),
# y eso pasa con un blank/unblank del framebuffer. Se hace aca, antes del
# debug-shell, para que la consola ya se vea. El estado de mdss_gdsc durante
# el blank queda en dmesg para confirmar que el dominio se apago.
if [ -e /sys/class/graphics/fb0/blank ]; then
mount -t debugfs none /sys/kernel/debug 2>/dev/null
echo 1 > /sys/class/graphics/fb0/blank
sleep 1
gdsc=$(grep '^mdss_gdsc ' /sys/kernel/debug/pm_genpd/pm_genpd_summary 2>/dev/null)
echo "rtwo-panel: mdss_gdsc durante blank: ${gdsc:-desconocido}" > /dev/kmsg
echo 0 > /sys/class/graphics/fb0/blank
fi
# Este initramfs no trae /etc/passwd ni /etc/shadow (confirmado: ni
# siquiera una entrada minima de root) -- sin esto dropbear no puede
# resolver el home de root para buscar authorized_keys, y 'passwd root'
# falla con "unknown uid 0". El shadow con el campo de clave vacio entre
# los primeros dos ':' permite login sin contrasena (para cuando -B mas
# abajo si esta soportado por este build de dropbear).
if [ ! -f /etc/passwd ]; then
cat > /etc/passwd <<'PASSWD'
root:x:0:0:root:/root:/bin/sh
PASSWD
fi
if [ ! -f /etc/group ]; then
cat > /etc/group <<'GROUP'
root:x:0:
GROUP
fi
if [ ! -f /etc/shadow ]; then
cat > /etc/shadow <<'SHADOW'
root::10933:0:99999:7:::
SHADOW
chmod 600 /etc/shadow
fi
mkdir -p /etc/dropbear
mkdir -p /root/.ssh
cat > /root/.ssh/authorized_keys <<'KEYS'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPpCHmYjP+h7qod3HLcAU55A05c/ZDSkzKEXhU8OpJZG rtwo-debug-shell
KEYS
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
# -R: genera las claves de host al vuelo (el initramfs es tmpfs, no
# persisten entre arranques, no tiene sentido guardarlas)
# -B: tambien permite login con clave en blanco, por si /etc/passwd trae
# root sin contrasena (mismo modelo de acceso que telnetd -l
# pmos_getty, que da shell sin ninguna autenticacion) -- si el build de
# dropbear no tiene compilado el soporte de blank-password esta opcion
# simplemente no hace nada, la clave publica de arriba sigue funcionando
# -E: loguea a stderr/consola, para poder ver por que no conecta si falla
/usr/sbin/dropbear -R -B -E -p 22 &
@@ -0,0 +1,6 @@
# rtwo (2026-09-25): el boton de encendido (pon_pwrkey, KEY_POWER) se usa para
# ocultar el dashboard de la consola y todavia no hay suspension probada en
# este equipo. Sin esto, systemd-logind apaga el telefono al tocarlo.
[Login]
HandlePowerKey=ignore
HandlePowerKeyLongPress=poweroff
@@ -7,9 +7,30 @@ spi-geni-qcom
goodix-berlin-core
goodix-berlin-spi
# USB gadget path (usually built-in, harmless if already so)
# USB keyboard (via &usb_1 host mode + a hub). CONFIG_HID/CONFIG_USB_HID
# are built-in, but CONFIG_HID_GENERIC=m is not -- without it, usbcore
# enumerates the keyboard fine (visible in dmesg) but no input device is
# ever created, so no keypress does anything. 2026-09-22: keyboard showed
# as "connected" but keys didn't work, this was the missing piece.
hid-generic
# USB gadget path. dwc3 + dwc3-qcom are built-in; the eUSB2 PHY and its
# PMIC repeater are modules and MUST be in the initramfs, otherwise
# usb@a600000 sits on -EPROBE_DEFER forever and no UDC appears.
# NOTE: in kernel 7.2 the SNPS eUSB2 PHY driver is 'phy-snps-eusb2'
# (CONFIG_PHY_SNPS_EUSB2), not the old 'phy-qcom-snps-eusb2'.
phy-snps-eusb2
phy-qcom-eusb2-repeater
phy-qcom-snps-eusb2
# Root filesystem lives on UFS (1d84000.ufshc) inside the dynamic 'super'
# partition. ufs-qcom and its QMP PHY are modules in this config, so without
# them here the initramfs never sees a root device and the kernel panics with
# "VFS: Unable to mount root fs on unknown-block(0,0)". qcom_ice is ufs-qcom's
# only modules.dep dependency; listed explicitly rather than relying on
# mkinitfs to pull it in.
ufs-qcom
qcom_ice
phy-qcom-qmp-ufs
# Panel / DRM are not wired up yet:
# panel driver for Novatek NT37705 (1080x2400 DSC cmd, reset TLMM133)
@@ -0,0 +1,13 @@
[Unit]
Description=Revive the NT37705 panel if it came up dead from the ABL handoff
# Antes de cualquier cliente DRM/fbcon interactivo: con un DRM master activo
# el blank de fbdev no hace nada (ver rtwo-panel-revive.sh).
Before=rtwo-dashboard.service getty@tty1.service display-manager.service
[Service]
Type=oneshot
ExecStart=/usr/libexec/rtwo-panel-revive
TimeoutStartSec=60
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,58 @@
#!/bin/sh
# Panel NT37705: el ABL deja el subsistema de display encendido (mdss_gdsc on)
# y a veces el primer encendido de msm sobre ese estado no arranca el panel
# (lecturas DCS -61, pantalla negra). Lo unico que lo revive es apagar
# mdss_gdsc, cosa que pasa con un blank/unblank del framebuffer.
#
# El initfs-hook hace lo mismo, pero msm suele enganchar el display recien
# despues del switch_root (no hay fb0 en el initramfs), asi que aca se
# repite desde el rootfs. Solo actua si el panel esta muerto: con el panel
# vivo actual_brightness devuelve el valor; muerto, falla con ENODATA (la
# lectura DCS del driver da -61).
#
# Tiene que correr antes que cualquier cliente DRM (cage): con un DRM
# master activo el blank de fbdev no hace nada.
BL=/sys/class/backlight/ae94000.dsi.0
FB=/sys/class/graphics/fb0
log() {
echo "rtwo-panel: $*" > /dev/kmsg
}
panel_alive() {
cat "$BL/actual_brightness" > /dev/null 2>&1
}
i=0
while [ ! -e "$BL/actual_brightness" ] || [ ! -e "$FB/blank" ]; do
i=$((i + 1))
if [ "$i" -gt 60 ]; then
log "sin backlight/fb0 tras 30 s, nada que hacer"
exit 0
fi
sleep 0.5
done
# Dar tiempo al primer prepare/enable del panel.
sleep 1
if panel_alive; then
log "panel vivo, sin cambios"
exit 0
fi
for try in 1 2 3; do
log "panel muerto, blank/unblank (intento $try)"
echo 1 > "$FB/blank"
sleep 1
echo 0 > "$FB/blank"
sleep 1
if panel_alive; then
log "panel revivido en el intento $try"
exit 0
fi
done
log "el panel sigue muerto tras 3 intentos"
exit 1
@@ -5,7 +5,7 @@ maintainer="snlavaise <snlavaise@gmail.com>"
_flavor="postmarketos-qcom-sm8550"
pkgname=linux-$_flavor
pkgver=7.2
pkgrel=6
pkgrel=48
pkgdesc="Mainline kernel for Qualcomm SM8550 (Snapdragon 8 Gen 2) + Motorola rtwo"
arch="aarch64"
_carch="arm64"
@@ -47,6 +47,16 @@ source="
config-motorola-rtwo.aarch64
rtwo.config
sm8550-motorola-rtwo.dts
ath12k-fix-regd-update-completion-hang.patch
ath12k-fix-regd-range-wideband.patch
icc-sm8550-keepalive-mm0-mdp-fetch.patch
ath12k-htc-log-every-rx-header.patch
ath12k-mhi-keep-awake.patch
msm-dsi-keep-panel-dsc-config.patch
msm-dp-ignore-invalid-lttpr-caps.patch
msm-dp-no-push-idle-when-link-down.patch
panel-novatek-nt37705.c
typec-mux-ps5169.c
"
builddir="$srcdir/linux-${pkgver/_/-}"
@@ -60,6 +70,55 @@ prepare() {
sed -i '/sm8550-mtp\.dtb/a dtb-$(CONFIG_ARCH_QCOM) += sm8550-motorola-rtwo.dtb' \
"$_dtsdir/Makefile"
fi
# Not-yet-upstream panel driver (DSI command mode + DSC, Motorola rtwo).
cp "$srcdir/panel-novatek-nt37705.c" \
drivers/gpu/drm/panel/panel-novatek-nt37705.c
if ! grep -q "CONFIG_DRM_PANEL_NOVATEK_NT37705" drivers/gpu/drm/panel/Kconfig; then
cat >> drivers/gpu/drm/panel/Kconfig <<'EOK'
config DRM_PANEL_NOVATEK_NT37705
tristate "Novatek NT37705 DSI panel"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
select DRM_DISPLAY_DSC_HELPER
select DRM_DISPLAY_HELPER
help
Say Y here if you want to enable support for Novatek NT37705 DSI
panel module. 1080x2400 command mode DSC panel from CSOT, used on
the Motorola rtwo.
EOK
fi
if ! grep -q "panel-novatek-nt37705" drivers/gpu/drm/panel/Makefile; then
echo 'obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37705) += panel-novatek-nt37705.o' \
>> drivers/gpu/drm/panel/Makefile
fi
# Not-yet-upstream Parade PS5169 USB-C redriver (DisplayPort alt mode).
cp "$srcdir/typec-mux-ps5169.c" drivers/usb/typec/mux/ps5169.c
if ! grep -q "CONFIG_TYPEC_MUX_PS5169" drivers/usb/typec/mux/Kconfig; then
cat >> drivers/usb/typec/mux/Kconfig <<'EOK'
config TYPEC_MUX_PS5169
tristate "Parade PS5169 Type-C redriver driver"
depends on I2C
depends on DRM || DRM=n
select DRM_AUX_BRIDGE if DRM_BRIDGE && OF
select REGMAP_I2C
help
Say Y or M if your system has a Parade PS5169 USB/DP Type-C
redriver, as found on the Motorola rtwo.
EOK
fi
if ! grep -q "ps5169" drivers/usb/typec/mux/Makefile; then
echo 'obj-$(CONFIG_TYPEC_MUX_PS5169) += ps5169.o' \
>> drivers/usb/typec/mux/Makefile
fi
# Build this DTB with -@ so it gets a /__symbols__ node. The Motorola
# SM8550 ABL runs its dtbo-apply path (libufdt) against the base DTB
# and drops to the bootloader menu if the symbols are missing.
if ! grep -q "DTC_FLAGS_sm8550-motorola-rtwo" "$_dtsdir/Makefile"; then
echo 'DTC_FLAGS_sm8550-motorola-rtwo := -@' >> "$_dtsdir/Makefile"
fi
# Base config (from linux-postmarketos-mainline) + rtwo bring-up fragment.
cp "$srcdir/config-motorola-rtwo.aarch64" .config
@@ -74,9 +133,9 @@ build() {
}
package() {
# The Motorola bootloader boots a *raw* uncompressed arm64 Image
# (BOARD_KERNEL_IMAGE_NAME := Image); Image.gz / vmlinuz.efi make it
# reset in <2s. CONFIG_EFI_ZBOOT is off in rtwo.config.
# Raw uncompressed Image for the Android boot.img: the stock rtwo
# LineageOS boot.img carries a raw 'MZ' Image (BOARD_KERNEL_IMAGE_NAME
# := Image), and CONFIG_EFI_ZBOOT is off in rtwo.config.
install -Dm644 "$builddir/arch/$_carch/boot/Image" \
"$pkgdir/boot/vmlinuz"
@@ -87,26 +146,12 @@ package() {
INSTALL_MOD_STRIP=1 \
INSTALL_DTBS_PATH="$pkgdir"/boot/dtbs
# Android DT-overlay image with a single no-op overlay. The bootloader
# would otherwise apply the stock kalama dtbo overlay onto our mainline
# base DTB - the phandles don't match, the merged tree is corrupt and
# the kernel panics. A no-op overlay keeps the base DTB intact. (A
# zero-entry table fails Motorola's pre-flash validation.)
# All-zero dtbo image (full partition size), same trick as
# device-motorola-dubai: the bootloader finds no valid DT-overlay
# table magic and applies nothing, so the stock kalama overlay does
# not get merged onto our mainline base DTB.
mkdir -p "$pkgdir"/boot
printf '/dts-v1/;\n/plugin/;\n/ { };\n' > "$srcdir"/empty-overlay.dts
scripts/dtc/dtc -@ -O dtb -o "$srcdir"/empty.dtbo "$srcdir"/empty-overlay.dts
python3 - "$srcdir/empty.dtbo" "$pkgdir/boot/dtbo.img" <<-'EOF'
import struct, sys
fdt = open(sys.argv[1], "rb").read()
hdr_sz = ent_sz = 32
ent_off = hdr_sz + ent_sz
hdr = struct.pack(">IIIIIIII", 0xd7b7ab1e, ent_off + len(fdt),
hdr_sz, ent_sz, 1, hdr_sz, 2048, 0)
ent = struct.pack(">IIIIIIII", len(fdt), ent_off, 0, 0, 0, 0, 0, 0)
blob = hdr + ent + fdt
blob += b"\x00" * ((4096 - len(blob) % 4096) % 4096)
open(sys.argv[2], "wb").write(blob)
EOF
dd if=/dev/zero of="$pkgdir/boot/dtbo.img" bs=1M count=24 status=none
rm -f "$pkgdir"/usr/lib/modules/*/build "$pkgdir"/usr/lib/modules/*/source
@@ -118,7 +163,17 @@ package() {
# (same tarball as linux-postmarketos-mainline, ~150 MB download).
sha512sums="
52d5aa7f94f6aa6141debf4ceb4fc7a2e75505a7cd38b8854e501be83c2b57863020f463f2fdb2fc93667e6cfe13274d077e5207a611e4797bd9404ee0e64c58 linux-postmarketos-qcom-sm8550-7.2.tar.gz
f9ae52489f6b9e6fef64952943cd09732a5bef90a0e8765db03c256483028368687d6d5a1d80dd813a513e7e80d91f8b47f49b91cff67266b76f1d6275a7fea0 config-motorola-rtwo.aarch64
53441c23146d76bb0eaa358a25f6cce4f21740cfb65b755df52bbb218daf2cda30b3d80fa6ca4e283ea1afe2b0fa708307b678b421898100710b8fb99b08d6c5 rtwo.config
740ac374913a1304d6b52048d89e14321952f0357d5bfd3c1305f76570fda6c1233b963ae77cc9631c60cc590885ed3c0877b19ad4cd65e66c5c441a94cd610b sm8550-motorola-rtwo.dts
0ab844d874289be5529c7f83a1bdd16d867ea6d5f3f873556779cbd27908316fd5076c201d6b6fee40991da648eadc982422cc0136fe2c9034b7b4846e08a028 config-motorola-rtwo.aarch64
a03843765d996a4555f991b277dd9cc35f1c96da1b740330a3497202d355ec95406f380a0ec6a778d638bf9acfc1f28dbc441e156fb75b1cf00720af136f69b8 rtwo.config
ff2da1726b87210497d3d3961af5d95b9dfb160d48af90bb604dbb321b89c50689a06722c50db265aaf5c95b0078da278939efda8e1a8f30232827d27da1c8ca sm8550-motorola-rtwo.dts
a068e35a031858161e7a5ac00d7d2db9fd0d842fc19d3ebf08b03275ef470eff5462921e36390f535e2556619b6911192b6e2ccf7c313edafaf22d6da44c1d8e ath12k-fix-regd-update-completion-hang.patch
891b9b15e9113488667f2dcf9f1502aab24291228cc4096f358acbf4b67d299fb90b2d00b4e103139cd2160339a10833cddb781838d1f03721fef07ea45b3213 ath12k-fix-regd-range-wideband.patch
acbe599c08877065ea4419363da7ab75a16a557aad4b13161f22270f03b27efac1be487884896900c3e80d6df8e3545ca6b1df940d24badef1825fe0d27147ac icc-sm8550-keepalive-mm0-mdp-fetch.patch
cf4889c4dca8d3bac7544d71284e6b592d6014d9ad94badfc6d2cbe56a8402c61e4c0811c2f48d3352b446de9fbcf36b58226431c9276956d7ff3fe08c9eb589 ath12k-htc-log-every-rx-header.patch
6e01370d013278faba203e57ba20700d500639136672c040c0924a600fc3dcd8185a9f558ee7bf8a9ad5412113f6b6ab1901423006bb839e808dbcb151a16383 ath12k-mhi-keep-awake.patch
7673bec5661e692721bce41e72a330180e653d2b1b0a5cb3d36bdc869ad4a32ce23a6048d01c9cf2cf5c78c2a7e33899a0a149c65e9754d9d1c2cd71b542b84c msm-dsi-keep-panel-dsc-config.patch
3268835bcb539a77d16c888d933c856df6bb591861d30c7c61c6e0ab70751bac6248961ec30fedf9a62bebdcd63113cf1dd14e58a1b53262493d5e0e90dbbe3f msm-dp-ignore-invalid-lttpr-caps.patch
4f5ea9bfe01a6df166fdad601f6bd74d4b39049be924e8b4df58a8acbba7e9ebfc568bd81df70354209642f68e113804b9d1b62cbd3c83c319fae5fefcf07595 msm-dp-no-push-idle-when-link-down.patch
e8df16de9b413fedd0d36414311216e68e69363e1bfbba8c7b892120f860d217b58ceeffeb92566a5617177b0f190a64e9e2284d6d4285e72db35cf40770e41a panel-novatek-nt37705.c
dd95e80f62879d5167e7bfc7d74b4a64669a43dbf5aacb6a13e62d1ed4a88677ef1042f8cecc46e05e3d12ba75f070a5927df308c90fe846af93b78c6b310e80 typec-mux-ps5169.c
"
@@ -0,0 +1,41 @@
Fix regulatory range for wideband radios (carried by OpenWrt/Quectel
trees for WCN7850 and wideband QCN9274; NOT in v7.2 mainline).
ath12k_regd_update() assumes that a radio advertising 6 GHz cannot also
operate on 5 GHz. For the rtwo the WCN7850 is a single SMM radio whose
5 GHz band spans [4900-7125] (hw_mode 5, 2.4+5+6 GHz in one pdev), so
this old check picks only the 6 GHz portion of the band and drops the
real 5 GHz half from ar->freq_range. ath12k_mac_update_freq_range()
unions multiple calls, so the fixed conditions do not overwrite -- they
extend the advertised range to cover both portions.
Diagnostic build 2026-09-22 (same experiment as the wcn7850-pmu pinctrl
DT change): determines whether the cropped regulatory range sent to the
WCN7850 WMI path is what stalls the firmware at ath12k_mac_start()
("wmi command 16387 timeout", -EAGAIN on insufficient HTC credits after
the WMI channel list).
Blame/author: Robert Marko <robert.marko@sartura.hr>
From: https://git-03.infra.openwrt.org/openwrt/staging/stintel/plain/package/kernel/mac80211/patches/ath12k/104-wifi-ath12k-fix-regulatory-range-for-wideband-radios.patch
--- a/drivers/net/wireless/ath/ath12k/reg.c
+++ b/drivers/net/wireless/ath/ath12k/reg.c
@@ -310,7 +310,8 @@ int ath12k_regd_update(struct ath12k *ar, bool init)
ath12k_mac_update_freq_range(ar, freq_low, freq_high);
}
- if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP && !ar->supports_6ghz) {
+ if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP &&
+ reg_cap->low_5ghz_chan < ATH12K_MIN_6GHZ_FREQ) {
if (ab->hw_params->single_pdev_only) {
phy_id = ar->pdev->cap.band[WMI_HOST_WLAN_5GHZ_CAP].phy_id;
reg_cap = &ab->hal_reg_cap[phy_id];
@@ -322,7 +323,8 @@ int ath12k_regd_update(struct ath12k *ar, bool init)
ath12k_mac_update_freq_range(ar, freq_low, freq_high);
}
- if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP && ar->supports_6ghz) {
+ if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP &&
+ reg_cap->high_5ghz_chan >= ATH12K_MIN_6GHZ_FREQ) {
freq_low = max(reg_cap->low_5ghz_chan, ab->reg_freq_6ghz.start_freq);
freq_high = min(reg_cap->high_5ghz_chan, ab->reg_freq_6ghz.end_freq);
@@ -0,0 +1,26 @@
--- a/drivers/net/wireless/ath/ath12k/wmi.c 2026-09-20 02:48:13.191255011 -0300
+++ b/drivers/net/wireless/ath/ath12k/wmi.c 2026-09-20 02:48:13.192653745 -0300
@@ -6939,6 +6939,15 @@
goto mem_free;
}
+ /* Assign pdev_idx as soon as we have a valid reg_info, BEFORE the
+ * FALLBACK/DROP paths below, so ar->regd_update_completed still gets
+ * signalled even when the event is dropped because the country code
+ * already matches the default regd (fixes a permanent hang of any
+ * caller waiting on regd_update_completed, e.g. WMI_PDEV_SET_PARAM
+ * during mac80211 hw_register on bring-up).
+ */
+ pdev_idx = reg_info->phy_id;
+
ret = ath12k_reg_validate_reg_info(ab, reg_info);
if (ret == ATH12K_REG_STATUS_FALLBACK) {
ath12k_warn(ab, "failed to validate reg info %d\n", ret);
@@ -6955,7 +6964,6 @@
}
/* free old reg_info if it exist */
- pdev_idx = reg_info->phy_id;
if (ab->reg_info[pdev_idx]) {
ath12k_reg_reset_reg_info(ab->reg_info[pdev_idx]);
kfree(ab->reg_info[pdev_idx]);
@@ -0,0 +1,39 @@
Log the raw HTC header of every received message.
Diagnostic-only, for the rtwo WiFi bring-up. ath12k_mac_start() dies on
the second WMI command with -EAGAIN out of ath12k_htc_send()'s
"insufficient credits" path, and the endpoint's credits are only ever
replenished by an ATH12K_HTC_RECORD_CREDITS record carried in the
*trailer* of a received HTC message -- there is no dedicated credit
event. /proc/interrupts shows ce2 (target->host WMI) taking real
interrupts, so messages do arrive; what cannot be seen from outside is
whether any of them ever carries a trailer at all.
The existing "htc rx completion ep %d" debug line does not answer that:
it is printed at the very end of the handler, so it misses both the EP0
control traffic and the "zero length packet with trailer data, just drop
these" early return -- which is exactly the shape a pure credit-report
message has. Print the decoded header right where the fields are read
instead, before anything can return early.
Sits under the existing ATH12K_DBG_HTC (0x4) mask, so it costs one
branch per RX when the mask is unset and disappears entirely without
CONFIG_ATH12K_DEBUG. Drop this patch once WiFi works.
--- a/drivers/net/wireless/ath/ath12k/htc.c
+++ b/drivers/net/wireless/ath/ath12k/htc.c
@@ -292,6 +292,14 @@
trailer_present = le32_get_bits(hdr->htc_info, HTC_HDR_FLAGS) &
ATH12K_HTC_FLAG_TRAILER_PRESENT;
+ ath12k_dbg(ab, ATH12K_DBG_HTC,
+ "htc rx hdr eid %d payload_len %d flags 0x%02x trailer %d trailer_len %d\n",
+ eid, payload_len,
+ (u8)le32_get_bits(hdr->htc_info, HTC_HDR_FLAGS),
+ trailer_present ? 1 : 0,
+ trailer_present ?
+ (int)le32_get_bits(hdr->ctrl_info, HTC_HDR_CONTROLBYTES0) : 0);
+
if (trailer_present) {
u8 *trailer;
@@ -0,0 +1,87 @@
Add ath12k.mhi_keep_awake to keep the WCN7850 out of MHI M1/M2.
Diagnostic for the rtwo WiFi bring-up. With CONFIG_ATH12K_DEBUG the log
shows that the firmware returns HTC credits for the WMI endpoint
normally (trailers on EP0, ~3 ms after each command) until the moment
the device reports M1 and the host moves it to M2 (MHI_CB_IDLE, 22 us
after the last credit is spent). After that the target sends nothing on
any endpoint and there is no RDDM/SYS_ERR, so it is asleep, not crashed.
WCN7850 uses shadow registers for the CE doorbells (below
ACCESS_ALWAYS_OFF), so those writes never go through pci_ops->wakeup().
mhi_keep_awake=1 takes an MHI device wake reference right after power
up and holds it until mhi_stop, so the device stays in M0. With it
off, MHI_CB_IDLE is logged at warn level to see when it happens.
--- a/drivers/net/wireless/ath/ath12k/pci.h 2026-09-22 20:54:12.225709859 -0300
+++ b/drivers/net/wireless/ath/ath12k/pci.h 2026-09-22 20:54:12.243985679 -0300
@@ -93,6 +93,7 @@
ATH12K_PCI_FLAG_IS_MSI_64,
ATH12K_PCI_ASPM_RESTORE,
ATH12K_PCI_FLAG_MULTI_MSI_VECTORS,
+ ATH12K_PCI_FLAG_MHI_KEEP_AWAKE,
};
struct ath12k_pci_ops {
--- a/drivers/net/wireless/ath/ath12k/mhi.c 2026-09-22 20:54:12.223301456 -0300
+++ b/drivers/net/wireless/ath/ath12k/mhi.c 2026-09-22 20:54:12.244083641 -0300
@@ -18,6 +18,16 @@
#define OTP_VALID_DUALMAC_BOARD_ID_MASK 0x1000
#define MHI_CB_INVALID 0xff
+/* rtwo bring-up: the WMI endpoint runs out of HTC credits at the exact
+ * moment the device reports M1 and the host moves it to M2
+ * (MHI_CB_IDLE); after that the target never sends anything again.
+ * Holding MHI device wake for the whole session keeps it in M0, to
+ * tell whether the M1/M2 transition is what loses the WMI doorbells.
+ */
+static bool ath12k_mhi_keep_awake;
+module_param_named(mhi_keep_awake, ath12k_mhi_keep_awake, bool, 0644);
+MODULE_PARM_DESC(mhi_keep_awake, "Hold MHI device wake while powered on (never enter M1/M2)");
+
void ath12k_mhi_set_mhictrl_reset(struct ath12k_base *ab)
{
u32 val;
@@ -148,6 +158,11 @@
case MHI_CB_SYS_ERROR:
ath12k_warn(ab, "firmware crashed: MHI_CB_SYS_ERROR\n");
break;
+ case MHI_CB_IDLE:
+ /* rtwo: log at warn level so it shows up without debug_mask */
+ ath12k_warn(ab, "mhi idle: device moved to M2 (keep_awake %d)\n",
+ test_bit(ATH12K_PCI_FLAG_MHI_KEEP_AWAKE, &ab_pci->flags));
+ break;
case MHI_CB_EE_RDDM:
if (ab_pci->mhi_pre_cb == MHI_CB_EE_RDDM) {
ath12k_dbg(ab, ATH12K_DBG_BOOT,
@@ -493,6 +508,20 @@
if (ret)
goto out;
+ if (ath12k_mhi_keep_awake) {
+ ret = mhi_device_get_sync(ab_pci->mhi_ctrl->mhi_dev);
+ if (ret) {
+ ath12k_warn(ab_pci->ab,
+ "mhi_keep_awake: failed to hold device wake: %d\n",
+ ret);
+ ret = 0;
+ } else {
+ set_bit(ATH12K_PCI_FLAG_MHI_KEEP_AWAKE, &ab_pci->flags);
+ ath12k_info(ab_pci->ab,
+ "mhi_keep_awake: holding device wake, M1/M2 disabled\n");
+ }
+ }
+
return 0;
out:
@@ -501,6 +530,9 @@
void ath12k_mhi_stop(struct ath12k_pci *ab_pci, bool is_suspend)
{
+ if (test_and_clear_bit(ATH12K_PCI_FLAG_MHI_KEEP_AWAKE, &ab_pci->flags))
+ mhi_device_put(ab_pci->mhi_ctrl->mhi_dev);
+
/* During suspend we need to use mhi_power_down_keep_dev()
* workaround, otherwise ath12k_core_resume() will timeout
* during resume.
@@ -5464,7 +5464,7 @@ CONFIG_BCM7038_WDT=m
#
# CONFIG_USBPCWATCHDOG is not set
# CONFIG_KEEMBAY_WATCHDOG is not set
# CONFIG_GUNYAH_WATCHDOG is not set
CONFIG_GUNYAH_WATCHDOG=y
CONFIG_SSB_POSSIBLE=y
# CONFIG_SSB is not set
CONFIG_BCMA_POSSIBLE=y
@@ -11732,10 +11732,10 @@ CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3
CONFIG_PSTORE=y
CONFIG_PSTORE_DEFAULT_KMSG_BYTES=10240
CONFIG_PSTORE_COMPRESS=y
# CONFIG_PSTORE_CONSOLE is not set
CONFIG_PSTORE_CONSOLE=y
# CONFIG_PSTORE_PMSG is not set
# CONFIG_PSTORE_FTRACE is not set
CONFIG_PSTORE_RAM=m
CONFIG_PSTORE_RAM=y
# CONFIG_PSTORE_BLK is not set
# CONFIG_UFS_FS is not set
CONFIG_EROFS_FS=m
@@ -0,0 +1,25 @@
--- a/drivers/interconnect/qcom/sm8550.c
+++ b/drivers/interconnect/qcom/sm8550.c
@@ -1136,6 +1136,22 @@ static struct qcom_icc_bcm bcm_mc0 = {
static struct qcom_icc_bcm bcm_mm0 = {
.name = "MM0",
+ /*
+ * qns_mem_noc_hf is the only node this BCM covers, and it's what
+ * qnm_mdp (the MDP's own interconnect master) feeds into. Our
+ * bring-up has no MDSS/DPU driver, so nothing in Linux ever calls
+ * icc_set_bw() on this path -- but the DPU hardware is still live,
+ * inherited from U-Boot, and keeps fetching the framebuffer over
+ * this exact bus. Without keepalive, icc_sync_state() (core.c,
+ * gated on every icc provider probing -- in practice on pcie0/
+ * WiFi finishing last) zeroes the firmware's boot-time vote on
+ * this BCM and the display freezes mid-boot, at a random-looking
+ * point that always lines up with pcie0 finishing probe. MC0/SH0/
+ * CN0/SN0/QUP0-2 already get this same treatment for the same
+ * reason (DDR/LLCC/config/system NoC/QUP buses that must never
+ * collapse to zero just because no driver claimed them yet).
+ */
+ .keepalive = true,
.num_nodes = 1,
.nodes = { &qns_mem_noc_hf },
};
@@ -0,0 +1,54 @@
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -251,16 +251,34 @@
int rc, lttpr_count;
if (drm_dp_read_lttpr_common_caps(dp->aux, dpcd, dp->link->lttpr_common_caps))
- return 0;
+ goto no_lttpr;
+
+ /*
+ * Some sinks without LTTPR support (e.g. DP->HDMI converters) mirror
+ * their receiver caps at 0xf0000 instead of NAKing the read. Only
+ * trust the block if it carries a valid LTTPR structure revision and
+ * repeater count: otherwise the caps would clamp the link to whatever
+ * lands in the "max lane count"/"max link rate" slots, and writing
+ * PHY_REPEATER_MODE would end up in the sink's own DPCD.
+ */
+ if (dp->link->lttpr_common_caps[0] < 0x14)
+ goto no_lttpr;
lttpr_count = drm_dp_lttpr_count(dp->link->lttpr_common_caps);
+ if (lttpr_count < 0)
+ goto no_lttpr;
+
rc = drm_dp_lttpr_init(dp->aux, lttpr_count);
if (rc) {
DRM_ERROR("failed to set LTTPRs transparency mode, rc=%d\n", rc);
- return 0;
+ goto no_lttpr;
}
return lttpr_count;
+
+no_lttpr:
+ memset(dp->link->lttpr_common_caps, 0, sizeof(dp->link->lttpr_common_caps));
+ return 0;
}
static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp)
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -101,6 +101,12 @@
msm_dp_panel->vsc_sdp_supported = drm_dp_vsc_sdp_supported(panel->aux, dpcd);
link_info = &msm_dp_panel->link_info;
+ /*
+ * Everything below is derived from the current sink. Start from
+ * scratch so a rate from a previous sink (the fallback path only runs
+ * when rate is 0) does not stick across unplug/replug.
+ */
+ memset(link_info, 0, sizeof(*link_info));
link_info->revision = dpcd[DP_DPCD_REV];
major = (link_info->revision >> 4) & 0x0f;
minor = link_info->revision & 0x0f;
@@ -0,0 +1,18 @@
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -1434,6 +1434,15 @@
msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display);
+ /*
+ * If link training failed in atomic_enable, the mainlink was already
+ * deinitialized (link clocks and PHY off) and the stream never came
+ * up. Writing DP_STATE_CTRL then hits an unclocked block, which hangs
+ * the bus and resets the SoC on unplug or the next modeset.
+ */
+ if (!dp->power_on)
+ return;
+
msm_dp_ctrl_push_idle(msm_dp_display->ctrl);
}
@@ -0,0 +1,18 @@
--- a/drivers/gpu/drm/msm/dsi/dsi_host.c
+++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
@@ -1870,6 +1870,15 @@
{
int ret;
+ /*
+ * Some panels (e.g. Novatek DDICs with fixed rate control tables in
+ * the decoder) need the exact RC configuration the vendor encoder
+ * used, not the one derived here from the generic DSC tables. Keep a
+ * configuration the panel driver already filled in completely.
+ */
+ if (dsc->rc_model_size && dsc->slice_chunk_size && dsc->initial_dec_delay)
+ return 0;
+
if (dsc->bits_per_pixel & 0xf) {
DRM_DEV_ERROR(&msm_host->pdev->dev, "DSI does not support fractional bits_per_pixel\n");
return -EINVAL;
@@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2026 snlavaise <snlavaise@gmail.com>
*
* DRM panel driver for the CSOT NT37705 (Novatek NT37705) command mode DSC
* panel used on the Motorola rtwo (Moto Edge 40 Pro, SM8550).
*
* Sequences transcribed byte-by-byte from the vendor device tree node
* "qcom,mdss_dsi_mot_csot_nt37705_667_curve_1080x2400_dsc_cmd_v1"
* (panel name mipi_mot_cmd_csot_1080p_dsc_667, panel supplier "csot"),
* see pmos/panel-nt37705-v1-dump.md. Only the 60 Hz timing (timing@0, 2f01 +
* e000) is exposed for the first bring-up. The other rates only differ in
* their timing-switch-command: 120 Hz 2f02/e041, 90 Hz 2f04/e000,
* 48 Hz 2f05/e000, 165 Hz 2f03/e041 (same porches and DSI clock).
*
* DSC: besides the Novatek MCS setup in the on command (0x90 enable, 0x91
* parameters), the standard PPS and compression-mode packets are sent after
* it, same as the mainline NT37801 driver (same Novatek flow) and the
* downstream driver, which sends the PPS by itself.
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
#include <drm/display/drm_dsc.h>
#include <drm/display/drm_dsc_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
struct nt37705_csot {
struct drm_panel panel;
struct mipi_dsi_device *dsi;
struct gpio_desc *reset_gpio;
struct regulator_bulk_data *supplies;
ktime_t off_since;
};
static const struct regulator_bulk_data nt37705_csot_supplies[] = {
{ .supply = "vddio" },
{ .supply = "vdd" },
{ .supply = "vci" },
};
static inline struct nt37705_csot *to_nt37705_csot(struct drm_panel *panel)
{
return container_of(panel, struct nt37705_csot, panel);
}
/*
* Vendor reset-sequence: <0 12 1 1 0 1 1 13> (value -> duration ms).
* Those are raw line levels (downstream uses gpio_set_value()), while
* reset-gpios is GPIO_ACTIVE_LOW here, so the logical values are inverted:
* assert 12 ms, release 1 ms, assert 1 ms, release 13 ms (ends released).
*/
static void nt37705_csot_reset(struct nt37705_csot *ctx)
{
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
usleep_range(12000, 13000);
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
usleep_range(1000, 2000);
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
usleep_range(1000, 2000);
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
usleep_range(13000, 14000);
}
/*
* Rate control for DSC 1.1, 10 bpc, 8 bpp as the Motorola/Qualcomm downstream
* driver sets it for panels flagged qcom,mdss-dsc-novateck-ic
* (sde_dsc_populate_dsc_config_nt(), DSC_V11_10BPC_8BPP entry of the *_nt
* tables). The DDIC decoder uses these fixed tables, which differ from the
* generic VESA/drm ones in 8 of the 15 ranges, so the host encoder must use
* them too; msm keeps a configuration filled in this completely (see
* msm-dsi-keep-panel-dsc-config.patch). Derived values reproduce the stock
* 0x91 bytes exactly.
*/
static const u8 nt37705_rc_min_qp[DSC_NUM_BUF_RANGES] = {
0, 4, 5, 5, 7, 7, 7, 7, 7, 8, 9, 9, 9, 12, 16
};
static const u8 nt37705_rc_max_qp[DSC_NUM_BUF_RANGES] = {
8, 8, 9, 10, 11, 11, 11, 12, 13, 14, 14, 15, 15, 16, 17
};
static const s8 nt37705_rc_bpg[DSC_NUM_BUF_RANGES] = {
2, 0, 0, -2, -4, -6, -8, -8, -8, -10, -10, -10, -12, -12, -12
};
static const u8 nt37705_rc_buf_thresh[DSC_NUM_BUF_RANGES - 1] = {
0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62,
0x69, 0x70, 0x77, 0x79, 0x7b, 0x7d, 0x7e
};
static void nt37705_csot_dsc_config_nt(struct drm_dsc_config *dsc)
{
int bpp = dsc->bits_per_pixel >> 4;
int bpc = dsc->bits_per_component;
int groups_per_line, groups_total, slice_bits;
int pre_mux_bits, mux_bits, final_scale, i;
dsc->rc_model_size = DSC_RC_MODEL_SIZE_CONST;
dsc->first_line_bpg_offset = 13;
dsc->rc_edge_factor = DSC_RC_EDGE_FACTOR_CONST;
dsc->rc_tgt_offset_high = DSC_RC_TGT_OFFSET_HI_CONST;
dsc->rc_tgt_offset_low = DSC_RC_TGT_OFFSET_LO_CONST;
dsc->simple_422 = false;
dsc->convert_rgb = true;
dsc->vbr_enable = false;
for (i = 0; i < DSC_NUM_BUF_RANGES - 1; i++)
dsc->rc_buf_thresh[i] = nt37705_rc_buf_thresh[i];
for (i = 0; i < DSC_NUM_BUF_RANGES; i++) {
dsc->rc_range_params[i].range_min_qp = nt37705_rc_min_qp[i];
dsc->rc_range_params[i].range_max_qp = nt37705_rc_max_qp[i];
dsc->rc_range_params[i].range_bpg_offset =
nt37705_rc_bpg[i] & DSC_RANGE_BPG_OFFSET_MASK;
}
dsc->rc_quant_incr_limit0 = 15;
dsc->rc_quant_incr_limit1 = 15;
dsc->initial_offset = 6144;
dsc->initial_xmit_delay = 512;
dsc->flatness_min_qp = 7;
dsc->flatness_max_qp = 16;
dsc->line_buf_depth = bpc + 1;
dsc->mux_word_size = DSC_MUX_WORD_SIZE_8_10_BPC;
groups_per_line = DIV_ROUND_UP(dsc->slice_width, 3);
dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width * bpp, 8);
dsc->initial_dec_delay = DIV_ROUND_UP(dsc->rc_model_size -
dsc->initial_offset +
dsc->initial_xmit_delay * bpp +
groups_per_line *
dsc->first_line_bpg_offset, bpp) -
dsc->initial_xmit_delay;
dsc->initial_scale_value = 8 * dsc->rc_model_size /
(dsc->rc_model_size - dsc->initial_offset);
slice_bits = 8 * dsc->slice_chunk_size * dsc->slice_height;
groups_total = groups_per_line * dsc->slice_height;
dsc->nfl_bpg_offset = DIV_ROUND_UP(dsc->first_line_bpg_offset * 2048,
dsc->slice_height - 1);
pre_mux_bits = 3 * (dsc->mux_word_size + (4 * bpc + 4) - 2);
mux_bits = pre_mux_bits - (dsc->mux_word_size -
((slice_bits - pre_mux_bits) % dsc->mux_word_size));
dsc->slice_bpg_offset = DIV_ROUND_UP(2048 * (dsc->rc_model_size -
dsc->initial_offset +
mux_bits), groups_total);
dsc->final_offset = dsc->rc_model_size -
dsc->initial_xmit_delay * bpp + mux_bits;
final_scale = 8 * dsc->rc_model_size /
(dsc->rc_model_size - dsc->final_offset);
dsc->scale_increment_interval = (2048 * dsc->final_offset) /
((final_scale - 9) * (dsc->nfl_bpg_offset +
dsc->slice_bpg_offset));
dsc->scale_decrement_interval = groups_per_line /
(dsc->initial_scale_value - 8);
}
/*
* Novatek 0x91: the panel-side DSC decoder parameters. The layout was checked
* field by field against the mainline NT37801 driver, whose hardcoded 0x91
* matches drm_dsc_compute_rc_parameters() exactly.
*
* Built from the config the host actually encodes with. With the Novatek
* rate control above it yields exactly the stock bytes
* (ab 28 00 0c d2 00 02 25 01 14 00 07 09 75 08 7a 10 f0).
*/
static void nt37705_csot_write_dsc_params(struct mipi_dsi_multi_context *dsi_ctx,
const struct drm_dsc_config *dsc)
{
const u8 buf[] = {
0x91,
(dsc->bits_per_component << 4) | dsc->line_buf_depth,
0x28, /* stock value, same on NT37801 (block pred, 8 bpp) */
dsc->slice_height >> 8, dsc->slice_height & 0xff,
(dsc->first_line_bpg_offset << 4) | 0x2, 0x00,
dsc->initial_dec_delay >> 8, dsc->initial_dec_delay & 0xff,
dsc->scale_increment_interval >> 8,
dsc->scale_increment_interval & 0xff,
dsc->scale_decrement_interval >> 8,
dsc->scale_decrement_interval & 0xff,
dsc->nfl_bpg_offset >> 8, dsc->nfl_bpg_offset & 0xff,
dsc->slice_bpg_offset >> 8, dsc->slice_bpg_offset & 0xff,
dsc->final_offset >> 8, dsc->final_offset & 0xff,
};
mipi_dsi_dcs_write_buffer_multi(dsi_ctx, buf, sizeof(buf));
}
/*
* Full 60 Hz on-command from timing@0 of the stock node, ending with
* sleep-out (100 ms) and display-on as in the vendor sequence.
*/
static int nt37705_csot_on(struct nt37705_csot *ctx)
{
struct mipi_dsi_device *dsi = ctx->dsi;
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0x00, 0x6a, 0x00, 0x10, 0x09, 0xb0, 0x10);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x07);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0x00, 0x6a, 0x00, 0x10, 0x00, 0x20, 0x10);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0e);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0x00, 0x4c, 0x00, 0x10, 0x00, 0x2c, 0x10);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0x00, 0x6a, 0x00, 0x10, 0x00, 0x20, 0x70);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0x4c, 0x00, 0x6a, 0x00, 0x6a);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x08);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0x41, 0x73, 0x73, 0x73, 0x83);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x00, 0x5c, 0xc4, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x10, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2, 0x41, 0x8d, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x07);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x37);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x08, 0x08, 0x08);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, 0x20);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x08);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xea, 0x22, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x08);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x21);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x83);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x03);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x08);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1e);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x0f);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x01);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x05);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc5, 0x15, 0x15, 0x15);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x19);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x2d);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x44);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1a);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x55);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x83);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x12);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x41);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x30);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x19);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf2, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x31);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x01, 0x09);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x15);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x01, 0x5e);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x10);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x00, 0x00, 0x04, 0x37);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x00, 0x00, 0x09, 0x5f);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x01);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x80);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x04);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x20);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03);
nt37705_csot_write_dsc_params(&dsi_ctx, dsi->dsc);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x01);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc7, 0x00, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x40);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x01, 0x60);
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
mipi_dsi_msleep(&dsi_ctx, 100);
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
return dsi_ctx.accum_err;
}
/*
* Bring-up aid: the stock ESD check reads DCS 0x0a and expects 0x9c (booster
* on, sleep out, display on, normal mode). Logs the result either way.
*/
static void nt37705_csot_log_power_mode(struct nt37705_csot *ctx,
const char *when)
{
struct device *dev = &ctx->dsi->dev;
u8 power_mode;
int ret;
ret = mipi_dsi_dcs_get_power_mode(ctx->dsi, &power_mode);
if (ret < 0)
dev_warn(dev, "power mode %s: read failed: %d\n", when, ret);
else
dev_info(dev, "power mode %s: 0x%02x (stock expects 0x9c)\n",
when, power_mode);
}
static int nt37705_csot_off(struct nt37705_csot *ctx)
{
struct mipi_dsi_device *dsi = ctx->dsi;
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
mipi_dsi_msleep(&dsi_ctx, 120);
return dsi_ctx.accum_err;
}
/*
* Everything is sent from prepare (DSI host already up thanks to
* prepare_prev_first), followed by the standard PPS and compression-mode
* packets, like the mainline NT37801 driver does with the same Novatek
* 0x90/0x91 flow. The downstream Qualcomm driver also sends the PPS on its
* own; it is not part of the vendor on-command.
*/
static int nt37705_csot_prepare(struct drm_panel *panel)
{
struct nt37705_csot *ctx = to_nt37705_csot(panel);
struct device *dev = &ctx->dsi->dev;
struct drm_dsc_picture_parameter_set pps;
s64 elapsed;
int ret;
/* Supplies were cut in probe or unprepare: keep them off >= 100 ms. */
elapsed = ktime_ms_delta(ktime_get(), ctx->off_since);
if (elapsed < 100)
msleep(100 - elapsed);
ret = regulator_bulk_enable(ARRAY_SIZE(nt37705_csot_supplies), ctx->supplies);
if (ret < 0)
return ret;
nt37705_csot_reset(ctx);
ret = nt37705_csot_on(ctx);
if (ret < 0) {
dev_err(dev, "failed to initialize panel: %d\n", ret);
goto err;
}
drm_dsc_pps_payload_pack(&pps, ctx->dsi->dsc);
ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps);
if (ret < 0) {
dev_err(dev, "failed to transmit PPS: %d\n", ret);
goto err;
}
ret = mipi_dsi_compression_mode(ctx->dsi, true);
if (ret < 0) {
dev_err(dev, "failed to enable compression mode: %d\n", ret);
goto err;
}
msleep(28);
nt37705_csot_log_power_mode(ctx, "after on");
msleep(100);
nt37705_csot_log_power_mode(ctx, "after on +100ms");
msleep(400);
nt37705_csot_log_power_mode(ctx, "after on +500ms");
return 0;
err:
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
regulator_bulk_disable(ARRAY_SIZE(nt37705_csot_supplies), ctx->supplies);
ctx->off_since = ktime_get();
return ret;
}
static int nt37705_csot_unprepare(struct drm_panel *panel)
{
struct nt37705_csot *ctx = to_nt37705_csot(panel);
struct device *dev = &ctx->dsi->dev;
int ret;
ret = nt37705_csot_off(ctx);
if (ret < 0)
dev_err(dev, "failed to un-initialize panel: %d\n", ret);
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
regulator_bulk_disable(ARRAY_SIZE(nt37705_csot_supplies), ctx->supplies);
ctx->off_since = ktime_get();
return 0;
}
/*
* 60 Hz modeline, geometry from the stock timing@0:
* hbp/hfp/hpw = 0x20/0x20/0x10, vbp/vfp/vpw = 0x04/0x0c/0x04 (1080x2400).
*/
static const struct drm_display_mode nt37705_csot_mode = {
.clock = (1080 + 32 + 16 + 32) * (2400 + 4 + 12 + 4) * 60 / 1000,
.hdisplay = 1080,
.hsync_start = 1080 + 32,
.hsync_end = 1080 + 32 + 16,
.htotal = 1080 + 32 + 16 + 32,
.vdisplay = 2400,
.vsync_start = 2400 + 4,
.vsync_end = 2400 + 4 + 12,
.vtotal = 2400 + 4 + 12 + 4,
.width_mm = 69,
.height_mm = 154,
.type = DRM_MODE_TYPE_DRIVER,
};
static int nt37705_csot_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
return drm_connector_helper_get_modes_fixed(connector, &nt37705_csot_mode);
}
static const struct drm_panel_funcs nt37705_csot_panel_funcs = {
.prepare = nt37705_csot_prepare,
.unprepare = nt37705_csot_unprepare,
.get_modes = nt37705_csot_get_modes,
};
/*
* DBV (0x51) backlight, 12-bit (0..0x3ffc on the stock panel). The vendor
* "bl-is-exponent"/"bl-inverted-dbv" curves are not reproduced; a raw value
* is written for now so the display has a working brightness control.
*/
static int nt37705_csot_bl_update_status(struct backlight_device *bl)
{
struct mipi_dsi_device *dsi = bl_get_data(bl);
u16 brightness = backlight_get_brightness(bl);
return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
}
static int nt37705_csot_bl_get_brightness(struct backlight_device *bl)
{
struct mipi_dsi_device *dsi = bl_get_data(bl);
u16 brightness;
int ret;
ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness);
if (ret < 0)
return ret;
return brightness;
}
static const struct backlight_ops nt37705_csot_bl_ops = {
.update_status = nt37705_csot_bl_update_status,
.get_brightness = nt37705_csot_bl_get_brightness,
};
static struct backlight_device *
nt37705_csot_create_backlight(struct mipi_dsi_device *dsi)
{
struct device *dev = &dsi->dev;
const struct backlight_properties props = {
.type = BACKLIGHT_RAW,
.brightness = 2047,
.max_brightness = 16380,
};
return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
&nt37705_csot_bl_ops, &props);
}
static int nt37705_csot_probe(struct mipi_dsi_device *dsi)
{
struct device *dev = &dsi->dev;
struct nt37705_csot *ctx;
struct drm_dsc_config *dsc;
int ret;
ctx = devm_drm_panel_alloc(dev, struct nt37705_csot, panel,
&nt37705_csot_panel_funcs,
DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(nt37705_csot_supplies),
nt37705_csot_supplies, &ctx->supplies);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to get regulators\n");
ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(ctx->reset_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
"Failed to get reset-gpios\n");
/*
* The ABL leaves the panel powered and running its splash. Resetting
* it, or even power-cycling it from prepare (DSI PHY already up, lanes
* in LP11), leaves the DDIC dead: no TE, no DCS read response. The same
* power cycle done with the DSI link down (fb blank/unblank) brings it
* up fine every time, so cut the inherited supplies here, while MDSS
* has just been reset and the DSI PHY is still off.
*/
ret = regulator_bulk_enable(ARRAY_SIZE(nt37705_csot_supplies),
ctx->supplies);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to enable regulators\n");
regulator_bulk_disable(ARRAY_SIZE(nt37705_csot_supplies), ctx->supplies);
ctx->off_since = ktime_get();
/* Stock DSC config: version 1.1, 10 bpc, 8 bpp, novateck-ic flow. */
dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
if (!dsc)
return -ENOMEM;
dsc->pic_width = 1080;
dsc->pic_height = 2400;
dsc->dsc_version_major = 0x1;
dsc->dsc_version_minor = 0x1;
dsc->slice_height = 12;
dsc->slice_width = 540;
dsc->slice_count = 2;
dsc->bits_per_component = 10;
dsc->bits_per_pixel = 8 << 4;
dsc->block_pred_enable = true;
nt37705_csot_dsc_config_nt(dsc);
dsi->dsc = dsc;
ctx->dsi = dsi;
mipi_dsi_set_drvdata(dsi, ctx);
dsi->lanes = 4;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM;
ctx->panel.prepare_prev_first = true;
ctx->panel.backlight = nt37705_csot_create_backlight(dsi);
if (IS_ERR(ctx->panel.backlight))
return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
"Failed to create backlight\n");
drm_panel_add(&ctx->panel);
ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
drm_panel_remove(&ctx->panel);
return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
}
return 0;
}
static void nt37705_csot_remove(struct mipi_dsi_device *dsi)
{
struct nt37705_csot *ctx = mipi_dsi_get_drvdata(dsi);
drm_panel_remove(&ctx->panel);
}
static const struct of_device_id nt37705_csot_of_match[] = {
{ .compatible = "novatek,nt37705" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, nt37705_csot_of_match);
static struct mipi_dsi_driver nt37705_csot_driver = {
.probe = nt37705_csot_probe,
.remove = nt37705_csot_remove,
.driver = {
.name = "panel-novatek-nt37705",
.of_match_table = nt37705_csot_of_match,
},
};
module_mipi_dsi_driver(nt37705_csot_driver);
MODULE_AUTHOR("snlavaise <snlavaise@gmail.com>");
MODULE_DESCRIPTION("DRM driver for the CSOT NT37705 (Novatek) cmd mode DSC panel on Motorola rtwo");
MODULE_LICENSE("GPL");
@@ -1,5 +1,62 @@
# Motorola rtwo bring-up fragment, appended to config-motorola-rtwo.aarch64.
# --- hardening OFF (bring-up diagnostic) --------------------------------
# config-motorola-rtwo.aarch64 (heredado de linux-postmarketos-mainline,
# nunca revisado a proposito para este equipo) trae CFI/BTI/PAC/MTE/SCS
# encendidos incluso para el propio codigo del kernel. Todas estas
# protecciones exigen que CUALQUIER cosa a la que el kernel salte o
# retorne indirectamente (trampolines de PSCI/SMCCC, callbacks del
# hipervisor Gunyah - el rtwo corre como VM primaria) respete las mismas
# convenciones (firmas CFI, landing pads BTI, x18 reservado para SCS,
# punteros autenticados). Si algun trampolin de firmware/hipervisor no
# fue compilado con las mismas convenciones, la CPU dispara una excepcion
# de hardware inmediata - encaja con un reset rapido y consistente que no
# cambio en absoluto al sacar reguladores/USB/UART/ramoops no-map. Apagado
# para aislar esto como variable; si el equipo arranca con esto apagado,
# reactivar de a una por vez para encontrar cual puntual es la culpable.
# CONFIG_CFI is not set
# CONFIG_ARM64_BTI_KERNEL is not set
# CONFIG_ARM64_PTR_AUTH_KERNEL is not set
# CONFIG_ARM64_MTE is not set
# CONFIG_SHADOW_CALL_STACK is not set
# --- kernel lockdown OFF (bring-up diagnostic) --------------------------
# config-motorola-rtwo.aarch64 trae CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y
# a secas, asi que el kernel arranca en lockdown "integrity" SIEMPRE,
# independientemente del estado de Secure Boot que reporte U-Boot vía EFI.
# Lockdown activa LOCKDOWN_MODULE_SIGNATURE: cualquier .ko cuya firma no
# verifique contra el certs/signing_key.pem de ESTE build puntual se
# rechaza de plano ("Lockdown: modprobe: unsigned module loading is
# restricted; see man kernel_lockdown.7"), aunque ese mismo modulo haya
# cargado bien ayer. Esto nos exploto justo al reconstruir el kernel (para
# soporte UFS) mientras dejabamos a proposito el ramdisk hand-patcheado
# (dropbear + retry loop del UDC) SIN regenerar: los modulos empaquetados
# ahi quedaron firmados con la clave del build ANTERIOR, asi que el kernel
# nuevo desconfia de todos por igual -- tanto los de UFS recien habilitados
# como los del PHY USB que hasta ese rebuild cargaban sin problema. Apagado
# mientras dure el bring-up activo (vamos a seguir reconstruyendo el kernel
# sin resincronizar el ramdisk cada vez); nada aca necesita lockdown todavia.
CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y
# CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set
# --- VA/PA de 52-bit -> 48-bit (bring-up diagnostic) --------------------
# config-motorola-rtwo.aarch64 trae VA_BITS/PA_BITS en 52 (LPA2), heredado
# del defconfig generico. device-motorola-dubai (unico port mainline de
# Motorola que arranca, mismo ABL/MbmPkg, aunque SIN hipervisor Gunyah de
# por medio) usa 48/48. El rtwo SI corre como VM primaria de Gunyah
# (confirmado: nodo qcom,gh-watchdog real en el DT stock), y el hipervisor
# es quien gestiona el espacio de direcciones intermedio (IPA) del huesped
# - si Gunyah en este equipo solo soporta 48-bit para la VM primaria,
# forzar 52-bit podria romper el establecimiento de la MMU en el arranque
# en ensamblador, ANTES de cualquier codigo en C y antes de parsear el
# device tree - coherente con un reset durisimo que no cambio en absoluto
# con nada de lo tocado en el .dts hasta ahora.
CONFIG_ARM64_VA_BITS_48=y
# CONFIG_ARM64_VA_BITS_52 is not set
CONFIG_ARM64_PA_BITS_48=y
# CONFIG_ARM64_PA_BITS_52 is not set
# CONFIG_ARM64_LPA2 is not set
# --- pstore / ramoops -------------------------------------------------------
# Capture the kernel console into the reserved ramoops region (0xae000000)
# so a failed boot can be read back from a later pmOS boot
@@ -14,16 +71,48 @@ CONFIG_PSTORE_COMPRESS=y
# sm8550.dtsi has the APSS watchdog node; build the driver in so the kernel
# pets it early (the bootloader may hand off with it armed).
CONFIG_QCOM_WDT=y
# CONFIG_QCOM_WDT above is the wrong watchdog for what actually matters here:
# it's DT-matched to the APSS timer at 0x17c10000, but rtwo boots as a Gunyah
# primary VM (qcom,gh-watchdog node confirmed in the stock DT) and the real
# watchdog guarding HLOS handoff is the HYPERVISOR's, reached only via SMCCC
# (ARM_SMCCC_OWNER_VENDOR_HYP), never via a DT node - it is registered by
# qcom_scm_gunyah_wdt_init() (drivers/firmware/qcom/qcom_scm.c), which
# unconditionally runs once CONFIG_QCOM_SCM=y probes AND arm_smccc_hypervisor_
# has_uuid() confirms Gunyah - both true for us. That function only skips
# registration if it finds an of_device_is_available() node compatible with
# "qcom,kpss-wdt" or "arm,sbsa-gwdt" - our stock DT's gh-watchdog node is
# neither, so it registers the "gunyah-wdt" platform_device regardless.
# Without CONFIG_GUNYAH_WATCHDOG, nothing ever binds to that platform_device,
# so if Gunyah/firmware hands off HLOS with the hyp watchdog already armed
# (a plausible safety net for a VM that never comes up), nothing ever calls
# gunyah_wdt_stop()/pings it, and it fires -> reset with no kernel-visible
# fault. gunyah_wdt_probe() calls gunyah_wdt_stop() unconditionally as its
# first action after the SMCCC status check succeeds - it does NOT arm or
# start the watchdog itself, so turning this on cannot make anything worse,
# it can only disarm a pre-armed timer earlier than before.
# Depends only on (ARCH_QCOM || COMPILE_TEST) && HAVE_ARM_SMCCC, both already
# satisfied; selects WATCHDOG_CORE (already =y via CONFIG_QCOM_WDT above).
CONFIG_GUNYAH_WATCHDOG=y
# --- framebuffer console ----------------------------------------------
# Reuse the bootloader framebuffer as an early console (simple-framebuffer
# node in the .dts). No panel driver needed.
CONFIG_FB=y
CONFIG_FB_SIMPLE=y
# Reactivado 2026-09-17 junto con framebuffer@b8000000 en el .dts (ver ese
# archivo): con el chainload de U-Boot confirmado en hardware, este
# framebuffer ya esta encendido y con el formato correcto antes de que
# arranque nuestro kernel, asi que simpledrm solo tiene que mapearlo.
#
# CONFIG_FB / CONFIG_SYSFB_SIMPLEFB no hacen falta: DRM_SIMPLEDRM solo
# depende de DRM && MMU (ambos ya =y en la config base) y bindea el nodo
# "simple-framebuffer" del DT directamente, sin pasar por el fbdev legacy
# ni por el sysfb de x86 (SYSFB_SIMPLEFB es para EFI/VESA en x86, no
# aplica aca). CONFIG_FRAMEBUFFER_CONSOLE ya viene =y de la config base
# (usa el struct fb_info que expone DRM_FBDEV_EMULATION, tambien ya =y).
#
# CONFIG_FB_SIMPLE se deja APAGADO a proposito (la config base ya lo trae
# asi): compite con DRM_SIMPLEDRM por el mismo nodo "simple-framebuffer"
# del DT y gana el que bindee primero -- confundio un intento anterior de
# este mismo bring-up (pkgrel 22). Con los dos no hace falta, uno alcanza.
CONFIG_DRM_SIMPLEDRM=y
CONFIG_SYSFB_SIMPLEFB=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set
# --- misc bring-up aids ---------------------------------------------
CONFIG_MAGIC_SYSRQ=y
@@ -81,3 +170,155 @@ CONFIG_DEBUG_INFO_NONE=y
# CONFIG_ARCH_VISCONTI is not set
# CONFIG_ARCH_XGENE is not set
# CONFIG_ARCH_ZYNQMP is not set
# --- module compression: OFF ------------------------------------------
# The base config (from linux-postmarketos-mainline) turns on
# CONFIG_MODULE_COMPRESS + CONFIG_MODULE_COMPRESS_ZSTD +
# CONFIG_MODULE_COMPRESS_ALL, so 'make modules_install' writes *.ko.zst.
# The pmOS initramfs loads modules with busybox's modprobe applet
# ('/bin/busybox --install -s' in /init, then 'modprobe -a' from
# load_modules): that applet only knows the plain '.ko' suffix and only
# decompresses gzip/bzip2/xz/lzma by magic - this busybox has NO zstd
# ("no gzip/bzip2/xz magic" is the error it prints). CONFIG_MODULE_DECOMPRESS
# does not help either: busybox reads the file itself and calls init_module(),
# never finit_module(MODULE_INIT_COMPRESSED_FILE).
#
# So with zstd every module in initramfs.load silently fails to load:
# ufs-qcom/qcom_ice/phy-qcom-qmp-ufs -> no root device -> "VFS: Unable to
# mount root fs on unknown-block(0,0)", and phy-snps-eusb2 -> no UDC ->
# no usb0, no telnet on 172.16.42.1.
#
# 7.2 has no MODULE_COMPRESS_NONE symbol (it was replaced by the
# MODULE_COMPRESS bool + a type choice), so turn the bool off; olddefconfig
# then drops COMPRESS_ZSTD / COMPRESS_ALL / DECOMPRESS with it.
# CONFIG_MODULE_COMPRESS is not set
# --- RETRACTED 2026-09-20: CONFIG_QCOMTEE / CONFIG_ARM_QCOM_CPUFREQ_HW --
# were NOT disabled here in the end. Both were disabled for a while
# (2026-09-18) after the screen appeared to freeze right after "qcomtee:
# QTEE version 5.2.0", then right after "cpu cpu0: EM: created perf
# domain" -- diagnosed purely from photos of the framebuffer console, no
# serial/SSH/telnet available at the time to confirm the system was
# actually stuck.
#
# That diagnosis didn't survive scrutiny: in every one of those "hung"
# attempts the device stayed reachable over ping the whole time. A kernel
# truly wedged in a probe function (e.g. an SMC call that never returns)
# would eventually stop servicing network softirqs too, at least once
# nothing else is left running; ping staying up is much better explained
# by the boot actually completing (or getting close) while only the
# on-screen console stopped updating -- unreachable over SSH because
# sshd/networking-online wasn't there yet at that point in the bring-up,
# and unreachable over telnet because no telnet/debug shell was installed
# in userspace yet (added later, see device-motorola-rtwo's
# debug-telnetd.service / dropbear work).
#
# Separately and since confirmed on 2026-09-20 (see sm8550-motorola-rtwo.dts,
# the &dispcc override): the inherited U-Boot framebuffer genuinely does
# freeze on this board, root-caused to dispcc (Display Clock Controller)
# touching disp_cc_mdss_mdp_clk_src -- the MDP pixel clock still actively
# driving that inherited buffer -- during its own probe. CONFIG_QCOMTEE=m
# is a *module*, only autoloaded once udev coldplugs devices in the real
# systemd rootfs; CONFIG_ARM_QCOM_CPUFREQ_HW=y is built-in but its probe
# depends on OPP/interconnect data that isn't ready until roughly the same
# late-boot window (matches the global deferred-probe-timeout retry seen
# at ~11s in current dmesg). Both cluster in the same few seconds of boot
# as dispcc's own module autoload -- entirely consistent with "whatever
# happened to be the last line printed before the screen froze" rather
# than either of them being the actual cause of a hang.
#
# Re-enabled to their config-motorola-rtwo.aarch64 defaults
# (CONFIG_QCOMTEE=m, CONFIG_ARM_QCOM_CPUFREQ_HW=y). Re-test now that a
# debug telnet/SSH shell exists in userspace, so aliveness can be checked
# directly instead of inferred from a photo of a console that we now know
# can freeze independently of whether the system is actually up.
# --- ath12k: debug messages ON (WiFi bring-up diagnostic) ---------------
# The whole "WMI credit exhaustion" diagnosis so far was done BLIND: the
# only hard evidence was ath12k_wmi_cmd_send() printing "wmi command 16387
# timeout" after WMI_SEND_TIMEOUT_HZ, plus -11/-EAGAIN, which in this
# driver can ONLY come from ath12k_htc_send()'s "insufficient credits"
# path (wmi.c:412 wait_event_timeout -> cmd_send_nowait -> htc_send; no
# other error path in that chain returns -EAGAIN). Everything else --
# how many credits the endpoint started with, whether a single credit
# report ever came back -- was inferred, because every one of those facts
# is printed by an ath12k_dbg() call and config-motorola-rtwo.aarch64
# (inherited, never reviewed for this device) has
# "# CONFIG_ATH12K_DEBUG is not set", which compiles __ath12k_dbg() down
# to an empty inline (debug.h). We were reading a silent driver.
#
# With this on, the messages that answer the open question directly:
# htc.c:543 "Target ready! transmit resources: %d size:%d"
# -> htc->total_transmit_credits / target_credit_size, the
# initial credit budget the firmware advertises.
# htc.c:104 "htc ep %d consumed %d credits (total %d)" (every TX)
# htc.c:166 "htc ep %d got %d credits (total %d)" (every credit
# report parsed out of an RX trailer)
# htc.c:96 "htc insufficient credits ep %d required %d available %d"
# all under ATH12K_DBG_HTC (0x4). Falsifiable prediction to check against
# them: if the firmware never returns a single credit, the number of WMI
# commands that succeed before the wall (INIT, SET_HW_MODE, ..., then the
# one PMF_QOS in ath12k_mac_start) should come out EXACTLY equal to the
# advertised initial budget -- and not one "got %d credits" line should
# ever appear. If instead credits do come back and then stop, the failure
# is somewhere else entirely and the "firmware never reports" theory dies.
#
# Enabled separately at runtime via the debug_mask module param (0644, so
# also writable through /sys/module/ath12k/parameters/debug_mask, and
# settable from the kernel cmdline as ath12k.debug_mask=0x66 even though
# ath12k is a module). Bits (debug.h): WMI 0x2 | HTC 0x4 | BOOT 0x20 |
# QMI 0x40. Cost when the mask is 0 is one branch per call site, and this
# is a bring-up kernel -- performance is not the constraint here.
CONFIG_ATH12K_DEBUG=y
# Firmware coredump on assert. WANT_DEV_COREDUMP/DEV_COREDUMP are already
# =y in the base config, so this only adds ath12k's own collector. It
# costs nothing while nothing crashes, and it settles a question the
# current evidence cannot: whether the firmware silently asserted at some
# point during bring-up (a dead firmware explains "credits never come
# back" completely). A dump would show up under /sys/class/devcoredump/.
CONFIG_ATH12K_COREDUMP=y
# Native display bring-up (CSOT NT37705 panel, see panel-novatek-nt37705.c).
# DRM_MSM is present as =m in the mainline base config; build it in so the
# mdss/dsi0 probe runs from the boot (the frame is committed by MSM itself,
# no simpledrm handover needed). DRM_DISPLAY_DSC_HELPER is already =y.
CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_DRM_MSM=y
CONFIG_DRM_MSM_DSI=y
CONFIG_DRM_MIPI_DSI=y
CONFIG_DRM_PANEL=y
CONFIG_DRM_PANEL_NOVATEK_NT37705=y
# dispcc/gpucc built in (2026-09-25): as =m they only load once the rootfs is
# mounted (~17 s), after the 10 s deferred_probe_timeout. Their GDSCs are the
# power-domains of mdss and of the GPU SMMU, and genpd returns -ETIMEDOUT once
# the timeout expires: seen in r43 as "arm-smmu 3da0000.iommu: probe ... failed
# with error -110". Built-in DRM_MSM would hit the same wall on MDSS_GDSC.
CONFIG_SM_DISPCC_8550=y
CONFIG_SM_GPUCC_8550=y
# DRM_MSM "depends on QCOM_LLCC || QCOM_LLCC=n" (same for QCOM_OCMEM): with
# both =m in the base config, DRM_MSM=y above was silently capped to =m by
# olddefconfig (r44: msm.ko in the rootfs, nothing drove the panel from the
# initramfs). Build them in so DRM_MSM can really be =y.
CONFIG_QCOM_LLCC=y
CONFIG_QCOM_OCMEM=y
# DisplayPort por USB-C (2026-09-25). Cadena: mdss_dp0 -> QMP USB3/DP combo
# PHY -> redriver Parade PS5169 (typec-mux-ps5169.c, integrado en este
# paquete) -> conector de pmic-glink (altmode + HPD), con el FSA4480 moviendo
# AUX por los SBU. Todo =y a proposito: el DP es un componente de msm, y msm
# (panel interno incluido) no se enlaza hasta que el DP encuentra su PHY y
# su cadena de bridges; pmic_glink_altmode solo registra su bridge HPD
# despues de conseguir el FSA4480 y el redriver. Como =m, eso esperaria a
# que monte el rootfs (y en la debug shell no llegaria nunca).
CONFIG_TYPEC=y
CONFIG_PHY_QCOM_QMP=y
CONFIG_PHY_QCOM_QMP_COMBO=y
CONFIG_DRM_AUX_BRIDGE=y
CONFIG_DRM_AUX_HPD_BRIDGE=y
CONFIG_QCOM_PMIC_GLINK=y
CONFIG_I2C_QCOM_GENI=y
CONFIG_TYPEC_MUX_FSA4480=y
CONFIG_TYPEC_MUX_PS5169=y
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,417 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parade PS5169 USB 3.1 Gen2 / DP 1.4 Type-C linear redriver
*
* Mainline typec switch + retimer front-end for the redriver sitting
* between the SM8550 QMP USB3/DP combo PHY and the USB-C connector of the
* Motorola rtwo (Moto Edge 40 Pro). Modelled on nb7vpq904m.c; the register
* programming (CONFIG_REG bits, 0xa0/0xa1 lane setup, chip id) comes from
* Motorola's downstream drivers/usb/redriver/ps5169.c, and the init table
* is the 'config-seq' of the stock rtwo device tree.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
#include <drm/bridge/aux-bridge.h>
#define PS5169_CONFIG_REG 0x40
#define PS5169_PD_ACTIVE BIT(7)
#define PS5169_USB3_EN BIT(6)
#define PS5169_DP_EN BIT(5)
#define PS5169_FLIPPED BIT(4)
#define PS5169_CONFIG_MASK (PS5169_PD_ACTIVE | PS5169_USB3_EN | \
PS5169_DP_EN | PS5169_FLIPPED)
/* Lane setup written by the downstream driver next to every mode change. */
#define PS5169_LANE_CFG0_REG 0xa0
#define PS5169_LANE_CFG1_REG 0xa1
#define PS5169_CHIPID_LO_REG 0xac
#define PS5169_CHIPID_HI_REG 0xad
#define PS5169_CHIP_ID 0x6987
/*
* Stock rtwo 'config-seq' (reg, value, delay in us): soft reset through
* 0x9d, then EQ/gain tuning for this board's traces.
*/
static const struct reg_sequence ps5169_init_seq[] = {
{ 0x9d, 0x80, 10000 },
{ 0x9d, 0x00 },
{ 0x40, 0x80 },
{ 0x8d, 0x01 },
{ 0x90, 0x01 },
{ 0x51, 0x87 },
{ 0x50, 0x20 },
{ 0x54, 0x11 },
{ 0x5d, 0x66 },
{ 0x52, 0x50 },
{ 0x55, 0x00 },
{ 0x56, 0x00 },
{ 0x57, 0x00 },
{ 0x58, 0x00 },
{ 0x59, 0x00 },
{ 0x5a, 0x00 },
{ 0x5b, 0x00 },
{ 0x5e, 0x07 },
{ 0x5f, 0x00 },
{ 0x60, 0x00 },
{ 0x61, 0x03 },
{ 0x65, 0x40 },
{ 0x66, 0x00 },
{ 0x67, 0x03 },
{ 0x75, 0x0c },
{ 0x77, 0x00 },
{ 0x78, 0x7c },
};
struct ps5169 {
struct i2c_client *client;
struct regmap *regmap;
struct gpio_desc *enable_gpio;
struct regulator *vcc_supply;
struct typec_switch_dev *sw;
struct typec_retimer *retimer;
struct typec_switch *typec_switch;
struct typec_mux *typec_mux;
struct mutex lock; /* protects the fields below */
bool powered;
unsigned int config; /* CONFIG_REG value last programmed, 0 = none */
enum typec_orientation orientation;
unsigned long mode;
unsigned int svid;
};
static void ps5169_power(struct ps5169 *ps, bool on)
{
if (ps->powered == on)
return;
gpiod_set_value_cansleep(ps->enable_gpio, on);
if (on)
usleep_range(10000, 11000);
ps->powered = on;
}
static int ps5169_set(struct ps5169 *ps)
{
bool reverse = ps->orientation == TYPEC_ORIENTATION_REVERSE;
unsigned int val;
u8 cfg0, cfg1;
int ret;
/* Only a disconnect (no orientation) powers the redriver down. */
if (ps->orientation == TYPEC_ORIENTATION_NONE) {
ps5169_power(ps, false);
ps->config = 0;
return 0;
}
switch (ps->mode) {
/*
* With a plain USB partner (OTG cable, pendrive) the rtwo ADSP did
* not bring the redriver up: no TYPEC_STATE_USB arrived, it stayed
* in TYPEC_STATE_SAFE (NO_CONN) with a valid orientation.
* Downstream routes USB3 on every connect (notify_connect: op mode
* NONE -> USB3), so do the same instead of leaving the chip off,
* which kept the SuperSpeed lanes dead.
*/
case TYPEC_STATE_SAFE:
case TYPEC_STATE_USB:
val = PS5169_USB3_EN;
cfg0 = 0;
cfg1 = 0;
break;
case TYPEC_DP_STATE_C:
case TYPEC_DP_STATE_E:
if (ps->svid != USB_TYPEC_DP_SID)
return -EINVAL;
val = PS5169_DP_EN;
cfg0 = 0x00;
cfg1 = 0x04;
break;
case TYPEC_DP_STATE_D:
case TYPEC_DP_STATE_F:
if (ps->svid != USB_TYPEC_DP_SID)
return -EINVAL;
val = PS5169_USB3_EN | PS5169_DP_EN;
cfg0 = 0x00;
cfg1 = 0x04;
break;
default:
return -EOPNOTSUPP;
}
if (reverse)
val |= PS5169_FLIPPED;
val |= PS5169_PD_ACTIVE;
if (ps->powered && ps->config == val)
return 0;
/*
* The downstream driver re-runs the full init on every mode change,
* and power cycles the chip first when it is already running
* (ps5169_host_work: enable low for 2 ms, then high again).
*/
if (ps->powered) {
ps5169_power(ps, false);
usleep_range(2000, 2500);
}
ps->config = 0;
ps5169_power(ps, true);
ret = regmap_multi_reg_write(ps->regmap, ps5169_init_seq,
ARRAY_SIZE(ps5169_init_seq));
if (ret)
goto err;
ret = regmap_update_bits(ps->regmap, PS5169_CONFIG_REG,
PS5169_CONFIG_MASK, val);
if (ret)
goto err;
if (val & PS5169_DP_EN) {
ret = regmap_write(ps->regmap, PS5169_LANE_CFG0_REG, cfg0);
if (!ret)
ret = regmap_write(ps->regmap, PS5169_LANE_CFG1_REG, cfg1);
if (ret)
goto err;
}
ps->config = val;
dev_dbg(&ps->client->dev, "mode %lu orientation %d -> config 0x%02x\n",
ps->mode, ps->orientation, val);
return 0;
err:
dev_err(&ps->client->dev, "failed to program mode %lu: %d\n",
ps->mode, ret);
return ret;
}
static int ps5169_sw_set(struct typec_switch_dev *sw,
enum typec_orientation orientation)
{
struct ps5169 *ps = typec_switch_get_drvdata(sw);
int ret;
ret = typec_switch_set(ps->typec_switch, orientation);
if (ret)
return ret;
dev_dbg(&ps->client->dev, "switch: orientation %d\n", orientation);
mutex_lock(&ps->lock);
if (ps->orientation != orientation) {
ps->orientation = orientation;
ret = ps5169_set(ps);
}
mutex_unlock(&ps->lock);
return ret;
}
static int ps5169_retimer_set(struct typec_retimer *retimer,
struct typec_retimer_state *state)
{
struct ps5169 *ps = typec_retimer_get_drvdata(retimer);
struct typec_mux_state mux_state;
int ret = 0;
dev_dbg(&ps->client->dev, "retimer: mode %lu svid 0x%04x\n",
state->mode, state->alt ? state->alt->svid : 0);
mutex_lock(&ps->lock);
if (ps->mode != state->mode) {
ps->mode = state->mode;
ps->svid = state->alt ? state->alt->svid : 0;
ret = ps5169_set(ps);
}
mutex_unlock(&ps->lock);
if (ret)
return ret;
mux_state.alt = state->alt;
mux_state.data = state->data;
mux_state.mode = state->mode;
return typec_mux_set(ps->typec_mux, &mux_state);
}
static const struct regmap_config ps5169_regmap = {
.max_register = 0xff,
.reg_bits = 8,
.val_bits = 8,
};
static int ps5169_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct typec_switch_desc sw_desc = { };
struct typec_retimer_desc retimer_desc = { };
unsigned int lo, hi;
struct ps5169 *ps;
int ret;
ps = devm_kzalloc(dev, sizeof(*ps), GFP_KERNEL);
if (!ps)
return -ENOMEM;
ps->client = client;
ps->mode = TYPEC_STATE_SAFE;
ps->orientation = TYPEC_ORIENTATION_NONE;
mutex_init(&ps->lock);
ps->regmap = devm_regmap_init_i2c(client, &ps5169_regmap);
if (IS_ERR(ps->regmap))
return dev_err_probe(dev, PTR_ERR(ps->regmap),
"failed to allocate register map\n");
ps->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(ps->enable_gpio))
return dev_err_probe(dev, PTR_ERR(ps->enable_gpio),
"unable to acquire enable gpio\n");
ps->vcc_supply = devm_regulator_get(dev, "vcc");
if (IS_ERR(ps->vcc_supply))
return dev_err_probe(dev, PTR_ERR(ps->vcc_supply),
"unable to acquire vcc supply\n");
ps->typec_switch = fwnode_typec_switch_get(dev->fwnode);
if (IS_ERR(ps->typec_switch))
return dev_err_probe(dev, PTR_ERR(ps->typec_switch),
"failed to acquire orientation-switch\n");
ps->typec_mux = fwnode_typec_mux_get(dev->fwnode);
if (IS_ERR(ps->typec_mux)) {
ret = dev_err_probe(dev, PTR_ERR(ps->typec_mux),
"failed to acquire mode-switch\n");
goto err_switch_put;
}
ret = regulator_enable(ps->vcc_supply);
if (ret) {
dev_err(dev, "failed to enable vcc: %d\n", ret);
goto err_mux_put;
}
/*
* Identify the chip, then leave it powered down until a partner
* shows up (same as downstream). An I2C failure is only reported:
* the DRM bridge chain of the DP controller runs through this device,
* so failing the probe here would keep the whole MSM display
* (internal panel included) from binding.
*/
ps5169_power(ps, true);
ret = regmap_read(ps->regmap, PS5169_CHIPID_LO_REG, &lo);
if (!ret)
ret = regmap_read(ps->regmap, PS5169_CHIPID_HI_REG, &hi);
if (ret)
dev_warn(dev, "failed to read chip id: %d\n", ret);
else if (((hi << 8) | lo) != PS5169_CHIP_ID)
dev_warn(dev, "unexpected chip id 0x%04x\n", (hi << 8) | lo);
else
dev_info(dev, "PS5169 redriver, chip id 0x%04x\n", PS5169_CHIP_ID);
ps5169_power(ps, false);
ret = drm_aux_bridge_register(dev);
if (ret)
goto err_disable_vcc;
sw_desc.drvdata = ps;
sw_desc.fwnode = dev->fwnode;
sw_desc.set = ps5169_sw_set;
ps->sw = typec_switch_register(dev, &sw_desc);
if (IS_ERR(ps->sw)) {
ret = dev_err_probe(dev, PTR_ERR(ps->sw),
"failed to register typec switch\n");
goto err_disable_vcc;
}
retimer_desc.drvdata = ps;
retimer_desc.fwnode = dev->fwnode;
retimer_desc.set = ps5169_retimer_set;
ps->retimer = typec_retimer_register(dev, &retimer_desc);
if (IS_ERR(ps->retimer)) {
ret = dev_err_probe(dev, PTR_ERR(ps->retimer),
"failed to register typec retimer\n");
goto err_switch_unregister;
}
i2c_set_clientdata(client, ps);
return 0;
err_switch_unregister:
typec_switch_unregister(ps->sw);
err_disable_vcc:
regulator_disable(ps->vcc_supply);
err_mux_put:
typec_mux_put(ps->typec_mux);
err_switch_put:
typec_switch_put(ps->typec_switch);
return ret;
}
static void ps5169_remove(struct i2c_client *client)
{
struct ps5169 *ps = i2c_get_clientdata(client);
typec_retimer_unregister(ps->retimer);
typec_switch_unregister(ps->sw);
gpiod_set_value_cansleep(ps->enable_gpio, 0);
regulator_disable(ps->vcc_supply);
typec_mux_put(ps->typec_mux);
typec_switch_put(ps->typec_switch);
}
static void ps5169_shutdown(struct i2c_client *client)
{
struct ps5169 *ps = i2c_get_clientdata(client);
if (ps)
gpiod_set_value_cansleep(ps->enable_gpio, 0);
}
static const struct i2c_device_id ps5169_table[] = {
{ .name = "ps5169" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ps5169_table);
static const struct of_device_id ps5169_of_table[] = {
{ .compatible = "parade,ps5169" },
{ }
};
MODULE_DEVICE_TABLE(of, ps5169_of_table);
static struct i2c_driver ps5169_driver = {
.driver = {
.name = "ps5169",
.of_match_table = ps5169_of_table,
},
.probe = ps5169_probe,
.remove = ps5169_remove,
.shutdown = ps5169_shutdown,
.id_table = ps5169_table,
};
module_i2c_driver(ps5169_driver);
MODULE_DESCRIPTION("Parade PS5169 Type-C redriver driver");
MODULE_LICENSE("GPL");