From patchwork Sat Sep 12 22:24:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: leow149 X-Patchwork-Id: 3286 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114]) by mxe881.netcup.net (Postfix) with ESMTPS id AF7461C1FE8 for ; Sun, 13 Sep 2026 00:25:09 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=lucidmail.xyz; spf=pass (sender IP is 172.105.105.114) smtp.mailfrom=linux-sunxi+bounces-25831-noreply=patchwork.local@lists.linux.dev smtp.helo=tor.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.105.105.114 as permitted sender) client-ip=172.105.105.114; envelope-from=linux-sunxi+bounces-25831-noreply=patchwork.local@lists.linux.dev; helo=tor.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by tor.lore.kernel.org (Postfix) with ESMTP id 9993F32A23 for ; Sat, 12 Sep 2026 22:25:07 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 852293803D2; Sat, 12 Sep 2026 22:25:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lucidmail.xyz header.i=@lucidmail.xyz header.b="wjHRKPJR" 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 63D0532B111 for ; Sat, 12 Sep 2026 22:24:56 +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=1789251905; cv=none; b=TY2aRzo/iOcC+r2OFshhv0y0a0EngwbRinFta7RgqH9aD1VZrqstj8PpfwmkL7byE2axoLPOD4QzYVVx1HulnMN07AzCg1wt4eTFNx+br41hxfbrnhk5OdniCbP/NcXc9hlTWK4XFqI6NoKtQT/5X0DfkAyzaQZaBkllQd1N83A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789251905; c=relaxed/simple; bh=72XrsrtFg0UPlOopsSVGZqXMfBOQ1bRPE0XGAja8M08=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=brFBWRd29Lhp0weOgeUZNTMNcFemm7C8l9g05uqjG5KEiKDSOLMXpCOwE24eTQDMwfquh/NwB7B4VZ79aEwoMDDyPYchK1txdiMd3i/KX5pQW+ax1v/8DaBotrP45HnO68/GBekOKwTSUiL1Fy73GzigoYhZAQPMENyQciCpoCc= 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=wjHRKPJR; 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 06FE0177C7A; Sun, 13 Sep 2026 00:24:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lucidmail.xyz; s=dkim; t=1789251894; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=BXnPbS6gO21ambxLpMeC7jlZ74KgMzmuCd8kt587g4E=; b=wjHRKPJRsH22iotW5bFBOErRtkTDyTJQL2fTPvrLsBiYzqExYpCtQknCHpPLzoWEGRSCiA jk769iFnfdwn3StNcWYOGAxDgV3YozEDmP9V983vNlbrGJ7KtYqk/C08u/4wMvNqaCGr7d tyH3lz6fp/YLibTGFtnG+8x6OBqgfgHL5zA0SIeN71dVwPFFM1lpzOElTlVfdz1OWPPYYu QnOiCNhHICDHLHYxTlaK9uNqODr9TWtRVG3bOqcgkzQ6AkiAP5XLzyEg2QZ+6GRjGabhY7 ObEeM8IeBcfAq9pkewjZYnc0M7Y3H5fEf+PTM9wYIyWPXNxwy51h5hvEpbzbAw== 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 v6] arm64: allwinner: add TrimUI Smart Pro (A133) board support Date: Sun, 13 Sep 2026 00:24:28 +0200 Message-ID: <20260912222429.107809-1-leow149@lucidmail.xyz> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260912214317.106150-1-leow149@lucidmail.xyz> References: <20260912214317.106150-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, 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. Same reviewer's v3 reply found 6 more findings. Three real, fixed: - Both sun4i_tcon_regmap_config and sun6i_dsi_regmap_config were missing fast_io, so regmap defaulted to a plain mutex for locking -- a sleeping primitive. sun4i_tcon_handler() is a genuine non-threaded hard-irq handler reading tcon->regs, and on DSI/CPU-mode boards sun4i_tcon_dsi_retrigger_timer_fn() (an hrtimer callback) reads/writes both regmaps concurrently on another CPU. If that mutex were ever actually contended between the two, the loser would try to sleep from hard-irq context -- illegal on any kernel, not just PREEMPT_RT, though our own higher call frequency here makes it far more likely to actually manifest than in the existing lower-frequency boards already using this same pattern. Fixed by adding fast_io to both, switching regmap to spinlock-based locking (verified against drivers/base/regmap/regmap.c: regmap_lock_spinlock() correctly uses spin_lock_irqsave(), so this doesn't introduce a same-CPU IRQ deadlock either). Also switched the retrigger timer to HRTIMER_MODE_REL_SOFT while here, moving its frequent callback out of genuine hard-irq context (verified hrtimer_start_range_ns()'s WARN_ON_ONCE() on a mode/is_soft mismatch and updated the other call site to match). - sun6i_dsi_tri_start()/read_int_status()/clear_int_status() had no EXPORT_SYMBOL, but sun4i_tcon.c (which calls all three) and sun6i_mipi_dsi.c build into separate kernel modules per drivers/gpu/drm/sun4i/Makefile -- confirmed a real modular-build failure, not just a modpost nag, since CONFIG_DRM_SUN4I=m would fail to resolve these symbols at load time. Added all three. - The non-DSI enable_irq() call was still living in sun4i_tcon_mode_set(), which a plain DPMS off/on cycle never calls again (only a real mode change does) -- leaving those boards' vblank IRQ permanently disabled after the first CRTC disable/enable that wasn't paired with a fresh mode_set. Moved to sun4i_tcon_set_status()'s enable=true branch instead, symmetric with the disable=false branch already there, which runs on every CRTC enable regardless of whether the mode changed. One more, correctness-precision rather than a bug: CLK_IS_CRITICAL on the TCON-TOP DSI gate was flagged as forcing that clock (and its CLK_MIPI_DSI parent/PLL) permanently on, which is true and an unintended power regression -- swapped for CLK_IGNORE_UNUSED, which exempts the same disable_unused sweep this was actually added for without forcing anything on. Two more checked and rejected, with reasoning, not just dismissed: the same "atomic context" claim as v2 reapplied to otm1289a's msleep() and sun4i_tcon_set_status()'s disable_irq() (already answered, comments already in place); and whether unconditionally treating every DSI/CPU mode IRQ firing as handled risks a CPU hog -- true, and documented as a real trade-off rather than a free fix, since the alternative (letting genirq disable the line) is empirically worse, and switching to DSI's own untested GIC line has no evidence behind it either way. Rebuilt clean, hardware-tested extensively (a full 120-second capture confirmed boots cleanly, reaches a shell, no crashes, no atomic-context warnings, no storm-disable, and the same stable-but-unresolved register state as every prior test). Core bug still completely unchanged. Same reviewer's v4 reply found 5 more findings. One real, confirmed and fixed by checking DRM's actual atomic-commit ordering directly (disable_outputs() in drm_atomic_helper.c: encoder/bridge disable runs before CRTC disable, always): sun6i_dsi_encoder_disable() gates dsi->mod_clk and asserts dsi->reset, but this driver's own hrtimer_cancel()/disable_irq() only ran later, in the CRTC's own disable path -- a real window where the 2ms retrigger timer (or the IRQ handler's TRI_FINISH-backstop branch, which also touches dsi->regs) could fire against now-gated DSI hardware, a genuine external-abort risk, not just wasted work. Fixed by factoring the stop logic into sun4i_tcon_dsi_stop_retrigger() and calling it from sun6i_dsi_encoder_disable() itself, before its own teardown, closing the window at its actual source; the CRTC-level call remains as a harmless backstop. One more real, defensive fix: the GINT0 acknowledge write only cleared 4 of the register's known status bits. Since DSI/CPU mode already treats every IRQ firing as handled unconditionally (from the earlier fix in this series), an unacknowledged bit that happened to be the one actually driving the line would loop forever with nothing to break out -- a genuine hard lockup, not just CPU overhead. Never observed TRI_COUNTER_INT asserting in any testing, but added it to the acknowledge mask since clearing it is free and closes the gap. One proportionate hardening, not a full fix: sun8i_tcon_top_set_dsi_gate()/ set_de0_port() (both added by this series) reach sun8i_tcon_top via a raw of_find_device_by_node() + dev_get_drvdata() lookup -- the same pre-existing pattern sun8i_r40_tcon_tv_set_mux() already uses for the TV path, entirely outside the component framework's own bind/unbind synchronization. this driver's .remove() (pristine, unmodified here) does correctly tear down the whole DRM aggregate before its own unbind runs (verified against take_down_aggregate_device() in drivers/base/component.c), so an orderly sysfs unbind is not itself the hazard, contrary to what an earlier version of this same fix claimed. What isn't covered is an in-flight atomic-commit worker calling in through that raw lookup concurrently with this device's own unbind clearing drvdata -- a narrow, pre-existing TOCTOU race in this shared driver's own lifecycle handling, affecting every board using it (the pristine hdmi_src/de_config functions have the identical gap), not something this series introduced. Properly fixing it needs real synchronization between the two drivers, or moving off the raw reach-around entirely, which I can't safely design or test blind against boards I don't have. Added a NULL guard at both of this series' own call sites instead, which stops these two functions from being what actually crashes on it, without claiming to fix the underlying race. Two findings from v4 were flagged as real but not fixed at the time: this board's TCON0 DT node reused the exact same sun8i_a83t_lcd_quirks struct instance as real A83T hardware (same compatible string), so sun4i_tcon0_mode_set_cpu()'s dsi_cpu_mode flag and this series' changed START_DELAY formula constant applied to every existing DSI board reaching that function, not just this one -- including Pinephone/Pinetab (A64), a real, currently-shipping user of this exact shared function (confirmed: its video-mode DSI panel still routes through the TCON's CPU/8080 register interface here, same as this board's command-mode one). TBS-A711 was also cited alongside it at the time as an A83T example; checking its actual DTS while answering a question about this later turned up that citation as wrong -- TBS-A711 is an LVDS panel and never reaches this function at all, and no currently-shipping A83T board in mainline has a real DSI panel. Noted here rather than quietly dropped; the underlying concern was still legitimate on the strength of the A64 example alone. That turned out to be fixable after all -- quirks can scope this, just not by trying to distinguish boards that share a compatible string. Added "allwinner,sun50i-a133-tcon-lcd" as a new, more specific compatible string for this board's tcon_lcd0 node (kept alongside the existing "allwinner,sun8i-a83t-tcon-lcd" as a fallback for drivers that don't know the new string yet), the same precedent already used for allwinner,sun50i-a64-tcon-lcd in this same binding. Verified directly against this tree's own drivers/of/base.c that DT match priority is scored by position in the *node's* own compatible list (__of_device_is_compatible()'s score = INT_MAX/2 - (index << 2)) and __of_match_node() picks the highest score across the whole table -- so listing the new string first deterministically wins the match regardless of table order, not just in theory. Added a new sun4i_tcon_quirks entry (sun50i_a133_tcon_lcd_quirks) with a new dsi_cpu_needs_retrigger field, and gated every one of this series' previously-unconditional additions to sun4i_tcon0_mode_set_cpu() behind it: the GCTL_IOMAP write, the dsi_cpu_mode/tcon->dsi assignment (and everything it in turn gates -- sun4i_tcon_enable_vblank()'s early return, the retrigger timer, sun4i_tcon_handler()'s unconditional-IRQ_HANDLED behavior), CLK_DELAY/DCLK_OUT_EN, the BLOCK_SPACE +1 correction, the START_DELAY formula (restored to pristine's exact N=11/149MHz for any board not opting in), SAFE_PERIOD_NUM, and IO_TRI_REG. Every other existing DSI board using this shared function (Pinephone/Pinetab on A64; no currently-shipping A83T board actually reaches it, see above) now gets byte-identical behavior to pristine mainline, since none of their DT nodes list the new compatible string. Updated Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml to add the new string to the existing A64 items/enum block. Hardware-tested repeatedly after this change: a register peek confirms the new quirk path actually activates on this board (TCON0_CPU_IF_REG shows TRIGGER_START/TRI_EN/TRI_FIFO_EN set, bits only ever written by the newly-gated retrigger code), and two separate full 120-second boots reached a stable interactive shell with the exact same TRIGGER_START-stuck symptom as every prior round -- no regression. One earlier capture on this same build showed a full hang past driver init that did not reproduce on retest; most likely a transient unrelated to this change, not a deterministic regression, but noted here rather than silently discarded. Same reviewer's v5 reply found 7 more findings, 5 new and 2 repeated from earlier rounds (the tcon_top raw-lookup TOCTOU race and the DCLK divide-by-zero guard, both already discussed above -- neither is caused by this board's changes, so both remain out of scope for this series). Two of the five new findings are real, confirmed and fixed: - 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, which the enable path only ever sets (briefly clearing it to reconfigure period/duty, then setting it again before returning) -- leaving a disabled channel's clock running indefinitely. Fixed by clearing the gate bit in the disable path too. - allwinner,sun8i-r40-tcon-top.yaml's base schema required port@4/ port@5 (the HDMI mux ports) unconditionally across every compatible string in the file, which this board's tcon-top node -- correctly, it has no HDMI mux -- cannot provide. This exact gap was already called out as an open, unresolved problem in an earlier round of this same commit message (see the in-code comment on this board's if/then block at the time), rather than actually fixed. Fixed now: moved port@2/port@3/port@4/port@5 out of the base required list and into each variant's own if/then block instead, checked against the real in-tree dtsi for every affected SoC to get each one right (sun8i-r40.dtsi and sunxi-d1s-t113.dtsi both wire up all four ports; sun50i-h6.dtsi wires up only port@4/port@5; this board wires up neither pair). Verified empirically, not just re-read: installed dtschema locally and ran dt-validate against this board's own compiled DTB and a real upstream H6 DTB (sun50i-h6-beelink-gs1.dtb) -- the old schema genuinely rejects this board's DTB ("port@4 is a required property"), the fixed schema accepts both DTBs cleanly. The other three new findings were checked against real source and are false positives: - Four instances of "sleeping function called from an atomic context" (otm1289a_prepare()'s msleep(), sun6i_dsi_encoder_enable()'s clk_prepare_enable(), sun6i_dsi_encoder_disable()'s (via sun4i_tcon_dsi_stop_retrigger()) disable_irq(), and the PWM driver's mutex_lock()) were flagged again this round, three of them at call sites already checked and answered as far back as v2/v3 above -- re-verified once more directly against drm_atomic_helper.c: commit_tail() (which calls every atomic_enable/atomic_disable hook) runs either synchronously in the ioctl caller's own process context or via INIT_WORK()/queue_work(system_dfl_wq, ...) on a workqueue -- both fully preemptible, sleep-safe contexts. "Atomic" in DRM's atomic-commit API names the all-or-nothing state-change semantics, not a spinlock-held execution context. No code changed; this is the same answer as before, repeated because the question was repeated. - A claimed TOCTOU race where sun4i_tcon_dsi_retrigger_timer_fn()'s tick==0 enable_irq() call could let a hardirq fire and kick TRIGGER_START a second time, racing the softirq's own about-to-happen first kick: this requires TRI_FINISH_INT to already be pending at that exact moment, which isn't reachable -- TRI_FINISH signals completion of a triggered CPU-interface transfer, and at tick==0 TRIGGER_START has never been written even once yet (that first write happens later in this same function call, after the enable_irq()). Reasonably confident this doesn't reproduce in practice; noted here rather than silently dropped in case the reasoning turns out wrong. Rebuilt clean (kernel Image only; the DT schema fix touches documentation, not the DTS itself, so no DTB rebuild was needed for it). Cc: Chen-Yu Tsai Cc: Jernej Skrabec Cc: Samuel Holland Cc: Maxime Ripard Cc: Paul Kocialkowski Signed-off-by: leow149 --- .../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 | 424 +++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 633 +++++++++++++++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 109 +++ 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, 2771 insertions(+), 48 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..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 +#include +#include +#include +#include + +#include