From patchwork Sat Sep 12 23:32:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: leow149 X-Patchwork-Id: 3288 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id 065621C02D1 for ; Sun, 13 Sep 2026 01:33:04 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=lucidmail.xyz; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-25835-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-25835-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 265A032F7C for ; Sat, 12 Sep 2026 23:33:02 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 89E7E361975; Sat, 12 Sep 2026 23:33:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lucidmail.xyz header.i=@lucidmail.xyz header.b="MHkB17NN" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail.lucidmail.xyz (mail.lucidmail.xyz [78.46.175.68]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B6342145B27 for ; Sat, 12 Sep 2026 23:32:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=78.46.175.68 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789255981; cv=none; b=D+fysKvjoub7n/3uxUHYv2D5D1LaTWTqTlj+BUI8x1nT5Chm7ARZE58J2MxgauBFybKjgYkMLZ2jCv3Lb1fhbJtm8axWT8u9NVj3ezOCSzSqkpVBkS/vkUJ+RXV6MtDzj5oXdAMxdYvkGU7xKV1kxhcVFcWVFypuSskSL1olQkg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789255981; c=relaxed/simple; bh=W9kPdXlUiEOMyDM277Av2G1Fb7Y5dzNLUrwninGO6TY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=o/m0wCXXRyditFY7878fAAienYevfNABNLi1TxHplQHD22Xy6p2HZmfkqDoqYiQfIEEKRv0+EOExMDnazX08l6W8FHsiWzn16yB9V6O0+suzx8Sdh0utlAmsTcMc0ymbRShLXv2RO11PFh91qnhDYGwGFFenIjp4zyVUeL3oCVo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lucidmail.xyz; spf=pass smtp.mailfrom=lucidmail.xyz; dkim=pass (2048-bit key) header.d=lucidmail.xyz header.i=@lucidmail.xyz header.b=MHkB17NN; arc=none smtp.client-ip=78.46.175.68 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lucidmail.xyz Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lucidmail.xyz Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id CA1B9177C7A; Sun, 13 Sep 2026 01:32:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lucidmail.xyz; s=dkim; t=1789255971; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=sH8BhKtaUa9VS/FHV6lxG5eXef3pjIn0knHBVYFlNmA=; b=MHkB17NNCXzRv08YfCWd9x8hc80K6ziB1XKe6DHoODKMndrTxRFUEI9xbr8AlD4XdQjlhc Tw2YiU98pE0nLOIFo/zRv6XiibPPxjMplvVHF5z+aHr3n1PnqzDQ0PIqiFAK5NMIQurPTc e3t1K3bGKdc2UAXg1nb4EKy+Ulo5zFlJ1Ctr+KILd0F8s+R/QFX1m0yW4KDeEsTtVSuRhe 5FTFujGxzSDIauMZH8JFyctAAqyQ1kFNPEUIXl5zzWZNGa9G6ZmkASP2SOk8OCub66/d2L 2I3k47Y7Rzvr+tFWQ9Eos/IVhvYWEXjr89AzeHmEFrnI6Di/AfFYGzxE0U8xFA== From: leow149 To: dri-devel@lists.freedesktop.org, linux-sunxi@lists.linux.dev Cc: wens@kernel.org, jernej.skrabec@gmail.com, samuel@sholland.org, mripard@kernel.org, paulk@sys-base.io, leow149 Subject: [RFC PATCH v8] arm64: allwinner: add TrimUI Smart Pro (A133) board support Date: Sun, 13 Sep 2026 01:32:36 +0200 Message-ID: <20260912233238.110960-1-leow149@lucidmail.xyz> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260912230131.109229-1-leow149@lucidmail.xyz> References: <20260912230131.109229-1-leow149@lucidmail.xyz> Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Last-TLS-Session-Version: TLSv1.3 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= 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, IRQ ordering/ack fixes) plus TCON-TOP wiring for this board's DSI clock gate and DE0 port routing. All of the CPU/8080-interface-specific additions are gated behind a new, more specific "allwinner,sun50i-a133-tcon-lcd" compatible string (dsi_cpu_needs_retrigger quirk field), so none of it affects Pinephone/Pinetab (A64), the one other real board sharing this driver's shared DSI/CPU-mode code path. - sun6i_mipi_dsi: an additional DSI_START_TRI instruction-jump-table case for continuous per-frame retriggering (distinct from the existing one-shot HS-clock-enable case), a DRQ_SET formula fix, and reordering the HS-clock-enable step to run before the panel's DCS init sequence. - ccu-sun50i-a100: CLK_SET_RATE_PARENT on the tcon-lcd0 clock so its rate request can actually explore parent PLL rates. Sending as RFC rather than waiting for the display to fully work: posting the board/DTS foundation now so it's in front of people who know this hardware, rather than sitting on it indefinitely. Known issue, and the actual reason for the RFC tag: the panel attaches and the backlight lights, but no pixel data reaches it. TCON0_CPU_IF_REG's TRIGGER_START bit (a documented self-clearing one-shot) never self-clears, despite the rest of the pixel pipeline matching a working vendor system register-for-register -- every static register value, D-PHY lane state, control-flow/sequencing cross-referenced against a symbol-resolved decompile of the vendor kernel, and even measured real-time timing all confirmed matching. Full writeup, including what's been ruled out and how: https://github.com/leow149/trimui-a133-mainline-port/blob/main/COMMUNITY_HELP_REQUEST.md If anyone recognizes this symptom on the CPU/8080-interface DSI path, or can spot something the writeup missed, I would very much like to hear about it. Cc: Chen-Yu Tsai Cc: Jernej Skrabec Cc: Samuel Holland Cc: Maxime Ripard Cc: Paul Kocialkowski Signed-off-by: leow149 --- Changelog (kept below the cut, not in the commit message -- see the automated-review replies on this thread for full per-finding detail): v1 -> v2: Fixed sun6i_dsi_clear_int_status() using regmap_update_bits() on a write-1-to-clear register, which silently no-op'd the write and broke the retrigger timer's edge detection. Also fixed a timer/IRQ not being torn down on CRTC disable, an IRQF_NO_AUTOEN regression that would have permanently disabled vblank for other boards sharing this TCON code, a spurious-vblank issue from treating bare FSYNC_INT as a frame event, a clock-gate write getting silently undone by "disable unused clocks", a PWM register race across channels, a PWM 100%-duty truncation bug, a divide-by-zero guard, and a DT schema gap. Hardware testing after all of the above showed the regmap fix, while real, did NOT resolve the core TRIGGER_START symptom. That same testing turned up two more real, hardware-confirmed bugs, also fixed here: TCON0's own interrupt line storming at tens of thousands of firings/sec, tripping genirq's "nobody cared" protection and permanently killing the IRQ handler's own retrigger backstop -- fixed by treating every firing as handled unconditionally in DSI/CPU mode, since this driver is the line's only legitimate consumer in that mode; and sun6i_dsi_encoder_enable() enabling both VIDEO_VBLK and VIDEO_LINE on DSI's own interrupt register when a live vendor register dump on a working reference system shows only VIDEO_VBLK enabled -- matched vendor exactly. Neither of these, nor the regmap fix, resolved the core bug. v2 -> v3: Fixed the retrigger timer racing sun6i_dsi_encoder_enable() on a guessed 1000ms wall-clock delay instead of sequencing on real completion (added a tcon back-reference so encoder_enable() arms the timer itself once actually done, via a new sun4i_tcon_dsi_start_retrigger()). Rejected two atomic-context sleeping claims (disable_irq()/mutex_lock()/msleep()) as false positives, checked directly against drm_atomic_helper_commit_tail()'s own wait_for_vblanks() call chain and the PWM core's own pwm_apply_might_sleep() naming. Answered a question about whether TRI_FINISH_ENABLE being unset blocks the retrigger backstop, using data already on hand: a working vendor system's live GINT0 dump (0x00000a00) shows TRI_FINISH_INT's status bit asserting without its own enable too, same as FSYNC -- not an untested assumption. v3 -> v4: Fixed both sun4i_tcon_regmap_config and sun6i_dsi_regmap_config missing fast_io -- without it, regmap defaults to mutex-based locking, a sleep-in-hardirq hazard given sun4i_tcon_handler() is a genuine non-threaded hardirq handler concurrently accessed by the retrigger hrtimer. Fixed by adding fast_io and switching to spinlock-based locking (verified against regmap_lock_spinlock()'s spin_lock_irqsave() usage), and switching the retrigger timer to HRTIMER_MODE_REL_SOFT while here, moving its frequent callback out of genuine hardirq context. Fixed sun6i_dsi_tri_start()/read_int_status()/clear_int_status() missing EXPORT_SYMBOL -- a real modular-build failure (CONFIG_DRM_SUN4I=m would fail to resolve these across the sun4i_tcon.c/sun6i_mipi_dsi.c module boundary), not just a modpost nag. Fixed a non-DSI enable_irq() call living in sun4i_tcon_mode_set(), which a plain DPMS off/on cycle never calls again -- moved to sun4i_tcon_set_status()'s enable=true branch, symmetric with the existing disable=false branch. Swapped CLK_IS_CRITICAL for CLK_IGNORE_UNUSED on the TCON-TOP DSI gate (the former forces the clock, and its parent PLL, permanently on -- an unintended power regression; the latter exempts the same disable_unused sweep without forcing anything on). Rejected the same atomic-context claim reapplied to otm1289a's msleep() and sun4i_tcon_set_status()'s disable_irq(), and a CPU-hog concern about unconditionally treating every DSI/CPU-mode IRQ firing as handled -- accepted as a real, documented trade-off (the alternative, letting genirq disable the line, is empirically worse), not fixed. v4 -> v5: Fixed a bus-fault race between sun6i_dsi_encoder_disable() gating DSI clocks/reset and this driver's own retrigger timer/IRQ teardown running later, by moving the teardown into sun6i_dsi_encoder_disable() itself (closing the window at its actual source, via a new sun4i_tcon_dsi_stop_retrigger()). Fixed the GINT0 acknowledge write missing TRI_COUNTER_INT (a potential hard lockup if it were ever the bit driving the level line). Hardened (not fully fixed -- it's a narrower, pre-existing TOCTOU shared with the pristine hdmi_src/de_config functions) the sun8i_tcon_top raw of_find_device_by_node()/dev_get_drvdata() lookup with a NULL guard; this driver's .remove() (pristine, unmodified here) was confirmed to correctly tear down the whole DRM aggregate first, contradicting an earlier, incorrect claim that it had no .remove() at all. Found and fixed a real quirks-scoping gap: this board's TCON0 DT node reused sun8i_a83t_lcd_quirks verbatim with real A83T hardware, so several of this series' changes applied to every DSI board sharing that struct (including Pinephone/Pinetab on A64). Fixed via a new "allwinner,sun50i-a133-tcon-lcd" compatible string and a new dsi_cpu_needs_retrigger quirk field gating every one of this series' additions to sun4i_tcon0_mode_set_cpu(), verified against drivers/of/base.c's actual DT compatible-matching algorithm. (TBS-A711 on A83T, cited earlier as another affected board, turned out to be LVDS on checking its DTS, not DSI -- corrected.) v5 -> v6: Fixed a PWM clock-gate leak: pwm_a133_apply()'s disable path cleared PWM_A133_PER_REG's channel enable bit but never cleared the matching PWM_A133_PCGR_REG channel clock-gate bit, leaving a disabled channel's clock running indefinitely. Fixed a DT binding schema gap: allwinner,sun8i-r40-tcon-top.yaml required port@4/port@5 unconditionally across every compatible in the file, which this board's HDMI-less tcon-top node can't satisfy -- restructured the schema so those ports are required per-variant instead, checked against R40/D1/H6's real in-tree dtsi and verified with dt-validate against real compiled DTBs (this board's own, and a real upstream H6 one): the old schema genuinely rejects this board's DTB, the fixed one accepts both. Rejected the same atomic-context claims again, and a retrigger-timer TOCTOU claim (not reachable: it requires TRI_FINISH_INT pending before TRIGGER_START has ever been written even once). v6 -> v7: Fixed a real IRQ re-enable race: a DPMS off/on cycle's sun4i_tcon_dsi_stop_retrigger() masks the IRQ but never acks GINT0, so a stale TRI_FINISH_INT/FSYNC_INT left latched from before the disable can fire the hardirq the instant sun4i_tcon_dsi_retrigger_timer_fn() re-enables it, double-kicking TRIGGER_START against this same function's own about-to-happen kick. Fixed by clearing GINT0 right before that enable_irq() call. Fixed the retrigger timer's own stop condition never triggering once the hardirq-driven backstop is fast enough to always win the race and keep TRIGGER_START looking busy from the timer's perspective -- it would otherwise poll forever as a low-frequency CPU hog instead of handing off. Fixed by adding an explicit dsi_irq_retriggered flag the hardirq handler sets once it successfully retriggers a frame, which the timer checks instead of relying on sampling a transient register bit. Narrowed (not eliminated) a separate, pre-existing write-0-to-clear TOCTOU window on the same GINT0 acknowledge, by moving the ack earlier in the handler, before the slower vblank/page-flip work. Rejected a front/back-porch- swap claim on the panel's drm_display_mode: independently verified against sun6i_dsi_setup_burst()'s DRQ_SET formula and a live register read that the current horizontal front-porch value (88, not the naively-derived 72) is the one already proven correct against real hardware -- 72 was tried previously and directly disproven this same way. Also caught and fixed a stale comment on that same mode struct that had drifted to claim 72 was the confirmed/in-use value when the code already correctly used 88; the mismatch is almost certainly what the automated reviewer's static analysis flagged, since it has no way to see the DRQ_SET hardware verification. Rejected the same atomic-context claims a third time. v7 -> v8: Fixed a permanent first-frame-loss deadlock: sun4i_tcon_dsi_retrigger_timer_fn()'s initial software kick was gated on tick==0 alone, so if TRIGGER_START happened to still read busy at that exact first tick (e.g. a stale value surviving a DPMS off/on cycle, since that doesn't re-run sun4i_tcon0_mode_set_cpu() and so never rewrites CPU_IF_REG to reset it), the kick was skipped and never attempted again for the rest of that cycle -- permanently losing the frame. Fixed by tracking "have we kicked yet" (a new dsi_retrigger_kicked flag) instead of "is this tick==0", so the timer keeps retrying every 2ms until busy actually clears, then kicks exactly once. Also corrected a stale comment claiming this driver sets SUN4I_TCON0_CPU_IF_AUTO to handle Vsync-driven retriggering -- checked directly, this driver never writes that bit at all. Fixed a real clock-gate leak: sun4i_tcon0_set_dsi_gate(tcon, true) is called on every DSI mode-set, but nothing ever called the matching false to ungate it again on disable, leaving the TCON-TOP DSI clock gate (and, via CLK_IGNORE_UNUSED, its CLK_MIPI_DSI parent) permanently on past this TCON's own disable. Fixed by calling sun4i_tcon0_set_dsi_gate(tcon, false) from sun4i_tcon_set_status()'s disable path; safe to call unconditionally since the function is already a no-op on boards without a matching TCON TOP remote node. Rejected a DT schema claim that two new DE2 mixer compatible strings (allwinner,sun50i-a133-de2-mixer-0/1) needed their own if/then conditional block in allwinner,sun8i-a83t-de2-mixer.yaml: verified with dt-validate that they correctly fall through to the schema's existing else branch (single reg region), the same as every other non-h616 compatible already in that enum -- there is no per-SoC validation to bypass here beyond the one genuine special case (sun50i-h616-de33-mixer-0's 3-region reg layout), which this board doesn't share. .../display/allwinner,sun4i-a10-tcon.yaml | 1 + .../allwinner,sun8i-a83t-de2-mixer.yaml | 2 + .../display/allwinner,sun8i-r40-tcon-top.yaml | 58 +- .../pwm/allwinner,sun50i-a133-pwm.yaml | 67 ++ arch/arm64/boot/dts/allwinner/Makefile | 1 + .../allwinner/sun50i-a133-trimui-smartpro.dts | 697 +++++++++++++++++ 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 | 435 +++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 727 +++++++++++++++++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 138 ++++ drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 227 +++++- 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 | 152 +++- drivers/gpu/drm/sun4i/sun8i_tcon_top.h | 2 + drivers/pwm/Kconfig | 14 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sun50i-a133.c | 335 ++++++++ 20 files changed, 2904 insertions(+), 49 deletions(-) create mode 100644 Documentation/devicetree/bindings/pwm/allwinner,sun50i-a133-pwm.yaml create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts create mode 100644 drivers/gpu/drm/panel/panel-orisetech-otm1289a.c create mode 100644 drivers/pwm/pwm-sun50i-a133.c diff --git a/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml b/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml index 724d93b91..c01a444cd 100644 --- a/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml +++ b/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml @@ -45,6 +45,7 @@ properties: - items: - enum: - allwinner,sun50i-a64-tcon-lcd + - allwinner,sun50i-a133-tcon-lcd - const: allwinner,sun8i-a83t-tcon-lcd - items: 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..533b06fbd 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 @@ -99,8 +100,6 @@ properties: required: - port@0 - port@1 - - port@4 - - port@5 required: - "#clock-cells" @@ -151,6 +150,8 @@ allOf: required: - port@2 - port@3 + - port@4 + - port@5 - if: properties: @@ -179,6 +180,13 @@ allOf: - description: TCON TV0 output clock name - description: DSI output clock name + ports: + required: + - port@2 + - port@3 + - port@4 + - port@5 + - if: properties: compatible: @@ -201,6 +209,52 @@ allOf: items: - description: TCON TV0 output clock name + ports: + required: + - port@4 + - port@5 + + - 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 + + # This variant has no HDMI mux (no port@4/port@5) and, on the one + # board using it so far, only mixer0 (no port@2/port@3). The base + # schema's ports.required list only covers port@0/port@1, which every + # variant wires up; port@2/port@3 and port@4/port@5 are required + # per-variant above instead (R40 and D1 both use two mixers and an + # HDMI/TCON-TV mux, so both are added to their own if/then blocks; + # H6 has one mixer but still an HDMI mux, so only port@4/port@5 is + # added there) -- checked against each SoC's actual in-tree + # dtsi/dts (sun8i-r40.dtsi, sunxi-d1s-t113.dtsi, sun50i-h6.dtsi), not + # guessed at. This compatible needs no addition here since it has + # neither extra pair. + examples: - | #include 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 + #include + + 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..91adc18ab --- /dev/null +++ b/arch/arm64/boot/dts/allwinner/sun50i-a133-trimui-smartpro.dts @@ -0,0 +1,697 @@ +// 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 +#include +#include +#include + +/ { + 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 = <®_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 = <®_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 = <®_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 = <®_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 = <®_vcc5v>; + vin2-supply = <®_vcc5v>; + vin3-supply = <®_vcc5v>; + vin4-supply = <®_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 "_vol = , 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 { + /* + * allwinner,sun50i-a133-tcon-lcd is this series' own addition + * (same physical IP block as A83T's TCON0, but this board's + * DSI/CPU-mode panel needs the driver's dsi_cpu_needs_retrigger + * quirk, which real A83T boards using the bare + * "allwinner,sun8i-a83t-tcon-lcd" compatible do not opt into + * -- see the comment on that quirks field in sun4i_tcon.h). + * The a83t-tcon-lcd fallback is kept, matching the existing + * sun50i-a64-tcon-lcd precedent in this binding, so this node + * would still bind (without the new quirk) even against an + * older driver that doesn't know the new compatible yet. + */ + compatible = "allwinner,sun50i-a133-tcon-lcd", + "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 = ; + 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 = ; + /* + * 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 = <®_cldo1>; + backlight = <&backlight>; + }; + }; + + dphy0: phy@6505000 { + compatible = "allwinner,sun50i-a100-mipi-dphy"; + reg = <0x06505000 0x1000>; + interrupts = ; + 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..86d42bdab --- /dev/null +++ b/drivers/gpu/drm/panel/panel-orisetech-otm1289a.c @@ -0,0 +1,435 @@ +// 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 +#include +#include +#include +#include + +#include