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