Board bring-up for the TrimUI Smart Pro handheld (Allwinner A133 Plus):

- New board DTS (sun50i-a133-trimui-smartpro.dts): display pipeline
  (mixer/TCON/DSI/D-PHY), regulators, MMC, PWM backlight.
- New OTM1289A/ER68576 DSI panel driver (panel-orisetech-otm1289a.c),
  including real panel timings and DCS init sequence.
- New PWM driver for this SoC's newer-generation PWM IP
  (pwm-sun50i-a133.c), with binding doc.
- sun8i_mixer: A133 DE2 mixer0/mixer1 configs.
- sun8i_tcon_top: A133 quirks entry, plus two new helpers
  (sun8i_tcon_top_set_dsi_gate/set_de0_port) needed because this
  board's TCON quirks table has no .set_mux callback to reach them
  through the existing paths.
- sun4i_tcon: CPU/8080-interface DSI bring-up (sun4i_tcon0_mode_set_cpu
  register formulas, an hrtimer-driven TRIGGER_START retrigger, an
  FSYNC_INT storm fix, IRQ deferred-enable ordering) plus TCON-TOP
  wiring for this board's DSI clock gate and DE0 port routing.
- sun6i_mipi_dsi: an additional DSI_START_TRI instruction-jump-table
  case for continuous per-frame retriggering (distinct from the
  existing one-shot HS-clock-enable case), a DRQ_SET formula fix, and
  reordering the HS-clock-enable step to run before the panel's DCS
  init sequence.
- ccu-sun50i-a100: CLK_SET_RATE_PARENT on the tcon-lcd0 clock so its
  rate request can actually explore parent PLL rates.

Sending as RFC rather than waiting for the display to fully work:
posting the board/DTS foundation now so it's in front of people who
know this hardware, rather than sitting on it indefinitely.

Known issue, and the actual reason for the RFC tag: the panel attaches
and the backlight lights, but no pixel data reaches it.
TCON0_CPU_IF_REG's TRIGGER_START bit (a documented self-clearing
one-shot) never self-clears, despite the rest of the pixel pipeline
matching a working vendor system register-for-register -- every
static register value, D-PHY lane state, control-flow/sequencing
cross-referenced against a symbol-resolved decompile of the vendor
kernel, and even measured real-time timing all confirmed matching.
Full writeup, including what's been ruled out and how:
https://github.com/leow149/trimui-a133-mainline-port/blob/main/COMMUNITY_HELP_REQUEST.md

If anyone recognizes this symptom on the CPU/8080-interface DSI path,
or can spot something the writeup missed, I would very much like to
hear about it.

Cc: Chen-Yu Tsai <[email protected]>
Cc: Jernej Skrabec <[email protected]>
Cc: Samuel Holland <[email protected]>
Cc: Maxime Ripard <[email protected]>
Cc: Paul Kocialkowski <[email protected]>
Signed-off-by: leow149 <[email protected]>
---
 .../allwinner,sun8i-a83t-de2-mixer.yaml       |   2 +
 .../display/allwinner,sun8i-r40-tcon-top.yaml |   1 +
 .../pwm/allwinner,sun50i-a133-pwm.yaml        |  67 ++
 arch/arm64/boot/dts/allwinner/Makefile        |   1 +
 .../allwinner/sun50i-a133-trimui-smartpro.dts | 691 ++++++++++++++++++
 drivers/clk/sunxi-ng/ccu-sun50i-a100.c        |  16 +-
 drivers/gpu/drm/panel/Kconfig                 |   8 +
 drivers/gpu/drm/panel/Makefile                |   1 +
 .../gpu/drm/panel/panel-orisetech-otm1289a.c  | 424 +++++++++++
 drivers/gpu/drm/sun4i/sun4i_tcon.c            | 343 ++++++++-
 drivers/gpu/drm/sun4i/sun4i_tcon.h            |  86 +++
 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c        | 161 +++-
 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h        |  12 +
 drivers/gpu/drm/sun4i/sun8i_mixer.c           |  48 ++
 drivers/gpu/drm/sun4i/sun8i_tcon_top.c        |  94 +++
 drivers/gpu/drm/sun4i/sun8i_tcon_top.h        |   2 +
 drivers/pwm/Kconfig                           |  14 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-sun50i-a133.c                 | 276 +++++++
 19 files changed, 2209 insertions(+), 39 deletions(-)
 create mode 100644 
Documentation/devicetree/bindings/pwm/allwinner,sun50i-a133-pwm.yaml
 create mode 100644 
arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts
 create mode 100644 drivers/gpu/drm/panel/panel-orisetech-otm1289a.c
 create mode 100644 drivers/pwm/pwm-sun50i-a133.c

diff --git 
a/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml 
b/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
index cbd18fd83..ce72c1ba7 100644
--- 
a/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
+++ 
b/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
@@ -23,6 +23,8 @@ properties:
       - allwinner,sun20i-d1-de2-mixer-1
       - allwinner,sun50i-a64-de2-mixer-0
       - allwinner,sun50i-a64-de2-mixer-1
+      - allwinner,sun50i-a133-de2-mixer-0
+      - allwinner,sun50i-a133-de2-mixer-1
       - allwinner,sun50i-h6-de3-mixer-0
       - allwinner,sun50i-h616-de33-mixer-0
 
diff --git 
a/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml 
b/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml
index 7d849c409..c3809902c 100644
--- 
a/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml
+++ 
b/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml
@@ -43,6 +43,7 @@ properties:
       - allwinner,sun8i-r40-tcon-top
       - allwinner,sun20i-d1-tcon-top
       - allwinner,sun50i-h6-tcon-top
+      - allwinner,sun50i-a133-tcon-top
 
   reg:
     maxItems: 1
diff --git 
a/Documentation/devicetree/bindings/pwm/allwinner,sun50i-a133-pwm.yaml 
b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-a133-pwm.yaml
new file mode 100644
index 000000000..59e4fba3b
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-a133-pwm.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/allwinner,sun50i-a133-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner A133 PWM Controller
+
+description: |
+  No public register documentation for this IP has ever been reused
+  upstream: it's a newer, richer generation than the existing
+  allwinner,sun4i-a10-pwm family (16 channels grouped in pairs, per-pair
+  dead-zone/group-sync control) with no existing mainline driver, and no
+  A100/A133-specific binding either. Confirmed real from the public A133
+  User Manual (chapter 10.11, base 0x0300a000), which documents this
+  block down to bit level.
+
+maintainers:
+  - out-of-tree hobbyist port, not yet upstream
+
+properties:
+  compatible:
+    const: allwinner,sun50i-a133-pwm
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Bus Clock (register access only — the PWM output
+          clock source itself is internal to this IP, selected between
+          OSC24M/APB1 per-pair in its own PCCR01/PCCR23 registers, not
+          exposed as a separate CCU clock)
+
+  clock-names:
+    items:
+      - const: bus
+
+  resets:
+    maxItems: 1
+
+  "#pwm-cells":
+    const: 3
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - resets
+  - "#pwm-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sun50i-a100-ccu.h>
+    #include <dt-bindings/reset/sun50i-a100-ccu.h>
+
+    pwm: pwm@300a000 {
+        compatible = "allwinner,sun50i-a133-pwm";
+        reg = <0x0300a000 0x400>;
+        clocks = <&ccu CLK_BUS_PWM>;
+        clock-names = "bus";
+        resets = <&ccu RST_BUS_PWM>;
+        #pwm-cells = <3>;
+    };
diff --git a/arch/arm64/boot/dts/allwinner/Makefile 
b/arch/arm64/boot/dts/allwinner/Makefile
index aa21f58a4..6132fda32 100644
--- a/arch/arm64/boot/dts/allwinner/Makefile
+++ b/arch/arm64/boot/dts/allwinner/Makefile
@@ -26,6 +26,7 @@ dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h64-remix-mini-pc.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a100-allwinner-perf1.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a133-helperboard.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a133-liontron-h-a133l.dtb
+dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a133-trimui-smartpro.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h5-bananapi-m2-plus.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h5-bananapi-m2-plus-v1.2.dtb
 dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h5-emlid-neutis-n5-devboard.dtb
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts 
b/arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts
new file mode 100644
index 000000000..531be1d90
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts
@@ -0,0 +1,691 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2026 (out-of-tree hobbyist port, not yet upstream)
+ *
+ * Board: TrimUI Smart Pro (Allwinner A133 Plus / sun50iw10p1)
+ *
+ * Sourced from:
+ *  - vendor DTB extracted from Knulli image (dtb_86065152.dts, offset
+ *    86065152 in knulli-a133-trimui-smart-pro-scarab-20260511.img)
+ *  - sun50i-a133-liontron-h-a133l.dts (mainline reference board, same SoC)
+ *  - sun50i-h700-anbernic-rg35xx-2024.dts (mainline handheld reference for
+ *    AXP717 regulator/battery wiring style)
+ *  - LIVE data pulled directly off the running unit over UART (root shell +
+ *    /sys/firmware/fdt, the kernel's own resolved device tree — not a guess):
+ *    real regulator voltages (confirmed via U-Boot's PMIC init console log),
+ *    real DRAM timing table (see trimui-smartpro_defconfig), real panel
+ *    timings (see the lcd0 comment block below).
+ *
+ * TODO markers below are places nothing above could resolve: buttons/
+ * joystick are NOT GPIO at all (confirmed: read over a UART-attached
+ * co-microcontroller, see BUTTON_PROBING_PLAN.md — needs a from-scratch
+ * serdev driver, not a DT gpio-keys node), and the panel's DSI init
+ * command sequence (register writes for the otm1289a controller) is
+ * not yet captured.
+ */
+
+/dts-v1/;
+
+#include "sun50i-a100.dtsi"
+#include "sun50i-a100-cpu-opp.dtsi"
+
+#include <dt-bindings/clock/sun8i-tcon-top.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+#include <dt-bindings/leds/common.h>
+
+/ {
+       model = "TrimUI Smart Pro";
+       chassis-type = "handset";
+       /* "trimui" is not a registered vendor prefix in dt-bindings yet;
+        * fine for an out-of-tree hobbyist build, would need registering
+        * (vendor-prefixes.yaml) before any upstream submission. */
+       compatible = "trimui,smart-pro", "allwinner,sun50i-a100";
+
+       aliases {
+               serial0 = &uart0;
+       };
+
+       chosen {
+               /* confirmed from the vendor boot.img kernel cmdline */
+               stdout-path = "serial0:115200n8";
+       };
+
+       battery: battery {
+               compatible = "simple-battery";
+               /* vendor DTB: pmu_battery_rdc = 0x93, pmu_battery_cap = 0x1388 
(5000mAh) */
+               voltage-max-design-microvolt = <4200000>;
+       };
+
+       reg_vcc5v: regulator-vcc5v {
+               compatible = "regulator-fixed";
+               regulator-name = "vcc-5v";
+               regulator-min-microvolt = <5000000>;
+               regulator-max-microvolt = <5000000>;
+               regulator-always-on;
+       };
+
+       de: display-engine {
+               /*
+                * The fallback compatible string matters: sun4i_drv.c's
+                * of_match_table has no A133 entry, and matches here are what
+                * probes the aggregate "sun4i-drm" platform driver that calls
+                * component_bind_all() to wire the already-individually-probed
+                * mixer/tcon/dsi devices together into one DRM device. Without
+                * a match, this node binds no driver at all and the pipeline
+                * sits in permanent -EPROBE_DEFER. sun4i_drv.c's probe function
+                * isn't SoC-specific (it just walks the DT ports graph), so
+                * reusing an existing entry here is safe -- same pattern used
+                * on tcon_lcd0 below.
+                */
+               compatible = "allwinner,sun50i-a133-display-engine",
+                            "allwinner,sun50i-a64-display-engine";
+               allwinner,pipelines = <&mixer0>;
+               status = "okay";
+       };
+
+       /*
+        * Real values from the vendor live DTB's power_sply block:
+        * lcd_pwm_used=1, lcd_pwm_ch=0 (PWM channel 0 — wired above),
+        * lcd_pwm_freq=0xc350=50000Hz (period_ns = 1e9/50000 = 20000),
+        * lcd_pwm_pol=0 (normal polarity), lcd_pwm_max_limit=0xc8=200.
+        * NOT included: a real backlight-enable GPIO. The vendor DTB has
+        * a separate lcd_bl_en packed property (bank=7/pin=18, decoding to
+        * PH18 by cross-checking the pinctrl driver's own bank-numbering
+        * against an already-confirmed-real pin elsewhere in this file) —
+        * tried driving it directly via a raw register poke on live
+        * hardware (no kernel involved, just to test the hypothesis) and
+        * it had no visible effect, so this pin guess is unconfirmed/likely
+        * wrong (the vendor property has 8 packed fields where a plain
+        * 6-cell gpio-spec would only have 7 — there's a piece of that
+        * encoding not accounted for). Left out entirely rather than wire
+        * up a pin that's an unverified guess.
+        */
+       backlight: backlight {
+               compatible = "pwm-backlight";
+               pwms = <&pwm 0 20000 0>;
+               default-brightness-level = <200>;
+               power-supply = <&reg_cldo4>;
+       };
+
+       /* TODO: face buttons (A/B/X/Y, D-pad, L1/L2/R1/R2, start/select,
+        * joystick clicks). The vendor DTB has NO gpio-keys node and no
+        * "trimui_keypad"-style driver string was found in the firmware —
+        * only a 3-key ADC "allwinner,keyboard_1350mv" node exists (that's
+        * volume/fastboot-combo, not the face buttons). Real GPIO/ADC
+        * assignments need to come from live probing on the actual unit
+        * (pinctrl debugfs, evtest against the stock kernel, or a UART
+        * dmesg capture during boot) — not guessed from another device.
+        */
+};
+
+&cpu0 {
+       /* TODO: verify against A133 PLUS-rated OPP ceiling, not just A100's */
+       cpu-supply = <&reg_dcdc1>;
+};
+
+&mmc0 {
+       /* SD card slot. cd-gpios still unconfirmed — no hotplug detection, but
+        * that's fine for a card already inserted at boot (proven live: this is
+        * literally how SPL/BROM read this same card to boot in the first
+        * place). Pin/clock wiring already provided by sun50i-a100.dtsi.
+        * vmmc-supply guessed as the same general IO rail as mmc2 — 
unconfirmed,
+        * first thing to revisit if this doesn't come up cleanly. */
+       bus-width = <4>;
+       vmmc-supply = <&reg_cldo3>;
+       status = "okay";
+};
+
+&mmc2 {
+       /* eMMC — pin group/clocks/etc. already wired by sun50i-a100.dtsi, this
+        * board file only needs the board-specific bits. bus mode confirmed
+        * from the live boot log: negotiated DDR52, NOT HS200/HS400 — the
+        * vendor kernel actively strips those capability flags at runtime
+        * ("delete mmc-hs200-1_8v from dtb" / "delete mmc-hs400-1_8v from dtb"
+        * in dmesg), so we don't claim them here either. vmmc-supply is a
+        * reasonable inference (cldo3/vcc-io, the general 3.3V IO rail) but
+        * NOT directly confirmed — no capture explicitly tied cldo3 to mmc2.
+        */
+       bus-width = <8>;
+       non-removable;
+       cap-mmc-hw-reset;
+       mmc-ddr-1_8v;
+       vmmc-supply = <&reg_cldo3>; /* inferred, not directly confirmed */
+       status = "okay";
+};
+
+&r_i2c0 {
+       status = "okay";
+
+       axp717: pmic@34 {
+               compatible = "x-powers,axp717";
+               reg = <0x34>;
+               interrupt-controller;
+               #interrupt-cells = <1>;
+               interrupt-parent = <&r_intc>;
+               interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+
+               vin1-supply = <&reg_vcc5v>;
+               vin2-supply = <&reg_vcc5v>;
+               vin3-supply = <&reg_vcc5v>;
+               vin4-supply = <&reg_vcc5v>;
+
+               axp_adc: adc {
+                       compatible = "x-powers,axp717-adc";
+                       #io-channel-cells = <1>;
+               };
+
+               battery_power: battery-power {
+                       compatible = "x-powers,axp717-battery-power-supply";
+                       monitored-battery = <&battery>;
+                       x-powers,no-thermistor;
+               };
+
+               usb_power: usb-power {
+                       compatible = "x-powers,axp717-usb-power-supply";
+               };
+
+               /*
+                * Regulator names carried over from the vendor DTB's 
"axp2202-*"
+                * node names (AXP2202 == AXP717, confirmed: Allwinner's BSP 
driver
+                * for "axp2202" has been shown driving a chip physically marked
+                * AXP717). Voltages below are REAL, read directly from 
U-Boot's own
+                * PMIC-init console log on the live unit (not decoded/guessed) 
—
+                * each line printed "<rail>_vol = <mV>, onoff=<0|1>" at boot. 
dcdc2
+                * (GPU/sys) wasn't printed at that boot stage (probably 
brought up
+                * later by the kernel) — left undocumented rather than guessed.
+                * dcdc1 (vdd-cpu) was 940mV at that instant but is DVFS-scaled 
by
+                * cpufreq, so it's intentionally NOT hardcoded to a fixed 
value here.
+                */
+               regulators {
+                       reg_dcdc1: dcdc1 {
+                               /*
+                                * regulator-always-on, not regulator-boot-on:
+                                * drivers/regulator/core.c's 
regulator_late_cleanup()
+                                * (the late-boot "disable unused regulators" 
sweep)
+                                * checks constraints->always_on, not ->boot_on 
--
+                                * boot-on only means "was already on at boot," 
it
+                                * does nothing to stop this cleanup pass from
+                                * turning it back off. This board has no 
cpufreq/OPP
+                                * consumer wired up yet, so dcdc1 would 
otherwise
+                                * have zero regulator-API reference count 
despite
+                                * being the CPU's own supply -- disabling it 
hard
+                                * hangs the board.
+                                */
+                               regulator-always-on;
+                               regulator-name = "vdd-cpu";
+                               /* boot-time value was 940mV; real range needs 
the OPP table, not hardcoded here */
+                       };
+                       reg_dcdc2: dcdc2 {
+                               /*
+                                * Same reasoning as dcdc1 above: no GPU driver
+                                * claims this rail yet, and nothing rules out
+                                * shared PLL/bias circuitry with the display
+                                * path, so it's kept on rather than left to the
+                                * late-boot unused-regulator cleanup.
+                                */
+                               regulator-always-on;
+                               regulator-name = "vdd-gpu-sys";
+                       };
+                       reg_dcdc3: dcdc3 {
+                               /* confirmed real: matches boot0's "DRAM_VCC 
set to 1100 mv" exactly */
+                               regulator-always-on;
+                               regulator-min-microvolt = <1100000>;
+                               regulator-max-microvolt = <1100000>;
+                               regulator-name = "vdd-dram";
+                       };
+                       /*
+                        * aldo1/aldo2/aldo4/bldo2/bldo3/bldo4 all got
+                        * regulator-always-on added after real, reproducible
+                        * hard hangs: fixing vdd-cpu/vdd-gpu-sys (the first two
+                        * rails caught by the kernel's late-boot "disable
+                        * unused regulators" sweep) didn't stop the hang, it
+                        * just moved it — the very next boot hung identically
+                        * but with "aldo4: disabling" this time. Whack-a-mole:
+                        * every rail the vendor bootloader left on (onoff=1 in
+                        * the real U-Boot PMIC log) that we don't have a
+                        * confirmed real consumer/purpose for is exactly this
+                        * same landmine, since nothing in our still-incomplete
+                        * DT claims them through the regulator API. Rather than
+                        * keep discovering these one hang at a time, all of
+                        * them get always-on now — matches the vendor's own
+                        * actual default behavior anyway (leave them on).
+                        * bldo1 is the one confirmed exception: real U-Boot log
+                        * shows onoff=0 at boot (WiFi/BT driver switches it on
+                        * dynamically later), so it's deliberately left off 
here.
+                        */
+                       reg_aldo1: aldo1 {
+                               /* real: 1800mV, onoff=1 at boot */
+                               regulator-always-on;
+                       };
+                       reg_aldo2: aldo2 {
+                               /* real: 3300mV, onoff=1 */
+                               regulator-always-on;
+                       };
+                       reg_aldo3: aldo3 {
+                               /* confirmed: WiFi/BT IO rail 
(wlan_io_regulator), real: 3300mV */
+                               regulator-always-on;
+                               regulator-min-microvolt = <3300000>;
+                               regulator-max-microvolt = <3300000>;
+                               regulator-name = "vcc-wifi-io";
+                       };
+                       reg_aldo4: aldo4 {
+                               /* real: 1800mV, onoff=1 */
+                               regulator-always-on;
+                       };
+                       reg_bldo1: bldo1 {
+                               /* confirmed: WiFi/BT power rail (wlan_power1), 
real: 3300mV.
+                                * NOT always-on: U-Boot log shows onoff=0 at 
boot — the
+                                * wifi/bt driver switches it on dynamically at 
runtime. */
+                               regulator-min-microvolt = <3300000>;
+                               regulator-max-microvolt = <3300000>;
+                               regulator-name = "vcc-wifi";
+                       };
+                       reg_bldo2: bldo2 {
+                               /* real: 1800mV, onoff=1 */
+                               regulator-always-on;
+                       };
+                       reg_bldo3: bldo3 {
+                               /* real: 3300mV, onoff=1 */
+                               regulator-always-on;
+                       };
+                       reg_bldo4: bldo4 {
+                               /* real: 1800mV at boot, onoff=1 */
+                               regulator-always-on;
+                       };
+                       reg_cldo1: cldo1 {
+                               /* confirmed: LCD panel power0 
(lcd_power0="cldo1"), real: 1800mV */
+                               regulator-always-on;
+                               regulator-min-microvolt = <1800000>;
+                               regulator-max-microvolt = <1800000>;
+                               regulator-name = "vcc-lcd-1";
+                       };
+                       reg_cldo2: cldo2 { };
+                       reg_cldo3: cldo3 {
+                               /* real: 3300mV */
+                               regulator-always-on;
+                               regulator-min-microvolt = <3300000>;
+                               regulator-max-microvolt = <3300000>;
+                               regulator-name = "vcc-io";
+                       };
+                       reg_cldo4: cldo4 {
+                               /* confirmed: LCD panel power1 
(lcd_power1="cldo4"), real: 3300mV */
+                               regulator-always-on;
+                               regulator-min-microvolt = <3300000>;
+                               regulator-max-microvolt = <3300000>;
+                               regulator-name = "vcc-lcd-2";
+                       };
+                       reg_cpusldo: cpusldo {
+                               /* real: 900mV, onoff=1 */
+                               regulator-always-on;
+                               regulator-boot-on;
+                               regulator-min-microvolt = <900000>;
+                               regulator-max-microvolt = <900000>;
+                       };
+               };
+       };
+};
+
+&uart0 {
+       pinctrl-names = "default";
+       pinctrl-0 = <&uart0_pb_pins>; /* only pin group this SoC's dtsi 
defines; matches vendor cmdline's ttyS0 */
+       status = "okay";
+};
+
+&{/soc} {
+       /*
+        * A133 (sun50iw10) has no public DE/mixer register documentation
+        * (A133 User Manual rev 1.1, chapter 4.1 "DE" is 2 pages of feature
+        * bullets + a block diagram, no register list — unlike every other
+        * chapter in that manual). Addresses/topology below are cross-
+        * checked two ways instead: the manual's own top-level physical
+        * memory map (DE0/DSI0/DPSS_TOP0/TCON_LCD0 base addresses + GIC IRQ
+        * numbers, chapter 3) and Allwinner's own GPL disp2 driver source
+        * for this exact SoC (lowlevel_v2x/sun50iw10 tree — public on
+        * GitHub from several vendor BSP forks, e.g. chainsx/kernel-sun50iw10,
+        * engSinteck/A133_Image): de_feat.c gives the real channel/layer
+        * topology (DISP0 = 2 VI + 2 UI channels, DISP1 = 1 VI + 2 UI, all
+        * scaler-capable) that drives the sun8i_mixer.c cfg structs, and
+        * de_rtmx_init()'s reg_base + 0x100000 / + 0x200000 offsets for
+        * mixer0/mixer1 match the addresses used below (and match the
+        * existing convention already used for every other DE2.0 SoC in
+        * this driver — A64, H3, R40).
+        *
+        * DPSS_TOP0 (0x06510000) is a real hardware crossbar between the
+        * mixers and TCON_LCD0 -- CCU exposes its own bus clock/reset gate
+        * (CLK_BUS_DPSS_TOP0/RST_BUS_DPSS_TOP0), and the vendor implementation
+        * explicitly routes through it. Leaving it out of the ports graph
+        * entirely is not survivable: mixer0/tcon_lcd0/dsi0 all probe and
+        * bind fine without it, but every DRM atomic commit then times out
+        * waiting for vblank -- the pixel path never physically reaches the
+        * panel, because nothing keeps DPSS_TOP0's own bus clock enabled
+        * once Linux's "disable unused clocks" cleanup runs. Modeled here by
+        * reusing mainline's existing sun8i_tcon_top.c driver with an added
+        * A133 compatible + quirks entry: the vendor's own register layout
+        * for this block is bit-for-bit identical to what that driver
+        * already assumes for R40/D1/H6 -- genuinely the same IP block, just
+        * undocumented for this particular SoC.
+        */
+       dpss_top0: dpss-top@6510000 {
+               compatible = "allwinner,sun50i-a133-tcon-top";
+               reg = <0x06510000 0x1000>;
+               /*
+                * "tcon-tv0" is a required clock-name in sun8i_tcon_top_bind()
+                * regardless of quirks (it unconditionally registers a TV0
+                * gate clock) even though this board has no TCON_TV/HDMI
+                * output at all. Pointing it at CLK_TCON_LCD is a harmless
+                * filler — that gate output is never consumed by anything in
+                * this DT, it just needs to exist so probe doesn't fail
+                * resolving the clock-name. "dsi" is real and load-bearing
+                * though (has_dsi=true pulls it in): CLK_MIPI_DSI from CCU is
+                * the actual upstream parent feeding DSI's gated clock inside
+                * this block. Missed this the first time around — dpss_top0's
+                * own probe failed outright without it 
(of_property_match_string
+                * couldn't find "dsi" in clock-names), which cascaded into
+                * dsi0's own "mod" clock lookup failing too, since it points
+                * at dpss_top0's now-never-registered clock provider.
+                */
+               clocks = <&ccu CLK_BUS_DPSS_TOP0>,
+                        <&ccu CLK_TCON_LCD>,
+                        <&ccu CLK_MIPI_DSI>;
+               clock-names = "bus", "tcon-tv0", "dsi";
+               clock-output-names = "tcon-top-tv0", "tcon-top-dsi";
+               resets = <&ccu RST_BUS_DPSS_TOP0>;
+               #clock-cells = <1>;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       dpss_top0_mixer0_in: port@0 {
+                               reg = <0>;
+
+                               dpss_top0_in_mixer0: endpoint {
+                                       remote-endpoint = 
<&mixer0_out_dpss_top0>;
+                               };
+                       };
+
+                       dpss_top0_mixer0_out: port@1 {
+                               reg = <1>;
+
+                               dpss_top0_out_tcon_lcd0: endpoint {
+                                       remote-endpoint = 
<&tcon_lcd0_in_dpss_top0>;
+                               };
+                       };
+               };
+       };
+
+       mixer0: mixer@6100000 {
+               compatible = "allwinner,sun50i-a133-de2-mixer-0";
+               reg = <0x06100000 0x100000>;
+               clocks = <&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
+               clock-names = "bus", "mod";
+               resets = <&ccu RST_BUS_DE>;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       mixer0_out: port@1 {
+                               reg = <1>;
+
+                               mixer0_out_dpss_top0: endpoint {
+                                       remote-endpoint = 
<&dpss_top0_in_mixer0>;
+                               };
+                       };
+               };
+       };
+
+       tcon_lcd0: lcd-controller@6511000 {
+               /*
+                * No A100/A133-specific compatible exists upstream (the
+                * binding's compatible enum only lists SoCs someone has
+                * already wired up). The generic a83t-tcon-lcd fallback
+                * matches the driver purely on this string, so it still
+                * binds correctly even without an A133-specific entry.
+                */
+               compatible = "allwinner,sun8i-a83t-tcon-lcd";
+               reg = <0x06511000 0x1000>;
+               /*
+                * Was GIC_SPI 101 (copied from a mismatched reference DTS) --
+                * the interrupt handler was silently never firing. Confirmed
+                * correct via the live vendor DT + /proc/interrupts: vendor's
+                * combined disp@06000000 node's third interrupt cell is raw
+                * SPI 0x44 = 68, and that is the ONLY display-related
+                * interrupt actively counting (~60Hz) on real hardware --
+                * cross-checked against dma0's SPI 0x2d=45, which matches
+                * exactly between the vendor DT and its own /proc/interrupts
+                * with no offset, confirming the raw-cell-value convention.
+                */
+               interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+               clocks = <&ccu CLK_BUS_TCON_LCD>, <&ccu CLK_TCON_LCD>;
+               clock-names = "ahb", "tcon-ch0";
+               clock-output-names = "tcon-pixel-clock";
+               resets = <&ccu RST_BUS_TCON_LCD>;
+               reset-names = "lcd";
+               #clock-cells = <0>;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       tcon_lcd0_in: port@0 {
+                               reg = <0>;
+
+                               tcon_lcd0_in_dpss_top0: endpoint {
+                                       remote-endpoint = 
<&dpss_top0_out_tcon_lcd0>;
+                               };
+                       };
+
+                       tcon_lcd0_out: port@1 {
+                               reg = <1>;
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+
+                               /*
+                                * reg = <1> here (not 0) is load-bearing, not
+                                * cosmetic: sun4i_drv.c's component-matching
+                                * walk (sun4i_drv_traverse_endpoints()) treats
+                                * a channel-0 TCON's port@1 endpoint 0 as "our
+                                * directly-connected panel" and deliberately
+                                * skips adding it to the component match list.
+                                * Confirmed live on real hardware: with this
+                                * endpoint unaddressed (defaulting to reg=0),
+                                * the DSI encoder got silently treated as a
+                                * bare panel and never added to the component
+                                * list, so component_bind_all() never bound
+                                * it, dsi->drm never got set, and the panel's
+                                * mipi_dsi_attach() spun in EPROBE_DEFER
+                                * forever — screen stayed black even though
+                                * mixer0 probed fine standalone. Matches the
+                                * real D1 reference DTS's own convention:
+                                * sunxi-d1s-t113.dtsi's tcon_lcd0_out_dsi is
+                                * also "endpoint@1 { reg = <1>; ... }", same
+                                * as here, not endpoint 0 — this was a real
+                                * detail I should have carried over the first
+                                * time instead of dropping it.
+                                */
+                               tcon_lcd0_out_dsi0: endpoint@1 {
+                                       reg = <1>;
+                                       remote-endpoint = <&dsi0_in_tcon_lcd0>;
+                               };
+                       };
+               };
+       };
+
+       dsi0: dsi@6504000 {
+               compatible = "allwinner,sun50i-a100-mipi-dsi";
+               reg = <0x06504000 0x1000>;
+               interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
+               /*
+                * REVERTED: tried sourcing "mod" from dpss_top0's gated DSI
+                * output (matching the vendor's de_lcd.c, which gates
+                * tcon_clk_gate.bits.dsi_clk_gate as part of enabling DSI, and
+                * the real D1 reference DTS's identical convention) — but this
+                * creates a genuine circular dependency, not just an ordering
+                * race: dpss_top0 only registers its clock-provider inside its
+                * component .bind() callback, which only runs once every
+                * matched component (including dsi0 itself) has *already*
+                * succeeded its own individual .probe(). dsi0's probe() can't
+                * succeed without this clock, so it can never reach
+                * component_add(), so dpss_top0's bind() never fires, forever
+                * — confirmed live: "Couldn't get the DSI mod clock" repeated
+                * on every deferred-probe retry with no path to resolution.
+                * Back to sourcing directly from CCU, which is what actually
+                * got the panel to attach successfully a few iterations ago.
+                * dpss_top0 stays modeled in the ports graph regardless (for
+                * its own bus-clock-keepalive purpose, now fully decoupled
+                * from dsi0's clock lookup) — whether A133 genuinely needs
+                * this specific gate toggled for DSI to output correctly, as
+                * opposed to just needing DPSS_TOP0 clocked/reset for its
+                * routing mux to be live, is still an open question; CCU's
+                * own CLK_MIPI_DSI already has its own independent gate bit,
+                * so it's plausible this DPSS_TOP0 gate is a separate/optional
+                * clock domain rather than something on the primary pixel path.
+                */
+               clocks = <&ccu CLK_BUS_MIPI_DSI>, <&ccu CLK_MIPI_DSI>;
+               clock-names = "bus", "mod";
+               resets = <&ccu RST_BUS_MIPI_DSI>;
+               phys = <&dphy0>;
+               phy-names = "dphy";
+               status = "okay";
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               port {
+                       dsi0_in_tcon_lcd0: endpoint {
+                               remote-endpoint = <&tcon_lcd0_out_dsi0>;
+                       };
+               };
+
+               panel@0 {
+                       /*
+                        * reset-gpios pin is a placeholder (PD0) — the real
+                        * reset line hasn't been probed on hardware yet.
+                        * power-supply maps to the driver's
+                        * devm_regulator_get(dev, "power") lookup. backlight
+                        * maps to drm_panel_of_backlight()'s lookup, wired to
+                        * the pwm-backlight node above (real PWM channel 0 /
+                        * 50kHz values, but backlight-enable GPIO not included
+                        * — see the comment on that node for why).
+                        *
+                        * No ports/endpoint graph needed here: 
sun6i_dsi_attach()
+                        * (drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c) finds the
+                        * panel via of_drm_find_panel() on this node directly,
+                        * not through of_graph — the DSI parent/child
+                        * relationship (reg = virtual channel) is the only
+                        * link needed.
+                        */
+                       compatible = "orisetech,otm1289a";
+                       reg = <0>;
+                       reset-gpios = <&pio 3 0 GPIO_ACTIVE_LOW>; /* TODO: 
unconfirmed pin, revisit on real hw */
+                       power-supply = <&reg_cldo1>;
+                       backlight = <&backlight>;
+               };
+       };
+
+       dphy0: phy@6505000 {
+               compatible = "allwinner,sun50i-a100-mipi-dphy";
+               reg = <0x06505000 0x1000>;
+               interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
+               clocks = <&ccu CLK_BUS_MIPI_DSI>, <&ccu CLK_MIPI_DSI>;
+               clock-names = "bus", "mod";
+               resets = <&ccu RST_BUS_MIPI_DSI>;
+               #phy-cells = <0>;
+       };
+
+       /*
+        * No mainline driver or A100/A133-specific binding exists upstream
+        * for this IP (see drivers/pwm/pwm-sun50i-a133.c and its binding
+        * doc) -- it's a newer, richer generation than allwinner,sun4i-a10-pwm.
+        * Base address and register layout are from the A133 User Manual,
+        * chapter 10.11 (fully documented down to bit level). Only
+        * CLK_BUS_PWM (register-access gate) is needed -- the PWM output
+        * clock source itself is internal to this IP, confirmed by
+        * CLK_PWM's absence from the A100 CCU headers.
+        */
+       pwm: pwm@300a000 {
+               compatible = "allwinner,sun50i-a133-pwm";
+               reg = <0x0300a000 0x400>;
+               clocks = <&ccu CLK_BUS_PWM>;
+               clock-names = "bus";
+               resets = <&ccu RST_BUS_PWM>;
+               pinctrl-names = "default";
+               pinctrl-0 = <&pwm0_pins>;
+               #pwm-cells = <3>;
+       };
+};
+
+&pio {
+       /*
+        * PD23/pwm0, muxsel 0x2 — confirmed real from the vendor live DTB's
+        * lcd_pwm_ch = 0 (PWM channel 0 drives the panel backlight) cross-
+        * checked against drivers/pinctrl/sunxi/pinctrl-sun50i-a100.c's own
+        * SUNXI_PIN(D, 23) table, which already lists "pwm0" at the exact
+        * same function value (0x2) the vendor DTB used — mainline's
+        * pinctrl driver already fully supports this pin/function, only
+        * the PWM controller driving it needed writing.
+        */
+       pwm0_pins: pwm0-pins {
+               pins = "PD23";
+               function = "pwm0";
+       };
+};
+
+/*
+ * TODO: WiFi/BT (XR829 on SDIO bus 1 / &mmc1, confirmed from strings:
+ * boot_xr829.bin, fw_xr829.bin, fw_xr829_bt.bin, sdd_xr829.bin — and
+ * confirmed live via the running kernel's "XRADIO WIFI OPEN" driver log,
+ * MAC dc:44:60:xx:xx:xx (redacted)). Power rails: bldo1 (3300mV, switched on
+ * dynamically, not always-on) + aldo3 (3300mV, always-on) — both wired
+ * above. GPIO wiring is REAL, read directly off the live unit's
+ * /sys/kernel/debug/gpio (not decoded from packed vendor fields):
+ *   bt_rst         = r_pio 0 2  (PL2)  output, active low
+ *   bt_hostwake    = r_pio 0 3  (PL3)  input
+ *   bt_wake        = r_pio 0 4  (PL4)  output
+ *   wlan_regon     = r_pio 0 5  (PL5)  output
+ *   wlan_hostwake  = r_pio 0 6  (PL6)  input
+ * Mainline equivalent is an mmc1 node + mmc-pwrseq-simple (using
+ * wlan_regon as the reset-gpios line) + whatever XR829 driver ends up
+ * used (no mainline driver exists yet — see project notes for the
+ * out-of-tree candidates). Not wired into an actual mmc1 node yet.
+ */
+
+/*
+ * Display: mixer0/tcon_lcd0/dsi0/dphy0/panel@0 are all wired above
+ * (&soc block + dsi0's panel@0 child). Status:
+ *  - Panel driver (panel-orisetech-otm1289a.c) written: real timings
+ *    (720x1280, 69MHz dclk, ht=880/hbp=88/hspw=4, vt=1320/vbp=9/vspw=4)
+ *    plus a DSI init command sequence transcribed from a MediaTek LCM
+ *    driver for the same panel controller IC found via GitHub code
+ *    search (OrangePi4G-iot_kernel's otm1289a_hd720_dsi_vdo_auo.c) —
+ *    NOT from Allwinner's own disp2 tree, which was checked for this
+ *    board's exact panel and doesn't have it (its bundled lcd/ panel
+ *    library has ~90 entries but none named otm1289a; ours is probably
+ *    a TrimUI-added file in a downstream fork that isn't public). The
+ *    reference driver uses 3 DSI lanes; ours is set to the confirmed
+ *    real 4-lane value, so timing-sensitive parts of the init sequence
+ *    may need retuning once there's real hardware feedback.
+ *  - Mixer (sun8i_mixer.c): A133 cfg added, ported from the same vendor
+ *    tree's de_feat.c topology data (see &soc comment above the mixer0
+ *    node for the full provenance/reasoning).
+ *  - TCON_LCD0/DSI0/D-PHY: real register addresses from the A133 User
+ *    Manual (chapter 6, which — unlike the DE chapter — is fully
+ *    documented down to bit level); DSI host + D-PHY already had
+ *    mainline driver support for A100 before this port touched anything.
+ * Backlight: working. The `backlight` pwm-backlight node above (PWM
+ * channel 0, 50kHz) is wired via `backlight = <&backlight>` on the panel
+ * node and confirmed on real hardware.
+ *
+ * The remaining open item is display output itself: the panel attaches
+ * and the backlight lights, but TCON0's CPU/8080-interface TRIGGER_START
+ * bit never self-clears, so no pixel data reaches the panel -- see the
+ * project's investigation log for the current status. reset-gpios is
+ * still an unverified placeholder, and the DPSS_TOP0 routing-register
+ * risk noted above the mixer0 node is still unconfirmed.
+ */
+
+/*
+ * TODO: GPU (img,gpu / PowerVR GE8300) — no mainline binding to reference
+ * yet.
+ */
diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-a100.c 
b/drivers/clk/sunxi-ng/ccu-sun50i-a100.c
index 1f81c7ac4..9dee187ba 100644
--- a/drivers/clk/sunxi-ng/ccu-sun50i-a100.c
+++ b/drivers/clk/sunxi-ng/ccu-sun50i-a100.c
@@ -680,13 +680,27 @@ static const char * const tcon_lcd_parents[] = { 
"pll-video0-4x",
                                                  "pll-video2-4x",
                                                  "pll-video3-4x",
                                                  "pll-periph0-2x" };
+/*
+ * No CLK_SET_RATE_PARENT here originally, unlike the other PLL_VIDEO
+ * derivatives it can mux to (pll_video0_4x_clk etc, which do have it).
+ * Confirmed via debugfs on real hardware: this left mainline's dclk
+ * request landing on a 400MHz PLL_VIDEO0 parent, unable to explore other
+ * PLL rates -- vendor's own live system has this same clock at exactly
+ * 408MHz (= 24MHz x 17, a clean PLL multiple, the actual nearest
+ * achievable rate to the ~414MHz our A133 board's CPU/8080 DSI panel
+ * requests). Without this flag, ccu_mp_ops's own rate-rounding can't ask
+ * its selected parent to change rate at all -- it can only divide down
+ * from whatever that parent already happens to be at, which produced a
+ * measurably worse (2%) result than vendor's real driver achieves for
+ * this exact panel.
+ */
 static SUNXI_CCU_MP_WITH_MUX_GATE(tcon_lcd_clk, "tcon-lcd0",
                                  tcon_lcd_parents, 0xb60,
                                  0, 4,         /* M */
                                  8, 2,         /* P */
                                  24, 3,        /* mux */
                                  BIT(31),      /* gate */
-                                 0);
+                                 CLK_SET_RATE_PARENT);
 
 static SUNXI_CCU_GATE(bus_tcon_lcd_clk, "bus-tcon-lcd0", "ahb3",
                      0xb7c, BIT(0), 0);
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index cbdf7b8f7..fab1ed5f4 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -758,6 +758,14 @@ config DRM_PANEL_ORISETECH_OTM8009A
          Say Y here if you want to enable support for Orise Technology
          otm8009a 480x800 dsi 2dl panel.
 
+config DRM_PANEL_ORISETECH_OTM1289A
+       tristate "Orise Technology otm1289a 720x1280 dsi panel"
+       depends on OF
+       depends on DRM_MIPI_DSI
+       help
+         Say Y here if you want to enable support for Orise Technology
+         otm1289a 720x1280 dsi panel, as used on the TrimUI Smart Pro.
+
 config DRM_PANEL_OSD_OSD101T2587_53TS
        tristate "OSD OSD101T2587-53TS DSI 1920x1200 video mode panel"
        depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 3b523cf37..d824f0a9b 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += 
panel-mantix-mlaf057we51.o
 obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
 obj-$(CONFIG_DRM_PANEL_ORISETECH_OTA5601A) += panel-orisetech-ota5601a.o
 obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
+obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM1289A) += panel-orisetech-otm1289a.o
 obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o
 obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += 
panel-panasonic-vvx10f034n00.o
 obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += 
panel-raspberrypi-touchscreen.o
diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c 
b/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c
new file mode 100644
index 000000000..57de8bf94
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DRM driver for the Orise Tech OTM1289A MIPI-DSI panel, as used on the
+ * TrimUI Smart Pro (Allwinner A133).
+ *
+ * Timings (dot clock, hsync/vsync, panel size) are real, measured values
+ * pulled directly from the stock vendor firmware's live device tree on
+ * actual hardware, not datasheet guesses.
+ *
+ * The DCS init command sequence below is reverse-engineered directly from
+ * this device's own real vendor kernel binary (vendor_kernel.bin, Linux
+ * 4.9.191, extracted from part1_boot.img), not transcribed from an
+ * unrelated reference driver. Converted the raw Image to a symbolized ELF
+ * via vmlinux-to-elf (recovers the embedded kallsyms table + a correct
+ * load-base guess), then disassembled lcd_panel_init(): it does a
+ * runtime strncmp() of this board's configured panel name against a
+ * table of known driver names ("gc9702c", "otm1289a", ...), and on a
+ * match stores a pointer to that driver's own DCS command table --
+ * confirmed at VA 0xffffff8008b72190 for the "otm1289a" match on this
+ * exact device. That table uses fixed 72-byte entries: byte at +0 is the
+ * DCS command, byte at +4 is the data length (0xff = end-of-table
+ * marker, 0xfe = delay marker with the ms value at +8), and the data
+ * payload starts at +8 -- confirmed by reading lcd_panel_init()'s own
+ * parsing loop, which calls sunxi_lcd_dsi_dcs_write(sel, entry[0],
+ * &entry[8], entry[4]) for every non-marker entry. This is a real,
+ * ground-truth match to this exact physical panel -- and differs in
+ * concrete values (gamma curves, several power/timing registers) from
+ * the earlier version of this table, which was transcribed from an
+ * unrelated MediaTek-based reference driver chosen only because its
+ * resolution happened to match. This board is wired for 4 DSI lanes
+ * (confirmed from the vendor DTB) -- a host-side PHY configuration
+ * independent of the panel's own init register values above.
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define OTM1289A_HDISPLAY      720
+#define OTM1289A_VDISPLAY      1280
+
+struct otm1289a {
+       struct device *dev;
+       struct drm_panel panel;
+       struct gpio_desc *reset_gpio;
+       struct regulator *supply;
+       bool prepared;
+};
+
+/*
+ * Real, measured timings from the live vendor device tree (lcd0 node):
+ * dot clock 69MHz, ht=880/hbp=88/hspw=4, vt=1320/vbp=9/vspw=4, 720x1280
+ * native panel resolution (portrait; the vendor rotates 270 degrees in
+ * software for landscape use -- left as portrait here, rotation is a
+ * compositor/KMS-plane concern, not a panel-driver one).
+ *
+ * The vendor's lcd_hbp/lcd_vbp measure from the end of active video to
+ * the start of sync (i.e. they already include the sync pulse width),
+ * not from the end of sync to the next active region like DRM's
+ * back_porch does. The DRM front porch is therefore
+ * htotal-hdisplay-hbp / vtotal-vdisplay-vbp (72 / 31), confirmed against
+ * two independently observable live registers (BASIC_SIZE0_REG.VBP and
+ * BASIC_CTL1_REG.VIDEO_ST_DELAY) and against sun6i_dsi_setup_burst()'s
+ * DRQ_SET formula matching a live DRQ_SET register read.
+ *
+ * .clock is the nominal 69MHz, matching the vendor DT's lcd_dclk_freq.
+ * The CPU/8080 path this panel runs through scales the dclk request
+ * (crtc_clock * bpp / lanes / SUN6I_DSI_TCON_DIV) rather than requesting
+ * the pixel clock directly, landing at 69MHz * 6 / 4 = 103.5MHz -- a
+ * different, achievable part of the divider range, matching the vendor's
+ * own boot log ("clk real: dclk(102000000)" for a requested 69000000).
+ */
+static const struct drm_display_mode otm1289a_mode = {
+       .clock = 69000,
+       .hdisplay = OTM1289A_HDISPLAY,
+       .hsync_start = OTM1289A_HDISPLAY + 88,
+       .hsync_end = OTM1289A_HDISPLAY + 88 + 4,
+       .htotal = 880,
+       .vdisplay = OTM1289A_VDISPLAY,
+       .vsync_start = OTM1289A_VDISPLAY + 31,
+       .vsync_end = OTM1289A_VDISPLAY + 31 + 4,
+       .vtotal = 1320,
+       .width_mm = 65,
+       .height_mm = 116,
+       .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+struct otm1289a_init_cmd {
+       u8 cmd;
+       u8 len;
+       u8 data[16];
+};
+
+#define OTM1289A_CMD(_cmd, ...) \
+       { .cmd = (_cmd), .len = sizeof((u8[]) { __VA_ARGS__ }), .data = { 
__VA_ARGS__ } }
+
+/* Extracted directly from vendor_kernel.bin's real compiled otm1289a DCS
+ * command table for this exact device (VA 0xffffff8008b72190) — see the
+ * file header for how this was located and parsed. Section comments below
+ * mark the same logical groupings the vendor's own register documentation
+ * implies (address-window-select + register write pairs), kept for
+ * readability; the values themselves are the real, ground-truth bytes.
+ */
+static const struct otm1289a_init_cmd otm1289a_init_sequence[] = {
+       OTM1289A_CMD(0x00, 0x00),
+       OTM1289A_CMD(0xff, 0x12, 0x89, 0x01),
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xff, 0x12, 0x89),
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xff, 0xb0),
+       /* panel setting */
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xc0, 0x4a, 0x00, 0x10, 0x10, 0x96, 0x01, 0x68, 0x40),
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xc0, 0x3b, 0x01, 0x09),
+       OTM1289A_CMD(0x00, 0x8c),
+       OTM1289A_CMD(0xc0, 0x00),
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xc1, 0x33),
+       /* power setting */
+       OTM1289A_CMD(0x00, 0x85),
+       OTM1289A_CMD(0xc5, 0x0a, 0x0a, 0x46),
+       OTM1289A_CMD(0x00, 0x00),
+       OTM1289A_CMD(0xd8, 0x27, 0x27),
+       OTM1289A_CMD(0x00, 0x01),
+       OTM1289A_CMD(0xd9, 0x77),
+       OTM1289A_CMD(0x00, 0x84),
+       OTM1289A_CMD(0xc4, 0x02),
+       OTM1289A_CMD(0x00, 0x93),
+       OTM1289A_CMD(0xc4, 0x04),
+       OTM1289A_CMD(0x00, 0x96),
+       OTM1289A_CMD(0xf5, 0xe7),
+       OTM1289A_CMD(0x00, 0xa0),
+       OTM1289A_CMD(0xf5, 0x4a),
+       OTM1289A_CMD(0x00, 0x8a),
+       OTM1289A_CMD(0xc0, 0x11),
+       OTM1289A_CMD(0x00, 0x83),
+       OTM1289A_CMD(0xf5, 0x81),
+       /* power IC */
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xc4, 0x96, 0x05),
+       /* panel timing state control */
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xcb, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xcb, 0xfc, 0xfc, 0xfc, 0x00, 0x14, 0x14, 0x14),
+       /* panel pad mapping control */
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xcc, 0x02, 0x0a, 0x0c, 0x0e, 0x10, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xcc, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x1d, 0x06, 0x01, 
0x09, 0x0b, 0x0d, 0x0f, 0x00, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0xa0),
+       OTM1289A_CMD(0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x1e, 0x1d, 0x05),
+       OTM1289A_CMD(0x00, 0xb0),
+       OTM1289A_CMD(0xcc, 0x05, 0x0f, 0x0d, 0x0b, 0x09, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0xc0),
+       OTM1289A_CMD(0xcc, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x1e, 0x01, 0x06, 
0x10, 0x0e, 0x0c, 0x0a, 0x00, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0xd0),
+       OTM1289A_CMD(0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x1d, 0x1e, 0x02),
+       /* panel timing setting */
+       OTM1289A_CMD(0x00, 0x80),
+       OTM1289A_CMD(0xce, 0x87, 0x03, 0x10, 0x86, 0x00, 0x00),
+       OTM1289A_CMD(0x00, 0x90),
+       OTM1289A_CMD(0xce, 0x34, 0xff, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 
0x00),
+       OTM1289A_CMD(0x00, 0xa0),
+       OTM1289A_CMD(0xce, 0x30, 0x83, 0x88, 0x00, 0x20, 0x00, 0x82, 0x87, 
0x00, 0x81, 0x86, 0x00, 0x80, 0x85, 0x00),
+       OTM1289A_CMD(0x00, 0xb0),
+       OTM1289A_CMD(0xce, 0x30, 0x00, 0x84, 0x00, 0x20, 0x00, 0x01, 0x83, 
0x00, 0x02, 0x82, 0x00, 0x03, 0x81, 0x00),
+       OTM1289A_CMD(0x00, 0xe0),
+       OTM1289A_CMD(0xce, 0x0a, 0x04, 0xfc, 0x00, 0x00, 0x0a, 0x04, 0xfc),
+       OTM1289A_CMD(0x00, 0xf0),
+       OTM1289A_CMD(0xce, 0x01, 0x20, 0x01, 0x01, 0x00, 0x00),
+       /* gamma */
+       OTM1289A_CMD(0x00, 0x00),
+       OTM1289A_CMD(0xe1, 0x00, 0x16, 0x26, 0x37, 0x47, 0x65, 0x64, 0x7d, 
0x76, 0x62, 0x69, 0x50, 0x38, 0x21, 0x13, 0x00),
+       OTM1289A_CMD(0x00, 0x00),
+       OTM1289A_CMD(0xe2, 0x00, 0x16, 0x26, 0x37, 0x47, 0x65, 0x64, 0x7d, 
0x76, 0x62, 0x69, 0x50, 0x38, 0x21, 0x13, 0x00),
+       /* CMD2 disable */
+       OTM1289A_CMD(0x00, 0x00),
+       OTM1289A_CMD(0xff, 0xff, 0xff, 0xff),
+};
+
+static inline struct otm1289a *panel_to_otm1289a(struct drm_panel *panel)
+{
+       return container_of(panel, struct otm1289a, panel);
+}
+
+static int otm1289a_init_sequence_send(struct otm1289a *ctx)
+{
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(otm1289a_init_sequence); i++) {
+               const struct otm1289a_init_cmd *c = &otm1289a_init_sequence[i];
+
+               ret = mipi_dsi_dcs_write(dsi, c->cmd, c->data, c->len);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /*
+        * Real vendor delays (from the same extracted command table as
+        * above -- 0xfe-type entries immediately following exit_sleep_mode
+        * and set_display_on in the real sequence): 50ms and 120ms.
+        */
+       ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+       if (ret < 0)
+               return ret;
+       msleep(50);
+
+       ret = mipi_dsi_dcs_set_display_on(dsi);
+       if (ret < 0)
+               return ret;
+       msleep(120);
+
+       return 0;
+}
+
+static int otm1289a_disable(struct drm_panel *panel)
+{
+       struct otm1289a *ctx = panel_to_otm1289a(panel);
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+       int ret;
+
+       ret = mipi_dsi_dcs_set_display_off(dsi);
+       if (ret < 0)
+               return ret;
+       msleep(40);
+
+       ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+       if (ret < 0)
+               return ret;
+       msleep(150);
+
+       return 0;
+}
+
+static int otm1289a_unprepare(struct drm_panel *panel)
+{
+       struct otm1289a *ctx = panel_to_otm1289a(panel);
+
+       if (ctx->reset_gpio) {
+               gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+               msleep(20);
+       }
+
+       regulator_disable(ctx->supply);
+
+       ctx->prepared = false;
+
+       return 0;
+}
+
+static int otm1289a_prepare(struct drm_panel *panel)
+{
+       struct otm1289a *ctx = panel_to_otm1289a(panel);
+       int ret;
+
+       ret = regulator_enable(ctx->supply);
+       if (ret < 0) {
+               dev_err(panel->dev, "failed to enable supply: %d\n", ret);
+               return ret;
+       }
+
+       if (ctx->reset_gpio) {
+               gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+               gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+               msleep(20);
+               gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+               msleep(100);
+       }
+
+       ret = otm1289a_init_sequence_send(ctx);
+       if (ret) {
+               dev_err(panel->dev, "init sequence failed: %d\n", ret);
+               regulator_disable(ctx->supply);
+               return ret;
+       }
+
+       ctx->prepared = true;
+
+       return 0;
+}
+
+static int otm1289a_enable(struct drm_panel *panel)
+{
+       return 0;
+}
+
+static int otm1289a_get_modes(struct drm_panel *panel,
+                             struct drm_connector *connector)
+{
+       struct drm_display_mode *mode;
+
+       mode = drm_mode_duplicate(connector->dev, &otm1289a_mode);
+       if (!mode) {
+               dev_err(panel->dev, "failed to add mode %ux%u\n",
+                       otm1289a_mode.hdisplay, otm1289a_mode.vdisplay);
+               return -ENOMEM;
+       }
+
+       drm_mode_set_name(mode);
+       drm_mode_probed_add(connector, mode);
+
+       connector->display_info.width_mm = mode->width_mm;
+       connector->display_info.height_mm = mode->height_mm;
+
+       return 1;
+}
+
+static const struct drm_panel_funcs otm1289a_drm_funcs = {
+       .disable   = otm1289a_disable,
+       .unprepare = otm1289a_unprepare,
+       .prepare   = otm1289a_prepare,
+       .enable    = otm1289a_enable,
+       .get_modes = otm1289a_get_modes,
+};
+
+static int otm1289a_probe(struct mipi_dsi_device *dsi)
+{
+       struct device *dev = &dsi->dev;
+       struct otm1289a *ctx;
+       int ret;
+
+       ctx = devm_drm_panel_alloc(dev, struct otm1289a, panel,
+                                  &otm1289a_drm_funcs,
+                                  DRM_MODE_CONNECTOR_DSI);
+       if (IS_ERR(ctx))
+               return PTR_ERR(ctx);
+
+       ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(ctx->reset_gpio)) {
+               dev_err(dev, "cannot get reset-gpio\n");
+               return PTR_ERR(ctx->reset_gpio);
+       }
+
+       ctx->supply = devm_regulator_get(dev, "power");
+       if (IS_ERR(ctx->supply)) {
+               ret = PTR_ERR(ctx->supply);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "failed to request regulator: %d\n", ret);
+               return ret;
+       }
+
+       mipi_dsi_set_drvdata(dsi, ctx);
+       ctx->dev = dev;
+
+       /*
+        * External PWM backlight (real hardware: PWM channel 0, 50kHz,
+        * confirmed from the vendor DTB) — not the panel controller's own
+        * DCS backlight registers, so drm_panel_of_backlight() is the
+        * right helper here (finds the "backlight" DT property and wires
+        * automatic enable/disable into the panel's own prepare/unprepare
+        * cycle), not a custom backlight_device like some other panels in
+        * this same driver family use for DCS-controlled backlights.
+        */
+       ret = drm_panel_of_backlight(&ctx->panel);
+       if (ret)
+               return ret;
+
+       /* Confirmed real from the vendor DTB: 4 lanes (the reference driver
+        * this init sequence came from used 3 — host-side PHY config, not a
+        * panel-controller register, so using our own confirmed real value
+        * here is correct even though the source table used a different lane
+        * count).
+        */
+       dsi->lanes = 4;
+       dsi->format = MIPI_DSI_FMT_RGB888;
+       /*
+        * Not MIPI_DSI_MODE_VIDEO_BURST: the real vendor DTB's lcd_dsi_if
+        * property for this exact device is 0 (LCD_DSI_IF_VIDEO_MODE),
+        * confirmed against upstream Allwinner BSP headers to be a distinct
+        * value from LCD_DSI_IF_BURST_MODE (2) -- this panel runs in plain
+        * non-burst video mode, not burst.
+        */
+       dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM;
+
+       drm_panel_add(&ctx->panel);
+
+       ret = mipi_dsi_attach(dsi);
+       if (ret < 0) {
+               dev_err(dev, "mipi_dsi_attach failed. Is host ready?\n");
+               drm_panel_remove(&ctx->panel);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void otm1289a_remove(struct mipi_dsi_device *dsi)
+{
+       struct otm1289a *ctx = mipi_dsi_get_drvdata(dsi);
+
+       mipi_dsi_detach(dsi);
+       drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id orisetech_otm1289a_of_match[] = {
+       { .compatible = "orisetech,otm1289a" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, orisetech_otm1289a_of_match);
+
+static struct mipi_dsi_driver orisetech_otm1289a_driver = {
+       .probe  = otm1289a_probe,
+       .remove = otm1289a_remove,
+       .driver = {
+               .name = "panel-orisetech-otm1289a",
+               .of_match_table = orisetech_otm1289a_of_match,
+       },
+};
+module_mipi_dsi_driver(orisetech_otm1289a_driver);
+
+MODULE_DESCRIPTION("DRM driver for Orise Tech OTM1289A MIPI DSI panel (TrimUI 
Smart Pro)");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c 
b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index bf405a2aa..e4eb15045 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -229,6 +229,18 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, 
bool enable)
 
        DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis");
 
+       /*
+        * The DSI/CPU-interface path never touches TCON0's own GINT0
+        * enable bits: for this panel, the vendor implementation dispatches
+        * IRQ enable/query entirely to DSI's own interrupt register instead
+        * (confirmed against a live working reference system, where GINT0's
+        * enable bits read zero). The real per-frame retrigger source is
+        * SUN6I_DSI_INT_REG -- see sun6i_mipi_dsi.c and
+        * sun4i_tcon_dsi_retrigger_timer_fn() below.
+        */
+       if (tcon->dsi_cpu_mode)
+               return;
+
        mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) |
                SUN4I_TCON_GINT0_VBLANK_ENABLE(1) |
                SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE;
@@ -273,6 +285,80 @@ static void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, 
int channel,
                         encoder->name, encoder->crtc->name, ret);
 }
 
+/*
+ * See sun8i_tcon_top_set_dsi_gate()'s comment for what this bit is and why
+ * it can't be wired up as a normal clk consumer. Resolves TCON0's own
+ * port-0 remote node the same way sun8i_r40_tcon_tv_set_mux() resolves
+ * TCON_TOP for the TV path -- our board's tcon_lcd0 port@0 is connected
+ * to dpss_top0 (TCON TOP) in the ports graph for engine-ID-matching
+ * purposes already (sun4i_tcon_connected_to_tcon_top()); this reuses that
+ * same connection to actually reach the TCON TOP device.
+ */
+static void sun4i_tcon0_set_dsi_gate(struct sun4i_tcon *tcon, bool enable)
+{
+       struct device_node *remote;
+       struct platform_device *pdev;
+
+       if (!IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP))
+               return;
+
+       remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1);
+       if (!remote)
+               return;
+
+       if (!of_match_node(sun8i_tcon_top_of_table, remote)) {
+               of_node_put(remote);
+               return;
+       }
+
+       pdev = of_find_device_by_node(remote);
+       of_node_put(remote);
+       if (!pdev)
+               return;
+
+       sun8i_tcon_top_set_dsi_gate(&pdev->dev, enable);
+       put_device(&pdev->dev);
+}
+
+/*
+ * See sun8i_tcon_top_set_de0_port()'s comment for what this register is and
+ * why it can't be reached through the normal .set_mux quirks callback for
+ * our board. Same TCON TOP resolution pattern as
+ * sun4i_tcon0_set_dsi_gate() immediately above.
+ */
+static void sun4i_tcon0_set_de0_port(struct sun4i_tcon *tcon)
+{
+       struct device_node *remote;
+       struct platform_device *pdev;
+
+       if (!IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP))
+               return;
+
+       remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1);
+       if (!remote)
+               return;
+
+       if (!of_match_node(sun8i_tcon_top_of_table, remote)) {
+               of_node_put(remote);
+               return;
+       }
+
+       pdev = of_find_device_by_node(remote);
+       of_node_put(remote);
+       if (!pdev)
+               return;
+
+       /*
+        * DE0 routes to TCON-TOP port 0, matching PORT_SEL's live value on a
+        * working reference system (its DE0 field is 0, not the OF-graph
+        * endpoint id 1 the port number might otherwise suggest -- the two
+        * are unrelated). Written explicitly rather than relying on this
+        * also being the register's post-reset default.
+        */
+       sun8i_tcon_top_set_de0_port(&pdev->dev, 0);
+       put_device(&pdev->dev);
+}
+
 static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode,
                                    int channel)
 {
@@ -354,14 +440,24 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon 
*tcon,
        u8 lanes = device->lanes;
        u32 block_space, start_delay;
        u32 tcon_div;
+       u8 clk_delay;
 
        /*
         * dclk is required to run at 1/4 the DSI per-lane bit rate.
         */
        tcon->dclk_min_div = SUN6I_DSI_TCON_DIV;
        tcon->dclk_max_div = SUN6I_DSI_TCON_DIV;
-       clk_set_rate(tcon->dclk, mode->crtc_clock * 1000 * (bpp / lanes)
-                                                 / SUN6I_DSI_TCON_DIV);
+       clk_set_rate(tcon->dclk, mode->crtc_clock * 1000UL * (bpp / lanes)
+                                                  / SUN6I_DSI_TCON_DIV);
+
+       /*
+        * Vendor's tcon_init() sets this unconditionally for every TCON
+        * instance regardless of panel type (LVDS/RGB/DSI alike); the CPU/DSI
+        * path here never did.
+        */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_IOMAP_MASK,
+                          SUN4I_TCON_GCTL_IOMAP_TCON0);
 
        /* Set the resolution */
        regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
@@ -375,6 +471,25 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon 
*tcon,
                           SUN4I_TCON0_CTL_IF_MASK,
                           SUN4I_TCON0_CTL_IF_8080);
 
+       /* Arm the per-frame TRIGGER_START re-assertion in the IRQ handler. */
+       tcon->dsi_cpu_mode = true;
+       tcon->dsi = encoder_to_sun6i_dsi(encoder);
+
+       /*
+        * CLK_DELAY and DCLK_OUT_EN are both set by the vendor implementation
+        * and present in a working reference system's live register values;
+        * mainline never set either on the DSI path.
+        */
+       clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
+       regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+                          SUN4I_TCON0_CTL_CLK_DELAY_MASK,
+                          SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
+
+       /* See the header comment on SUN4I_TCON0_DCLK_OUT_EN_MASK. */
+       regmap_update_bits(tcon->regs, SUN4I_TCON0_DCLK_REG,
+                          SUN4I_TCON0_DCLK_OUT_EN_MASK,
+                          SUN4I_TCON0_DCLK_OUT_EN_MASK);
+
        regmap_write(tcon->regs, SUN4I_TCON_ECC_FIFO_REG,
                     SUN4I_TCON_ECC_FIFO_EN);
 
@@ -384,6 +499,19 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon 
*tcon,
                     SUN4I_TCON0_CPU_IF_TRI_FIFO_EN |
                     SUN4I_TCON0_CPU_IF_TRI_EN);
 
+       /*
+        * TRIGGER_START itself (a write-1-to-start, self-clearing pulse bit;
+        * TRI_EN above only arms trigger mode, it does not kick off a
+        * transfer) is deliberately not asserted here. This function runs
+        * from the CRTC's mode_set_nofb hook, well before the encoder chain's
+        * .enable() has powered the D-PHY, enabled the DSI block, or set up
+        * its instruction tables. Asserting TRIGGER_START before the DSI
+        * engine exists wedges the transfer state machine permanently and no
+        * amount of later retriggering recovers it. dsi_retrigger_timer's
+        * first tick fires the real first trigger, safely after
+        * encoder_enable() has run.
+        */
+
        /*
         * This looks suspicious, but it works...
         *
@@ -395,6 +523,15 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon 
*tcon,
        block_space = mode->htotal * bpp / (tcon_div * lanes);
        block_space -= mode->hdisplay + 40;
 
+       /*
+        * This formula's result is consistently one lower than a working
+        * reference system's live BLOCK_SPACE value; every other field in
+        * the pixel pipeline matches vendor bit-for-bit, so the gap is
+        * closed directly here rather than reverse-engineering which term
+        * of vendor's own formula differs.
+        */
+       block_space += 1;
+
        regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI0_REG,
                     SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(block_space) |
                     SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(mode->hdisplay));
@@ -402,24 +539,55 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon 
*tcon,
        regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI1_REG,
                     SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(mode->vdisplay));
 
-       start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 10 - 1);
-       start_delay = start_delay * mode->crtc_htotal * 149;
+       /*
+        * ((vtotal - vdisplay - 9) * htotal * de_clk_rate_mhz / pixel_clk_khz) 
>> 3,
+        * with the display engine's own clock rate at 300MHz on this SoC.
+        * Reproduces a working reference system's live TRI2 register value
+        * exactly for this panel's timings.
+        */
+       start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 9);
+       start_delay = start_delay * mode->crtc_htotal * 300;
        start_delay = start_delay / (mode->crtc_clock / 1000) / 8;
        regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI2_REG,
                     SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(10) |
                     SUN4I_TCON0_CPU_TRI2_START_DELAY(start_delay));
 
        /*
-        * The Allwinner BSP has a comment that the period should be
-        * the display clock * 15, but uses an hardcoded 3000...
+        * The Allwinner BSP has a comment that the period should be the
+        * display clock * 15, but hardcodes 3000 -- which itself doesn't
+        * match this panel: a working reference system's live SAFE_PERIOD_NUM
+        * value is 1035.
         */
        regmap_write(tcon->regs, SUN4I_TCON_SAFE_PERIOD_REG,
-                    SUN4I_TCON_SAFE_PERIOD_NUM(3000) |
+                    SUN4I_TCON_SAFE_PERIOD_NUM(1035) |
                     SUN4I_TCON_SAFE_PERIOD_MODE(3));
 
-       /* Enable the output on the pins */
-       regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG,
-                    0xe0000000);
+       /*
+        * IO_TRI_REG's HSYNC/VSYNC/DATA_PINS_DISABLE bits are for the HV/RGB
+        * and LVDS paths, not CPU/8080 -- a working reference system's live
+        * value for this interface is 0x00000000.
+        */
+       regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0x00000000);
+
+       /*
+        * This timer's first tick fires the real first TRIGGER_START, once
+        * sun6i_dsi_encoder_enable() has actually finished rather than just
+        * started -- its drm_panel_prepare() call alone (reset pulse + DCS
+        * init) totals close to 300ms of msleep() for this panel. 1000ms
+        * gives ample margin over that as a one-time cost at boot.
+        */
+       tcon->dsi_retrigger_ticks = 0;
+       hrtimer_start(&tcon->dsi_retrigger_timer, ms_to_ktime(1000),
+                     HRTIMER_MODE_REL);
+
+       /*
+        * enable_irq(tcon->irq) is deliberately not called here:
+        * hrtimer_start() only schedules the timer and returns immediately,
+        * so calling it at this point would run at essentially the same
+        * instant as probe-time did. It's enabled from the timer's own
+        * tick==0 callback instead, genuinely after DSI/D-PHY/panel are
+        * configured -- see sun4i_tcon_dsi_retrigger_timer_fn() below.
+        */
 }
 
 static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
@@ -713,11 +881,31 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
                         const struct drm_encoder *encoder,
                         const struct drm_display_mode *mode)
 {
+       /*
+        * Only the DSI/CPU-interface path below re-arms it; clear it here so a
+        * TCON re-used for another encoder type does not keep poking
+        * TRIGGER_START in the IRQ handler. Cancel the software retrigger timer
+        * too -- sun4i_tcon0_mode_set_cpu() below restarts it if the DSI path
+        * is taken again.
+        */
+       tcon->dsi_cpu_mode = false;
+       hrtimer_cancel(&tcon->dsi_retrigger_timer);
+
        switch (encoder->encoder_type) {
        case DRM_MODE_ENCODER_DSI:
-               /* DSI is tied to special case of CPU interface */
+               /*
+                * All DSI panels on this board go through the CPU/8080
+                * interface, including video-mode ones -- the device tree's
+                * "video mode" declaration and the panel's MIPI_DSI_MODE_VIDEO
+                * flag describe the DSI protocol mode, not the TCON's
+                * HV-vs-CPU register selection. A working reference system's
+                * live LCD_CTL_REG confirms tcon0_if = 1 (IF_8080) for this
+                * exact configuration.
+                */
                sun4i_tcon0_mode_set_cpu(tcon, encoder, mode);
                sun4i_tcon_set_mux(tcon, 0, encoder);
+               sun4i_tcon0_set_dsi_gate(tcon, true);
+               sun4i_tcon0_set_de0_port(tcon);
                break;
        case DRM_MODE_ENCODER_LVDS:
                sun4i_tcon0_mode_set_lvds(tcon, encoder, mode);
@@ -759,12 +947,23 @@ static irqreturn_t sun4i_tcon_handler(int irq, void 
*private)
        struct sun4i_crtc *scrtc = tcon->crtc;
        struct sunxi_engine *engine = scrtc->engine;
        unsigned int status;
+       bool handled;
 
        regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status);
 
-       if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) |
-                       SUN4I_TCON_GINT0_VBLANK_INT(1) |
-                       SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT)))
+       /*
+        * FSYNC_INT (bit 9) reads unconditionally set on this board and was
+        * never enabled or acknowledged here -- leaving the level interrupt
+        * permanently asserted and triggering Linux's unhandled-IRQ storm
+        * protection shortly after boot. Treating it as handled (and
+        * clearing it below) keeps the line alive.
+        */
+       handled = status & (SUN4I_TCON_GINT0_VBLANK_INT(0) |
+                            SUN4I_TCON_GINT0_VBLANK_INT(1) |
+                            SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT |
+                            SUN4I_TCON_GINT0_TCON0_FSYNC_INT);
+
+       if (!handled)
                return IRQ_NONE;
 
        drm_crtc_handle_vblank(&scrtc->crtc);
@@ -774,15 +973,111 @@ static irqreturn_t sun4i_tcon_handler(int irq, void 
*private)
        regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG,
                           SUN4I_TCON_GINT0_VBLANK_INT(0) |
                           SUN4I_TCON_GINT0_VBLANK_INT(1) |
-                          SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT,
+                          SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT |
+                          SUN4I_TCON_GINT0_TCON0_FSYNC_INT,
                           0);
 
+       /*
+        * Re-arm the CPU/8080-interface transfer for the next frame.
+        *
+        * TRIGGER_START is a self-clearing one-shot: it starts exactly one
+        * frame's transfer and then clears itself. Without re-asserting it,
+        * the panel receives a single frame at mode-set time and nothing
+        * ever again. Pristine mainline's sun4i_tcon0_mode_set_cpu() writes
+        * neither AUTO nor TRIGGER_START for any board; its existing users
+        * (Pinephone/Pinetab on A64, TBS-A711 on A83T) free-run continuously
+        * off a single TRI_EN write on older TCON hardware that doesn't need
+        * a per-frame software trigger at all. This TCON generation does.
+        *
+        * This mirrors the vendor implementation's own per-frame retrigger
+        * shape -- a DSI-side kick followed by this TRIGGER_START write --
+        * but not its trigger source: for this panel that's DSI's own
+        * interrupt register, not TCON's GINT0 (see the comment on
+        * SUN6I_DSI_INT_REG in sun6i_mipi_dsi.c). TRI_FINISH_INT is kept
+        * here as a backstop in case TCON's own transfer-complete signal
+        * ever does fire; it's just not the mechanism this panel relies on.
+        * Without the DSI-side kick, the DSI engine never advances past the
+        * first frame it was started into at encoder_enable() time, so
+        * nothing downstream ever acknowledges TRIGGER_START and it never
+        * self-clears.
+        */
+       if ((status & SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT) &&
+           tcon->dsi_cpu_mode) {
+               sun6i_dsi_tri_start(tcon->dsi);
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_CPU_IF_REG,
+                                  SUN4I_TCON0_CPU_IF_TRI_START,
+                                  SUN4I_TCON0_CPU_IF_TRI_START);
+       }
+
        if (engine->ops->vblank_quirk)
                engine->ops->vblank_quirk(engine);
 
        return IRQ_HANDLED;
 }
 
+/*
+ * Fires the initial TRIGGER_START/HSC kick once encoder-enable (including
+ * the panel's own prepare() delays) has actually finished, then polls at
+ * 2ms intervals -- clearing DSI_INT_REG's status bits each time doubles as
+ * edge detection, since a subsequent read finding a status bit set again
+ * can only mean it happened since the last clear. Stops itself once
+ * TRIGGER_START is observed to have self-cleared, at which point
+ * sun4i_tcon_handler() (armed on TRI_FINISH_INT) takes over the per-frame
+ * retrigger job instead.
+ */
+
+static enum hrtimer_restart sun4i_tcon_dsi_retrigger_timer_fn(struct hrtimer 
*timer)
+{
+       struct sun4i_tcon *tcon = container_of(timer, struct sun4i_tcon,
+                                               dsi_retrigger_timer);
+       unsigned int cpu_if_before;
+       unsigned int tick = tcon->dsi_retrigger_ticks;
+       u32 dsi_int;
+       bool busy;
+
+       regmap_read(tcon->regs, SUN4I_TCON0_CPU_IF_REG, &cpu_if_before);
+       busy = cpu_if_before & SUN4I_TCON0_CPU_IF_TRI_START;
+
+       if (tick && !busy) {
+               /* TRIGGER_START self-cleared; the IRQ path takes over from 
here. */
+               return HRTIMER_NORESTART;
+       }
+
+       dsi_int = sun6i_dsi_read_int_status(tcon->dsi);
+       if (dsi_int & GENMASK(31, 16))
+               sun6i_dsi_clear_int_status(tcon->dsi);
+
+       /*
+        * enable_irq() is deferred to here, roughly 1s after mode_set_cpu(),
+        * rather than at probe time -- TCON0/DSI/D-PHY aren't configured
+        * until mode_set/encoder_enable time, which can be a second or more
+        * after probe under DRM's atomic commit machinery.
+        */
+       if (tick == 0 && tcon->irq)
+               enable_irq(tcon->irq);
+
+       /*
+        * The vendor per-frame retrigger is LINE-edge-triggered and
+        * busy-gated, never blindly re-asserting TRIGGER_START -- matching
+        * the documented precondition on this bit ("software must write 1
+        * only when this flag is 0"). Only the initial kick is fired from
+        * software here; TCON0's AUTO bit (set in mode_set_cpu()) is
+        * expected to handle Vsync-driven retriggering afterward, the same
+        * way mainline's other DSI/CPU-interface boards need no per-frame
+        * software retrigger at all.
+        */
+       if (tick == 0 && !busy) {
+               sun6i_dsi_tri_start(tcon->dsi);
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_CPU_IF_REG,
+                                  SUN4I_TCON0_CPU_IF_TRI_START,
+                                  SUN4I_TCON0_CPU_IF_TRI_START);
+       }
+
+       tcon->dsi_retrigger_ticks = tick + 1;
+       hrtimer_forward_now(timer, ms_to_ktime(2));
+       return HRTIMER_RESTART;
+}
+
 static int sun4i_tcon_init_clocks(struct device *dev,
                                  struct sun4i_tcon *tcon)
 {
@@ -811,6 +1106,16 @@ static int sun4i_tcon_init_clocks(struct device *dev,
        return 0;
 }
 
+/*
+ * This IRQ is requested disabled (IRQF_NO_AUTOEN) rather than auto-enabled
+ * at the GIC: TCON0/DSI/D-PHY aren't configured until mode_set/
+ * encoder_enable time, commonly a second or more after probe under DRM's
+ * atomic commit machinery, and an unmasked interrupt during that window
+ * storms and gets permanently disabled by Linux's own protection before
+ * anything is ready to handle it. enable_irq() is called later from
+ * sun4i_tcon_dsi_retrigger_timer_fn(), once the hardware is actually
+ * configured.
+ */
 static int sun4i_tcon_init_irq(struct device *dev,
                               struct sun4i_tcon *tcon)
 {
@@ -821,13 +1126,15 @@ static int sun4i_tcon_init_irq(struct device *dev,
        if (irq < 0)
                return irq;
 
-       ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0,
+       ret = devm_request_irq(dev, irq, sun4i_tcon_handler, IRQF_NO_AUTOEN,
                               dev_name(dev), tcon);
        if (ret) {
                dev_err(dev, "Couldn't request the IRQ\n");
                return ret;
        }
 
+       tcon->irq = irq;
+
        return 0;
 }
 
@@ -1136,6 +1443,9 @@ static int sun4i_tcon_bind(struct device *dev, struct 
device *master,
        tcon->id = engine->id;
        tcon->quirks = of_device_get_match_data(dev);
 
+       hrtimer_setup(&tcon->dsi_retrigger_timer, 
sun4i_tcon_dsi_retrigger_timer_fn,
+                     CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
        tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
        if (IS_ERR(tcon->lcd_rst)) {
                dev_err(dev, "Couldn't get our reset line\n");
@@ -1304,6 +1614,7 @@ static void sun4i_tcon_unbind(struct device *dev, struct 
device *master,
 {
        struct sun4i_tcon *tcon = dev_get_drvdata(dev);
 
+       hrtimer_cancel(&tcon->dsi_retrigger_timer);
        list_del(&tcon->list);
        if (tcon->quirks->has_channel_0)
                sun4i_dclk_free(tcon);
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h 
b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index fa23aa23f..ce59cbea7 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -12,11 +12,14 @@
 
 #include <drm/drm_crtc.h>
 
+#include <linux/hrtimer.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/mod_devicetable.h>
 #include <linux/reset.h>
 
+struct sun6i_dsi;
+
 #define SUN4I_TCON_GCTL_REG                    0x0
 #define SUN4I_TCON_GCTL_TCON_ENABLE                    BIT(31)
 #define SUN4I_TCON_GCTL_IOMAP_MASK                     BIT(0)
@@ -27,9 +30,19 @@
 #define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe)           BIT(31 - (pipe))
 #define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE       BIT(27)
 #define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_ENABLE      BIT(26)
+/*
+ * LCD_IRQ_FSYNC_INT: a real, named status bit this driver never enabled
+ * or checked, found live and unconditionally set on this board. Left
+ * unhandled it storms the IRQ line and gets it permanently disabled by
+ * Linux's own unhandled-interrupt protection shortly after boot.
+ * Enable-bit position follows the same status+16 pairing every other
+ * GINT0 bit here uses.
+ */
+#define SUN4I_TCON_GINT0_TCON0_FSYNC_ENABLE            BIT(25)
 #define SUN4I_TCON_GINT0_VBLANK_INT(pipe)              BIT(15 - (pipe))
 #define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT          BIT(11)
 #define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_INT         BIT(10)
+#define SUN4I_TCON_GINT0_TCON0_FSYNC_INT               BIT(9)
 
 #define SUN4I_TCON_GINT1_REG                   0x8
 
@@ -62,6 +75,15 @@
 #define SUN4I_TCON0_DCLK_GATE_BIT                      (31)
 #define SUN4I_TCON0_DCLK_DIV_SHIFT                     (0)
 #define SUN4I_TCON0_DCLK_DIV_WIDTH                     (7)
+/*
+ * The vendor implementation sets bits 30-28 together with the documented
+ * GATE_BIT (31) for DSI output; mainline's existing dclk gate-only clock
+ * framework handling only ever touches bit 31, leaving these three at
+ * their power-on-reset value of 0. Mainline has no name for these bits;
+ * setting them unconditionally alongside bit 31 is additive, not a
+ * replacement for the existing clk_hw gate behavior.
+ */
+#define SUN4I_TCON0_DCLK_OUT_EN_MASK                   GENMASK(30, 28)
 
 #define SUN4I_TCON0_BASIC0_REG                 0x48
 #define SUN4I_TCON0_BASIC0_X(width)                    ((((width) - 1) & 
0xfff) << 16)
@@ -84,8 +106,25 @@
 #define SUN4I_TCON0_CPU_IF_REG                 0x60
 #define SUN4I_TCON0_CPU_IF_MODE_MASK                   GENMASK(31, 28)
 #define SUN4I_TCON0_CPU_IF_MODE_DSI                    (1 << 28)
+/*
+ * Bit 17 "AUTO" ("if 1, all the valid data during this frame are written
+ * to panel, sampled by Vsync") and bit 16 "FLUSH" ("pixel data keeps
+ * being transferred unless the input FIFO is empty") are two alternate,
+ * mutually-exclusive trigger modes, per the A133 User Manual. This board
+ * matches a working reference system's live configuration (FLUSH,
+ * AUTO clear) and drives transfers via an explicit per-frame
+ * TRIGGER_START instead.
+ */
+#define SUN4I_TCON0_CPU_IF_AUTO                                BIT(17)
 #define SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH              BIT(16)
 #define SUN4I_TCON0_CPU_IF_TRI_FIFO_EN                 BIT(2)
+/*
+ * "TRIGGER_START -- Write '1' to start a frame flush... This flag
+ * indicates frame flush is running. Software must write '1' only when
+ * this flag is '0'." TRI_EN below only arms trigger mode; it doesn't
+ * kick off a transfer by itself.
+ */
+#define SUN4I_TCON0_CPU_IF_TRI_START                   BIT(1)
 #define SUN4I_TCON0_CPU_IF_TRI_EN                      BIT(0)
 
 #define SUN4I_TCON0_CPU_WR_REG                 0x64
@@ -257,6 +296,13 @@ struct sun4i_tcon {
        struct drm_device               *drm;
        struct regmap                   *regs;
 
+       /*
+        * Requested with IRQF_NO_AUTOEN at probe time and enabled only later,
+        * once TCON0/DSI/D-PHY are actually configured -- see the comment on
+        * sun4i_tcon_init_irq().
+        */
+       int                             irq;
+
        /* Main bus clock */
        struct clk                      *clk;
 
@@ -279,6 +325,46 @@ struct sun4i_tcon {
        /* Platform adjustments */
        const struct sun4i_tcon_quirks  *quirks;
 
+       /*
+        * Set while TCON0 drives a DSI panel through the CPU/8080 interface
+        * in explicit (non-AUTO) trigger mode. In that mode TRIGGER_START is
+        * a self-clearing one-shot that has to be re-asserted once per frame
+        * from the TRI_FINISH interrupt -- see sun4i_tcon_handler(). Gates
+        * that re-trigger so RGB/LVDS/HDMI TCONs, where LCD_CPU_IF_REG is
+        * meaningless, are left untouched.
+        */
+       bool                            dsi_cpu_mode;
+
+       /*
+        * The DSI device driving this TCON, captured in sun4i_tcon_mode_set()
+        * when dsi_cpu_mode is set. Needed so sun4i_tcon_handler() can also
+        * re-issue the DSI-side HSC start sequence each frame -- see
+        * sun6i_dsi_tri_start(). Only valid when dsi_cpu_mode is true.
+        */
+       struct sun6i_dsi                *dsi;
+
+       /*
+        * Delayed kick for the first TRIGGER_START/HSC assertion, started
+        * (with a delay margin) at the end of sun4i_tcon0_mode_set_cpu() --
+        * that function runs before sun6i_dsi_encoder_enable() in DRM's
+        * atomic commit order, and the panel's own prepare() delays haven't
+        * elapsed yet either, so TRIGGER_START can't be asserted synchronously
+        * from mode_set.
+        *
+        * sun4i_tcon_dsi_retrigger_timer_fn() writes TRIGGER_START once, at
+        * tick==0, matching the vendor per-frame retrigger's own busy-gated
+        * behavior (it never blindly re-asserts the bit either -- see that
+        * function's comment in sun4i_tcon.c). Every tick after that just
+        * re-arms itself every 2ms to poll and clear DSI_INT_REG's status,
+        * until TRI_START is observed to have self-cleared, at which point
+        * it stops (HRTIMER_NORESTART) and sun4i_tcon_handler() -- armed on
+        * the real TRI_FINISH_INT -- takes over the per-frame retrigger job.
+        */
+       struct hrtimer                  dsi_retrigger_timer;
+
+       /* Tick counter for dsi_retrigger_timer, reset each time it (re)starts. 
*/
+       unsigned int                    dsi_retrigger_ticks;
+
        /* Associated crtc */
        struct sun4i_crtc               *crtc;
 
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c 
b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index d504ae583..4691f7a26 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
@@ -35,6 +35,33 @@
 #define SUN6I_DSI_CTL_REG              0x000
 #define SUN6I_DSI_CTL_EN                       BIT(0)
 
+/*
+ * The real per-frame retrigger source for CPU/8080-interface DSI panels
+ * turns out to be this register, not TCON's own GINT0. Allwinner's vendor
+ * kernel dispatches both its VBLK- and LINE-equivalent IRQ queries straight
+ * to DSI_INT_REG for this panel type instead of touching TCON0's GINT0 at
+ * all, which is also why GINT0's enable bits read as zero on a working
+ * reference system: they're genuinely unused for this panel.
+ *
+ * bit0 = INSTR_END, bit1 = INSTR_STEP, bit2 = VIDEO_VBLK, bit3 = VIDEO_LINE
+ * in the enable half (low 16 bits); the status half mirrors the same
+ * layout at bit16-19. Status bits are write-1-to-clear, matching the
+ * vendor implementation's own acknowledge behavior.
+ *
+ * Not wired to a GIC interrupt (no devm_request_irq() here) -- enabling
+ * these bits only latches this block's internal status and its own IRQ
+ * output pin, which the GIC never forwards unless something requests that
+ * SPI. sun4i_tcon.c's retrigger timer polls and clears this register
+ * directly instead of using an interrupt handler.
+ */
+#define SUN6I_DSI_INT_REG              0x004
+#define SUN6I_DSI_INT_EN_VIDEO_VBLK            BIT(2)
+#define SUN6I_DSI_INT_EN_VIDEO_LINE            BIT(3)
+/*
+ * SUN6I_DSI_INT_STA_VIDEO_VBLK / _LINE are declared in sun6i_mipi_dsi.h --
+ * sun4i_tcon.c needs them too.
+ */
+
 #define SUN6I_DSI_BASIC_CTL_REG                0x00c
 #define SUN6I_DSI_BASIC_CTL_TRAIL_INV(n)               (((n) & 0xf) << 4)
 #define SUN6I_DSI_BASIC_CTL_TRAIL_FILL         BIT(3)
@@ -165,6 +192,21 @@ enum sun6i_dsi_start_inst {
        DSI_START_LPTX,
        DSI_START_HSC,
        DSI_START_HSD,
+       /*
+        * Falls through to sun6i_dsi_start()'s default case (JUMP_SEL = END
+        * only, i.e. idle/stop everything). Vendor's DSI bring-up routine
+        * enables the block's IRQ bits and resets the instruction engine to
+        * this same idle state before any mode-specific setup runs.
+        */
+       DSI_START_IDLE,
+       /*
+        * The real per-frame retrigger table, distinct from DSI_START_HSC:
+        * DSI_START_HSC's JUMP_SEL table is the one-shot LP11->HSC->END used
+        * for the one-time HS-clock-enable step, while this table is the
+        * continuous streaming loop (LP11->HSC->NOP->HSD->DLY->loop) that
+        * needs to be reissued every frame to keep pixel data flowing.
+        */
+       DSI_START_TRI,
 };
 
 enum sun6i_dsi_inst_id {
@@ -436,9 +478,16 @@ static void sun6i_dsi_setup_burst(struct sun6i_dsi *dsi,
                             
SUN6I_DSI_BURST_LINE_SYNC_POINT(SUN6I_DSI_SYNC_POINT));
 
                val = SUN6I_DSI_TCON_DRQ_ENABLE_MODE;
-       } else if ((mode->hsync_start - mode->hdisplay) > 20) {
-               /* Maaaaaagic */
-               u16 drq = (mode->hsync_start - mode->hdisplay) - 20;
+       } else if ((mode->htotal - mode->hsync_start) > 20) {
+               /*
+                * This is (back porch + hsync width), not the front porch a
+                * previous version of this driver used here. Verified against
+                * the vendor implementation's own DRQ lookup table and a
+                * live-measured TCON_DRQ_REG value on a working reference
+                * system: (htotal - hsync_start - 20) * bpp / 32 reproduces
+                * it exactly for this panel's timings.
+                */
+               u16 drq = (mode->htotal - mode->hsync_start) - 20;
 
                drq *= mipi_dsi_pixel_format_to_bpp(device->format);
                drq /= 32;
@@ -696,6 +745,16 @@ static int sun6i_dsi_start(struct sun6i_dsi *dsi,
                             DSI_INST_ID_NOP  << (4 * DSI_INST_ID_DLY) |
                             DSI_INST_ID_END  << (4 * DSI_INST_ID_HSCEXIT));
                break;
+       case DSI_START_TRI:
+               /* vendor dsi_start(id=2) -- see the DSI_START_TRI comment 
above */
+               regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
+                            DSI_INST_ID_HSC  << (4 * DSI_INST_ID_LP11) |
+                            DSI_INST_ID_NOP  << (4 * DSI_INST_ID_HSC) |
+                            DSI_INST_ID_HSD  << (4 * DSI_INST_ID_NOP) |
+                            DSI_INST_ID_DLY  << (4 * DSI_INST_ID_HSD) |
+                            DSI_INST_ID_NOP  << (4 * DSI_INST_ID_DLY) |
+                            DSI_INST_ID_END  << (4 * DSI_INST_ID_HSCEXIT));
+               break;
        default:
                regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG,
                             DSI_INST_ID_END  << (4 * DSI_INST_ID_LP11));
@@ -713,6 +772,48 @@ static int sun6i_dsi_start(struct sun6i_dsi *dsi,
        return 0;
 }
 
+/*
+ * Vendor's per-frame retrigger asserts two things together: the DSI-side
+ * kick handled here, then the TCON-side TRIGGER_START bit (handled by the
+ * caller). This DSI side previously reused DSI_START_HSC, which is the
+ * one-shot HS-clock-enable table (LP11->HSC->END) rather than the
+ * continuous streaming table DSI_START_TRI represents
+ * (LP11->HSC->NOP->HSD->DLY->loop). Without reissuing the continuous table
+ * every frame, the DSI engine never advances past the first frame it was
+ * started into at encoder-enable time.
+ */
+void sun6i_dsi_tri_start(struct sun6i_dsi *dsi)
+{
+       sun6i_dsi_start(dsi, DSI_START_TRI);
+}
+
+/*
+ * Read-only poll of DSI_INT_REG -- see the comment on SUN6I_DSI_INT_REG
+ * near the top of this file. Called from sun4i_tcon.c's retrigger timer.
+ */
+u32 sun6i_dsi_read_int_status(struct sun6i_dsi *dsi)
+{
+       unsigned int val = 0;
+
+       regmap_read(dsi->regs, SUN6I_DSI_INT_REG, &val);
+
+       return val;
+}
+
+/*
+ * Write back whatever status bits (high 16) are currently set, which
+ * write-1-to-clears exactly those bits; regmap_update_bits leaves the
+ * enable half (low 16) untouched.
+ */
+void sun6i_dsi_clear_int_status(struct sun6i_dsi *dsi)
+{
+       unsigned int val = 0;
+
+       regmap_read(dsi->regs, SUN6I_DSI_INT_REG, &val);
+       regmap_update_bits(dsi->regs, SUN6I_DSI_INT_REG,
+                          GENMASK(31, 16), val & GENMASK(31, 16));
+}
+
 static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder)
 {
        struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
@@ -720,7 +821,6 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder 
*encoder)
        struct mipi_dsi_device *device = dsi->device;
        union phy_configure_opts opts = { };
        struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
-       u16 delay;
        int err;
 
        DRM_DEBUG_DRIVER("Enabling DSI output\n");
@@ -737,6 +837,17 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder 
*encoder)
         */
        regmap_write(dsi->regs, SUN6I_DSI_CTL_REG, SUN6I_DSI_CTL_EN);
 
+       /* See the comment on SUN6I_DSI_INT_REG above. */
+       regmap_write(dsi->regs, SUN6I_DSI_INT_REG,
+                    SUN6I_DSI_INT_EN_VIDEO_VBLK | SUN6I_DSI_INT_EN_VIDEO_LINE);
+
+       /*
+        * Reset the instruction engine to a known idle state before any of
+        * the mode-specific setup below, matching the vendor bring-up
+        * sequence for this block.
+        */
+       sun6i_dsi_start(dsi, DSI_START_IDLE);
+
        regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG,
                     SUN6I_DSI_BASIC_CTL0_ECC_EN | SUN6I_DSI_BASIC_CTL0_CRC_EN);
 
@@ -747,9 +858,14 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder 
*encoder)
 
        regmap_write(dsi->regs, SUN6I_DSI_DEBUG_DATA_REG, 0xff);
 
-       delay = sun6i_dsi_get_video_start_delay(dsi, mode);
+       /*
+        * This panel's CPU/8080-interface TCON path still runs the DSI block
+        * in video mode -- confirmed by a live register read from a working
+        * reference system, where VIDEO_MODE, VIDEO_PRECISION and VIDEO_FILL
+        * are all set with a matching VIDEO_ST_DELAY.
+        */
        regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL1_REG,
-                    SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(delay) |
+                    
SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(sun6i_dsi_get_video_start_delay(dsi, mode)) 
|
                     SUN6I_DSI_BASIC_CTL1_VIDEO_FILL |
                     SUN6I_DSI_BASIC_CTL1_VIDEO_PRECISION |
                     SUN6I_DSI_BASIC_CTL1_VIDEO_MODE);
@@ -769,28 +885,29 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder 
*encoder)
        phy_configure(dsi->dphy, &opts);
        phy_power_on(dsi->dphy);
 
-       if (dsi->panel)
-               drm_panel_prepare(dsi->panel);
-
        /*
-        * FIXME: This should be moved after the switch to HS mode.
-        *
-        * Unfortunately, once in HS mode, it seems like we're not
-        * able to send DCS commands anymore, which would prevent any
-        * panel to send any DCS command as part as their enable
-        * method, which is quite common.
-        *
-        * I haven't seen any artifact due to that sub-optimal
-        * ordering on the panels I've tested it with, so I guess this
-        * will do for now, until that IP is better understood.
+        * The HS clock-enable step (DSI_START_HSC) runs before the panel's
+        * DCS init sequence rather than after, unlike a previous revision of
+        * this driver. Upstream's own comment on this ordering ("this should
+        * be moved after the switch to HS mode... I haven't seen any artifact
+        * due to that sub-optimal ordering on the panels I've tested it
+        * with") already flagged it as suspect without resolving it. The
+        * vendor panel driver for this board enables the HS clock as the
+        * very first step of its panel-init routine, before sending any DCS
+        * command. This doesn't conflict with DCS needing LP mode: HSC only
+        * puts the clock lane into its continuous-HS state, and DCS commands
+        * use the entirely separate LPTX/LPDT instruction tables.
         */
-       if (dsi->panel)
-               drm_panel_enable(dsi->panel);
-
        sun6i_dsi_start(dsi, DSI_START_HSC);
 
        udelay(1000);
 
+       if (dsi->panel)
+               drm_panel_prepare(dsi->panel);
+
+       if (dsi->panel)
+               drm_panel_enable(dsi->panel);
+
        sun6i_dsi_start(dsi, DSI_START_HSD);
 }
 
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h 
b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
index f1ddefe0f..4a8f4a09a 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
@@ -15,6 +15,14 @@
 
 #define SUN6I_DSI_TCON_DIV     4
 
+/*
+ * Status half of SUN6I_DSI_INT_REG (see the comment on that register in
+ * sun6i_mipi_dsi.c), exposed so sun4i_tcon.c's retrigger timer can
+ * edge-detect against sun6i_dsi_read_int_status()'s return value.
+ */
+#define SUN6I_DSI_INT_STA_VIDEO_VBLK   BIT(2 + 16)
+#define SUN6I_DSI_INT_STA_VIDEO_LINE   BIT(3 + 16)
+
 struct sun6i_dsi_variant {
        bool                    has_mod_clk;
        bool                    set_mod_clk;
@@ -55,4 +63,8 @@ static inline struct sun6i_dsi *encoder_to_sun6i_dsi(const 
struct drm_encoder *e
        return container_of(encoder, struct sun6i_dsi, encoder);
 };
 
+void sun6i_dsi_tri_start(struct sun6i_dsi *dsi);
+u32 sun6i_dsi_read_int_status(struct sun6i_dsi *dsi);
+void sun6i_dsi_clear_int_status(struct sun6i_dsi *dsi);
+
 #endif /* _SUN6I_MIPI_DSI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c 
b/drivers/gpu/drm/sun4i/sun8i_mixer.c
index b6cd7352e..b3419f6d3 100644
--- a/drivers/gpu/drm/sun4i/sun8i_mixer.c
+++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c
@@ -853,6 +853,46 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg 
= {
        .vi_num         = 1,
 };
 
+/*
+ * A133 (sun50iw10) has no public register documentation. This config
+ * is derived from Allwinner's GPL disp2 driver (de_feat.c/de_rtmx.c,
+ * lowlevel_v2x/sun50iw10 tree), which confirms it is a standard DE2.0
+ * dual-pipe mixer: DISP0 has 4 channels (2 VI + 2 UI, all 4 scaler
+ * capable), DISP1 has 3 channels (1 VI + 2 UI, all 3 scaler capable).
+ * de_rtmx_init() places mixer1's register block at DE0 base + 0x200000
+ * and mixer0's at DE0 base + 0x100000, matching the existing A64/H3/R40
+ * DE2.0 layout convention already used below.
+ */
+static const struct sun8i_mixer_cfg sun50i_a133_mixer0_cfg = {
+       .lay_cfg = {
+               .ccsc           = CCSC_MIXER0_LAYOUT,
+               .de_type        = SUN8I_MIXER_DE2,
+               .vi_scaler_num  = 2,
+               .scaler_mask    = 0xf,
+               .scanline_yuv   = 2560,
+               .de2_fcc_alpha  = 1,
+       },
+       .de_type        = SUN8I_MIXER_DE2,
+       .mod_rate       = 297000000,
+       .ui_num         = 2,
+       .vi_num         = 2,
+};
+
+static const struct sun8i_mixer_cfg sun50i_a133_mixer1_cfg = {
+       .lay_cfg = {
+               .ccsc           = CCSC_MIXER1_LAYOUT,
+               .de_type        = SUN8I_MIXER_DE2,
+               .vi_scaler_num  = 1,
+               .scaler_mask    = 0x7,
+               .scanline_yuv   = 2048,
+               .de2_fcc_alpha  = 1,
+       },
+       .de_type        = SUN8I_MIXER_DE2,
+       .mod_rate       = 297000000,
+       .ui_num         = 2,
+       .vi_num         = 1,
+};
+
 static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = {
        .lay_cfg = {
                .de_type        = SUN8I_MIXER_DE3,
@@ -920,6 +960,14 @@ static const struct of_device_id sun8i_mixer_of_table[] = {
                .compatible = "allwinner,sun50i-a64-de2-mixer-1",
                .data = &sun50i_a64_mixer1_cfg,
        },
+       {
+               .compatible = "allwinner,sun50i-a133-de2-mixer-0",
+               .data = &sun50i_a133_mixer0_cfg,
+       },
+       {
+               .compatible = "allwinner,sun50i-a133-de2-mixer-1",
+               .data = &sun50i_a133_mixer1_cfg,
+       },
        {
                .compatible = "allwinner,sun50i-h6-de3-mixer-0",
                .data = &sun50i_h6_mixer0_cfg,
diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c 
b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
index 9cbd65551..c074fd223 100644
--- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
+++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
@@ -143,6 +143,79 @@ int sun8i_tcon_top_de_config(struct device *dev, int 
mixer, int tcon)
 }
 EXPORT_SYMBOL(sun8i_tcon_top_de_config);
 
+/*
+ * The vendor implementation's DSI configuration path unconditionally
+ * enables this same bit (TCON_TOP_GATE_SRC_REG, TCON_TOP_TCON_DSI_GATE)
+ * for TCON0. Mainline already registers it as a standard clk gate
+ * (CLK_TCON_TOP_DSI, see sun8i_tcon_top_register_gate() above), but
+ * nothing consumes it: wiring it as dsi0's "mod" clock creates a circular
+ * dependency between TCON TOP's .bind() and dsi0's .probe() (see the DTS
+ * comment on the dsi0 node). This sets the bit directly instead, the same
+ * way sun8i_tcon_top_set_hdmi_src() and sun8i_tcon_top_de_config() above
+ * already bypass the clk-consumer graph for their own TCON TOP writes.
+ */
+int sun8i_tcon_top_set_dsi_gate(struct device *dev, bool enable)
+{
+       struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
+       unsigned long flags;
+       u32 val;
+
+       if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
+               dev_err(dev, "Device is not TCON TOP!\n");
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&tcon_top->reg_lock, flags);
+
+       val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG);
+       if (enable)
+               val |= BIT(TCON_TOP_TCON_DSI_GATE);
+       else
+               val &= ~BIT(TCON_TOP_TCON_DSI_GATE);
+       writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG);
+
+       spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL(sun8i_tcon_top_set_dsi_gate);
+
+/*
+ * TCON_TOP_PORT_SEL_REG routes each DE's output to one of the 4 TCON
+ * ports. The only existing caller of the read-modify-write helper for
+ * this register (sun8i_tcon_top_de_config()) is wired up exclusively from
+ * sun8i_r40_tcon_tv_set_mux(), which only runs for TCON quirks tables
+ * that provide a .set_mux callback -- ours doesn't, the same gap
+ * sun8i_tcon_top_set_dsi_gate() above works around for the DSI clock gate
+ * bit. DE0's live PORT_SEL field on a working reference system is 0
+ * (port 0), matching this register's reset default -- written explicitly
+ * here rather than relying on that default, since nothing in mainline
+ * otherwise reaches this register for a .set_mux-less quirks table.
+ */
+int sun8i_tcon_top_set_de0_port(struct device *dev, int port)
+{
+       struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
+       unsigned long flags;
+       u32 val;
+
+       if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
+               dev_err(dev, "Device is not TCON TOP!\n");
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&tcon_top->reg_lock, flags);
+
+       val = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG);
+       val &= ~TCON_TOP_PORT_DE0_MSK;
+       val |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, port);
+       writel(val, tcon_top->regs + TCON_TOP_PORT_SEL_REG);
+
+       spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL(sun8i_tcon_top_set_de0_port);
+
 
 static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
                                                   const char *parent,
@@ -325,6 +398,23 @@ static const struct sun8i_tcon_top_quirks 
sun50i_h6_tcon_top_quirks = {
        /* Nothing special */
 };
 
+/*
+ * A133 has no public register documentation for its DPSS_TOP0 block, but
+ * the vendor implementation's own register layout for it is bit-for-bit
+ * identical to this driver's existing R40/D1/H6 assumptions -- same
+ * PORT_SEL/DE0 field and GATE_SRC/DSI-TV0-TV1-HDMI gate bit positions.
+ * This IP block is genuinely present and load-bearing on real hardware:
+ * skipping it (wiring the mixer straight to tcon_lcd0) lets every
+ * component probe and bind, but every DRM atomic commit then times out
+ * waiting for vblank -- the pixel path never actually reaches the panel.
+ * The vendor implementation explicitly gates its DSI clock as part of
+ * enabling DSI output, matching this driver's has_dsi quirk. TV1/HDMI
+ * aren't used by this board (single DSI-LCD output only).
+ */
+static const struct sun8i_tcon_top_quirks sun50i_a133_tcon_top_quirks = {
+       .has_dsi        = true,
+};
+
 /* sun4i_drv uses this list to check if a device node is a TCON TOP */
 const struct of_device_id sun8i_tcon_top_of_table[] = {
        {
@@ -339,6 +429,10 @@ const struct of_device_id sun8i_tcon_top_of_table[] = {
                .compatible = "allwinner,sun50i-h6-tcon-top",
                .data = &sun50i_h6_tcon_top_quirks
        },
+       {
+               .compatible = "allwinner,sun50i-a133-tcon-top",
+               .data = &sun50i_a133_tcon_top_quirks
+       },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table);
diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h 
b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h
index 2b887470a..6956439c7 100644
--- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h
+++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h
@@ -46,5 +46,7 @@ extern const struct of_device_id sun8i_tcon_top_of_table[];
 
 int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon);
 int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon);
+int sun8i_tcon_top_set_dsi_gate(struct device *dev, bool enable);
+int sun8i_tcon_top_set_de0_port(struct device *dev, int port);
 
 #endif /* _SUN8I_TCON_TOP_H_ */
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 729776086..cead75470 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -748,6 +748,20 @@ config PWM_SUN4I
          To compile this driver as a module, choose M here: the module
          will be called pwm-sun4i.
 
+config PWM_SUN50I_A133
+       tristate "Allwinner A133 PWM support"
+       depends on ARCH_SUNXI || COMPILE_TEST
+       depends on HAS_IOMEM
+       help
+         PWM driver for the Allwinner A133 (sun50iw10) PWM controller — a
+         newer, richer IP generation than what PWM_SUN4I models (16
+         channels grouped in pairs, per-pair dead-zone/group-sync control),
+         with no existing mainline driver. This driver only implements
+         plain single-channel cycle-mode output, enough for backlight use.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-sun50i-a133.
+
 config PWM_SUNPLUS
        tristate "Sunplus PWM support"
        depends on ARCH_SUNPLUS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 5630a521a..9c906e5b0 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_PWM_STM32)               += pwm-stm32.o
 obj-$(CONFIG_PWM_STM32_LP)     += pwm-stm32-lp.o
 obj-$(CONFIG_PWM_STMPE)                += pwm-stmpe.o
 obj-$(CONFIG_PWM_SUN4I)                += pwm-sun4i.o
+obj-$(CONFIG_PWM_SUN50I_A133)  += pwm-sun50i-a133.o
 obj-$(CONFIG_PWM_SUNPLUS)      += pwm-sunplus.o
 obj-$(CONFIG_PWM_TEGRA)                += pwm-tegra.o
 obj-$(CONFIG_PWM_TH1520)       += pwm_th1520.o
diff --git a/drivers/pwm/pwm-sun50i-a133.c b/drivers/pwm/pwm-sun50i-a133.c
new file mode 100644
index 000000000..4201ca29d
--- /dev/null
+++ b/drivers/pwm/pwm-sun50i-a133.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Allwinner A133 (sun50iw10) PWM Controller
+ *
+ * No mainline driver exists for this IP: it's a newer, richer generation
+ * than what drivers/pwm/pwm-sun4i.c models (16 channels grouped in pairs,
+ * per-pair dead-zone control, group-sync start, cache-loaded period/duty
+ * registers) — register layout confirmed from the real, public A133 User
+ * Manual (chapter 10.11, base 0x0300a000), which documents this block in
+ * full down to bit level (unlike the Display Engine chapter elsewhere in
+ * the same manual). This driver only implements plain single-channel PWM
+ * output (cycle mode, no capture/dead-zone/group-sync) — enough to drive
+ * a backlight, not a full port of every feature this IP has.
+ *
+ * The clock source (OSC24M or APB1, selected per-pair in PCCR01/PCCR23)
+ * is entirely internal to this IP block — CCU only exposes a bus/register
+ * access gate (CLK_BUS_PWM), no separate "mod" clock, confirmed by its
+ * absence from include/dt-bindings/clock/sun50i-a100-ccu.h. This driver
+ * hardcodes OSC24M (24MHz), matching the register field's own reset
+ * default and the only clock source needed for typical backlight rates.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+
+#define PWM_A133_OSC24M_RATE           24000000
+
+#define PWM_A133_PCCR01_REG            0x0020
+#define PWM_A133_PCCR23_REG            0x0024
+#define PWM_A133_PCCR_CLK_SRC_OSC24M   (0 << 7)
+#define PWM_A133_PCCR_CLK_SRC_MASK     GENMASK(8, 7)
+
+#define PWM_A133_PCGR_REG              0x0040
+#define PWM_A133_PCGR_BYPASS(ch)       BIT(16 + (ch))
+#define PWM_A133_PCGR_GATING(ch)       BIT(ch)
+
+#define PWM_A133_PER_REG               0x0080
+#define PWM_A133_PER_EN(ch)            BIT(ch)
+
+#define PWM_A133_CH_STRIDE             0x0020
+#define PWM_A133_PCR_REG(ch)           (0x0100 + PWM_A133_CH_STRIDE * (ch))
+#define PWM_A133_PPR_REG(ch)           (0x0104 + PWM_A133_CH_STRIDE * (ch))
+
+#define PWM_A133_PCR_MODE_CYCLE                (0 << 9)
+#define PWM_A133_PCR_ACT_STA_HIGH      BIT(8)
+#define PWM_A133_PCR_PRESCAL_K_MASK    GENMASK(7, 0)
+
+#define PWM_A133_PPR_ENTIRE_CYCLE_SHIFT        16
+#define PWM_A133_PPR_ENTIRE_CYCLE_MASK         GENMASK(31, 16)
+#define PWM_A133_PPR_ACT_CYCLE_MASK            GENMASK(15, 0)
+
+#define PWM_A133_PRESCALE_K_MAX        256
+#define PWM_A133_CYCLE_MAX     65536
+
+struct pwm_a133 {
+       void __iomem *base;
+       struct clk *bus_clk;
+       struct reset_control *rst;
+};
+
+static struct pwm_a133 *to_pwm_a133(struct pwm_chip *chip)
+{
+       return pwmchip_get_drvdata(chip);
+}
+
+static u32 pwm_a133_readl(struct pwm_a133 *pc, u32 offset)
+{
+       return readl(pc->base + offset);
+}
+
+static void pwm_a133_writel(struct pwm_a133 *pc, u32 offset, u32 val)
+{
+       writel(val, pc->base + offset);
+}
+
+static int pwm_a133_calc(u64 period_ns, u64 duty_ns, u32 *entire_cycle,
+                        u32 *act_cycle, u32 *prescale_k)
+{
+       u64 period_cycles_total, prescale;
+       u64 period_cycles, duty_cycles;
+
+       period_cycles_total = mul_u64_u32_div(period_ns, PWM_A133_OSC24M_RATE,
+                                             NSEC_PER_SEC);
+       if (period_cycles_total < 1)
+               period_cycles_total = 1;
+
+       prescale = DIV_ROUND_UP_ULL(period_cycles_total, PWM_A133_CYCLE_MAX);
+       if (prescale < 1)
+               prescale = 1;
+       if (prescale > PWM_A133_PRESCALE_K_MAX)
+               return -ERANGE;
+
+       period_cycles = DIV_ROUND_CLOSEST_ULL(period_cycles_total, prescale);
+       if (period_cycles < 1)
+               period_cycles = 1;
+       if (period_cycles > PWM_A133_CYCLE_MAX)
+               period_cycles = PWM_A133_CYCLE_MAX;
+
+       duty_cycles = DIV_ROUND_CLOSEST_ULL(period_cycles * duty_ns, period_ns);
+       if (duty_cycles > period_cycles)
+               duty_cycles = period_cycles;
+
+       *prescale_k = prescale - 1;
+       *entire_cycle = period_cycles - 1;
+       *act_cycle = duty_cycles;
+
+       return 0;
+}
+
+static int pwm_a133_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+                         const struct pwm_state *state)
+{
+       struct pwm_a133 *pc = to_pwm_a133(chip);
+       unsigned int ch = pwm->hwpwm;
+       u32 entire_cycle, act_cycle, prescale_k, val;
+       u32 pccr_reg = (ch < 2) ? PWM_A133_PCCR01_REG : PWM_A133_PCCR23_REG;
+       int ret;
+
+       if (!state->enabled) {
+               val = pwm_a133_readl(pc, PWM_A133_PER_REG);
+               val &= ~PWM_A133_PER_EN(ch);
+               pwm_a133_writel(pc, PWM_A133_PER_REG, val);
+               return 0;
+       }
+
+       ret = pwm_a133_calc(state->period, state->duty_cycle, &entire_cycle,
+                           &act_cycle, &prescale_k);
+       if (ret) {
+               dev_err(pwmchip_parent(chip), "period exceeds the maximum 
value\n");
+               return ret;
+       }
+
+       /* Clock source: OSC24M for the whole pair this channel belongs to. */
+       val = pwm_a133_readl(pc, pccr_reg);
+       val &= ~PWM_A133_PCCR_CLK_SRC_MASK;
+       val |= PWM_A133_PCCR_CLK_SRC_OSC24M;
+       pwm_a133_writel(pc, pccr_reg, val);
+
+       /* Gate the channel clock off while changing prescale/period/duty. */
+       val = pwm_a133_readl(pc, PWM_A133_PCGR_REG);
+       val &= ~(PWM_A133_PCGR_GATING(ch) | PWM_A133_PCGR_BYPASS(ch));
+       pwm_a133_writel(pc, PWM_A133_PCGR_REG, val);
+
+       val = PWM_A133_PCR_MODE_CYCLE | (prescale_k & 
PWM_A133_PCR_PRESCAL_K_MASK);
+       if (state->polarity == PWM_POLARITY_NORMAL)
+               val |= PWM_A133_PCR_ACT_STA_HIGH;
+       pwm_a133_writel(pc, PWM_A133_PCR_REG(ch), val);
+
+       val = (entire_cycle << PWM_A133_PPR_ENTIRE_CYCLE_SHIFT) & 
PWM_A133_PPR_ENTIRE_CYCLE_MASK;
+       val |= act_cycle & PWM_A133_PPR_ACT_CYCLE_MASK;
+       pwm_a133_writel(pc, PWM_A133_PPR_REG(ch), val);
+
+       /* Re-enable the channel clock now that period/duty are set. */
+       val = pwm_a133_readl(pc, PWM_A133_PCGR_REG);
+       val |= PWM_A133_PCGR_GATING(ch);
+       pwm_a133_writel(pc, PWM_A133_PCGR_REG, val);
+
+       val = pwm_a133_readl(pc, PWM_A133_PER_REG);
+       val |= PWM_A133_PER_EN(ch);
+       pwm_a133_writel(pc, PWM_A133_PER_REG, val);
+
+       return 0;
+}
+
+static int pwm_a133_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+                             struct pwm_state *state)
+{
+       struct pwm_a133 *pc = to_pwm_a133(chip);
+       unsigned int ch = pwm->hwpwm;
+       u32 per, pcr, ppr;
+       u32 entire_cycle, act_cycle, prescale;
+
+       per = pwm_a133_readl(pc, PWM_A133_PER_REG);
+       pcr = pwm_a133_readl(pc, PWM_A133_PCR_REG(ch));
+       ppr = pwm_a133_readl(pc, PWM_A133_PPR_REG(ch));
+
+       state->enabled = !!(per & PWM_A133_PER_EN(ch));
+       state->polarity = (pcr & PWM_A133_PCR_ACT_STA_HIGH) ?
+                          PWM_POLARITY_NORMAL : PWM_POLARITY_INVERSED;
+
+       prescale = (pcr & PWM_A133_PCR_PRESCAL_K_MASK) + 1;
+       entire_cycle = ((ppr & PWM_A133_PPR_ENTIRE_CYCLE_MASK) >>
+                       PWM_A133_PPR_ENTIRE_CYCLE_SHIFT) + 1;
+       act_cycle = ppr & PWM_A133_PPR_ACT_CYCLE_MASK;
+
+       state->period = DIV_ROUND_CLOSEST_ULL((u64)entire_cycle * prescale * 
NSEC_PER_SEC,
+                                             PWM_A133_OSC24M_RATE);
+       state->duty_cycle = DIV_ROUND_CLOSEST_ULL((u64)act_cycle * prescale * 
NSEC_PER_SEC,
+                                                 PWM_A133_OSC24M_RATE);
+
+       return 0;
+}
+
+static const struct pwm_ops pwm_a133_ops = {
+       .apply = pwm_a133_apply,
+       .get_state = pwm_a133_get_state,
+};
+
+static const struct of_device_id pwm_a133_dt_ids[] = {
+       { .compatible = "allwinner,sun50i-a133-pwm" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pwm_a133_dt_ids);
+
+static int pwm_a133_probe(struct platform_device *pdev)
+{
+       struct pwm_chip *chip;
+       struct pwm_a133 *pc;
+       int ret;
+
+       chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*pc));
+       if (IS_ERR(chip))
+               return PTR_ERR(chip);
+       pc = to_pwm_a133(chip);
+
+       pc->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(pc->base))
+               return PTR_ERR(pc->base);
+
+       pc->bus_clk = devm_clk_get_enabled(&pdev->dev, "bus");
+       if (IS_ERR(pc->bus_clk))
+               return dev_err_probe(&pdev->dev, PTR_ERR(pc->bus_clk),
+                                    "get bus clock failed\n");
+
+       pc->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+       if (IS_ERR(pc->rst))
+               return dev_err_probe(&pdev->dev, PTR_ERR(pc->rst),
+                                    "get reset failed\n");
+
+       ret = reset_control_deassert(pc->rst);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "cannot deassert reset control\n");
+
+       chip->ops = &pwm_a133_ops;
+
+       ret = pwmchip_add(chip);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+               reset_control_assert(pc->rst);
+               return ret;
+       }
+
+       platform_set_drvdata(pdev, chip);
+
+       return 0;
+}
+
+static void pwm_a133_remove(struct platform_device *pdev)
+{
+       struct pwm_chip *chip = platform_get_drvdata(pdev);
+       struct pwm_a133 *pc = to_pwm_a133(chip);
+
+       pwmchip_remove(chip);
+       reset_control_assert(pc->rst);
+}
+
+static struct platform_driver pwm_a133_driver = {
+       .driver = {
+               .name = "sun50i-a133-pwm",
+               .of_match_table = pwm_a133_dt_ids,
+       },
+       .probe = pwm_a133_probe,
+       .remove = pwm_a133_remove,
+};
+module_platform_driver(pwm_a133_driver);
+
+MODULE_DESCRIPTION("Allwinner A133 PWM driver");
+MODULE_LICENSE("GPL");
-- 
2.55.0

Reply via email to