[RFC,v3] arm64: allwinner: add TrimUI Smart Pro (A133) board support

Message ID 20260912103139.90657-1-leow149@lucidmail.xyz (mailing list archive)
State New
Headers
Series [RFC,v3] arm64: allwinner: add TrimUI Smart Pro (A133) board support |

Commit Message

leow149 Sept. 12, 2026, 10:31 a.m. UTC
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.

Since the RFC went out, an automated review turned up a real bug worth
calling out specifically: sun6i_dsi_clear_int_status() was clearing a
write-1-to-clear register via regmap_update_bits(), which silently
skips the actual write whenever its computed new value equals the
last-read one -- and for a W1C acknowledge, writing back what you just
read *is* the correct pattern, so this register's status bits were
never actually clearing in hardware. This directly breaks the
edge-detection the DSI retrigger timer depends on. Switched to
regmap_write_bits() (unconditional write) to fix it. Also addressed,
same review pass: a timer/IRQ not being torn down on CRTC disable, an
IRQF_NO_AUTOEN regression that would have permanently disabled vblank
for any other board sharing this TCON code, a spurious-vblank issue
from treating a bare FSYNC_INT as a real frame event, a raw
clock-gate write getting silently undone by the "disable unused
clocks" sweep, a PWM register race across channels, a PWM 100%-duty
truncation bug, a divide-by-zero guard, and a DT schema gap.

Re-tested all of the above on real hardware. Result: the regmap fix,
while real, did NOT resolve the TRIGGER_START symptom -- register
peeks (TCON0_CPU_IF_REG still shows TRIGGER_START stuck at 1;
DSI_INT_REG's VIDEO_VBLK status never latches) confirm the panel still
never receives a completed frame. The known-issue description above
still stands.

Testing did turn up two more real, hardware-confirmed bugs, since
fixed:

- TCON0's own interrupt line (GIC SPI 68) storms at tens of thousands
  of firings/sec with sun4i_tcon_handler() unable to reliably sample
  FSYNC_INT fast enough, which was tripping genirq's "nobody cared"
  unhandled-IRQ storm protection and permanently disabling the line
  60-100s into boot -- killing the handler's own TRI_FINISH-based
  backstop retrigger for the rest of the session. In DSI/CPU mode this
  driver is the only consumer of that GIC line, so there's no
  legitimate "not for us" case for genirq's heuristic to protect
  against here; treating every firing in that mode as handled
  unconditionally stops the storm from disabling it, confirmed via a
  full boot with zero "nobody cared" events afterward (previously
  reproduced twice).
- sun6i_dsi_encoder_enable() enabled both VIDEO_VBLK and VIDEO_LINE on
  DSI's own interrupt register; a live vendor register dump on a
  genuinely working reference system (already in this repo's
  vendor-notes/, gathered weeks ago but never acted on) shows only
  VIDEO_VBLK enabled. Matched vendor exactly. VIDEO_LINE fires once per
  horizontal line rather than once per frame -- a plausible contributor
  to the IRQ storm above, though removing it alone did not stop the
  storm (the genirq fix above was still needed).

Neither of these, nor the regmap fix, resolves the core bug. They're
real, independently-verified improvements to the driver's correctness
and robustness, kept because they're correct, not because they fixed
the black screen.

The same automated reviewer replied to v2 with 4 more findings. One is
real and fixed: sun4i_tcon0_mode_set_cpu() armed the DSI retrigger
timer on a guessed 1000ms wall-clock delay from mode_set time, racing
against sun6i_dsi_encoder_enable() (which can legitimately take longer
under real scheduling delays) -- both write SUN6I_DSI_INST_JUMP_SEL_REG
via sun6i_dsi_start() with no synchronization between them if the
timer fired first. Fixed by sequencing on encoder_enable()'s actual
completion instead of a timeout: added a tcon back-reference to struct
sun6i_dsi, and sun6i_dsi_encoder_enable() now arms the timer itself via
a new sun4i_tcon_dsi_start_retrigger() once it's genuinely done,
removing the race by construction. Two more findings were checked
against the actual kernel source and confirmed false positives, not
just asserted: disable_irq()/mutex_lock()/msleep() in these driver
callbacks are all claimed unsafe outside "atomic context", but
drm_atomic_helper_commit_tail()'s own drm_atomic_helper_wait_for_vblanks()
calls wait_event_timeout() in this exact call chain, and the PWM core's
own apply hook is named pwm_apply_might_sleep() specifically because
sleeping there is expected -- both confirm this is normal sleepable
process/workqueue context, not genuinely atomic. Added comments citing
this at each site so it doesn't get re-flagged. The fourth (whether
TRI_FINISH_ENABLE being unset means the retrigger backstop can't
signal without the FSYNC storm) is answered by data already in this
series' companion repo: the same working vendor system's live GINT0
dump (0x00000a00, every enable bit at 0) shows TRI_FINISH_INT's status
bit asserting without its own enable too, same as FSYNC -- not an
untested assumption. Added a comment citing that dump directly.

Cc: Chen-Yu Tsai <wens@kernel.org>
Cc: Jernej Skrabec <jernej.skrabec@gmail.com>
Cc: Samuel Holland <samuel@sholland.org>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: Paul Kocialkowski <paulk@sys-base.io>
Signed-off-by: leow149 <leow149@lucidmail.xyz>
---
Changes since v2:
- Fixed a real timer/register race the same automated reviewer found in
  v2: the DSI retrigger timer was armed on a guessed 1000ms wall-clock
  delay from mode_set time, racing sun6i_dsi_encoder_enable() (which
  can legitimately take longer under real scheduling delays) -- both
  write SUN6I_DSI_INST_JUMP_SEL_REG via sun6i_dsi_start() with no
  synchronization if the timer fired first. Fixed by sequencing on
  encoder_enable()'s actual completion instead of a timeout, removing
  the race by construction.
- Checked, and rejected with cited evidence, two more findings claiming
  disable_irq()/mutex_lock()/msleep() are unsafe in these callbacks
  because DRM "atomic commit" forbids sleeping: the exact function in
  this series' own earlier crash backtrace
  (drm_atomic_helper_commit_tail()) itself sleeps via
  wait_event_timeout() in the same call chain, and the PWM core names
  this exact hook pwm_apply_might_sleep() specifically because sleeping
  there is expected. Added comments citing this at both sites.
- A fourth finding (whether the retrigger backstop only "works" because
  of the FSYNC storm, since TRI_FINISH_ENABLE is never set) is answered
  by data already in this series' companion repo: the same live vendor
  register dump already cited for the FSYNC behavior shows
  TRI_FINISH_INT's status bit also asserting without its own enable.
  Added a comment citing it directly.
- Hardware-tested all of the above: boots clean, no crashes, no new
  warnings, previous fixes still hold. Core issue unchanged -- none of
  this was expected to address it.

 .../allwinner,sun8i-a83t-de2-mixer.yaml       |   2 +
 .../display/allwinner,sun8i-r40-tcon-top.yaml |  43 ++
 .../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            | 468 +++++++++++-
 drivers/gpu/drm/sun4i/sun4i_tcon.h            |  96 +++
 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c        | 201 ++++-
 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h        |  23 +
 drivers/gpu/drm/sun4i/sun8i_mixer.c           |  48 ++
 drivers/gpu/drm/sun4i/sun8i_tcon_top.c        | 119 ++-
 drivers/gpu/drm/sun4i/sun8i_tcon_top.h        |   2 +
 drivers/pwm/Kconfig                           |  14 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-sun50i-a133.c                 | 322 ++++++++
 19 files changed, 2501 insertions(+), 46 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
  

Comments

sashiko-bot@kernel.org Sept. 12, 2026, 10:46 a.m. UTC | #1
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Sleeping functions used in CRTC and encoder atomic callbacks (Subsystem Guide Violation)
- [High] Vblank interrupts are permanently disabled for non-DSI boards after DPMS cycle
- [High] hrtimer callback attempts to acquire sleepable spinlock on PREEMPT_RT
- [High] TCON interrupt storm causes severe CPU hog by unconditionally returning IRQ_HANDLED
- [Medium] CLK_IS_CRITICAL on tcon-top-dsi gate causes power management regression
- [Low] Missing EXPORT_SYMBOL for sun6i_mipi_dsi functions causes module linkage failure
--

commit 5d94a8dca17c72188e51f01e2e6fb28930a6a4da
Author: leow149 <leow149@lucidmail.xyz>

arm64: allwinner: add TrimUI Smart Pro (A133) board support
    
Board bring-up for the TrimUI Smart Pro handheld (Allwinner A133 Plus).
This includes a new board DTS, a new OTM1289A/ER68576 DSI panel driver,
a new PWM driver for the A133, and display pipeline support.

> diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c b/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c
[ ... ]
> +static int otm1289a_init_sequence_send(struct otm1289a *ctx)
> +{
[ ... ]
> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> +	if (ret < 0)
> +		return ret;
> +	msleep(50);

[Severity: High]
Does this msleep() introduce a sleep in atomic context? The panel
initialization is invoked from the encoder atomic enable callback, which is an
atomic context where sleeping is forbidden. This can lead to kernel panics and
deadlocks during display atomic commits.

> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
[ ... ]
> @@ -221,6 +221,34 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
[ ... ]
> +		if (tcon->irq_enabled) {
> +			disable_irq(tcon->irq);

[Severity: High]
Could this disable_irq() call cause issues by sleeping in atomic context? This
is executed within the CRTC atomic disable path, which shouldn't contain
sleepable functions.

[ ... ]
> @@ -713,11 +948,31 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
[ ... ]
> +	if (!tcon->dsi_cpu_mode && tcon->irq && !tcon->irq_enabled) {
> +		enable_irq(tcon->irq);
> +		tcon->irq_enabled = true;
> +	}
>  }

[Severity: High]
Will vblank interrupts stay permanently disabled for non-DSI boards after a
DPMS off/on cycle? Because standard DPMS unblanking doesn't invoke the
mode_set_nofb hook, placing the IRQ enable logic here means it is skipped
during DPMS cycles, leaving the IRQ disabled.

[ ... ]
> @@ -759,30 +1030,173 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private)
[ ... ]
> +	handled = tcon->dsi_cpu_mode ||
> +		  (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;

[Severity: High]
Does unconditionally returning IRQ_HANDLED for DSI/CPU mode leave the system
vulnerable to an unmitigated interrupt storm? By defeating genirq's
protection, the FSYNC_INT storm could cause a severe CPU hog. The DSI block's
own interrupt (SPI 100) might be the safer hardware approach here.

[ ... ]
> +static enum hrtimer_restart sun4i_tcon_dsi_retrigger_timer_fn(struct hrtimer *timer)
> +{
[ ... ]
> +	regmap_read(tcon->regs, SUN4I_TCON0_CPU_IF_REG, &cpu_if_before);

[Severity: High]
Can this regmap_read() cause a kernel panic on PREEMPT_RT kernels? It attempts
to acquire a sleepable spinlock on an MMIO regmap, but the hrtimer is
initialized with HRTIMER_MODE_REL (hardirq context).

[ ... ]
> @@ -1136,6 +1562,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
[ ... ]
> +	hrtimer_setup(&tcon->dsi_retrigger_timer, sun4i_tcon_dsi_retrigger_timer_fn,
> +		      CLOCK_MONOTONIC, HRTIMER_MODE_REL);

[Severity: High]
Should this timer be configured as HRTIMER_MODE_REL_SOFT instead, so that its
callback can safely execute sleepable functions in softirq context on
PREEMPT_RT?

> diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
> --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
> +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
[ ... ]
> +void sun6i_dsi_tri_start(struct sun6i_dsi *dsi)
> +{
> +	sun6i_dsi_start(dsi, DSI_START_TRI);
> +}

[Severity: Low]
Is this function missing an EXPORT_SYMBOL macro? Without it, modular builds of
sun4i-tcon.ko depending on this function will fail at the modpost stage.

[ ... ]
> +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;
> +}

[Severity: Low]
Does this function also need an EXPORT_SYMBOL macro to prevent module linkage
failures?

[ ... ]
> +void sun6i_dsi_clear_int_status(struct sun6i_dsi *dsi)
> +{
> +	unsigned int val = 0;
> +
> +	regmap_read(dsi->regs, SUN6I_DSI_INT_REG, &val);
> +	regmap_write_bits(dsi->regs, SUN6I_DSI_INT_REG,
> +			  GENMASK(31, 16), val & GENMASK(31, 16));
> +}

[Severity: Low]
Is an EXPORT_SYMBOL macro required here as well for modular builds?

> diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
> --- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
> +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
[ ... ]
> @@ -241,19 +315,33 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
[ ... ]
>  	if (quirks->has_dsi)
>  		clk_data->hws[CLK_TCON_TOP_DSI] =
>  			sun8i_tcon_top_register_gate(dev, "dsi", regs,
>  						     &tcon_top->reg_lock,
> -						     TCON_TOP_TCON_DSI_GATE, i++);
> +						     TCON_TOP_TCON_DSI_GATE, i++,
> +						     CLK_IS_CRITICAL);

[Severity: Medium]
Will using CLK_IS_CRITICAL on this gate cause a power management regression?
This forces the clock to remain permanently enabled, holding references to its
parent (CLK_MIPI_DSI) and preventing the PLL from ever powering down.
CLK_IGNORE_UNUSED might be more appropriate to prevent the clock core's
disable_unused sweep.

> diff --git a/drivers/pwm/pwm-sun50i-a133.c b/drivers/pwm/pwm-sun50i-a133.c
> --- /dev/null
> +++ b/drivers/pwm/pwm-sun50i-a133.c
[ ... ]
> +static int pwm_a133_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			  const struct pwm_state *state)
> +{
[ ... ]
> +	mutex_lock(&pc->lock);

[Severity: High]
Does this use a sleeping mutex inside an atomic context? The PWM apply hook
is reached via backlight_enable() from the encoder atomic enable callback,
which is an atomic context where sleeping is forbidden. This can lead to
kernel panics and deadlocks.
  

Patch

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..2dd2c255e 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
@@ -201,6 +202,48 @@  allOf:
           items:
             - description: TCON TV0 output clock name
 
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: allwinner,sun50i-a133-tcon-top
+
+    then:
+      properties:
+        clocks:
+          items:
+            - description: The TCON TOP interface clock
+            - description: >
+                The TCON TOP TV0 clock. This SoC has no TCON-TV/HDMI output
+                at all, so this is an otherwise-unused filler clock, only
+                present because the driver unconditionally registers a TV0
+                gate regardless of quirks.
+            - description: The TCON TOP MIPI DSI clock
+
+        clock-names:
+          items:
+            - const: bus
+            - const: tcon-tv0
+            - const: dsi
+
+        clock-output-names:
+          items:
+            - description: TCON TV0 output clock name (unused, see above)
+            - description: DSI output clock name
+
+    # NOTE: this variant has no HDMI mux (no port@4/port@5) and, on at
+    # least the one board using it so far, only mixer0 (no port@2/port@3)
+    # -- but properties.ports.required above applies unconditionally to
+    # every compatible in this file, including this one, and an `if/then`
+    # can only add to that required list, not relax it. A DTS for this
+    # compatible that only wires up port@0/port@1 is therefore expected to
+    # fail `dtbs_check` against this schema as it stands. Properly fixing
+    # that means restructuring the base schema so port@2-5 are required
+    # per-variant instead of unconditionally, which would need re-checking
+    # against R40/D1/H6's actual in-tree DTS files to do safely -- left
+    # for a maintainer with those boards to verify, rather than guessed at
+    # here.
+
 examples:
   - |
     #include <dt-bindings/interrupt-controller/arm-gic.h>
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..21ff77233 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -221,6 +221,34 @@  void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
 		sun4i_tcon_lvds_set_status(tcon, encoder, true);
 
 	sun4i_tcon_channel_set_status(tcon, channel, enabled);
+
+	/*
+	 * On disable, stop the DSI/CPU-mode retrigger timer before anything
+	 * downstream (clocks, D-PHY) gets gated off. Left running, it keeps
+	 * touching tcon->regs/dsi->regs every 2ms regardless of the CRTC's
+	 * state; hitting gated hardware from there is a real bus-abort risk,
+	 * not just wasted work. hrtimer_cancel() on an already-inactive timer
+	 * is a safe no-op, so this is fine to call unconditionally rather
+	 * than gating it on dsi_cpu_mode.
+	 */
+	if (!enabled) {
+		hrtimer_cancel(&tcon->dsi_retrigger_timer);
+		/*
+		 * disable_irq() (which might_sleep()s internally) is safe
+		 * here: this function is reached via sun4i_crtc_atomic_disable(),
+		 * itself called from drm_atomic_helper_commit_tail()'s
+		 * modeset-disables step, the same call chain that later calls
+		 * drm_atomic_helper_wait_for_vblanks() -> wait_event_timeout()
+		 * in this same commit. If sleeping weren't legal here, that
+		 * wait_event_timeout() call would already be a bug in DRM's
+		 * own core helper, not just in this driver -- this is normal
+		 * process/workqueue context, not a genuinely atomic section.
+		 */
+		if (tcon->irq_enabled) {
+			disable_irq(tcon->irq);
+			tcon->irq_enabled = false;
+		}
+	}
 }
 
 void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
@@ -229,6 +257,26 @@  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.
+	 *
+	 * This also means SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE is never
+	 * set, so it's fair to ask whether TRI_FINISH_INT's status bit can
+	 * assert at all without its own enable bit -- the same live vendor
+	 * dump answers this directly: TCON0_GINT0 read 0x00000a00 on that
+	 * reference system (TRI_FINISH_INT and FSYNC_INT both set) with every
+	 * enable bit at 0. Both status bits assert regardless of their own
+	 * enable, confirmed on real working hardware, not assumed.
+	 */
+	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 +321,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 +476,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 +507,32 @@  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);
+	/*
+	 * Back-reference so sun6i_dsi_encoder_enable() can arm the retrigger
+	 * timer itself once it actually finishes -- see the comment on
+	 * sun4i_tcon_dsi_start_retrigger() for why this replaced a wall-clock
+	 * guess made from here.
+	 */
+	tcon->dsi->tcon = tcon;
+
+	/*
+	 * 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 +542,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 +566,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,25 +582,80 @@  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;
-	start_delay = start_delay / (mode->crtc_clock / 1000) / 8;
+	/*
+	 * ((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.
+	 *
+	 * mode->crtc_clock is in kHz; a sub-1MHz mode (pre-existing
+	 * possibility, not specific to this panel/board) would make the
+	 * "/ 1000" term truncate to 0 and divide-by-zero below. Clamped to a
+	 * minimum of 1 -- purely a crash guard, no real mode on this panel
+	 * comes remotely close to that boundary.
+	 */
+	start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 9);
+	start_delay = start_delay * mode->crtc_htotal * 300;
+	start_delay = start_delay / max(mode->crtc_clock / 1000, 1) / 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);
+
+	/*
+	 * The retrigger timer itself is armed from sun6i_dsi_encoder_enable()
+	 * once that function actually finishes, not from here -- see
+	 * sun4i_tcon_dsi_start_retrigger()'s comment for why a wall-clock
+	 * guess made at this point (mode_set_nofb time, well before
+	 * encoder_enable even starts) was replaced with sequencing on real
+	 * completion instead.
+	 */
+}
+
+/*
+ * Called from sun6i_dsi_encoder_enable() once it has genuinely finished
+ * (DSI_START_HSD already issued) rather than from mode_set_cpu() above on a
+ * guessed wall-clock delay. The previous 1000ms guess (sized for this panel's
+ * drm_panel_prepare() sequence, ~300ms of msleep()) raced against
+ * encoder_enable() under real scheduling delays: both this timer's callback
+ * (hrtimer, hardirq context) and encoder_enable() (process context) write
+ * SUN6I_DSI_INST_JUMP_SEL_REG via sun6i_dsi_start(), with no synchronization
+ * between them if the timer fired before encoder_enable() actually finished.
+ * Sequencing on real completion instead of a timeout removes that race
+ * entirely rather than just widening the margin.
+ */
+void sun4i_tcon_dsi_start_retrigger(struct sun4i_tcon *tcon)
+{
+	tcon->dsi_retrigger_ticks = 0;
+	hrtimer_start(&tcon->dsi_retrigger_timer, ms_to_ktime(2),
+		      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.
+	 */
 }
+EXPORT_SYMBOL(sun4i_tcon_dsi_start_retrigger);
 
 static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
 				      const struct drm_encoder *encoder,
@@ -713,11 +948,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);
@@ -735,6 +990,22 @@  void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
 	default:
 		DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n");
 	}
+
+	/*
+	 * Every other encoder type above relies on the IRQ being live from
+	 * probe, same as before this driver ever supported DSI/CPU mode --
+	 * only the DSI/CPU-mode path itself needs the delayed enable via
+	 * dsi_retrigger_timer, to avoid an interrupt storm before TCON0/DSI/
+	 * D-PHY are configured (see the comment on sun4i_tcon_init_irq()).
+	 * Without this, tcon->irq was requested IRQF_NO_AUTOEN for every
+	 * board using this driver, but only ever enabled again from the
+	 * DSI-specific retrigger timer -- any non-DSI board's vblank IRQ
+	 * would otherwise stay permanently disabled forever.
+	 */
+	if (!tcon->dsi_cpu_mode && tcon->irq && !tcon->irq_enabled) {
+		enable_irq(tcon->irq);
+		tcon->irq_enabled = true;
+	}
 }
 EXPORT_SYMBOL(sun4i_tcon_mode_set);
 
@@ -759,30 +1030,173 @@  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 -- a
+	 * confirmed vendor register dump on a genuinely working reference
+	 * system shows the exact same bit persistently set too (stable
+	 * across 5 rapid samples), so this is real, expected hardware
+	 * behavior in DSI/CPU mode, not a bug. What IS a real, measured
+	 * problem: it toggles far faster than a single regmap_read() can
+	 * reliably catch (live testing shows genirq's "nobody cared"
+	 * unhandled-IRQ storm protection tripping and permanently disabling
+	 * this line within 60-100s, based on this exact read occasionally
+	 * landing between pulses and seeing status=0). Once disabled,
+	 * sun4i_tcon_handler()'s own TRI_FINISH-based backstop retrigger
+	 * (see below) is gone for the rest of the boot, for good.
+	 *
+	 * In DSI/CPU mode, this driver is the only thing wired to this GIC
+	 * line -- there is no other legitimate source that this interrupt
+	 * could be "not for us", unlike the general shared-IRQ case genirq's
+	 * heuristic is meant to protect against. So in that mode, treat
+	 * every firing as ours unconditionally rather than trusting a single
+	 * status snapshot; non-DSI boards sharing this same driver are
+	 * unaffected and keep the normal status-based check.
+	 */
+	handled = tcon->dsi_cpu_mode ||
+		  (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);
-	sun4i_tcon_finish_page_flip(drm, scrtc);
+	/*
+	 * FSYNC_INT alone is not a real vblank/frame-complete event -- it's
+	 * being acked purely to keep the level interrupt from storming (see
+	 * the comment above). Firing drm_crtc_handle_vblank()/
+	 * finish_page_flip() on every FSYNC_INT pulse, unconditionally,
+	 * generates spurious vblank events and can complete page-flip fences
+	 * before the frame they're for has actually reached the screen.
+	 * Only a genuine VBLANK or TRI_FINISH source should do that.
+	 */
+	if (status & (SUN4I_TCON_GINT0_VBLANK_INT(0) |
+		      SUN4I_TCON_GINT0_VBLANK_INT(1) |
+		      SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT)) {
+		drm_crtc_handle_vblank(&scrtc->crtc);
+		sun4i_tcon_finish_page_flip(drm, scrtc);
+	}
 
 	/* Acknowledge the interrupt */
 	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. Guarded by
+	 * irq_enabled (shared with sun4i_tcon_mode_set()'s non-DSI path and
+	 * sun4i_tcon_set_status()'s disable path) so a second mode_set --
+	 * e.g. a DPMS off/on cycle re-arming this same timer -- can't call
+	 * enable_irq() on an already-enabled IRQ, which is a genirq
+	 * usage error (WARN + stack dump, enable/disable calls must balance).
+	 */
+	if (tick == 0 && tcon->irq && !tcon->irq_enabled) {
+		enable_irq(tcon->irq);
+		tcon->irq_enabled = true;
+	}
+
+	/*
+	 * 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 +1225,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 +1245,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 +1562,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 +1733,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..666fa9a07 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,22 @@  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;
+
+	/*
+	 * Tracks the enable_irq()/disable_irq() balance for the above: both
+	 * the DSI/CPU-mode retrigger timer and the non-DSI mode_set path can
+	 * each try to bring the IRQ up, and set_status(false) tears it back
+	 * down on disable -- this flag is the single source of truth so
+	 * neither path ever double-enables or double-disables it.
+	 */
+	bool				irq_enabled;
+
 	/* Main bus clock */
 	struct clk			*clk;
 
@@ -279,6 +334,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;
 
@@ -297,6 +392,7 @@  void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
 			 const struct drm_display_mode *mode);
 void sun4i_tcon_set_status(struct sun4i_tcon *crtc,
 			   const struct drm_encoder *encoder, bool enable);
+void sun4i_tcon_dsi_start_retrigger(struct sun4i_tcon *tcon);
 
 extern const struct of_device_id sun4i_tcon_of_table[];
 
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index d504ae583..38960d88d 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,60 @@  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; the enable half (low 16) is left
+ * untouched by masking it out of the write.
+ *
+ * This has to be regmap_write_bits(), not regmap_update_bits(): the value
+ * being written here is, by construction, identical to what was just read
+ * (that's the write-1-to-clear pattern -- write back the bits you saw set).
+ * regmap_update_bits() skips the actual bus write whenever its computed new
+ * value equals the last-read value, which for every other register is a
+ * harmless no-op elision but for a W1C register silently drops the clear
+ * entirely: the acknowledge write never reaches hardware, the status bits
+ * never actually clear, and the edge-detection this function exists for
+ * (see sun4i_tcon_dsi_retrigger_timer_fn()'s comment) never sees an edge
+ * again after the first one. regmap_write_bits() forces the write
+ * unconditionally, matching what a raw write-1-to-clear needs.
+ */
+void sun6i_dsi_clear_int_status(struct sun6i_dsi *dsi)
+{
+	unsigned int val = 0;
+
+	regmap_read(dsi->regs, SUN6I_DSI_INT_REG, &val);
+	regmap_write_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 +833,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 +849,27 @@  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. VIDEO_VBLK only, not
+	 * VIDEO_LINE -- a live vendor register dump on a working reference
+	 * system (vendor-notes/live_reg_dump_20260823.md) shows DSI_INT_REG
+	 * at 0x00020004, i.e. only the VBLK enable bit set. VIDEO_LINE fires
+	 * once per horizontal line (tens of thousands of times a second at
+	 * this panel's timing) rather than once per frame; enabling it here
+	 * doesn't match vendor and is a plausible contributor to a real,
+	 * separately-observed TCON0 interrupt storm (genirq's "nobody cared"
+	 * unhandled-IRQ storm protection has been seen tripping on tcon0's
+	 * own GIC line during testing).
+	 */
+	regmap_write(dsi->regs, SUN6I_DSI_INT_REG, SUN6I_DSI_INT_EN_VIDEO_VBLK);
+
+	/*
+	 * 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 +880,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,29 +907,50 @@  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.
+	 * 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.
 	 *
-	 * 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.
+	 * This ordering change is unconditional for every panel using this
+	 * shared encoder-enable path, not just this board's. It's verified
+	 * correct against this board's OTM1289A/ER68576 panel and its vendor
+	 * driver specifically; it has not been re-verified against every
+	 * other existing sun6i_mipi_dsi panel. If a panel's own prepare()
+	 * sequence has some other, non-DCS-related dependency on the clock
+	 * lane staying in LP mode until after prepare() -- which is exactly
+	 * what upstream's own prior comment here flagged as unresolved --
+	 * that panel would be the one to break. Flagging this explicitly for
+	 * reviewers/maintainers of other sun6i_mipi_dsi boards.
 	 */
-	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);
+
+	/*
+	 * DSI/CPU-mode boards: arm the retrigger timer now that this function
+	 * is genuinely done, instead of guessing a wall-clock delay from
+	 * sun4i_tcon0_mode_set_cpu() (see sun4i_tcon_dsi_start_retrigger()'s
+	 * comment) -- NULL for any board not using that path.
+	 */
+	if (dsi->tcon)
+		sun4i_tcon_dsi_start_retrigger(dsi->tcon);
 }
 
 static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder)
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
index f1ddefe0f..651acf3b4 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
@@ -15,6 +15,16 @@ 
 
 #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 sun4i_tcon;
+
 struct sun6i_dsi_variant {
 	bool			has_mod_clk;
 	bool			set_mod_clk;
@@ -38,6 +48,15 @@  struct sun6i_dsi {
 	struct drm_panel	*panel;
 
 	const struct sun6i_dsi_variant *variant;
+
+	/*
+	 * Set by sun4i_tcon0_mode_set_cpu() at the same time it sets its own
+	 * tcon->dsi, so sun6i_dsi_encoder_enable() can arm the DSI/CPU-mode
+	 * retrigger timer itself once it actually finishes -- see the
+	 * comment on sun4i_tcon_dsi_start_retrigger(). NULL for any board
+	 * not using that path.
+	 */
+	struct sun4i_tcon	*tcon;
 };
 
 static inline struct sun6i_dsi *host_to_sun6i_dsi(struct mipi_dsi_host *host)
@@ -55,4 +74,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..cab9396ea 100644
--- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
+++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
@@ -143,12 +143,86 @@  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,
 						   void __iomem *regs,
 						   spinlock_t *lock,
-						   u8 bit, int name_index)
+						   u8 bit, int name_index,
+						   unsigned long extra_flags)
 {
 	const char *clk_name, *parent_name;
 	int ret, index;
@@ -166,7 +240,7 @@  static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
 		return ERR_PTR(ret);
 
 	return clk_hw_register_gate(dev, clk_name, parent_name,
-				    CLK_SET_RATE_PARENT,
+				    CLK_SET_RATE_PARENT | extra_flags,
 				    regs + TCON_TOP_GATE_SRC_REG,
 				    bit, 0, lock);
 };
@@ -241,19 +315,33 @@  static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
 	clk_data->hws[CLK_TCON_TOP_TV0] =
 		sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs,
 					     &tcon_top->reg_lock,
-					     TCON_TOP_TCON_TV0_GATE, i++);
+					     TCON_TOP_TCON_TV0_GATE, i++, 0);
 
 	if (quirks->has_tcon_tv1)
 		clk_data->hws[CLK_TCON_TOP_TV1] =
 			sun8i_tcon_top_register_gate(dev, "tcon-tv1", regs,
 						     &tcon_top->reg_lock,
-						     TCON_TOP_TCON_TV1_GATE, i++);
+						     TCON_TOP_TCON_TV1_GATE, i++, 0);
 
+	/*
+	 * CLK_IS_CRITICAL: this same bit is also toggled directly by
+	 * sun8i_tcon_top_set_dsi_gate() (see the comment there for why it
+	 * can't just be a normal clk consumer). Without this flag, the clk
+	 * core's own "disable unused clocks" late_initcall sweep sees this
+	 * clk_hw's enable_count still at 0 -- nothing ever called
+	 * clk_prepare_enable() on it, since nothing consumes it that way --
+	 * and turns the bit back off shortly after boot, silently undoing
+	 * whatever sun8i_tcon_top_set_dsi_gate(true) had just set. Marking
+	 * it critical only exempts it from that one sweep; it doesn't force
+	 * the bit on or otherwise interfere with the raw enable/disable
+	 * calls, which remain the actual control path.
+	 */
 	if (quirks->has_dsi)
 		clk_data->hws[CLK_TCON_TOP_DSI] =
 			sun8i_tcon_top_register_gate(dev, "dsi", regs,
 						     &tcon_top->reg_lock,
-						     TCON_TOP_TCON_DSI_GATE, i++);
+						     TCON_TOP_TCON_DSI_GATE, i++,
+						     CLK_IS_CRITICAL);
 
 	for (i = 0; i < CLK_NUM; i++)
 		if (IS_ERR(clk_data->hws[i])) {
@@ -325,6 +413,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 +444,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..877d76d07
--- /dev/null
+++ b/drivers/pwm/pwm-sun50i-a133.c
@@ -0,0 +1,322 @@ 
+// 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/mutex.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;
+	/*
+	 * PWM_A133_PCGR_REG (channel clock gating) and PWM_A133_PER_REG
+	 * (channel enable) are each single shared registers covering every
+	 * channel on this chip, and PWM_A133_PCCR01_REG/PCCR23_REG are each
+	 * shared by a pair of channels. The PWM core only serializes calls
+	 * per pwm_device, not across different channels on the same chip, so
+	 * two channels' .apply() read-modify-write sequences on one of these
+	 * shared registers can race and silently drop one channel's update.
+	 * This lock serializes the whole apply() body across channels.
+	 */
+	struct mutex lock;
+};
+
+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;
+
+	/*
+	 * act_cycle's hardware field is 16 bits wide (0-65535). period_cycles
+	 * can legitimately be PWM_A133_CYCLE_MAX (65536, representable in
+	 * entire_cycle as period_cycles - 1 = 65535), so a 100%-duty request
+	 * at that exact period would set duty_cycles = 65536 too -- which
+	 * masks down to 0 when written to the 16-bit field below, silently
+	 * turning 100% duty into 0%. Clamping to the field's real maximum
+	 * trades a negligible ~0.0015% duty error in that one edge case for
+	 * never emitting a fully wrong output.
+	 */
+	if (duty_cycles >= PWM_A133_CYCLE_MAX)
+		duty_cycles = PWM_A133_CYCLE_MAX - 1;
+
+	*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) {
+		mutex_lock(&pc->lock);
+		val = pwm_a133_readl(pc, PWM_A133_PER_REG);
+		val &= ~PWM_A133_PER_EN(ch);
+		pwm_a133_writel(pc, PWM_A133_PER_REG, val);
+		mutex_unlock(&pc->lock);
+		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;
+	}
+
+	/*
+	 * PWM_A133_PCCR01_REG/PCCR23_REG (shared by a channel pair) and
+	 * PWM_A133_PCGR_REG/PWM_A133_PER_REG (shared by all 16 channels) are
+	 * each read-modify-written below; the PWM core doesn't serialize
+	 * .apply() calls across different channels on the same chip, so this
+	 * whole sequence needs its own lock against a concurrent apply() on
+	 * another channel racing on the same register. Sleeping here is
+	 * within contract: this op is named pwm_apply_might_sleep() in the
+	 * PWM core's own public API specifically because implementations are
+	 * expected to be able to sleep; only the separate pwm_apply_atomic()
+	 * path requires a non-sleeping implementation, which this chip
+	 * doesn't provide.
+	 */
+	mutex_lock(&pc->lock);
+
+	/* 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);
+
+	mutex_unlock(&pc->lock);
+
+	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);
+
+	mutex_init(&pc->lock);
+
+	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");