From bae242f15722a5010875fc321772162835cde01e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 4 Apr 2025 20:04:49 +0200 Subject: [PATCH 001/208] [DEBUG] usb: typec: tcpm: also log to dmesg Also log to normal dmesg to assist debugging hard reset issues. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/tcpm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 7ef746a90a1774..d4e656f8cf34c1 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -826,6 +826,8 @@ static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + dev_dbg(port->dev, "%s\n", tmpbuffer); + if (tcpm_log_full(port)) { port->logbuffer_head = max(port->logbuffer_head - 1, 0); strscpy(tmpbuffer, "overflow"); From 4daa2ed6715a644c942e8b989eff39cc38905181 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 1 Jul 2025 22:58:37 +0200 Subject: [PATCH 002/208] [DEBUG] usb: typec: fusb302: also log to dmesg Also log to normal dmesg to assist debugging hard reset issues. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 9ab1277b7ed1ef..34354c47c49547 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -150,6 +150,8 @@ static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + dev_dbg(chip->dev, "%s\n", tmpbuffer); + mutex_lock(&chip->logbuffer_lock); if (fusb302_log_full(chip)) { From 39da5973ae72a8e3c8f657bb9e5a8b6581d6ad17 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 24 Oct 2023 16:09:35 +0200 Subject: [PATCH 003/208] math.h: add DIV_ROUND_UP_NO_OVERFLOW Add a new DIV_ROUND_UP helper, which cannot overflow when big numbers are being used. Signed-off-by: Sebastian Reichel --- include/linux/math.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/linux/math.h b/include/linux/math.h index 1e8fb3efbc8ce9..d7c384fd19d181 100644 --- a/include/linux/math.h +++ b/include/linux/math.h @@ -48,6 +48,17 @@ #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP +/** + * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up + * @n: numerator / dividend + * @d: denominator / divisor + * + * This functions does the same as DIV_ROUND_UP, but internally uses a + * division and a modulo operation instead of math tricks. This way it + * avoids overflowing when handling big numbers. + */ +#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d))) + #define DIV_ROUND_DOWN_ULL(ll, d) \ ({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; }) From 1507c05e11776768f64830bf6f324a3b268a832b Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 24 Oct 2023 16:13:50 +0200 Subject: [PATCH 004/208] clk: divider: Fix divisor masking on 64 bit platforms The clock framework handles clock rates as "unsigned long", so u32 on 32-bit architectures and u64 on 64-bit architectures. The current code casts the dividend to u64 on 32-bit to avoid a potential overflow. For example DIV_ROUND_UP(3000000000, 1500000000) = (3.0G + 1.5G - 1) / 1.5G = = OVERFLOW / 1.5G, which has been introduced in commit 9556f9dad8f5 ("clk: divider: handle integer overflow when dividing large clock rates"). On 64 bit platforms this masks the divisor, so that only the lower 32 bit are used. Thus requesting a frequency >= 4.3GHz results in incorrect values. For example requesting 4300000000 (4.3 GHz) will effectively request ca. 5 MHz. Requesting clk_round_rate(clk, ULONG_MAX) is a bit of a special case, since that still returns correct values as long as the parent clock is below 8.5 GHz. Fix this by switching to DIV_ROUND_UP_NO_OVERFLOW, which cannot overflow. This avoids any requirements on the arguments (except that divisor should not be 0 obviously). Signed-off-by: Sebastian Reichel --- drivers/clk/clk-divider.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index b3b485d23ea859..ef163c90d7f11a 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -226,7 +226,7 @@ static int _div_round_up(const struct clk_div_table *table, unsigned long parent_rate, unsigned long rate, unsigned long flags) { - int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + int div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (flags & CLK_DIVIDER_POWER_OF_TWO) div = __roundup_pow_of_two(div); @@ -243,7 +243,7 @@ static int _div_round_closest(const struct clk_div_table *table, int up, down; unsigned long up_rate, down_rate; - up = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + up = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); down = parent_rate / rate; if (flags & CLK_DIVIDER_POWER_OF_TWO) { @@ -414,7 +414,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, { unsigned int div, value; - div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (!_is_valid_div(table, div, flags)) return -EINVAL; From a0541ce27e7d8097a31a93fa80bc80bcf586118d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 24 Oct 2023 18:09:57 +0200 Subject: [PATCH 005/208] clk: composite: replace open-coded abs_diff() Replace the open coded abs_diff() with the existing helper function. Suggested-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/clk/clk-composite.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 835b1e4e58697c..a4529172776e6b 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -6,6 +6,7 @@ #include #include #include +#include #include static u8 clk_composite_get_parent(struct clk_hw *hw) @@ -106,10 +107,7 @@ static int clk_composite_determine_rate(struct clk_hw *hw, if (ret) continue; - if (req->rate >= tmp_req.rate) - rate_diff = req->rate - tmp_req.rate; - else - rate_diff = tmp_req.rate - req->rate; + rate_diff = abs_diff(req->rate, tmp_req.rate); if (!rate_diff || !req->best_parent_hw || best_rate_diff > rate_diff) { From 8e8a84a7577b1a9dfd399f4626d94e25d61ebd89 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 2 Jan 2024 09:35:43 +0100 Subject: [PATCH 006/208] arm64: dts: rockchip: rk3588-evb1: add bluetooth rfkill Add rfkill support for bluetooth. Bluetooth support itself is still missing, but this ensures bluetooth can be powered off properly. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index 09bc7b68dcc057..41249ff136598a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -121,6 +121,15 @@ pwms = <&pwm2 0 25000 0>; }; + bluetooth-rfkill { + compatible = "rfkill-gpio"; + label = "rfkill-bluetooth"; + radio-type = "bluetooth"; + shutdown-gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&bluetooth_pwren>; + }; + hdmi0-con { compatible = "hdmi-connector"; type = "a"; @@ -599,6 +608,12 @@ }; }; + bluetooth { + bluetooth_pwren: bluetooth-pwren { + rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + rtl8111 { rtl8111_isolate: rtl8111-isolate { rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>; From a12ac66f7bcd3d410467509b942ad25bb60126ec Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 2 Jan 2024 09:39:11 +0100 Subject: [PATCH 007/208] arm64: dts: rockchip: rk3588-evb1: improve PCIe ethernet pin muxing Also describe wake signal PCIe pinmux for the onboard LAN card. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index 41249ff136598a..f2ebd099231037 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -555,7 +555,7 @@ &pcie2x1l1 { reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; - pinctrl-0 = <&pcie2_1_rst>, <&rtl8111_isolate>, <&pcie30x1m1_1_clkreqn>; + pinctrl-0 = <&pcie2_1_rst>, <&rtl8111_isolate>, <&pcie30x1m1_1_clkreqn>, <&pcie30x1m1_1_waken>; supports-clkreq; status = "okay"; }; From 0d3206b31cd649cd5c5936fc2e1ef72e8dedf34d Mon Sep 17 00:00:00 2001 From: "Carsten Haitzler (Rasterman)" Date: Tue, 6 Feb 2024 10:12:54 +0000 Subject: [PATCH 008/208] arm64: dts: rockchip: Slow down EMMC a bit to keep IO stable This drops to hs200 mode and 150Mhz as this is actually stable across eMMC modules. There exist some that are incompatible at higher rates with the rk3588 and to avoid your filesystem corrupting due to IO errors, be more conservative and reduce the max. speed. Signed-off-by: Carsten Haitzler Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index bf4a1d2e55ca30..f6dc2f6fd23f11 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -592,8 +592,8 @@ no-sdio; no-sd; non-removable; - mmc-hs400-1_8v; - mmc-hs400-enhanced-strobe; + max-frequency = <150000000>; + mmc-hs200-1_8v; status = "okay"; }; From b9ec399a8f863f9034d0e9a548aa3140685ddfb0 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 25 Jul 2024 18:12:24 +0200 Subject: [PATCH 009/208] mfd: rk8xx: Fix shutdown handler When I converted rk808 to device managed resources I converted the rk808 specific pm_power_off handler to devm_register_sys_off_handler() using SYS_OFF_MODE_POWER_OFF_PREPARE, which is allowed to sleep. I did this because the driver's poweroff function makes use of regmap and the backend of that might sleep. But the PMIC poweroff function will kill off the board power and the kernel does some extra steps after the prepare handler. Thus the prepare handler should not be used for the PMIC's poweroff routine. Instead the normal SYS_OFF_MODE_POWER_OFF phase should be used. The old pm_power_off method is also being called from there, so this would have been a cleaner conversion anyways. But it still makes sense to investigate the sleep handling and check if there are any issues. Apparently the Rockchip and Meson I2C drivers (the only platforms using the PMICs handled by this driver) both have support for atomic transfers and thus may be called from the atomic poweroff context. Things are different on the SPI side. That is so far only used by rk806 and that one is only used by Rockchip RK3588. Unfortunately the Rockchip SPI driver does not support atomic transfers. That means this change will introduce an error splash directly before doing the final power off on all upstream supported RK3588 boards: [ 13.761353] ------------[ cut here ]------------ [ 13.761764] Voluntary context switch within RCU read-side critical section! [ 13.761776] WARNING: CPU: 0 PID: 1 at kernel/rcu/tree_plugin.h:330 rcu_note_context_switch+0x3ac/0x404 [ 13.763219] Modules linked in: [ 13.763498] CPU: 0 UID: 0 PID: 1 Comm: systemd-shutdow Not tainted 6.10.0-12284-g2818a9a19514 #1499 [ 13.764297] Hardware name: Rockchip RK3588 EVB1 V10 Board (DT) [ 13.764812] pstate: 604000c9 (nZCv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 13.765427] pc : rcu_note_context_switch+0x3ac/0x404 [ 13.765871] lr : rcu_note_context_switch+0x3ac/0x404 [ 13.766314] sp : ffff800084f4b5b0 [ 13.766609] x29: ffff800084f4b5b0 x28: ffff00040139b800 x27: 00007dfb4439ae80 [ 13.767245] x26: ffff00040139bc80 x25: 0000000000000000 x24: ffff800082118470 [ 13.767880] x23: 0000000000000000 x22: ffff000400300000 x21: ffff000400300000 [ 13.768515] x20: ffff800083a9d600 x19: ffff0004fee48600 x18: fffffffffffed448 [ 13.769151] x17: 000000040044ffff x16: 005000f2b5503510 x15: 0000000000000048 [ 13.769787] x14: fffffffffffed490 x13: ffff80008473b3c0 x12: 0000000000000900 [ 13.770421] x11: 0000000000000300 x10: ffff800084797bc0 x9 : ffff80008473b3c0 [ 13.771057] x8 : 00000000ffffefff x7 : ffff8000847933c0 x6 : 0000000000000300 [ 13.771692] x5 : 0000000000000301 x4 : 40000000fffff300 x3 : 0000000000000000 [ 13.772328] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff000400300000 [ 13.772964] Call trace: [ 13.773184] rcu_note_context_switch+0x3ac/0x404 [ 13.773598] __schedule+0x94/0xb0c [ 13.773907] schedule+0x34/0x104 [ 13.774198] schedule_timeout+0x84/0xfc [ 13.774544] wait_for_completion_timeout+0x78/0x14c [ 13.774980] spi_transfer_one_message+0x588/0x690 [ 13.775403] __spi_pump_transfer_message+0x19c/0x4ec [ 13.775846] __spi_sync+0x2a8/0x3c4 [ 13.776161] spi_write_then_read+0x120/0x208 [ 13.776543] rk806_spi_bus_read+0x54/0x88 [ 13.776905] _regmap_raw_read+0xec/0x16c [ 13.777257] _regmap_bus_read+0x44/0x7c [ 13.777601] _regmap_read+0x60/0xd8 [ 13.777915] _regmap_update_bits+0xf4/0x13c [ 13.778289] regmap_update_bits_base+0x64/0x98 [ 13.778686] rk808_power_off+0x70/0xfc [ 13.779024] sys_off_notify+0x40/0x6c [ 13.779356] atomic_notifier_call_chain+0x60/0x90 [ 13.779776] do_kernel_power_off+0x54/0x6c [ 13.780146] machine_power_off+0x18/0x24 [ 13.780499] kernel_power_off+0x70/0x7c [ 13.780845] __do_sys_reboot+0x210/0x270 [ 13.781198] __arm64_sys_reboot+0x24/0x30 [ 13.781558] invoke_syscall+0x48/0x10c [ 13.781897] el0_svc_common+0x3c/0xe8 [ 13.782228] do_el0_svc+0x20/0x2c [ 13.782528] el0_svc+0x34/0xd8 [ 13.782806] el0t_64_sync_handler+0x120/0x12c [ 13.783197] el0t_64_sync+0x190/0x194 [ 13.783527] ---[ end trace 0000000000000000 ]--- The board will shutdown nevertheless, since this also re-enables interrupts. A proper fix for this requires changes to the core SPI subsystem and will be done as a follow-up series. Note, that this patch also fixes a problem for the Asus C201. Without the function being registered as a proper shutdown handler the syscall for poweroff exits early and does not even call the shutdown prepare handler. This in turn means the system can no longer poweroff properly since my original change. Fixes: 4fec8a5a85c49 ("mfd: rk808: Convert to device managed resources") Cc: stable@vger.kernel.org Reported-by: Urja Signed-off-by: Sebastian Reichel --- drivers/mfd/rk8xx-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/rk8xx-core.c b/drivers/mfd/rk8xx-core.c index 3dcf6abfda74ff..21182df45d1b6b 100644 --- a/drivers/mfd/rk8xx-core.c +++ b/drivers/mfd/rk8xx-core.c @@ -875,7 +875,7 @@ int rk8xx_probe(struct device *dev, int variant, unsigned int irq, struct regmap if (device_property_read_bool(dev, "system-power-controller") || device_property_read_bool(dev, "rockchip,system-power-controller")) { ret = devm_register_sys_off_handler(dev, - SYS_OFF_MODE_POWER_OFF_PREPARE, SYS_OFF_PRIO_HIGH, + SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, &rk808_power_off, rk808); if (ret) return dev_err_probe(dev, ret, From 4cbbae34e53c46d14b7e7f9a4d2e7b1bf8d88b91 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 15 Nov 2024 11:20:40 -0500 Subject: [PATCH 010/208] dt-bindings: display: vop2: Add VP clock resets Add the documentation for VOP2 video ports reset clocks. One reset can be set per video port. Reviewed-by: Conor Dooley Signed-off-by: Detlev Casanova --- .../display/rockchip/rockchip-vop2.yaml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml index 93da1fb9adc47b..8a8993fea896dc 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml @@ -82,6 +82,26 @@ properties: - {} - {} + resets: + minItems: 5 + items: + - description: AXI clock reset. + - description: AHB clock reset. + - description: Pixel clock reset for video port 0. + - description: Pixel clock reset for video port 1. + - description: Pixel clock reset for video port 2. + - description: Pixel clock reset for video port 3. + + reset-names: + minItems: 5 + items: + - const: aclk + - const: hclk + - const: dclk_vp0 + - const: dclk_vp1 + - const: dclk_vp2 + - const: dclk_vp3 + rockchip,grf: $ref: /schemas/types.yaml#/definitions/phandle description: @@ -153,6 +173,12 @@ allOf: interrupt-names: false + resets: + maxItems: 5 + + reset-names: + maxItems: 5 + ports: required: - port@0 @@ -200,6 +226,12 @@ allOf: interrupt-names: minItems: 4 + resets: + maxItems: 5 + + reset-names: + maxItems: 5 + ports: required: - port@0 @@ -251,6 +283,12 @@ allOf: interrupt-names: false + resets: + minItems: 6 + + reset-names: + minItems: 6 + ports: required: - port@0 @@ -289,6 +327,16 @@ examples: "dclk_vp0", "dclk_vp1", "dclk_vp2"; + resets = <&cru SRST_A_VOP>, + <&cru SRST_H_VOP>, + <&cru SRST_VOP0>, + <&cru SRST_VOP1>, + <&cru SRST_VOP2>; + reset-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2"; power-domains = <&power RK3568_PD_VO>; rockchip,grf = <&grf>; iommus = <&vop_mmu>; From e323ece16e6cbfb9e48d2290d380017f6c6ee643 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 15 Nov 2024 11:20:41 -0500 Subject: [PATCH 011/208] drm/rockchip: vop2: Add clock resets support At the end of initialization, each VP clock needs to be reset before they can be used. Failing to do so can put the VOP in an undefined state where the generated HDMI signal is either lost or not matching the selected mode. This issue can be reproduced by switching modes multiple times. Depending on the setup, after about 10 mode switches, the signal will be lost and the value in register 0x890 (VSYNCWIDTH + VFRONT) will take the value `0x0000018c`. That makes VSYNCWIDTH=0, which is wrong. Adding the clock resets after the VOP configuration fixes the issue. Signed-off-by: Detlev Casanova --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 28 ++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 1 + 2 files changed, 29 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 843c7ef979b218..507d87010a6207 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -1621,6 +1622,26 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us) return us * mode->clock / mode->htotal / 1000; } +static int vop2_clk_reset(struct vop2_video_port *vp) +{ + struct reset_control *rstc = vp->dclk_rst; + struct vop2 *vop2 = vp->vop2; + int ret; + + if (!rstc) + return 0; + + ret = reset_control_assert(rstc); + if (ret < 0) + drm_warn(vop2->drm, "failed to assert reset\n"); + udelay(10); + ret = reset_control_deassert(rstc); + if (ret < 0) + drm_warn(vop2->drm, "failed to deassert reset\n"); + + return ret; +} + static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -1805,6 +1826,8 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl); + vop2_clk_reset(vp); + vop2_crtc_atomic_try_set_gamma(vop2, vp, crtc, crtc_state); drm_crtc_vblank_on(crtc); @@ -2383,6 +2406,11 @@ static int vop2_create_crtcs(struct vop2 *vop2) vp->id = vp_data->id; vp->data = vp_data; + vp->dclk_rst = devm_reset_control_get_optional(vop2->dev, dclk_name); + if (IS_ERR(vp->dclk_rst)) + return dev_err_probe(drm->dev, PTR_ERR(vp->dclk_rst), + "failed to get %s reset\n", dclk_name); + snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id); vp->dclk = devm_clk_get(vop2->dev, dclk_name); if (IS_ERR(vp->dclk)) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 37722652844a92..2cfe505f913c4c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -238,6 +238,7 @@ struct vop2_video_port { struct vop2 *vop2; struct clk *dclk; struct clk *dclk_src; + struct reset_control *dclk_rst; unsigned int id; const struct vop2_video_port_data *data; From 270f0aae5bc21627ca897bc5d0a7725605fa0d51 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 15 Nov 2024 11:20:42 -0500 Subject: [PATCH 012/208] arm64: dts: rockchip: Add VOP clock resets for rk3588s This adds the needed clock resets for all rk3588(s) based SOCs. Signed-off-by: Detlev Casanova --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index 4fb8888c281c8c..30fb74303fb1f1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1453,6 +1453,18 @@ "pll_hdmiphy0"; iommus = <&vop_mmu>; power-domains = <&power RK3588_PD_VOP>; + resets = <&cru SRST_A_VOP>, + <&cru SRST_H_VOP>, + <&cru SRST_D_VOP0>, + <&cru SRST_D_VOP1>, + <&cru SRST_D_VOP2>, + <&cru SRST_D_VOP3>; + reset-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2", + "dclk_vp3"; rockchip,grf = <&sys_grf>; rockchip,vop-grf = <&vop_grf>; rockchip,vo1-grf = <&vo1_grf>; From 3e76b9defda7bc3838d092abc63d84d1e3bf2e4a Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 8 Nov 2024 17:35:44 +0100 Subject: [PATCH 013/208] arm64: dts: rockchip: rk3588-evb1: add DSI panel The RK3588 EVB1 comes with a W552793DBA-V10 Touchscreen/Display combination. It contains a Wanchanglong W552793BAA panel and a Goodix GT1158 touchscreen. This adds the DT description of it. Signed-off-by: Sebastian Reichel --- .../boot/dts/rockchip/rk3588-evb1-v10.dts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index f2ebd099231037..76fd3c95c234f4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -213,6 +213,15 @@ regulator-max-microvolt = <12000000>; }; + vcc3v3_lcd_mipi: regulator-vcc3v3-lcd-mipi { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_lcd_mipi"; + regulator-boot-on; + enable-active-high; + gpio = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc_3v3_s0>; + }; + vcc3v3_pcie30: regulator-vcc3v3-pcie30 { compatible = "regulator-fixed"; regulator-name = "vcc3v3_pcie30"; @@ -347,6 +356,43 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dsi0 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + panel@0 { + compatible = "wanchanglong,w552793baa", "raydium,rm67200"; + reg = <0>; + backlight = <&backlight>; + pinctrl-names = "default"; + pinctrl-0 = <&lcd_rst_gpio>; + vdd-supply = <&vcc3v3_lcd_mipi>; + iovcc-supply = <&vcc3v3_lcd_mipi>; + vsp-supply = <&vcc5v0_sys>; + vsn-supply = <&vcc5v0_sys>; + reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; + + port { + mipi_panel_in: endpoint { + remote-endpoint = <&dsi0_out_panel>; + }; + }; + }; +}; + +&dsi0_in { + dsi0_in_vp3: endpoint { + remote-endpoint = <&vp3_out_dsi0>; + }; +}; + +&dsi0_out { + dsi0_out_panel: endpoint { + remote-endpoint = <&mipi_panel_in>; + }; +}; + &gmac0 { clock_in_out = "output"; phy-handle = <&rgmii_phy>; @@ -488,6 +534,22 @@ }; }; +&i2c6 { + status = "okay"; + + gt1x: touchscreen@14 { + compatible = "goodix,gt1158"; + reg = <0x14>; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&touchscreen_pins>; + reset-gpios = <&gpio0 RK_PD2 GPIO_ACTIVE_HIGH>; + AVDD28-supply = <&vcc3v3_lcd_mipi>; + VDDIO-supply = <&vcc3v3_lcd_mipi>; + }; +}; + &i2c7 { status = "okay"; @@ -527,6 +589,10 @@ }; }; +&mipidcphy0 { + status = "okay"; +}; + &pcie2x1l0 { pinctrl-names = "default"; pinctrl-0 = <&pcie2_0_rst>, <&pcie2_0_wake>, <&pcie2_0_clkreq>, <&wifi_host_wake_irq>; @@ -639,6 +705,12 @@ }; }; + mipi_dsi { + lcd_rst_gpio: lcd-rst { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + pcie2 { pcie2_0_rst: pcie2-0-rst { rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; @@ -667,6 +739,14 @@ }; }; + touchscreen { + touchscreen_pins: touchscreen-pins { + rockchip,pins = + <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_up>, + <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + usb { vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; @@ -1478,3 +1558,13 @@ remote-endpoint = <&hdmi1_in_vp1>; }; }; + +&vp3 { + #address-cells = <1>; + #size-cells = <0>; + + vp3_out_dsi0: endpoint@ROCKCHIP_VOP2_EP_MIPI0 { + reg = ; + remote-endpoint = <&dsi0_in_vp3>; + }; +}; From 3407a00022725aee418ddcc3a870620f5ae84f3e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 14 Nov 2024 17:36:18 +0100 Subject: [PATCH 014/208] drm/rockchip: vop2: Add core reset support A previous from Detlev Casanova adds reset handling for the video ports. This also resets the AHB and AXI interface when the system binds the VOP2 controller. This fixes issues when the bootloader (or a previously running kernel when using kexec) left the VOP2 initialized to some degree. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 15 +++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 507d87010a6207..2601446f9f0748 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -2692,6 +2692,21 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) dev_set_drvdata(dev, vop2); + vop2->resets[RST_ACLK].id = "aclk"; + vop2->resets[RST_HCLK].id = "hclk"; + ret = devm_reset_control_bulk_get_optional_exclusive(vop2->dev, + RST_VOP2_MAX, vop2->resets); + if (ret) + return dev_err_probe(drm->dev, ret, "failed to get resets\n"); + + ret = reset_control_bulk_assert(RST_VOP2_MAX, vop2->resets); + if (ret < 0) + drm_warn(vop2->drm, "failed to assert resets\n"); + udelay(10); + ret = reset_control_bulk_deassert(RST_VOP2_MAX, vop2->resets); + if (ret < 0) + drm_warn(vop2->drm, "failed to deassert resets\n"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vop"); if (!res) return dev_err_probe(drm->dev, -EINVAL, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 2cfe505f913c4c..b8198714c1c912 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -8,6 +8,7 @@ #define _ROCKCHIP_DRM_VOP2_H #include +#include #include #include #include "rockchip_drm_drv.h" @@ -165,6 +166,12 @@ enum vop2_win_regs { VOP2_WIN_MAX_REG, }; +enum { + RST_ACLK, + RST_HCLK, + RST_VOP2_MAX +}; + struct vop2_regs_dump { const char *name; u32 base; @@ -330,6 +337,7 @@ struct vop2 { struct clk *pclk; struct clk *pll_hdmiphy0; struct clk *pll_hdmiphy1; + struct reset_control_bulk_data resets[RST_VOP2_MAX]; /* optional internal rgb encoder */ struct rockchip_rgb *rgb; From d857639a815d8d0e367e29f02b311b3043e694bc Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 29 Jul 2025 18:21:03 +0200 Subject: [PATCH 015/208] arm64: dts: rockchip: Fix USB-C description for RK3588 EVB1 Fix the USB-C connector description, so that it follows the binding: port@0 is the high-speed lanes port@1 is the super-speed lanes port@2 is the SBU lanes Right now the high-speed and super-speed links are swapped and for the high-speed lanes the link points to the controller instead of the PHY. I'm still investigating if this should be changed. This also updates the port naming, so that it describes the hardware instead of how the drivers are using the information. These are effectively the same, but the DT should describe hardware and not software. Fixes: b37146b5a555 ("arm64: dts: rockchip: add USB3 to rk3588-evb1") Signed-off-by: Sebastian Reichel --- .../boot/dts/rockchip/rk3588-evb1-v10.dts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index 76fd3c95c234f4..85adfadc6e4a9a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -497,24 +497,24 @@ port@0 { reg = <0>; - usbc0_orien_sw: endpoint { - remote-endpoint = <&usbdp_phy0_orientation_switch>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_host0_xhci_to_usbc0>; }; }; port@1 { reg = <1>; - usbc0_role_sw: endpoint { - remote-endpoint = <&dwc3_0_role_switch>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy0_ss>; }; }; port@2 { reg = <2>; - dp_altmode_mux: endpoint { - remote-endpoint = <&usbdp_phy0_dp_altmode_mux>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_sbu>; }; }; }; @@ -1493,14 +1493,14 @@ #address-cells = <1>; #size-cells = <0>; - usbdp_phy0_orientation_switch: endpoint@0 { + usbdp_phy0_ss: endpoint@0 { reg = <0>; - remote-endpoint = <&usbc0_orien_sw>; + remote-endpoint = <&usbc0_ss>; }; - usbdp_phy0_dp_altmode_mux: endpoint@1 { + usbdp_phy0_sbu: endpoint@1 { reg = <1>; - remote-endpoint = <&dp_altmode_mux>; + remote-endpoint = <&usbc0_sbu>; }; }; }; @@ -1525,9 +1525,9 @@ #address-cells = <1>; #size-cells = <0>; - dwc3_0_role_switch: endpoint@0 { + usb_host0_xhci_to_usbc0: endpoint@0 { reg = <0>; - remote-endpoint = <&usbc0_role_sw>; + remote-endpoint = <&usbc0_hs>; }; }; }; From 82cf52a539d2fa42135f64a53862d87bc0f1e796 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 15 Mar 2025 09:34:02 +0100 Subject: [PATCH 016/208] arm64: dts: rockchip: enable camera I2C interfaces for ROCK 5B family Any camera related IP of the RK3588 is not yet supported and the cameras must be handled via overlays anyways, but it is sensible to expose the related I2C interfaces by default. This allows using i2cdetect to investigate anything connected to the CSI connectors right now. Since the Rockchip I2C driver implements proper power management there are no disadvantages, if nothing is connected to the port. Note, that the second CSI port's I2C in the Rock 5B+ and Rock 5T reuse I2C4, which is already used by fusb302 and thus already enabled. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index f6dc2f6fd23f11..bb84286cb11c7f 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -304,6 +304,10 @@ }; }; +&i2c3 { + status = "okay"; +}; + &i2c4 { pinctrl-names = "default"; pinctrl-0 = <&i2c4m1_xfer>; From c8c5df8b11fe24b984847331067db48d83cc2638 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Tue, 10 Jun 2025 16:07:09 +0200 Subject: [PATCH 017/208] phy: rockchip: inno-usb2: add soft vbusvalid control With USB type C connectors, the vbus detect pin of the OTG controller attached to it is pulled high by a USB Type C controller chip such as the fusb302. This means USB enumeration on Type-C ports never works, as the vbus is always seen as high. Rockchip added some GRF register flags to deal with this situation. The RK3576 TRM calls these "soft_vbusvalid_bvalid" (con0 bit index 15) and "soft_vbusvalid_bvalid_sel" (con0 bit index 14). Downstream introduces a new vendor property which tells the USB 2 PHY that it's connected to a type C port, but we can do better. Since in such an arrangement, we'll have an OF graph connection from the USB controller to the USB connector anyway, we can walk said OF graph and check the connector's compatible to determine this without adding any further vendor properties. Do keep in mind that the usbdp PHY driver seemingly fiddles with these register fields as well, but what it does doesn't appear to be enough for us to get working USB enumeration, presumably because the whole vbus_attach logic needs to be adjusted as well either way. Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20250610-rk3576-sige5-usb-v4-1-7e7f779619c1@collabora.com Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 113 +++++++++++++++++- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 8f4c08e599aa22..42116db38863e2 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,8 @@ struct rockchip_chg_det_reg { /** * struct rockchip_usb2phy_port_cfg - usb-phy port configuration. * @phy_sus: phy suspend register. + * @svbus_en: soft vbus bvalid enable register. + * @svbus_sel: soft vbus bvalid selection register. * @bvalid_det_en: vbus valid rise detection enable register. * @bvalid_det_st: vbus valid rise detection status register. * @bvalid_det_clr: vbus valid rise detection clear register. @@ -140,6 +143,8 @@ struct rockchip_chg_det_reg { */ struct rockchip_usb2phy_port_cfg { struct usb2phy_reg phy_sus; + struct usb2phy_reg svbus_en; + struct usb2phy_reg svbus_sel; struct usb2phy_reg bvalid_det_en; struct usb2phy_reg bvalid_det_st; struct usb2phy_reg bvalid_det_clr; @@ -203,6 +208,7 @@ struct rockchip_usb2phy_cfg { * @event_nb: hold event notification callback. * @state: define OTG enumeration states before device reset. * @mode: the dr_mode of the controller. + * @typec_vbus_det: whether to apply Type C logic to OTG vbus detection. */ struct rockchip_usb2phy_port { struct phy *phy; @@ -222,6 +228,7 @@ struct rockchip_usb2phy_port { struct notifier_block event_nb; enum usb_otg_state state; enum usb_dr_mode mode; + bool typec_vbus_det; }; /** @@ -495,6 +502,13 @@ static int rockchip_usb2phy_init(struct phy *phy) mutex_lock(&rport->mutex); if (rport->port_id == USB2PHY_PORT_OTG) { + if (rport->typec_vbus_det) { + if (rport->port_cfg->svbus_en.enable && + rport->port_cfg->svbus_sel.enable) { + property_enable(rphy->grf, &rport->port_cfg->svbus_en, true); + property_enable(rphy->grf, &rport->port_cfg->svbus_sel, true); + } + } if (rport->mode != USB_DR_MODE_HOST && rport->mode != USB_DR_MODE_UNKNOWN) { /* clear bvalid status and enable bvalid detect irq */ @@ -535,8 +549,7 @@ static int rockchip_usb2phy_init(struct phy *phy) if (ret) goto out; - schedule_delayed_work(&rport->otg_sm_work, - OTG_SCHEDULE_DELAY * 3); + schedule_delayed_work(&rport->otg_sm_work, 0); } else { /* If OTG works in host only mode, do nothing. */ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); @@ -666,8 +679,17 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) unsigned long delay; bool vbus_attach, sch_work, notify_charger; - vbus_attach = property_enabled(rphy->grf, - &rport->port_cfg->utmi_bvalid); + if (rport->port_cfg->svbus_en.enable && rport->typec_vbus_det) { + if (property_enabled(rphy->grf, &rport->port_cfg->svbus_en) && + property_enabled(rphy->grf, &rport->port_cfg->svbus_sel)) { + vbus_attach = true; + } else { + vbus_attach = false; + } + } else { + vbus_attach = property_enabled(rphy->grf, + &rport->port_cfg->utmi_bvalid); + } sch_work = false; notify_charger = false; @@ -1280,6 +1302,83 @@ static int rockchip_otg_event(struct notifier_block *nb, return NOTIFY_DONE; } +static const char *const rockchip_usb2phy_typec_cons[] = { + "usb-c-connector", + NULL, +}; + +static struct device_node *rockchip_usb2phy_to_controller(struct rockchip_usb2phy *rphy) +{ + struct device_node *np; + struct device_node *parent; + + for_each_node_with_property(np, "phys") { + struct of_phandle_iterator it; + int ret; + + of_for_each_phandle(&it, ret, np, "phys", NULL, 0) { + parent = of_get_parent(it.node); + if (it.node != rphy->dev->of_node && rphy->dev->of_node != parent) { + if (parent) + of_node_put(parent); + continue; + } + + /* + * Either the PHY phandle we're iterating or its parent + * matched, we don't care about which out of the two in + * particular as we just need to know it's the right + * USB controller for this PHY. + */ + of_node_put(it.node); + of_node_put(parent); + return np; + } + } + + return NULL; +} + +static bool rockchip_usb2phy_otg_is_type_c(struct rockchip_usb2phy *rphy) +{ + struct device_node *controller = rockchip_usb2phy_to_controller(rphy); + struct device_node *ports; + struct device_node *ep = NULL; + struct device_node *parent; + + if (!controller) + return false; + + ports = of_get_child_by_name(controller, "ports"); + if (ports) { + of_node_put(controller); + controller = ports; + } + + for_each_of_graph_port(controller, port) { + ep = of_get_child_by_name(port, "endpoint"); + if (!ep) + continue; + + parent = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!parent) + continue; + + if (of_device_compatible_match(parent, rockchip_usb2phy_typec_cons)) { + of_node_put(parent); + of_node_put(controller); + return true; + } + + of_node_put(parent); + } + + of_node_put(controller); + + return false; +} + static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, struct rockchip_usb2phy_port *rport, struct device_node *child_np) @@ -1301,6 +1400,8 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, mutex_init(&rport->mutex); + rport->typec_vbus_det = rockchip_usb2phy_otg_is_type_c(rphy); + rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); if (rport->mode == USB_DR_MODE_HOST || rport->mode == USB_DR_MODE_UNKNOWN) { @@ -2054,6 +2155,8 @@ static const struct rockchip_usb2phy_cfg rk3576_phy_cfgs[] = { .port_cfgs = { [USB2PHY_PORT_OTG] = { .phy_sus = { 0x0000, 8, 0, 0, 0x1d1 }, + .svbus_en = { 0x0000, 15, 15, 0, 1 }, + .svbus_sel = { 0x0000, 14, 14, 0, 1 }, .bvalid_det_en = { 0x00c0, 1, 1, 0, 1 }, .bvalid_det_st = { 0x00c4, 1, 1, 0, 1 }, .bvalid_det_clr = { 0x00c8, 1, 1, 0, 1 }, @@ -2091,6 +2194,8 @@ static const struct rockchip_usb2phy_cfg rk3576_phy_cfgs[] = { .port_cfgs = { [USB2PHY_PORT_OTG] = { .phy_sus = { 0x2000, 8, 0, 0, 0x1d1 }, + .svbus_en = { 0x2000, 15, 15, 0, 1 }, + .svbus_sel = { 0x2000, 14, 14, 0, 1 }, .bvalid_det_en = { 0x20c0, 1, 1, 0, 1 }, .bvalid_det_st = { 0x20c4, 1, 1, 0, 1 }, .bvalid_det_clr = { 0x20c8, 1, 1, 0, 1 }, From 5ef53d134dbc9f034f22caa3f19f540adf78382c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 30 Jun 2025 12:19:24 +0200 Subject: [PATCH 018/208] dt-bindings: input: adc-keys: allow linux,input-type property adc-keys, unlike gpio-keys, does not allow linux,input-type as a valid property. This makes it impossible to model devices that have ADC inputs that should generate switch events. Add the property to the binding with the same default as gpio-keys. Signed-off-by: Nicolas Frattaroli Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/20250630-rock4d-audio-v1-1-0b3c8e8fda9c@collabora.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/input/adc-keys.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/input/adc-keys.yaml b/Documentation/devicetree/bindings/input/adc-keys.yaml index 7aa078dead3781..e372ebc23d1651 100644 --- a/Documentation/devicetree/bindings/input/adc-keys.yaml +++ b/Documentation/devicetree/bindings/input/adc-keys.yaml @@ -42,6 +42,9 @@ patternProperties: linux,code: true + linux,input-type: + default: 1 # EV_KEY + press-threshold-microvolt: description: Voltage above or equal to which this key is considered pressed. No From c2b39c6c79c09bd756f48fa06521cd8198683ce1 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 30 Jun 2025 12:19:25 +0200 Subject: [PATCH 019/208] Input: adc-keys - support types that aren't just keyboard keys Instead of doing something like what gpio-keys is doing, adc-keys hardcodes that all keycodes must be of type EV_KEY. This limits the usefulness of adc-keys, and overcomplicates the code with manual bit-setting logic. Instead, refactor the code to read the linux,input-type fwnode property, and get rid of the custom bit setting logic, replacing it with input_set_capability instead. input_report_key is replaced with input_event, which allows us to explicitly pass the type. Signed-off-by: Nicolas Frattaroli Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/20250630-rock4d-audio-v1-2-0b3c8e8fda9c@collabora.com Signed-off-by: Sebastian Reichel --- drivers/input/keyboard/adc-keys.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c index f1753207429db0..339dd4d4a08421 100644 --- a/drivers/input/keyboard/adc-keys.c +++ b/drivers/input/keyboard/adc-keys.c @@ -19,12 +19,14 @@ struct adc_keys_button { u32 voltage; u32 keycode; + u32 type; }; struct adc_keys_state { struct iio_channel *channel; u32 num_keys; u32 last_key; + u32 last_type; u32 keyup_voltage; const struct adc_keys_button *map; }; @@ -35,6 +37,7 @@ static void adc_keys_poll(struct input_dev *input) int i, value, ret; u32 diff, closest = 0xffffffff; int keycode = 0; + u32 type = EV_KEY; ret = iio_read_channel_processed(st->channel, &value); if (unlikely(ret < 0)) { @@ -46,6 +49,7 @@ static void adc_keys_poll(struct input_dev *input) if (diff < closest) { closest = diff; keycode = st->map[i].keycode; + type = st->map[i].type; } } } @@ -54,13 +58,14 @@ static void adc_keys_poll(struct input_dev *input) keycode = 0; if (st->last_key && st->last_key != keycode) - input_report_key(input, st->last_key, 0); + input_event(input, st->last_type, st->last_key, 0); if (keycode) - input_report_key(input, keycode, 1); + input_event(input, type, keycode, 1); input_sync(input); st->last_key = keycode; + st->last_type = type; } static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st) @@ -93,6 +98,10 @@ static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st) return -EINVAL; } + if (fwnode_property_read_u32(child, "linux,input-type", + &map[i].type)) + map[i].type = EV_KEY; + i++; } @@ -156,9 +165,8 @@ static int adc_keys_probe(struct platform_device *pdev) input->id.product = 0x0001; input->id.version = 0x0100; - __set_bit(EV_KEY, input->evbit); for (i = 0; i < st->num_keys; i++) - __set_bit(st->map[i].keycode, input->keybit); + input_set_capability(input, st->map[i].type, st->map[i].keycode); if (device_property_read_bool(dev, "autorepeat")) __set_bit(EV_REP, input->evbit); From be3c285008fb832a2bb0f4e7688b7adbb9777445 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 30 Jun 2025 12:19:26 +0200 Subject: [PATCH 020/208] arm64: dts: rockchip: add analog audio to ROCK 4D The RADXA ROCK 4D, like many other Rockchip-based boards, uses an ES8388 analog audio codec. On the production version of the board, the codec's LOUT1 and ROUT1 pins are tied to the headphone jack, whereas pins LOUT2 and ROUT2 lead to a non-populated speaker amplifier that itself leads to a non-populated speaker jack. The schematic is still haunted by the ghosts of those symbols, but it clearly marks them as "NC". The 3.5mm TRRS jack has its microphone ring (and ground ring) wired to the codec's LINPUT1 and RINPUT1 pins for differential signalling. Furthermore, it uses the SoCs ADC to detect whether the inserted cable is of headphones (i.e., no microphone), or a headset (i.e., with microphone). The way this is done is that the ADC input taps the output of a 100K/100K resistor divider that divides the microphone ring pin that's pulled up to 3.3V. There is no ADC level difference between a completely empty jack and one with a set of headphones (i.e., ones that don't have a microphone) connected. Consequently headphone insertion detection isn't something that can be done. Add the necessary codec and audio card nodes. The non-populated parts, i.e. LOUT2 and ROUT2, are not modeled at all, as they are not present on the hardware. Also, add an adc-keys node for the headset detection, which uses an input type of EV_SW with the SW_MICROPHONE_INSERT keycode. Below the 220mV pressed voltage level of our SW_MICROPHONE_INSERT switch, we also define a button that emits a KEY_RESERVED code, which is there to model this part of the voltage range as not just being extra legroom for the button above it, but actually a state that is encountered in the real world, and should be recognised as a valid state for the ADC range to be in so that no "closer" ADC button is chosen. Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20250630-rock4d-audio-v1-3-0b3c8e8fda9c@collabora.com Signed-off-by: Sebastian Reichel --- .../boot/dts/rockchip/rk3576-rock-4d.dts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index 899a84b1fbf9e8..c005e9b041bea2 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -6,6 +6,7 @@ /dts-v1/; #include +#include #include #include #include @@ -45,6 +46,31 @@ shutdown-gpios = <&gpio2 RK_PD1 GPIO_ACTIVE_HIGH>; }; + es8388_sound: es8388-sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,name = "On-board Analog ES8388"; + simple-audio-card,widgets = "Microphone", "Headphone Mic", + "Headphone", "Headphone"; + simple-audio-card,routing = "Headphone", "LOUT1", + "Headphone", "ROUT1", + "Left PGA Mux", "Differential Mux", + "Differential Mux", "LINPUT1", + "Differential Mux", "RINPUT1", + "LINPUT1", "Headphone Mic", + "RINPUT1", "Headphone Mic"; + + simple-audio-card,cpu { + sound-dai = <&sai1>; + }; + + simple-audio-card,codec { + sound-dai = <&es8388>; + system-clock-frequency = <12288000>; + }; + }; + leds: leds { compatible = "gpio-leds"; pinctrl-names = "default"; @@ -65,6 +91,37 @@ }; }; + saradc_keys: adc-keys { + compatible = "adc-keys"; + io-channels = <&saradc 3>; + io-channel-names = "buttons"; + keyup-threshold-microvolt = <3000000>; + poll-interval = <100>; + + /* + * During insertion and removal of a regular set of headphones, + * i.e. one without a microphone, the voltage level briefly + * dips below the 220mV of the headset connection switch. + * By having a button definition with a KEY_RESERVED signal + * between 0 to 220, we ensure no driver implementation thinks + * that the closest thing to 0V is 220mV so clearly there must + * be a headset connected. + */ + + button-headset-disconnected { + label = "Headset Microphone Disconnected"; + linux,code = ; + press-threshold-microvolt = <0>; + }; + + button-headset-connected { + label = "Headset Microphone Connected"; + linux,code = ; + linux,input-type = ; + press-threshold-microvolt = <220000>; + }; + }; + vcc_5v0_dcin: regulator-vcc-5v0-dcin { compatible = "regulator-fixed"; regulator-always-on; @@ -682,6 +739,25 @@ }; }; +&i2c3 { + status = "okay"; + + es8388: audio-codec@10 { + compatible = "everest,es8388", "everest,es8328"; + reg = <0x10>; + clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>; + AVDD-supply = <&vcca_3v3_s0>; + DVDD-supply = <&vcc_3v3_s0>; + HPVDD-supply = <&vcca_3v3_s0>; + PVDD-supply = <&vcc_3v3_s0>; + assigned-clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>; + assigned-clock-rates = <12288000>; + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&sai1m0_mclk>; + }; +}; + &i2c6 { pinctrl-names = "default"; pinctrl-0 = <&i2c6m3_xfer>; @@ -770,10 +846,24 @@ }; }; +&sai1 { + pinctrl-names = "default"; + pinctrl-0 = <&sai1m0_lrck + &sai1m0_sclk + &sai1m0_sdi0 + &sai1m0_sdo0>; + status = "okay"; +}; + &sai6 { status = "okay"; }; +&saradc { + vref-supply = <&vcca1v8_pldo2_s0>; + status = "okay"; +}; + &sdmmc { bus-width = <4>; cap-mmc-highspeed; From 32eb12ebae4cbcf880bc4820670b82f29e37c008 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 3 Jul 2025 01:21:14 +0200 Subject: [PATCH 021/208] net: phy: realtek: Reset after clock enable On Radxa ROCK 4D boards we are seeing some issues with PHY detection and stability (e.g. link loss or not capable of transceiving packages) after new board revisions switched from a dedicated crystal to providing the 25 MHz PHY input clock from the SoC instead. This board is using a RTL8211F PHY, which is connected to an always-on regulator. Unfortunately the datasheet does not explicitly mention the power-up sequence regarding the clock, but it seems to assume that the clock is always-on (i.e. dedicated crystal). By doing an explicit reset after enabling the clock, the issue on the boards could no longer be observed. Note, that the RK3576 SoC used by the ROCK 4D board does not yet support system level PM, so the resume path has not been tested. Cc: stable@vger.kernel.org Fixes: 7300c9b574cc ("net: phy: realtek: Add optional external PHY clock") Signed-off-by: Sebastian Reichel --- drivers/net/phy/realtek/realtek_main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 79c867ef64dad0..c35c89941d283b 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -291,6 +291,8 @@ static int rtl821x_probe(struct phy_device *phydev) if (IS_ERR(priv->clk)) return dev_err_probe(dev, PTR_ERR(priv->clk), "failed to get phy clock\n"); + if (priv->clk) + phy_reset_after_clk_enable(phydev); priv->enable_aldps = of_property_read_bool(dev->of_node, "realtek,aldps-enable"); @@ -936,8 +938,10 @@ static int rtl821x_resume(struct phy_device *phydev) struct rtl821x_priv *priv = phydev->priv; int ret; - if (!phydev->wol_enabled) + if (!phydev->wol_enabled && priv->clk) { clk_prepare_enable(priv->clk); + phy_reset_after_clk_enable(phydev); + } ret = genphy_resume(phydev); if (ret < 0) @@ -2456,7 +2460,7 @@ static struct phy_driver realtek_drvs[] = { .resume = rtl8211f_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, - .flags = PHY_ALWAYS_CALL_SUSPEND, + .flags = PHY_ALWAYS_CALL_SUSPEND | PHY_RST_AFTER_CLK_EN, .led_hw_is_supported = rtl8211x_led_hw_is_supported, .led_hw_control_get = rtl8211f_led_hw_control_get, .led_hw_control_set = rtl8211f_led_hw_control_set, From 6091327b3aa338066b1ed751eae2b7873866efec Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 8 Jul 2025 19:36:31 +0200 Subject: [PATCH 022/208] arm64: dts: rockchip: use MAC TX delay for ROCK 4D According to the Ethernet controller device tree binding "rgmii-id" means, that the PCB does not have extra long lines to add the required delays. This is indeed the case for the ROCK 4D. The problem is, that the Rockchip MAC Linux driver interprets the interface type differently and abuses the information to configure RX and TX delays in the MAC using (vendor) properties 'rx_delay' and 'tx_delay'. When Detlev Casanova upstreamed the ROCK 4D device tree, he used the correct description for the board ("rgmii-id"). This results in no delays being configured in the MAC. At the same time the PHY will provide some delays. This works to some degree, but is not a stable configuration. All five ROCK 4D production boards, which have recently been added to the Collabora LAVA lab for CI purposes have trouble with data not getting through after a connection has been established. Using the same delay setup as the vendor device tree fixes the functionality (at the cost of not properly following the DT binding). As we cannot fix the driver behavior for RK3576 (some other boards already depend on this), let's update the ROCK 4D DT instead. Cc: Andrew Lunn Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index c005e9b041bea2..a0730c50c09653 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -329,7 +329,7 @@ &gmac0 { clock_in_out = "output"; phy-handle = <&rgmii_phy0>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii-rxid"; pinctrl-names = "default"; pinctrl-0 = <ð0m0_miim ð0m0_tx_bus2 @@ -338,6 +338,8 @@ ð0m0_rgmii_bus ðm0_clk0_25m_out>; status = "okay"; + tx_delay = <0x20>; + rx_delay = <0x00>; }; &gpu { From 7d23606ea281b7ea12df2c91bcb713297754730d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 21 Nov 2025 19:27:00 +0100 Subject: [PATCH 023/208] PCI: dw-rockchip: Fix LTSSM set functions Before the Rockchip PCIe driver has been switched over to the FIELD_PREP_WM16 macro, PCIE_CLIENT_ENABLE_LTSSM and PCIE_CLIENT_DISABLE_LTSSM were setting bits with a mask of 0xc = 0b1100, which means BIT 2 and BIT 3. After the conversion it only sets bit 2, with bit 3 being handled by a separate define named PCIE_CLIENT_LD_RQ_RST_GRT. Apparently the conversion missed to make use of this new macros resulting in the third bit not being set. Fixes: 30e919570581 ("PCI: dw-rockchip: Switch to FIELD_PREP_WM16 macro") Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 731d93663ccae5..e86d7ee686fcbe 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -317,14 +317,14 @@ static void rockchip_pcie_ltssm_trace(struct rockchip_pcie *rockchip, static void rockchip_pcie_enable_ltssm(struct rockchip_pcie *rockchip) { - rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_ENABLE_LTSSM, - PCIE_CLIENT_GENERAL_CON); + u32 val = PCIE_CLIENT_ENABLE_LTSSM | PCIE_CLIENT_LD_RQ_RST_GRT; + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_GENERAL_CON); } static void rockchip_pcie_disable_ltssm(struct rockchip_pcie *rockchip) { - rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_DISABLE_LTSSM, - PCIE_CLIENT_GENERAL_CON); + u32 val = PCIE_CLIENT_DISABLE_LTSSM | PCIE_CLIENT_LD_RQ_RST_GRT; + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_GENERAL_CON); } static bool rockchip_pcie_link_up(struct dw_pcie *pci) From 33f971786d172ccda376969dd6ddf5f6f5ad7cae Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 22 Dec 2025 20:25:56 +0100 Subject: [PATCH 024/208] PCI: dw-rockchip: Restore vpcie3v3 regulator handle This reverts c930b10f17c0 ("PCI: dw-rockchip: Simplify regulator setup with devm_regulator_get_enable_optional()"), which nicely cleaned up the code. The vpcie3v3 regulator handle is needed to disable the regulator during system suspend (to be added in its own patch). Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index e86d7ee686fcbe..04ffb16261de9c 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -111,6 +111,7 @@ struct rockchip_pcie { unsigned int clk_cnt; struct reset_control *rst; struct gpio_desc *rst_gpio; + struct regulator *vpcie3v3; struct irq_domain *irq_domain; const struct rockchip_pcie_of_data *data; bool supports_clkreq; @@ -784,15 +785,22 @@ static int rockchip_pcie_probe(struct platform_device *pdev) return ret; /* DON'T MOVE ME: must be enable before PHY init */ - ret = devm_regulator_get_enable_optional(dev, "vpcie3v3"); - if (ret < 0 && ret != -ENODEV) - return dev_err_probe(dev, ret, - "failed to enable vpcie3v3 regulator\n"); + rockchip->vpcie3v3 = devm_regulator_get_optional(dev, "vpcie3v3"); + if (IS_ERR(rockchip->vpcie3v3)) { + if (PTR_ERR(rockchip->vpcie3v3) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(rockchip->vpcie3v3), + "failed to get vpcie3v3 regulator\n"); + rockchip->vpcie3v3 = NULL; + } else { + ret = regulator_enable(rockchip->vpcie3v3); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable vpcie3v3 regulator\n"); + } ret = rockchip_pcie_phy_init(rockchip); if (ret) - return dev_err_probe(dev, ret, - "failed to initialize the phy\n"); + goto disable_regulator; ret = reset_control_deassert(rockchip->rst); if (ret) @@ -825,6 +833,9 @@ static int rockchip_pcie_probe(struct platform_device *pdev) clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); deinit_phy: rockchip_pcie_phy_deinit(rockchip); +disable_regulator: + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); return ret; } From 5df82b563619e8ffb96d9423e45fbac329c69dc9 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:06:36 +0200 Subject: [PATCH 025/208] PCI: dw-rockchip: Move devm_phy_get out of phy_init By moving devm_phy_get() to the probe routine, rockchip_pcie_phy_init() can be used to re-initialize the PCIe PHY, which is for example needed after a system suspend/resume cycle. Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 04ffb16261de9c..42bc4ed4b22e2d 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -603,14 +603,8 @@ static int rockchip_pcie_resource_get(struct platform_device *pdev, static int rockchip_pcie_phy_init(struct rockchip_pcie *rockchip) { - struct device *dev = rockchip->pci.dev; int ret; - rockchip->phy = devm_phy_get(dev, "pcie-phy"); - if (IS_ERR(rockchip->phy)) - return dev_err_probe(dev, PTR_ERR(rockchip->phy), - "missing PHY\n"); - ret = phy_init(rockchip->phy); if (ret < 0) return ret; @@ -798,6 +792,13 @@ static int rockchip_pcie_probe(struct platform_device *pdev) "failed to enable vpcie3v3 regulator\n"); } + rockchip->phy = devm_phy_get(dev, "pcie-phy"); + if (IS_ERR(rockchip->phy)) { + ret = PTR_ERR(rockchip->phy); + dev_err_probe(dev, ret, "missing PHY\n"); + goto disable_regulator; + } + ret = rockchip_pcie_phy_init(rockchip); if (ret) goto disable_regulator; From 54472be1ac5cc4810b306b979e21d9640d7a5a2e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:21:02 +0200 Subject: [PATCH 026/208] PCI: dw-rockchip: Add helper function for enhanced LTSSM control mode Remove code duplication and improve readability by introducing a new function to setup the enhanced LTSSM mode. Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 42bc4ed4b22e2d..e0709e29e4584b 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -661,17 +661,25 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) return IRQ_HANDLED; } +static void rockchip_pcie_enable_enhanced_ltssm_control_mode(struct rockchip_pcie *rockchip, u32 flags) +{ + u32 val; + + /* Enable the enhanced control mode of signal app_ltssm_enable */ + val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); + if (flags) + val |= FIELD_PREP_WM16(flags, 1); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); +} + static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) { struct dw_pcie_rp *pp; - u32 val; if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_HOST)) return -ENODEV; - /* LTSSM enable control mode */ - val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, 0); rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_RC), @@ -705,13 +713,7 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, return ret; } - /* - * LTSSM enable control mode, and automatically delay link training on - * hot reset/link-down reset. - */ - val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1) | - FIELD_PREP_WM16(PCIE_LTSSM_APP_DLY2_EN, 1); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, PCIE_LTSSM_APP_DLY2_EN); rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_EP), From 27980caed49fd324612ff6795be9c64e1db17a49 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:27:04 +0200 Subject: [PATCH 027/208] PCI: dw-rockchip: Add helper function for controller mode Remove code duplication and improve readability by introducing a new function to setup the controller mode. Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index e0709e29e4584b..8d6f05d04d8327 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -672,6 +672,11 @@ static void rockchip_pcie_enable_enhanced_ltssm_control_mode(struct rockchip_pci rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); } +static void rockchip_pcie_set_controller_mode(struct rockchip_pcie *rockchip, u32 mode) +{ + rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_SET_MODE(mode), PCIE_CLIENT_GENERAL_CON); +} + static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) { struct dw_pcie_rp *pp; @@ -680,10 +685,7 @@ static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) return -ENODEV; rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, 0); - - rockchip_pcie_writel_apb(rockchip, - PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_RC), - PCIE_CLIENT_GENERAL_CON); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_RC); pp = &rockchip->pci.pp; pp->ops = &rockchip_pcie_host_ops; @@ -714,10 +716,7 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, } rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, PCIE_LTSSM_APP_DLY2_EN); - - rockchip_pcie_writel_apb(rockchip, - PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_EP), - PCIE_CLIENT_GENERAL_CON); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_EP); rockchip->pci.ep.ops = &rockchip_pcie_ep_ops; rockchip->pci.ep.page_size = SZ_64K; From e43018b2d294025e089841cbb32d313b5fbb5c45 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:30:11 +0200 Subject: [PATCH 028/208] PCI: dw-rockchip: Add helper function for DDL indicator Remove code duplication and improve readability by introducing a new function to setup the DLL indicator. Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 8d6f05d04d8327..4be536681e2bcf 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -677,6 +677,16 @@ static void rockchip_pcie_set_controller_mode(struct rockchip_pcie *rockchip, u3 rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_SET_MODE(mode), PCIE_CLIENT_GENERAL_CON); } +static void rockchip_pcie_unmask_dll_indicator(struct rockchip_pcie *rockchip) +{ + u32 val; + + /* unmask DLL up/down indicator and hot reset/link-down reset */ + val = FIELD_PREP_WM16(PCIE_RDLH_LINK_UP_CHGED, 0) | + FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); +} + static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) { struct dw_pcie_rp *pp; @@ -698,7 +708,6 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, { struct device *dev = &pdev->dev; int irq, ret; - u32 val; if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_EP)) return -ENODEV; @@ -738,10 +747,7 @@ static int rockchip_pcie_configure_ep(struct platform_device *pdev, pci_epc_init_notify(rockchip->pci.ep.epc); - /* unmask DLL up/down indicator and hot reset/link-down reset */ - val = FIELD_PREP_WM16(PCIE_RDLH_LINK_UP_CHGED, 0) | - FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); + rockchip_pcie_unmask_dll_indicator(rockchip); return ret; } From 483e92565b981328a9769a85a4f217878012876a Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:38:54 +0200 Subject: [PATCH 029/208] PCI: dw-rockchip: Add pme_turn_off support Prepare Rockchip PCIe controller for system suspend support by adding the PME turn off operation. Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 4be536681e2bcf..e1203d069b3a88 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -44,6 +44,7 @@ #define PCIE_CLIENT_LD_RQ_RST_GRT FIELD_PREP_WM16(BIT(3), 1) #define PCIE_CLIENT_ENABLE_LTSSM FIELD_PREP_WM16(BIT(2), 1) #define PCIE_CLIENT_DISABLE_LTSSM FIELD_PREP_WM16(BIT(2), 0) +#define PCIE_CLIENT_INTR_STATUS_MSG_RX 0x04 /* Interrupt Status Register Related to Legacy Interrupt */ #define PCIE_CLIENT_INTR_STATUS_LEGACY 0x8 @@ -63,6 +64,11 @@ /* Interrupt Mask Register Related to Miscellaneous Operation */ #define PCIE_CLIENT_INTR_MASK_MISC 0x24 +#define PCIE_CLIENT_POWER 0x2c +#define PCIE_CLIENT_MSG_GEN 0x34 +#define PME_READY_ENTER_L23 BIT(3) +#define PME_TURN_OFF FIELD_PREP_WM16(BIT(4), 1) +#define PME_TO_ACK FIELD_PREP_WM16(BIT(9), 1) /* Power Management Control Register */ #define PCIE_CLIENT_POWER_CON 0x2c @@ -445,8 +451,46 @@ static int rockchip_pcie_host_init(struct dw_pcie_rp *pp) return 0; } +static void rockchip_pcie_pme_turn_off(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); + struct device *dev = rockchip->pci.dev; + u32 status; + int ret; + + /* 1. Broadcast PME_Turn_Off Message, bit 4 self-clear once done */ + rockchip_pcie_writel_apb(rockchip, PME_TURN_OFF, PCIE_CLIENT_MSG_GEN); + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_MSG_GEN, + status, !(status & BIT(4)), PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) { + dev_warn(dev, "Failed to send PME_Turn_Off\n"); + return; + } + + /* 2. Wait for PME_TO_Ack, bit 9 will be set once received */ + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_INTR_STATUS_MSG_RX, + status, status & BIT(9), PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) { + dev_warn(dev, "Failed to receive PME_TO_Ack\n"); + return; + } + + /* 3. Clear PME_TO_Ack and Wait for ready to enter L23 message */ + rockchip_pcie_writel_apb(rockchip, PME_TO_ACK, PCIE_CLIENT_INTR_STATUS_MSG_RX); + ret = readl_poll_timeout(rockchip->apb_base + PCIE_CLIENT_POWER, + status, status & PME_READY_ENTER_L23, + PCIE_PME_TO_L2_TIMEOUT_US / 10, + PCIE_PME_TO_L2_TIMEOUT_US); + if (ret) + dev_err(dev, "Failed to get ready to enter L23 message\n"); +} + static const struct dw_pcie_host_ops rockchip_pcie_host_ops = { .init = rockchip_pcie_host_init, + .pme_turn_off = rockchip_pcie_pme_turn_off, }; /* From 706129f0f93bb64359ac9e9ae59f723d026f4693 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 20 Oct 2025 20:49:34 +0200 Subject: [PATCH 030/208] PCI: dw-rockchip: Add system PM support Add system PM support for Rockchip PCIe Designware Controllers. I've tested this on the Rockchip RK3576 EVB1, the Radxa ROCK 4D and the ArmSom Sige5 boards. While I haven't experienced any issues, most of my tests have been done without any devices attached (i.e. default board without any extras), so there _might_ still be some problems. As system suspend does not work at all right now, I think it makes sense to get at least the basic configurations working as soon as possible as it will allow us to catch regressions by enabling system suspend in CI systems like KernelCI. Co-developed-by: Shawn Lin Signed-off-by: Shawn Lin Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index e1203d069b3a88..9c6be61cf66ceb 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -119,6 +119,7 @@ struct rockchip_pcie { struct gpio_desc *rst_gpio; struct regulator *vpcie3v3; struct irq_domain *irq_domain; + u32 intx; const struct rockchip_pcie_of_data *data; bool supports_clkreq; struct delayed_work trace_work; @@ -892,6 +893,99 @@ static int rockchip_pcie_probe(struct platform_device *pdev) return ret; } +static int rockchip_pcie_suspend(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + struct dw_pcie *pci = &rockchip->pci; + int ret; + + if (rockchip->data->mode == DW_PCIE_EP_TYPE) { + dev_err(dev, "suspend is not supported in EP mode\n"); + return -EOPNOTSUPP; + } + + rockchip->intx = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_INTR_MASK_LEGACY); + + ret = dw_pcie_suspend_noirq(pci); + if (ret) + return ret; + + gpiod_set_value_cansleep(rockchip->rst_gpio, 0); + rockchip_pcie_phy_deinit(rockchip); + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + reset_control_assert(rockchip->rst); + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); + + return 0; +} + +static int rockchip_pcie_resume(struct device *dev) +{ + struct rockchip_pcie *rockchip = dev_get_drvdata(dev); + struct dw_pcie *pci = &rockchip->pci; + int ret; + + if (rockchip->data->mode == DW_PCIE_EP_TYPE) { + dev_err(dev, "resume is not supported in EP mode\n"); + return -EOPNOTSUPP; + } + + ret = clk_bulk_prepare_enable(rockchip->clk_cnt, rockchip->clks); + if (ret) { + dev_err(dev, "clock init failed: %d\n", ret); + return ret; + } + + if (rockchip->vpcie3v3) { + ret = regulator_enable(rockchip->vpcie3v3); + if (ret) + goto err_disable_clk; + } + + ret = rockchip_pcie_phy_init(rockchip); + if (ret) { + dev_err(dev, "phy init failed: %d\n", ret); + goto err_disable_regulator; + } + + reset_control_deassert(rockchip->rst); + + rockchip_pcie_writel_apb(rockchip, FIELD_PREP_WM16(0xffff, rockchip->intx), + PCIE_CLIENT_INTR_MASK_LEGACY); + + rockchip_pcie_enable_enhanced_ltssm_control_mode(rockchip, 0); + rockchip_pcie_set_controller_mode(rockchip, PCIE_CLIENT_MODE_RC); + rockchip_pcie_unmask_dll_indicator(rockchip); + + gpiod_set_value_cansleep(rockchip->rst_gpio, 1); + + ret = dw_pcie_resume_noirq(pci); + if (ret) { + dev_err(dev, "failed to resume: %d\n", ret); + /* + * During resume, dw_pcie_wait_for_link() is called and when + * there is no device connected at all it returns -EIO with + * the message "Device found, but not active". Ignore it for + * now. + */ + if (ret != -EIO) + goto err_deinit_phy; + } + + return 0; + +err_deinit_phy: + gpiod_set_value_cansleep(rockchip->rst_gpio, 0); + rockchip_pcie_phy_deinit(rockchip); +err_disable_regulator: + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); +err_disable_clk: + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + return ret; +} + static const struct rockchip_pcie_of_data rockchip_pcie_rc_of_data_rk3568 = { .mode = DW_PCIE_RC_TYPE, }; @@ -922,11 +1016,17 @@ static const struct of_device_id rockchip_pcie_of_match[] = { {}, }; +static const struct dev_pm_ops rockchip_pcie_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_pcie_suspend, + rockchip_pcie_resume) +}; + static struct platform_driver rockchip_pcie_driver = { .driver = { .name = "rockchip-dw-pcie", .of_match_table = rockchip_pcie_of_match, .suppress_bind_attrs = true, + .pm = &rockchip_pcie_pm_ops, }, .probe = rockchip_pcie_probe, }; From 78cf300382979cc677b0ad60ca4e0c95844d2c06 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 24 Nov 2025 20:06:39 +0100 Subject: [PATCH 031/208] [RFC] PCI: dw-rockchip: port some suspend code from vendor kernel Rockchip's vendor kernel does these calls before starting the actual process of going into L2 state. I'm not sure about the rationale, hopefully Shawn can help out with that. Cc: Shawn Lin Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 9c6be61cf66ceb..65e85c7eab0587 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -76,6 +76,9 @@ #define PCIE_CLKREQ_NOT_READY FIELD_PREP_WM16(BIT(0), 0) #define PCIE_CLKREQ_PULL_DOWN FIELD_PREP_WM16(GENMASK(13, 12), 1) +/* General Debug Register */ +#define PCIE_CLIENT_GENERAL_DEBUG 0x104 + /* RASDES TBA information */ #define PCIE_CLIENT_CDM_RASDES_TBA_INFO_CMN 0x154 #define PCIE_CLIENT_CDM_RASDES_TBA_L1_1 BIT(4) @@ -893,6 +896,12 @@ static int rockchip_pcie_probe(struct platform_device *pdev) return ret; } + +static inline void rockchip_pcie_link_status_clear(struct rockchip_pcie *rockchip) +{ + rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_GENERAL_DEBUG, 0x0); +} + static int rockchip_pcie_suspend(struct device *dev) { struct rockchip_pcie *rockchip = dev_get_drvdata(dev); @@ -906,6 +915,11 @@ static int rockchip_pcie_suspend(struct device *dev) rockchip->intx = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_INTR_MASK_LEGACY); + /* All sub-devices are in D3hot by PCIe stack */ + dw_pcie_dbi_ro_wr_dis(pci); + + rockchip_pcie_link_status_clear(rockchip); + ret = dw_pcie_suspend_noirq(pci); if (ret) return ret; From 03df8f612e3c2a2d9cd1d7823ef5800bf7cbeeb4 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 22 Aug 2025 18:49:35 +0200 Subject: [PATCH 032/208] dt-bindings: phy: rockchip-usbdp: add improved ports scheme Currently the Rockchip USBDP PHY is missing a documented port scheme. Meanwhile upstream RK3588 DTS files are a bit messy and use different port schemes. The upstream USBDP PHY Linux kernel driver does not yet parse the ports at all and thus does not create any implicit ABI either. But with the current mess it is not possible to properly support USB-C DP AltMode. Thus this introduces a proper port scheme following roughly the ports design of the Qualcomm QMP USB4-USB3-DP PHY controller binding with a slight difference that there is an additional port for the USB-C SBU port as the Rockchip USB-DP PHY also contains the SBU mux. Signed-off-by: Sebastian Reichel --- .../bindings/phy/phy-rockchip-usbdp.yaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml index 8b7059d5b1826f..f728acf057e404 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-usbdp.yaml @@ -114,6 +114,29 @@ properties: A port node to link the PHY to a TypeC controller for the purpose of handling orientation switching. + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for USB (or DP when configured into 4 lane + mode), which should point to the superspeed port of a USB connector. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the USB controller + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the DisplayPort controller + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of the PHY for DP, which should either point to the + SBU port of a USB-C connector or a DisplayPort connector input port. + required: - compatible - reg From 6336277e3b201f0a9cba5a0505c3a800d58322f9 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 12 Feb 2026 01:22:02 +0100 Subject: [PATCH 033/208] phy: rockchip: usbdp: Do not loose USB3 PHY status By default (i.e. without manually enabling runtime PM) DWC3 requests the USB3 PHY once and keeps it enabled all the time. When DisplayPort is being requested later on, a mode change is needed. This re-initializes the PHY. During re-initialization the status variable has incorrectly been cleared, which means the tracking information for USB3 ist lost. This is not an immediate problem, since the DP side keeps the PHY enabled. But once DP is toggled off, the whole PHY will be disabled. This is a problem, because the USB side still needs it powered. Fix things by not clearing the status flags. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index fba35510d88ced..744cc7c642f498 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1009,7 +1009,6 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) rk_udphy_u3_port_disable(udphy, false); } else if (udphy->mode_change) { udphy->mode_change = false; - udphy->status = UDPHY_MODE_NONE; if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); From e467c0f5cc21bc9e0b65f31bb5cd0544464ee177 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 5 Feb 2026 19:26:12 +0100 Subject: [PATCH 034/208] phy: rockchip: usbdp: Keep clocks running on PHY re-init When a mode change is required rk_udphy_power_on() disables the clocks and then calls rk_udphy_setup(), which then enables all the clocks again before continuing with rk_udphy_init(). Considering that rk_udphy_init() does assert the reset lines, re-enabling the clocks is just delaying things. Avoid it by directly calling rk_udphy_init(). Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 744cc7c642f498..98562a888b42a5 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -1012,8 +1012,7 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); - rk_udphy_disable(udphy); - ret = rk_udphy_setup(udphy); + ret = rk_udphy_init(udphy); if (ret) return ret; } From 9250229d02afcaf8ff74cc1335d90acfd009a058 Mon Sep 17 00:00:00 2001 From: Frank Wang Date: Fri, 16 Jan 2026 20:28:08 +0100 Subject: [PATCH 035/208] phy: rockchip: usbdp: Amend SSC modulation deviation Move SSC modulation deviation into private config of clock - 24M: 0x00d4[5:0] = 0x30 - 26M: 0x00d4[5:0] = 0x33 Signed-off-by: Frank Wang [Taken over from rockchip's kernel tree; register 0x00d4 is not described in the TRM] Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 98562a888b42a5..1f686844c3379d 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -350,7 +350,8 @@ static const struct reg_sequence rk_udphy_24m_refclk_cfg[] = { {0x0a64, 0xa8}, {0x1a3c, 0xd0}, {0x1a44, 0xd0}, {0x1a48, 0x01}, {0x1a4c, 0x0d}, {0x1a54, 0xe0}, - {0x1a5c, 0xe0}, {0x1a64, 0xa8} + {0x1a5c, 0xe0}, {0x1a64, 0xa8}, + {0x00d4, 0x30} }; static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { @@ -377,7 +378,7 @@ static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = { {0x0c30, 0x0e}, {0x0c48, 0x06}, {0x1c30, 0x0e}, {0x1c48, 0x06}, {0x028c, 0x18}, {0x0af0, 0x00}, - {0x1af0, 0x00} + {0x1af0, 0x00}, {0x00d4, 0x33} }; static const struct reg_sequence rk_udphy_init_sequence[] = { @@ -412,8 +413,7 @@ static const struct reg_sequence rk_udphy_init_sequence[] = { {0x0070, 0x7d}, {0x0074, 0x68}, {0x0af4, 0x1a}, {0x1af4, 0x1a}, {0x0440, 0x3f}, {0x10d4, 0x08}, - {0x20d4, 0x08}, {0x00d4, 0x30}, - {0x0024, 0x6e}, + {0x20d4, 0x08}, {0x0024, 0x6e} }; static inline int rk_udphy_grfreg_write(struct regmap *base, From 9ac07c6066919f417c8ac2c7dd8c19bcbbf17906 Mon Sep 17 00:00:00 2001 From: William Wu Date: Fri, 16 Jan 2026 20:31:20 +0100 Subject: [PATCH 036/208] phy: rockchip: usbdp: Fix LFPS detect threshold control According to the LFPS Tx Low Power/LFPS Rx Detect Threshold [1], the device under test(DUT) must not respond if LFPS below the minimum LFPS Rx Detect Threshold 100mV. Test fail on Rockchip platforms, because the default LFPS detect threshold is set to 65mV. The USBDP PHY LFPS detect threshold voltage could be set to 30mV ~ 140mV, and since there could be 10-20% PVT variation, we set LFPS detect threshold voltage to 110mV. [1] https://compliance.usb.org/resources/LFPS_Rx_Tx_Low_Power_Compliance_Update_Rev5.pdf Signed-off-by: William Wu [Taken over from rockchip's kernel tree; the registers are not described in the TRM] Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 1f686844c3379d..97e53b933225f7 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -413,7 +413,8 @@ static const struct reg_sequence rk_udphy_init_sequence[] = { {0x0070, 0x7d}, {0x0074, 0x68}, {0x0af4, 0x1a}, {0x1af4, 0x1a}, {0x0440, 0x3f}, {0x10d4, 0x08}, - {0x20d4, 0x08}, {0x0024, 0x6e} + {0x20d4, 0x08}, {0x0024, 0x6e}, + {0x09c0, 0x0a}, {0x19c0, 0x0a} }; static inline int rk_udphy_grfreg_write(struct regmap *base, From 90e1e42674eeb2b4b2bdc02e80f98d53f02cdd32 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 29 Jan 2026 21:32:59 +0100 Subject: [PATCH 037/208] phy: rockchip: usbdp: Add missing mode_change update rk_udphy_set_typec_default_mapping() updates the available modes, but does not set the mode_change as required. This results in missing re-initialization and thus non-working DisplayPort. Fix this issue by introducing a new helper to update the available modes. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 97e53b933225f7..febc148a754e09 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -619,6 +619,15 @@ static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd) rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); } +static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) +{ + if (udphy->mode == mode) + return; + + udphy->mode_change = true; + udphy->mode = mode; +} + static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) { if (udphy->flip) { @@ -649,7 +658,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1); } - udphy->mode = UDPHY_MODE_DP_USB; + rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -1385,10 +1394,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, usleep_range(750, 800); rk_udphy_dp_hpd_event_trigger(udphy, true); } else if (data->status & DP_STATUS_HPD_STATE) { - if (udphy->mode != mode) { - udphy->mode = mode; - udphy->mode_change = true; - } + rk_udphy_mode_set(udphy, mode); rk_udphy_dp_hpd_event_trigger(udphy, true); } else { rk_udphy_dp_hpd_event_trigger(udphy, false); From d0f601b50b49e4d6d43a87811d56b026361c718e Mon Sep 17 00:00:00 2001 From: Zhang Yubing Date: Thu, 29 Jan 2026 13:04:47 +0100 Subject: [PATCH 038/208] phy: rockchip: usbdp: Support single-lane DP Implement support for using just a single DisplayPort line. Signed-off-by: Zhang Yubing Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 61 ++++++++++------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index febc148a754e09..bf8394174294e6 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -193,6 +193,7 @@ struct rk_udphy { int id; bool dp_in_use; + int dp_lanes; /* PHY const config */ const struct rk_udphy_cfg *cfgs; @@ -537,6 +538,13 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) * <0 1> dpln0 dpln1 usbrx usbtx * <2 3> usbrx usbtx dpln0 dpln1 * --------------------------------------------------------------------------- + * if 1 lane for dp function, 2 lane for usb function, define rockchip,dp-lane-mux = ; + * sample as follow: + * --------------------------------------------------------------------------- + * B11-B10 A2-A3 A11-A10 B2-B3 + * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx) + * <0> dpln0 \ usbrx usbtx + * --------------------------------------------------------------------------- */ static void rk_udphy_dplane_select(struct rk_udphy *udphy) @@ -544,18 +552,18 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) const struct rk_udphy_cfg *cfg = udphy->cfgs; u32 value = 0; - switch (udphy->mode) { - case UDPHY_MODE_DP: - value |= 2 << udphy->dp_lane_sel[2] * 2; + switch (udphy->dp_lanes) { + case 4: value |= 3 << udphy->dp_lane_sel[3] * 2; + value |= 2 << udphy->dp_lane_sel[2] * 2; fallthrough; - case UDPHY_MODE_DP_USB: - value |= 0 << udphy->dp_lane_sel[0] * 2; + case 2: value |= 1 << udphy->dp_lane_sel[1] * 2; - break; + fallthrough; - case UDPHY_MODE_USB: + case 1: + value |= 0 << udphy->dp_lane_sel[0] * 2; break; default: @@ -568,28 +576,6 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); } -static int rk_udphy_dplane_get(struct rk_udphy *udphy) -{ - int dp_lanes; - - switch (udphy->mode) { - case UDPHY_MODE_DP: - dp_lanes = 4; - break; - - case UDPHY_MODE_DP_USB: - dp_lanes = 2; - break; - - case UDPHY_MODE_USB: - default: - dp_lanes = 0; - break; - } - - return dp_lanes; -} - static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) { u32 val = 0; @@ -659,6 +645,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) } rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); + udphy->dp_lanes = 2; } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -897,7 +884,7 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) return 0; } - if (num_lanes != 2 && num_lanes != 4) + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) return dev_err_probe(udphy->dev, -EINVAL, "invalid number of lane mux\n"); @@ -923,7 +910,8 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy) } udphy->mode = UDPHY_MODE_DP; - if (num_lanes == 2) { + udphy->dp_lanes = num_lanes; + if (num_lanes == 1 || num_lanes == 2) { udphy->mode |= UDPHY_MODE_USB; udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP); } @@ -1074,18 +1062,17 @@ static int rk_udphy_dp_phy_exit(struct phy *phy) static int rk_udphy_dp_phy_power_on(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret, dp_lanes; + int ret; mutex_lock(&udphy->mutex); - dp_lanes = rk_udphy_dplane_get(udphy); - phy_set_bus_width(phy, dp_lanes); + phy_set_bus_width(phy, udphy->dp_lanes); ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); if (ret) goto unlock; - rk_udphy_dplane_enable(udphy, dp_lanes); + rk_udphy_dplane_enable(udphy, udphy->dp_lanes); rk_udphy_dplane_select(udphy); @@ -1365,6 +1352,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; mode = UDPHY_MODE_DP; + udphy->dp_lanes = 4; break; case TYPEC_DP_STATE_D: @@ -1381,6 +1369,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; } mode = UDPHY_MODE_DP_USB; + udphy->dp_lanes = 2; break; } @@ -1529,7 +1518,7 @@ static int rk_udphy_probe(struct platform_device *pdev) ret = PTR_ERR(udphy->phy_dp); return dev_err_probe(dev, ret, "failed to create DP phy\n"); } - phy_set_bus_width(udphy->phy_dp, rk_udphy_dplane_get(udphy)); + phy_set_bus_width(udphy->phy_dp, udphy->dp_lanes); udphy->phy_dp->attrs.max_link_rate = 8100; phy_set_drvdata(udphy->phy_dp, udphy); From c6c5ab806208ac299ac0a661b63c69327468d44a Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 29 Jan 2026 22:45:18 +0100 Subject: [PATCH 039/208] phy: rockchip: usbdp: Rename DP lane functions The common prefix for DisplayPort related functions is rk_udphy_dp_ (with a final _), so update the two DP lane functions to follow that scheme. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index bf8394174294e6..6d7ca11b308e9a 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -547,7 +547,7 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) * --------------------------------------------------------------------------- */ -static void rk_udphy_dplane_select(struct rk_udphy *udphy) +static void rk_udphy_dp_lane_select(struct rk_udphy *udphy) { const struct rk_udphy_cfg *cfg = udphy->cfgs; u32 value = 0; @@ -576,7 +576,7 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy) FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); } -static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes) +static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) { u32 val = 0; int i; @@ -1072,9 +1072,9 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy) if (ret) goto unlock; - rk_udphy_dplane_enable(udphy, udphy->dp_lanes); + rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); - rk_udphy_dplane_select(udphy); + rk_udphy_dp_lane_select(udphy); unlock: mutex_unlock(&udphy->mutex); @@ -1092,7 +1092,7 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) struct rk_udphy *udphy = phy_get_drvdata(phy); mutex_lock(&udphy->mutex); - rk_udphy_dplane_enable(udphy, 0); + rk_udphy_dp_lane_enable(udphy, 0); rk_udphy_power_off(udphy, UDPHY_MODE_DP); mutex_unlock(&udphy->mutex); From f149092782ef2f7f434600cbeadec52cd006fe80 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 29 Jan 2026 15:15:23 +0100 Subject: [PATCH 040/208] phy: rockchip: usbdp: Use FIELD_PREP_WM16_CONST Cleanup code by replacing open-coded version of FIELD_PREP_WM16_CONST with the existing helper macro. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 6d7ca11b308e9a..1bfc365e2b2c6e 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,6 @@ #define TRSV_LN2_MON_RX_CDR_DONE_OFFSET 0x1b84 /* trsv_reg06E1 */ #define TRSV_LN2_MON_RX_CDR_LOCK_DONE BIT(0) -#define BIT_WRITEABLE_SHIFT 16 #define PHY_AUX_DP_DATA_POL_NORMAL 0 #define PHY_AUX_DP_DATA_POL_INVERT 1 #define PHY_LANE_MUX_USB 0 @@ -104,8 +104,8 @@ struct rk_udphy_grf_reg { #define _RK_UDPHY_GEN_GRF_REG(offset, mask, disable, enable) \ {\ offset, \ - FIELD_PREP_CONST(mask, disable) | (mask << BIT_WRITEABLE_SHIFT), \ - FIELD_PREP_CONST(mask, enable) | (mask << BIT_WRITEABLE_SHIFT), \ + FIELD_PREP_WM16_CONST(mask, disable), \ + FIELD_PREP_WM16_CONST(mask, enable), \ } #define RK_UDPHY_GEN_GRF_REG(offset, bitend, bitstart, disable, enable) \ From 899ba9c0567e6b9067fc43b86a8c7447f74a89fa Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 29 Jan 2026 15:24:20 +0100 Subject: [PATCH 041/208] phy: rockchip: usbdp: Cleanup DP lane selection function Use FIELD_PREP_WM16() helpers to simplify the DP lane selection logic. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 28 ++++++----------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 1bfc365e2b2c6e..beab20e4c512c9 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -550,30 +550,16 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable) static void rk_udphy_dp_lane_select(struct rk_udphy *udphy) { const struct rk_udphy_cfg *cfg = udphy->cfgs; - u32 value = 0; - - switch (udphy->dp_lanes) { - case 4: - value |= 3 << udphy->dp_lane_sel[3] * 2; - value |= 2 << udphy->dp_lane_sel[2] * 2; - fallthrough; - - case 2: - value |= 1 << udphy->dp_lane_sel[1] * 2; - fallthrough; + u32 value = FIELD_PREP_WM16(DP_LANE_SEL_ALL, 0); + int i; - case 1: - value |= 0 << udphy->dp_lane_sel[0] * 2; - break; + for (i = 0; i < udphy->dp_lanes; i++) + value |= field_prep(DP_LANE_SEL_N(udphy->dp_lane_sel[i]), i); - default: - break; - } + value |= FIELD_PREP_WM16(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel); + value |= FIELD_PREP_WM16(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel); - regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, - ((DP_AUX_DIN_SEL | DP_AUX_DOUT_SEL | DP_LANE_SEL_ALL) << 16) | - FIELD_PREP(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel) | - FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value); + regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, value); } static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) From 664d3cb866546abc400b05f1a78916aa6fa0b721 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 5 Mar 2026 18:06:48 +0100 Subject: [PATCH 042/208] phy: rockchip: usbdp: Register DP aux bridge Add support to use USB-C connectors with the DP altmode helper code on devicetree based platforms. To get this working there must be a DRM bridge chain from the DisplayPort controller to the USB-C connector. E.g. on Rockchip RK3576: root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges bridge[0]: dw_dp_bridge_funcs refcount: 7 type: [10] DP OF: /soc/dp@27e40000:rockchip,rk3576-dp ops: [0x47] detect edid hpd bridge[1]: drm_aux_bridge_funcs refcount: 4 type: [0] Unknown OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy ops: [0x0] bridge[2]: drm_aux_hpd_bridge_funcs refcount: 5 type: [10] DP OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector ops: [0x4] hpd Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/Kconfig | 2 ++ drivers/phy/rockchip/phy-rockchip-usbdp.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b60759..39759bb2fa1d10 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -136,8 +136,10 @@ config PHY_ROCKCHIP_USBDP tristate "Rockchip USBDP COMBO PHY Driver" depends on ARCH_ROCKCHIP && OF depends on TYPEC + depends on DRM || DRM=n select GENERIC_PHY select USB_COMMON + select DRM_AUX_BRIDGE if DRM_BRIDGE help Enable this to support the Rockchip USB3.0/DP combo PHY with Samsung IP block. This is required for USB3 support on RK3588. diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index beab20e4c512c9..77ad2a89d4f2d0 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -6,6 +6,7 @@ * Copyright (C) 2024 Collabora Ltd */ +#include #include #include #include @@ -1434,6 +1435,7 @@ static int rk_udphy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct phy_provider *phy_provider; + struct fwnode_handle *dp_aux_ep; struct resource *res; struct rk_udphy *udphy; void __iomem *base; @@ -1492,6 +1494,18 @@ static int rk_udphy_probe(struct platform_device *pdev) return ret; } + /* + * Only register the DRM bridge, if the DP aux channel is connected. + * Some boards use the USBDP PHY only for its USB3 capabilities. + */ + dp_aux_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 3, 0, 0); + if (dp_aux_ep) { + ret = drm_aux_bridge_register(dev); + fwnode_handle_put(dp_aux_ep); + if (ret) + return ret; + } + udphy->phy_u3 = devm_phy_create(dev, dev->of_node, &rk_udphy_usb3_phy_ops); if (IS_ERR(udphy->phy_u3)) { ret = PTR_ERR(udphy->phy_u3); From 8390f5777f0305a6704922e53e74e838e8b63a37 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 4 Mar 2026 15:53:21 +0100 Subject: [PATCH 043/208] phy: rockchip: usbdp: Drop DP HPD handling Drop the HPD handling logic from the USBDP PHY. The registers involved require the display controller power domain being enabled and thus the HPD signal should be handled by the displayport controller itself. Apart from that the HPD handling as it is done here is incorrect and misses hotplug events happening after the USB-C connector (e.g. when a USB-C to HDMI adapter is involved and the HDMI cable is replugged). Proper USB-C DP HPD support requires some restructuring of the DP controller driver, which will happen independent of this patch. The mainline kernel does not yet support USB-C DP AltMode on RK3588 and RK3576, so it is fine to drop this code without adding the counterpart in the DRM in an atomic change. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 47 +---------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 77ad2a89d4f2d0..3f1233f5068653 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -186,8 +186,6 @@ struct rk_udphy { u32 dp_lane_sel[4]; u32 dp_aux_dout_sel; u32 dp_aux_din_sel; - bool dp_sink_hpd_sel; - bool dp_sink_hpd_cfg; unsigned int link_rate; unsigned int lanes; u8 bw; @@ -579,19 +577,6 @@ static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes) CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0)); } -static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd) -{ - const struct rk_udphy_cfg *cfg = udphy->cfgs; - - udphy->dp_sink_hpd_sel = true; - udphy->dp_sink_hpd_cfg = hpd; - - if (!udphy->dp_in_use) - return; - - rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd); -} - static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) { if (udphy->mode == mode) @@ -1360,22 +1345,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, break; } - if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { - struct typec_displayport_data *data = state->data; - - if (!data) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } else if (data->status & DP_STATUS_IRQ_HPD) { - rk_udphy_dp_hpd_event_trigger(udphy, false); - usleep_range(750, 800); - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else if (data->status & DP_STATUS_HPD_STATE) { - rk_udphy_mode_set(udphy, mode); - rk_udphy_dp_hpd_event_trigger(udphy, true); - } else { - rk_udphy_dp_hpd_event_trigger(udphy, false); - } - } + rk_udphy_mode_set(udphy, mode); mutex_unlock(&udphy->mutex); return 0; @@ -1531,20 +1501,6 @@ static int rk_udphy_probe(struct platform_device *pdev) return 0; } -static int __maybe_unused rk_udphy_resume(struct device *dev) -{ - struct rk_udphy *udphy = dev_get_drvdata(dev); - - if (udphy->dp_sink_hpd_sel) - rk_udphy_dp_hpd_event_trigger(udphy, udphy->dp_sink_hpd_cfg); - - return 0; -} - -static const struct dev_pm_ops rk_udphy_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, rk_udphy_resume) -}; - static const char * const rk_udphy_rst_list[] = { "init", "cmn", "lane", "pcs_apb", "pma_apb" }; @@ -1649,7 +1605,6 @@ static struct platform_driver rk_udphy_driver = { .driver = { .name = "rockchip-usbdp-phy", .of_match_table = rk_udphy_dt_match, - .pm = &rk_udphy_pm_ops, }, }; module_platform_driver(rk_udphy_driver); From 09a5306b2a39d5873c14c7c70851eb4323caa6c4 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 24 Apr 2026 11:25:21 +0200 Subject: [PATCH 044/208] phy: rockchip: usbdp: Rename mode_change to phy_needs_reinit Right now the mode_change property is set whenever the mode changes between USB-only, DP-only and USB-DP. It is needed, because on any mode change the PHY needs to be re-initialized. Apparently at least DP also requires a re-init when the cable orientation is changed, which is currently not being done (except when the orientation switch also involves a mode change). Prepare for this by renaming mode_change to phy_needs_reinit. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 3f1233f5068653..694832cc161e21 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -172,7 +172,7 @@ struct rk_udphy { /* PHY status management */ bool flip; - bool mode_change; + bool phy_needs_reinit; u8 mode; u8 status; @@ -582,7 +582,7 @@ static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) if (udphy->mode == mode) return; - udphy->mode_change = true; + udphy->phy_needs_reinit = true; udphy->mode = mode; } @@ -970,15 +970,15 @@ static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) } if (udphy->status == UDPHY_MODE_NONE) { - udphy->mode_change = false; + udphy->phy_needs_reinit = false; ret = rk_udphy_setup(udphy); if (ret) return ret; if (udphy->mode & UDPHY_MODE_USB) rk_udphy_u3_port_disable(udphy, false); - } else if (udphy->mode_change) { - udphy->mode_change = false; + } else if (udphy->phy_needs_reinit) { + udphy->phy_needs_reinit = false; if (udphy->mode == UDPHY_MODE_DP) rk_udphy_u3_port_disable(udphy, true); From e9e88add8faedb51774217dbe7a574787e73b686 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 24 Apr 2026 12:25:32 +0200 Subject: [PATCH 045/208] phy: rockchip: usbdp: Re-init the PHY on orientation change Changing the cable orientation reconfigures the lane muxing, which requires re-initializing the PHY. Without this DP functionality breaks, if the cable is re-plugged with swapped orientation. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 694832cc161e21..80fe5993c6c708 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -624,6 +624,7 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, enum typec_orientation orien) { struct rk_udphy *udphy = typec_switch_get_drvdata(sw); + bool flipped = orien == TYPEC_ORIENTATION_REVERSE; mutex_lock(&udphy->mutex); @@ -635,7 +636,10 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, goto unlock_ret; } - udphy->flip = orien == TYPEC_ORIENTATION_REVERSE; + if (udphy->flip != flipped) + udphy->phy_needs_reinit = true; + + udphy->flip = flipped; rk_udphy_set_typec_default_mapping(udphy); rk_udphy_usb_bvalid_enable(udphy, true); From fe07af12e9ca2767a5cea1eb9d69755c69cd7af8 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 24 Apr 2026 12:45:50 +0200 Subject: [PATCH 046/208] phy: rockchip: usbdp: Factor out lane_mux_sel setup Avoid describing the USB+DP lane_mux_sel logic twice by introducing a helper function to reduce code duplication. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 81 +++++++++++------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 80fe5993c6c708..17637d92cf9b91 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -586,6 +586,42 @@ static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode) udphy->mode = mode; } +static void rk_udphy_set_typec_state(struct rk_udphy *udphy, unsigned long state) +{ + u8 mode; + + switch (state) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + mode = UDPHY_MODE_DP; + udphy->dp_lanes = 4; + break; + + case TYPEC_DP_STATE_D: + default: + if (udphy->flip) { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; + } else { + udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; + udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; + udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; + } + mode = UDPHY_MODE_DP_USB; + udphy->dp_lanes = 2; + break; + } + + rk_udphy_mode_set(udphy, mode); +} + static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) { if (udphy->flip) { @@ -593,10 +629,6 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 1; udphy->dp_lane_sel[2] = 3; udphy->dp_lane_sel[3] = 2; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_INVERT; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_INVERT; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 1); @@ -606,18 +638,14 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy) udphy->dp_lane_sel[1] = 3; udphy->dp_lane_sel[2] = 1; udphy->dp_lane_sel[3] = 0; - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_NORMAL; udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_NORMAL; gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1); } - rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB); - udphy->dp_lanes = 2; + /* default to USB3 + DP as 4 lane USB is not supported */ + rk_udphy_set_typec_state(udphy, TYPEC_DP_STATE_D); } static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, @@ -1316,42 +1344,13 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct rk_udphy *udphy = typec_mux_get_drvdata(mux); - u8 mode; mutex_lock(&udphy->mutex); - switch (state->mode) { - case TYPEC_DP_STATE_C: - case TYPEC_DP_STATE_E: - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - mode = UDPHY_MODE_DP; - udphy->dp_lanes = 4; - break; - - case TYPEC_DP_STATE_D: - default: - if (udphy->flip) { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB; - } else { - udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB; - udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP; - udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP; - } - mode = UDPHY_MODE_DP_USB; - udphy->dp_lanes = 2; - break; - } - - rk_udphy_mode_set(udphy, mode); + rk_udphy_set_typec_state(udphy, state->mode); mutex_unlock(&udphy->mutex); + return 0; } From 9320448abd0f0830152e7b9c5ce9e81ffc9b7c4e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 24 Apr 2026 13:43:09 +0200 Subject: [PATCH 047/208] phy: rockchip: usbdp: Use guard functions for mutex Convert the driver to use guard functions for mutex handling as a small cleanup. There is a small functional change in the DP PHY power up function, which no longer sleeps if the internal powerup code returns an error. This is not a problem as the sleep is only relevant for successful power-up. Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 60 ++++++++++------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 17637d92cf9b91..f318b04c097d00 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -654,14 +655,15 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, struct rk_udphy *udphy = typec_switch_get_drvdata(sw); bool flipped = orien == TYPEC_ORIENTATION_REVERSE; - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); if (orien == TYPEC_ORIENTATION_NONE) { gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0); gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0); /* unattached */ rk_udphy_usb_bvalid_enable(udphy, false); - goto unlock_ret; + + return 0; } if (udphy->flip != flipped) @@ -671,8 +673,6 @@ static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw, rk_udphy_set_typec_default_mapping(udphy); rk_udphy_usb_bvalid_enable(udphy, true); -unlock_ret: - mutex_unlock(&udphy->mutex); return 0; } @@ -1044,12 +1044,10 @@ static int rk_udphy_dp_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); udphy->dp_in_use = true; - mutex_unlock(&udphy->mutex); - return 0; } @@ -1057,9 +1055,10 @@ static int rk_udphy_dp_phy_exit(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + udphy->dp_in_use = false; - mutex_unlock(&udphy->mutex); + return 0; } @@ -1068,26 +1067,25 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy) struct rk_udphy *udphy = phy_get_drvdata(phy); int ret; - mutex_lock(&udphy->mutex); + scoped_guard(mutex, &udphy->mutex) { + phy_set_bus_width(phy, udphy->dp_lanes); - phy_set_bus_width(phy, udphy->dp_lanes); - - ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); - if (ret) - goto unlock; + ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); + if (ret) + return ret; - rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); + rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); - rk_udphy_dp_lane_select(udphy); + rk_udphy_dp_lane_select(udphy); + } -unlock: - mutex_unlock(&udphy->mutex); /* * If data send by aux channel too fast after phy power on, * the aux may be not ready which will cause aux error. Adding * delay to avoid this issue. */ usleep_range(10000, 11000); + return ret; } @@ -1095,10 +1093,10 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + rk_udphy_dp_lane_enable(udphy, 0); rk_udphy_power_off(udphy, UDPHY_MODE_DP); - mutex_unlock(&udphy->mutex); return 0; } @@ -1302,19 +1300,18 @@ static const struct phy_ops rk_udphy_dp_phy_ops = { static int rk_udphy_usb3_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - int ret = 0; + int ret; + + guard(mutex)(&udphy->mutex); - mutex_lock(&udphy->mutex); /* DP only or high-speed, disable U3 port */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) { rk_udphy_u3_port_disable(udphy, true); - goto unlock; + return 0; } ret = rk_udphy_power_on(udphy, UDPHY_MODE_USB); -unlock: - mutex_unlock(&udphy->mutex); return ret; } @@ -1322,15 +1319,14 @@ static int rk_udphy_usb3_phy_exit(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); + /* DP only or high-speed */ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) - goto unlock; + return 0; rk_udphy_power_off(udphy, UDPHY_MODE_USB); -unlock: - mutex_unlock(&udphy->mutex); return 0; } @@ -1345,12 +1341,10 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux, { struct rk_udphy *udphy = typec_mux_get_drvdata(mux); - mutex_lock(&udphy->mutex); + guard(mutex)(&udphy->mutex); rk_udphy_set_typec_state(udphy, state->mode); - mutex_unlock(&udphy->mutex); - return 0; } From da8d37d3ae15bb88eab24fe9f7521f8eda12a1b8 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 28 Jul 2025 16:58:21 +0200 Subject: [PATCH 048/208] arm64: dts: rockchip: add USB-C DP AltMode for ROCK 5B family Enable support for USB-C DP AltMode to the ROCK 5B/5B+/5T. Signed-off-by: Sebastian Reichel --- .../dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 83 ++++++++++++++++--- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index bb84286cb11c7f..8625f35a64e125 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -177,6 +177,22 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dp0 { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp2: endpoint { + remote-endpoint = <&vp2_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy0_dp_in>; + }; +}; + &gpu { mali-supply = <&vdd_gpu_s0>; status = "okay"; @@ -356,21 +372,21 @@ port@0 { reg = <0>; usbc0_hs: endpoint { - remote-endpoint = <&usb_host0_xhci_to_usbc0>; + remote-endpoint = <&usb_host0_xhci_hs>; }; }; port@1 { reg = <1>; usbc0_ss: endpoint { - remote-endpoint = <&usbdp_phy0_ss>; + remote-endpoint = <&usbdp_phy0_ss_out>; }; }; port@2 { reg = <2>; usbc0_sbu: endpoint { - remote-endpoint = <&usbdp_phy0_sbu>; + remote-endpoint = <&usbdp_phy0_dp_out>; }; }; }; @@ -1006,18 +1022,41 @@ orientation-switch; status = "okay"; - port { + ports { #address-cells = <1>; #size-cells = <0>; - usbdp_phy0_ss: endpoint@0 { + + port@0 { reg = <0>; - remote-endpoint = <&usbc0_ss>; + + usbdp_phy0_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; }; - usbdp_phy0_sbu: endpoint@1 { + port@1 { reg = <1>; - remote-endpoint = <&usbc0_sbu>; + + usbdp_phy0_ss_in: endpoint { + remote-endpoint = <&usb_host0_xhci_ss>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy0_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; }; }; }; @@ -1038,9 +1077,24 @@ usb-role-switch; status = "okay"; - port { - usb_host0_xhci_to_usbc0: endpoint { - remote-endpoint = <&usbc0_hs>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb_host0_xhci_hs: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + + usb_host0_xhci_ss: endpoint { + remote-endpoint = <&usbdp_phy0_ss_in>; + }; }; }; }; @@ -1079,3 +1133,10 @@ remote-endpoint = <&hdmi1_in_vp1>; }; }; + +&vp2 { + vp2_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp2>; + }; +}; From 863904f0ab0532efe6dc2ea8c2fa218567deded5 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:13 +0200 Subject: [PATCH 049/208] media: dt-bindings: add rockchip rk3588 vicap Add documentation for the Rockchip RK3588 Video Capture (VICAP) unit. Signed-off-by: Michael Riesch --- .../bindings/media/rockchip,rk3588-vicap.yaml | 236 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 237 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml new file mode 100644 index 00000000000000..0911a6b07d1819 --- /dev/null +++ b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml @@ -0,0 +1,236 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/rockchip,rk3588-vicap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip RK3588 Video Capture (VICAP) + +maintainers: + - Michael Riesch + +description: + The Rockchip RK3588 Video Capture (VICAP) block features a digital video + port (DVP, a parallel video interface) and six MIPI CSI-2 ports. It receives + the data from camera sensors, video decoders, or other companion ICs and + transfers it into system main memory by AXI bus and/or passes it the image + signal processing (ISP) blocks. + +properties: + compatible: + enum: + - rockchip,rk3588-vicap + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: ACLK + - description: HCLK + - description: DCLK + - description: ICLK0 + - description: ICLK1 + + clock-names: + items: + - const: aclk + - const: hclk + - const: dclk + - const: iclk_host0 + - const: iclk_host1 + + iommus: + maxItems: 1 + + resets: + maxItems: 9 + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to general register file used for video input block control. + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: The digital video port (DVP, a parallel video interface). + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + enum: [5, 6] + + required: + - bus-type + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 0 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 1 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 2 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 3 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@5: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 4 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + port@6: + $ref: /schemas/graph.yaml#/properties/port + description: Port connected to the MIPI CSI-2 receiver 5 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - clocks + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + vicap: video-capture@fdce0000 { + compatible = "rockchip,rk3588-vicap"; + reg = <0x0 0xfdce0000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST1>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_CSIHOST0_VICAP>, + <&cru SRST_CSIHOST1_VICAP>, <&cru SRST_CSIHOST2_VICAP>, + <&cru SRST_CSIHOST3_VICAP>, <&cru SRST_CSIHOST4_VICAP>, + <&cru SRST_CSIHOST5_VICAP>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_dvp: port@0 { + reg = <0>; + + vicap_dvp_input: endpoint { + bus-type = ; + bus-width = <16>; + pclk-sample = ; + remote-endpoint = <&it6801_output>; + }; + }; + + vicap_mipi0: port@1 { + reg = <1>; + + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; + }; + + vicap_mipi1: port@2 { + reg = <2>; + + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + + vicap_mipi5: port@6 { + reg = <6>; + + vicap_mipi5_input: endpoint { + remote-endpoint = <&csi5_output>; + }; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e035a3be797c4d..f29a5d4ac9f032 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23125,6 +23125,7 @@ S: Maintained F: Documentation/admin-guide/media/rkcif* F: Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml F: Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml +F: Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml F: drivers/media/platform/rockchip/rkcif/ ROCKCHIP CRYPTO DRIVERS From 13c736d99091694feff7001a07e7d55ea1da8dfd Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:14 +0200 Subject: [PATCH 050/208] media: rockchip: rkcif: add support for rk3588 vicap The RK3588 Video Capture (VICAP) unit features a Digital Video Port (DVP) and six MIPI CSI-2 capture interfaces. Add initial support for this variant to the rkcif driver and enable the MIPI CSI-2 capture interfaces. Signed-off-by: Michael Riesch --- .../rockchip/rkcif/rkcif-capture-mipi.c | 136 ++++++++++++++++++ .../rockchip/rkcif/rkcif-capture-mipi.h | 1 + .../platform/rockchip/rkcif/rkcif-common.h | 2 +- .../media/platform/rockchip/rkcif/rkcif-dev.c | 18 +++ 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c index 9e67160a16e468..aa70d3e9db047f 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c @@ -30,10 +30,18 @@ #define RK3568_MIPI_CTRL0_CROP_EN BIT(5) #define RK3568_MIPI_CTRL0_WRDDR(type) ((type) << 1) +#define RK3588_MIPI_CTRL0_DMA_EN BIT(28) +#define RK3588_MIPI_CTRL0_HIGH_ALIGN BIT(27) +#define RK3588_MIPI_CTRL0_WRDDR(type) ((type) << 5) +#define RK3588_MIPI_CTRL0_CROP_EN BIT(4) +#define RK3588_MIPI_CTRL0_PARSE(type) ((type) << 1) + #define RKCIF_MIPI_CTRL0_DT_ID(id) ((id) << 10) #define RKCIF_MIPI_CTRL0_VC_ID(id) ((id) << 8) #define RKCIF_MIPI_CTRL0_CAP_EN BIT(0) +#define RKCIF_MIPI_CTRL_CAP_EN BIT(0) + #define RKCIF_MIPI_INT_FRAME0_END(id) BIT(8 + (id) * 2 + 0) #define RKCIF_MIPI_INT_FRAME1_END(id) BIT(8 + (id) * 2 + 1) @@ -481,6 +489,132 @@ const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = { }, }; +static u32 +rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream, + const struct rkcif_output_fmt *active_out_fmt) +{ + u32 ctrl0 = 0; + + ctrl0 |= RK3588_MIPI_CTRL0_DMA_EN; + ctrl0 |= RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt); + ctrl0 |= RK3588_MIPI_CTRL0_CROP_EN; + ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN; + + switch (active_out_fmt->mipi.type) { + case RKCIF_MIPI_TYPE_RAW8: + break; + case RKCIF_MIPI_TYPE_RAW10: + ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x1); + if (!active_out_fmt->mipi.compact) + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); + break; + case RKCIF_MIPI_TYPE_RAW12: + ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x2); + if (!active_out_fmt->mipi.compact) + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); + break; + case RKCIF_MIPI_TYPE_RGB888: + break; + case RKCIF_MIPI_TYPE_YUV422SP: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x4); + break; + case RKCIF_MIPI_TYPE_YUV420SP: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x5); + break; + case RKCIF_MIPI_TYPE_YUV400: + ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x3); + break; + default: + break; + } + + return ctrl0; +} + +const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = { + .mipi_num = 6, + .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, + .regs = { + [RKCIF_MIPI_CTRL] = 0x20, + [RKCIF_MIPI_INTEN] = 0x74, + [RKCIF_MIPI_INTSTAT] = 0x78, + }, + .regs_id = { + [RKCIF_ID0] = { + [RKCIF_MIPI_CTRL0] = 0x00, + [RKCIF_MIPI_CTRL1] = 0x04, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x8c, + }, + [RKCIF_ID1] = { + [RKCIF_MIPI_CTRL0] = 0x08, + [RKCIF_MIPI_CTRL1] = 0x0c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x38, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x40, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x48, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x3c, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x44, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x90, + }, + [RKCIF_ID2] = { + [RKCIF_MIPI_CTRL0] = 0x10, + [RKCIF_MIPI_CTRL1] = 0x14, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x4c, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x54, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x5c, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x50, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x58, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x94, + }, + [RKCIF_ID3] = { + [RKCIF_MIPI_CTRL0] = 0x18, + [RKCIF_MIPI_CTRL1] = 0x1c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x60, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x68, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x70, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x64, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x6c, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x98, + }, + }, + .blocks = { + { + .offset = 0x100, + }, + { + .offset = 0x200, + }, + { + .offset = 0x300, + }, + { + .offset = 0x400, + }, + { + .offset = 0x500, + }, + { + .offset = 0x600, + }, + }, +}; + static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface, unsigned int index) { @@ -631,6 +765,8 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1); rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0); + rkcif_mipi_write(interface, RKCIF_MIPI_CTRL, RKCIF_MIPI_CTRL_CAP_EN); + ret = 0; out: diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h index 7f16eadc474c3b..7edaca44f653ca 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h @@ -13,6 +13,7 @@ #include "rkcif-common.h" extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data; +extern const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data; int rkcif_mipi_register(struct rkcif_device *rkcif); diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h index dd92cfbc879f01..4d9211ba9bda8d 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h +++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h @@ -27,7 +27,7 @@ #include "rkcif-regs.h" #define RKCIF_DRIVER_NAME "rockchip-cif" -#define RKCIF_CLK_MAX 4 +#define RKCIF_CLK_MAX 5 enum rkcif_format_type { RKCIF_FMT_TYPE_INVALID, diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c index b4cf1146f13118..86575d398f807f 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c @@ -53,6 +53,20 @@ static const struct rkcif_match_data rk3568_vicap_match_data = { .mipi = &rkcif_rk3568_vicap_mipi_match_data, }; +static const char *const rk3588_vicap_clks[] = { + "aclk", + "hclk", + "dclk", + "iclk_host0", + "iclk_host1", +}; + +static const struct rkcif_match_data rk3588_vicap_match_data = { + .clks = rk3588_vicap_clks, + .clks_num = ARRAY_SIZE(rk3588_vicap_clks), + .mipi = &rkcif_rk3588_vicap_mipi_match_data, +}; + static const struct of_device_id rkcif_plat_of_match[] = { { .compatible = "rockchip,px30-vip", @@ -62,6 +76,10 @@ static const struct of_device_id rkcif_plat_of_match[] = { .compatible = "rockchip,rk3568-vicap", .data = &rk3568_vicap_match_data, }, + { + .compatible = "rockchip,rk3588-vicap", + .data = &rk3588_vicap_match_data, + }, {} }; MODULE_DEVICE_TABLE(of, rkcif_plat_of_match); From a8beb0cca05de2e2480fc56de7ce2e29d2d3bf66 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 16 Feb 2026 20:16:09 +0100 Subject: [PATCH 051/208] media: dt-bindings: rockchip csi-2: Add RK3588 support Add compatible for the RK3588 CSI-2 receiver, which is compatible with the one used by RK3568. Signed-off-by: Michael Riesch [Ported over from Michael's previous patch, rebased] Signed-off-by: Sebastian Reichel --- .../bindings/media/rockchip,rk3568-mipi-csi2.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml index 4ac4a3b6f40640..aa9a41927410bb 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -16,9 +16,13 @@ description: properties: compatible: - enum: - - fsl,imx93-mipi-csi2 - - rockchip,rk3568-mipi-csi2 + oneOf: + - const: fsl,imx93-mipi-csi2 + - items: + - enum: + - rockchip,rk3588-mipi-csi2 + - const: rockchip,rk3568-mipi-csi2 + - const: rockchip,rk3568-mipi-csi2 reg: maxItems: 1 From d810a5af3e6e81987cc772e26345cf505f0e8dce Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:15 +0200 Subject: [PATCH 052/208] arm64: dts: rockchip: add mipi csi-2 receiver node to rk356x Add the device tree node for the RK356x MIPI CSI-2 Receiver. Signed-off-by: Michael Riesch Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/rk356x-base.dtsi | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi index 64bdd8b7754b5a..adb52e31c0c3fb 100644 --- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi @@ -638,6 +638,36 @@ #iommu-cells = <0>; }; + csi: csi@fdfb0000 { + compatible = "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdfb0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI2HOST1>; + phys = <&csi_dphy>; + power-domains = <&power RK3568_PD_VI>; + resets = <&cru SRST_P_CSI2HOST1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi_in: port@0 { + reg = <0>; + }; + + csi_out: port@1 { + reg = <1>; + + csi_output: endpoint { + remote-endpoint = <&vicap_mipi_input>; + }; + }; + }; + }; + vicap: video-capture@fdfe0000 { compatible = "rockchip,rk3568-vicap"; reg = <0x0 0xfdfe0000 0x0 0x200>; @@ -666,6 +696,10 @@ vicap_mipi: port@1 { reg = <1>; + + vicap_mipi_input: endpoint { + remote-endpoint = <&csi_output>; + }; }; }; }; From 0ec3522b12cf44ade55af9a329502786ecfa5e9f Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:15 +0200 Subject: [PATCH 053/208] arm64: dts: rockchip: add mipi csi-2 receiver nodes to rk3588 Add the device tree nodes for the six MIPI CSI-2 Receiver units of the RK3588. Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index 30fb74303fb1f1..c24fe445b0b4dd 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1430,6 +1430,162 @@ resets = <&cru SRST_A_AV1>, <&cru SRST_P_AV1>, <&cru SRST_A_AV1_BIU>, <&cru SRST_P_AV1_BIU>; }; + csi0: csi@fdd10000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd10000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_0>; + phys = <&mipidcphy0 PHY_TYPE_DPHY>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi0_in: port@0 { + reg = <0>; + }; + + csi0_out: port@1 { + reg = <1>; + }; + }; + }; + + csi1: csi@fdd20000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd20000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_1>; + phys = <&mipidcphy1 PHY_TYPE_DPHY>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi1_in: port@0 { + reg = <0>; + }; + + csi1_out: port@1 { + reg = <1>; + }; + }; + }; + + csi2: csi@fdd30000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd30000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_2>; + phys = <&csi_dphy0>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi2_in: port@0 { + reg = <0>; + }; + + csi2_out: port@1 { + reg = <1>; + }; + }; + }; + + csi3: csi@fdd40000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd40000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_3>; + phys = <&csi_dphy0>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_3>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi3_in: port@0 { + reg = <0>; + }; + + csi3_out: port@1 { + reg = <1>; + }; + }; + }; + + csi4: csi@fdd50000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd50000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_4>; + phys = <&csi_dphy1>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_4>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi4_in: port@0 { + reg = <0>; + }; + + csi4_out: port@1 { + reg = <1>; + }; + }; + }; + + csi5: csi@fdd60000 { + compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdd60000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_5>; + phys = <&csi_dphy1>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_5>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi5_in: port@0 { + reg = <0>; + }; + + csi5_out: port@1 { + reg = <1>; + }; + }; + }; + vop: vop@fdd90000 { compatible = "rockchip,rk3588-vop"; reg = <0x0 0xfdd90000 0x0 0x4200>, <0x0 0xfdd95000 0x0 0x1000>; From 1d7cd6452620c42b42eb520da8605be408c86f2a Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:15 +0200 Subject: [PATCH 054/208] arm64: dts: rockchip: add vicap node to rk3588 Add the device tree node for the RK3588 Video Capture (VICAP) unit. Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index c24fe445b0b4dd..c02e325a9366db 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1430,6 +1430,93 @@ resets = <&cru SRST_A_AV1>, <&cru SRST_P_AV1>, <&cru SRST_A_AV1_BIU>, <&cru SRST_P_AV1_BIU>; }; + vicap: video-capture@fdce0000 { + compatible = "rockchip,rk3588-vicap"; + reg = <0x0 0xfdce0000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST1>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3588_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_CSIHOST0_VICAP>, + <&cru SRST_CSIHOST1_VICAP>, <&cru SRST_CSIHOST2_VICAP>, + <&cru SRST_CSIHOST3_VICAP>, <&cru SRST_CSIHOST4_VICAP>, + <&cru SRST_CSIHOST5_VICAP>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_dvp: port@0 { + reg = <0>; + }; + + vicap_mipi0: port@1 { + reg = <1>; + + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; + }; + + vicap_mipi1: port@2 { + reg = <2>; + + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + + vicap_mipi5: port@6 { + reg = <6>; + + vicap_mipi5_input: endpoint { + remote-endpoint = <&csi5_output>; + }; + }; + }; + }; + + vicap_mmu: iommu@fdce0800 { + compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu"; + reg = <0x0 0xfdce0800 0x0 0x40>, <0x0 0xfdce0900 0x0 0x40>; + interrupts = ; + clock-names = "aclk", "iface"; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>; + #iommu-cells = <0>; + power-domains = <&power RK3588_PD_VI>; + rockchip,disable-mmu-reset; + status = "disabled"; + }; + csi0: csi@fdd10000 { compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; reg = <0x0 0xfdd10000 0x0 0x10000>; @@ -1452,6 +1539,10 @@ csi0_out: port@1 { reg = <1>; + + csi0_output: endpoint { + remote-endpoint = <&vicap_mipi0_input>; + }; }; }; }; @@ -1478,6 +1569,10 @@ csi1_out: port@1 { reg = <1>; + + csi1_output: endpoint { + remote-endpoint = <&vicap_mipi1_input>; + }; }; }; }; @@ -1504,6 +1599,10 @@ csi2_out: port@1 { reg = <1>; + + csi2_output: endpoint { + remote-endpoint = <&vicap_mipi2_input>; + }; }; }; }; @@ -1530,6 +1629,10 @@ csi3_out: port@1 { reg = <1>; + + csi3_output: endpoint { + remote-endpoint = <&vicap_mipi3_input>; + }; }; }; }; @@ -1556,6 +1659,10 @@ csi4_out: port@1 { reg = <1>; + + csi4_output: endpoint { + remote-endpoint = <&vicap_mipi4_input>; + }; }; }; }; @@ -1582,6 +1689,10 @@ csi5_out: port@1 { reg = <1>; + + csi5_output: endpoint { + remote-endpoint = <&vicap_mipi5_input>; + }; }; }; }; From 462b2ae10c51800b44f293200b345211539ad26e Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:15 +0200 Subject: [PATCH 055/208] arm64: dts: rockchip: use correct pinctrl for i2c3 on rock 5b family Use the correct pinctrl for the I2C4 bus on both the Radxa ROCK 5B family in their shared base dtsi. Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index 8625f35a64e125..611a23b5fb87cb 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -321,6 +321,8 @@ }; &i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3m0_xfer>; status = "okay"; }; From 69667c1895c3eedef8dc54c964473687e8b6eea1 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:15 +0200 Subject: [PATCH 056/208] arm64: dts: rockchip: add radxa camera 4k on rock 5b+ cam0 Add device tree overlay for the Radxa Camera 4K (featuring the Sony IMX415 image sensor) to applied on the Radxa ROCK 5B+ CAM0 port. Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/Makefile | 5 ++ .../rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso | 90 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index cb55c6b70d0e56..d4ff476fb9814b 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -206,6 +206,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-ep.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-srns.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5t.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo @@ -321,6 +322,10 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-srns.dtb rk3588-rock-5b-pcie-srns-dtbs := rk3588-rock-5b.dtb \ rk3588-rock-5b-pcie-srns.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-4k-cam.dtb +rk3588-rock-5b-plus-radxa-4k-cam-dtbs := rk3588-rock-5b-plus.dtb \ + rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-haikou-video-demo.dtb rk3588-tiger-haikou-haikou-video-demo-dtbs := rk3588-tiger-haikou.dtb \ rk3588-tiger-haikou-video-demo.dtbo diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso new file mode 100644 index 00000000000000..a6d52607fa93ce --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam0.dtso @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Device tree overlay for the Radxa Camera 4K attached to the CAM0 port of + * the Radxa ROCK 5B+. + */ + +/dts-v1/; +/plugin/; + +#include +#include +#include + +&{/} { + vcc_cam0: regulator-vcc-cam0 { + compatible = "regulator-fixed"; + enable-active-high; + gpio = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam0_power0_en>; + regulator-name = "vcc_cam0"; + vin-supply = <&vcc_3v3_s3>; + }; +}; + +&i2c3 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + imx415: camera-sensor@1a { + compatible = "sony,imx415"; + reg = <0x1a>; + assigned-clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; + assigned-clock-rates = <37125000>; + avdd-supply = <&vcc_cam0>; + clocks = <&cru CLK_MIPI_CAMARAOUT_M3>; + clock-names = "inck"; // TODO should not be necessary according to binding, apply driver fix + dvdd-supply = <&vcc_cam0>; + orientation = <2>; /* External */ + ovdd-supply = <&vcc_cam0>; + pinctrl-names = "default"; + pinctrl-0 = <&cam0_rstn &mipim0_camera3_clk>; + reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + + port { + imx415_output: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csi2_input>; + }; + }; + }; +}; + +&pinctrl { + cam0 { + cam0_power0_en: cam0-power0-en-pinctrl { + rockchip,pins = <1 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cam0_rstn: cam0-rstn-pinctrl { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&csi2 { + status = "okay"; +}; + +&csi2_in { + csi2_input: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&imx415_output>; + }; +}; + +&csi_dphy0 { + status = "okay"; +}; + +&vicap { + status = "okay"; +}; + +&vicap_mmu { + status = "okay"; +}; From f55d9219c89b89a1ad1ddc3abf47e68e2f404d34 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 1 Sep 2025 23:40:16 +0200 Subject: [PATCH 057/208] arm64: dts: rockchip: add radxa camera 4k on rock 5b+ cam1 Add device tree overlay for the Radxa Camera 4K (featuring the Sony IMX415 image sensor) to applied on the Radxa ROCK 5B+ CAM1 port. Signed-off-by: Michael Riesch --- arch/arm64/boot/dts/rockchip/Makefile | 4 +- .../rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index d4ff476fb9814b..761d82b4f4f2ac 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -207,6 +207,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-ep.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-pcie-srns.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-cam4k-cam1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5t.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo @@ -324,7 +325,8 @@ rk3588-rock-5b-pcie-srns-dtbs := rk3588-rock-5b.dtb \ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b-plus-radxa-4k-cam.dtb rk3588-rock-5b-plus-radxa-4k-cam-dtbs := rk3588-rock-5b-plus.dtb \ - rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo + rk3588-rock-5b-plus-radxa-cam4k-cam0.dtbo \ + rk3588-rock-5b-plus-radxa-cam4k-cam1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-haikou-video-demo.dtb rk3588-tiger-haikou-haikou-video-demo-dtbs := rk3588-tiger-haikou.dtb \ diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso new file mode 100644 index 00000000000000..d7e9d873096540 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus-radxa-cam4k-cam1.dtso @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Device tree overlay for the Radxa Camera 4K attached to the CAM1 port of + * the Radxa ROCK 5B+. + */ + +/dts-v1/; +/plugin/; + +#include +#include +#include + +&{/} { + vcc_cam1: regulator-vcc-cam1 { + compatible = "regulator-fixed"; + enable-active-high; + gpio = <&gpio2 RK_PA6 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam1_power0_en>; + regulator-name = "vcc_cam1"; + vin-supply = <&vcc_3v3_s3>; + }; +}; + +&i2c4 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cam1_imx415: camera-sensor@1a { + compatible = "sony,imx415"; + reg = <0x1a>; + assigned-clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; + assigned-clock-rates = <37125000>; + avdd-supply = <&vcc_cam1>; + clocks = <&cru CLK_MIPI_CAMARAOUT_M4>; + clock-names = "inck"; // TODO should not be necessary according to binding, apply driver fix + dvdd-supply = <&vcc_cam1>; + orientation = <2>; /* External */ + ovdd-supply = <&vcc_cam1>; + pinctrl-names = "default"; + pinctrl-0 = <&cam1_rstn &mipim0_camera4_clk>; + reset-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + + port { + cam1_imx415_output: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csi4_input>; + }; + }; + }; +}; + +&pinctrl { + cam1 { + cam1_power0_en: cam1-power0-en-pinctrl { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cam1_rstn: cam1-rstn-pinctrl { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&csi4 { + status = "okay"; +}; + +&csi4_in { + csi4_input: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&cam1_imx415_output>; + }; +}; + +&csi_dphy1 { + status = "okay"; +}; + +&vicap { + status = "okay"; +}; + +&vicap_mmu { + status = "okay"; +}; From f55c7c7e7d068a1868c62a3805945664418d3d45 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Tue, 2 Sep 2025 01:24:33 +0200 Subject: [PATCH 058/208] [HACK] disable mipi hosts that are attached to csi dcphy While the DSI part of the MIPI Combo DCPHYs is supported, the CSI side should probably not be used right now. TODO: figure out the best approach to deal with this situation. --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index c02e325a9366db..7fd45ace52590d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1458,17 +1458,21 @@ vicap_mipi0: port@1 { reg = <1>; +/* vicap_mipi0_input: endpoint { remote-endpoint = <&csi0_output>; }; +*/ }; vicap_mipi1: port@2 { reg = <2>; +/* vicap_mipi1_input: endpoint { remote-endpoint = <&csi1_output>; }; +*/ }; vicap_mipi2: port@3 { @@ -1517,6 +1521,7 @@ status = "disabled"; }; +/* csi0: csi@fdd10000 { compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; reg = <0x0 0xfdd10000 0x0 0x10000>; @@ -1576,6 +1581,7 @@ }; }; }; +*/ csi2: csi@fdd30000 { compatible = "rockchip,rk3588-mipi-csi2", "rockchip,rk3568-mipi-csi2"; From f17c875653dae03ca0c31dc1e248d1fade59439c Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 8 Oct 2025 18:15:24 +0200 Subject: [PATCH 059/208] arm64: dts: rockchip: add missing UFS regulators to ROCK 4D Add missing regulator information for the ROCK 4D UFS interface. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index a0730c50c09653..90c4c251a9537e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -920,6 +920,9 @@ }; &ufshc { + vcc-supply = <&vcc_3v3_s0>; + vccq-supply = <&vcc_1v2_ufs_vccq_s0>; + vccq2-supply = <&vcc_1v8_ufs_vccq2_s0>; status = "okay"; }; From 07e9863dfc45f08b41744c82de9e90086cd1e432 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 6 Nov 2025 16:21:05 +0100 Subject: [PATCH 060/208] net: phy: realtek: re-init after reset The reset in the resume function results in the custom configuration being lost, which effectively renders the 'realtek,aldps-enable' functionality useless. Fix this up by doing a re-init as necessary. Signed-off-by: Sebastian Reichel --- drivers/net/phy/realtek/realtek_main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index c35c89941d283b..5895d2e941abc9 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -936,11 +936,13 @@ static int rtl8211f_suspend(struct phy_device *phydev) static int rtl821x_resume(struct phy_device *phydev) { struct rtl821x_priv *priv = phydev->priv; + bool reinit = false; int ret; if (!phydev->wol_enabled && priv->clk) { clk_prepare_enable(priv->clk); phy_reset_after_clk_enable(phydev); + reinit = true; } ret = genphy_resume(phydev); @@ -949,6 +951,9 @@ static int rtl821x_resume(struct phy_device *phydev) msleep(20); + if (reinit) + phy_init_hw(phydev); + return 0; } From 1bb7d30ddaf95919131bf56698390bc5691e3d9e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 5 Nov 2025 18:44:09 +0100 Subject: [PATCH 061/208] arm64: dts: rockchip: Enable ALDPS on both network interfaces of RK3576 EVB1 ALDPS reduces the power consumption while the network interfaces are enabled, but no cable is plugged in. The datasheet specifies: > Whole system power consumption in ALDPS low power mode (with PLL > turned off) is 10.3mW for the RTL8211F(I), and 23.1 mW for the > RTL8211FD(I). For the 3.3V line supplying the network PHYs I see the following power numbers: * both interfaces down: 239mW * both interface up, 1 cable plugged: 1005mW * both interfaces up, 1 cable plugged after this series: 995mW Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts index fb0dd1bc51482c..7081c7cdd12641 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts @@ -810,6 +810,7 @@ assigned-clock-rates = <25000000>; pinctrl-names = "default"; pinctrl-0 = <&rgmii_phy0_rst>; + realtek,aldps-enable; reset-assert-us = <20000>; reset-deassert-us = <100000>; reset-gpios = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>; @@ -825,6 +826,7 @@ assigned-clock-rates = <25000000>; pinctrl-names = "default"; pinctrl-0 = <&rgmii_phy1_rst>; + realtek,aldps-enable; reset-assert-us = <20000>; reset-deassert-us = <100000>; reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>; From 35c5f76778a994295539a9c1f8680238e3a3e314 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 10 Jan 2026 04:29:23 +0100 Subject: [PATCH 062/208] arm64: defconfig: enable Rockchip VDEC Enable the VDPU381/VDPU383 video decoder driver used for H.264 and H.265 decoding on Rockchip RK3588 and RK3576. Signed-off-by: Sebastian Reichel --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 96ce783f24e722..807b40374bd45a 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -942,6 +942,7 @@ CONFIG_VIDEO_RENESAS_FDP1=m CONFIG_VIDEO_RENESAS_VSP1=m CONFIG_VIDEO_RCAR_DRIF=m CONFIG_VIDEO_ROCKCHIP_CIF=m +CONFIG_VIDEO_ROCKCHIP_VDEC=m CONFIG_VIDEO_ROCKCHIP_RGA=m CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m From 4818dc2c5bdcb641cee1bf872d705519d3b42e40 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 15 Apr 2026 09:23:38 +0200 Subject: [PATCH 063/208] dt-bindings: iommu: verisilicon: Add binding for VSI IOMMU Add a device tree binding for the Verisilicon (VSI) IOMMU. This IOMMU sits in front of hardware encoder and decoder blocks on SoCs using Verisilicon IP, such as the Rockchip RK3588. Signed-off-by: Benjamin Gaignard Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20260415072349.44237-3-benjamin.gaignard@collabora.com Signed-off-by: Sebastian Reichel --- .../bindings/iommu/verisilicon,iommu.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml diff --git a/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml b/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml new file mode 100644 index 00000000000000..d3ce9e603b61d7 --- /dev/null +++ b/Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iommu/verisilicon,iommu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Verisilicon IOMMU + +maintainers: + - Benjamin Gaignard + +description: |+ + A Versilicon iommu translates io virtual addresses to physical addresses for + its associated video decoder. + +properties: + compatible: + items: + - const: rockchip,rk3588-av1-iommu + - const: verisilicon,iommu-1.2 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Core clock + - description: Interface clock + + clock-names: + items: + - const: core + - const: iface + + "#iommu-cells": + const: 0 + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - "#iommu-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + iommu@fdca0000 { + compatible = "rockchip,rk3588-av1-iommu","verisilicon,iommu-1.2"; + reg = <0x0 0xfdca0000 0x0 0x600>; + interrupts = ; + clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>; + clock-names = "core", "iface"; + #iommu-cells = <0>; + }; + }; From 3f30893f2fff987c97dcbbb4d0928f5fda6de787 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 15 Apr 2026 09:23:39 +0200 Subject: [PATCH 064/208] iommu: Add verisilicon IOMMU driver The Verisilicon IOMMU hardware block can be found in combination with Verisilicon hardware video codecs (encoders or decoders) on different SoCs. Enable it will allow us to use non contiguous memory allocators for Verisilicon video codecs. Signed-off-by: Benjamin Gaignard Link: https://lore.kernel.org/r/20260415072349.44237-4-benjamin.gaignard@collabora.com Signed-off-by: Sebastian Reichel --- MAINTAINERS | 8 + drivers/iommu/Kconfig | 11 + drivers/iommu/Makefile | 1 + drivers/iommu/vsi-iommu.c | 796 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 816 insertions(+) create mode 100644 drivers/iommu/vsi-iommu.c diff --git a/MAINTAINERS b/MAINTAINERS index f29a5d4ac9f032..046d2572ceefd8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27958,6 +27958,14 @@ F: drivers/media/v4l2-core/v4l2-isp.c F: include/media/v4l2-isp.h F: include/uapi/linux/media/v4l2-isp.h +VERISILICON IOMMU DRIVER +M: Benjamin Gaignard +L: iommu@lists.linux.dev +S: Maintained +F: Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml +F: drivers/iommu/vsi-iommu.c +F: include/linux/vsi-iommu.h + VF610 NAND DRIVER M: Stefan Agner L: linux-mtd@lists.infradead.org diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f86262b11416d1..18d3d68af7cdfd 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -384,6 +384,17 @@ config SPRD_IOMMU Say Y here if you want to use the multimedia devices listed above. +config VSI_IOMMU + tristate "Verisilicon IOMMU Support" + depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST + select IOMMU_API + help + Support for IOMMUs used by Verisilicon sub-systems like video + decoders or encoder hardware blocks. + + Say Y here if you want to use this IOMMU in front of these + hardware blocks. + config IOMMU_DEBUG_PAGEALLOC bool "Debug IOMMU mappings against page allocations" depends on DEBUG_PAGEALLOC && IOMMU_API && PAGE_EXTENSION diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 0275821f4ef985..887af357a7c963 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -36,4 +36,5 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o obj-$(CONFIG_APPLE_DART) += apple-dart.o +obj-$(CONFIG_VSI_IOMMU) += vsi-iommu.o obj-$(CONFIG_IOMMU_DEBUG_PAGEALLOC) += iommu-debug-pagealloc.o diff --git a/drivers/iommu/vsi-iommu.c b/drivers/iommu/vsi-iommu.c new file mode 100644 index 00000000000000..5d0721bd2c7af8 --- /dev/null +++ b/drivers/iommu/vsi-iommu.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Collabora Ltd. + * + * IOMMU API for Verisilicon + * + * Module Authors: Yandong Lin + * Simon Xue + * Benjamin Gaignard + * + * This hardware block is using a 2 pages tables allocation structure. + * That make very similar to Rockhip iommu hardware blocks but it has + * it own driver because the registers offset and configuration bits + * are completely different. An additional reason is that this hardware + * has been developed by Verisilicon to be used by their hardware video + * decoders and not for a general purpose like Rockchip iommus. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iommu-pages.h" + +struct vsi_iommu { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clocks; + int num_clocks; + struct iommu_device iommu; + struct list_head node; /* entry in vsi_iommu_domain.iommus */ + struct iommu_domain *domain; /* domain to which iommu is attached */ + spinlock_t lock; /* lock to protect vsi_iommu fields */ + int irq; + bool enable; +}; + +struct vsi_iommu_domain { + struct list_head iommus; + struct device *dev; + u32 *dt; + dma_addr_t dt_dma; + struct iommu_domain domain; + u64 *pta; + dma_addr_t pta_dma; + spinlock_t lock; /* lock to protect vsi_iommu_domain fields */ +}; + +static struct iommu_domain vsi_identity_domain; + +#define NUM_DT_ENTRIES 1024 +#define NUM_PT_ENTRIES 1024 + +#define SPAGE_SIZE BIT(12) + +/* vsi iommu regs address */ +#define VSI_MMU_CONFIG1_BASE 0x1ac +#define VSI_MMU_AHB_EXCEPTION_BASE 0x380 +#define VSI_MMU_AHB_CONTROL_BASE 0x388 +#define VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE 0x38C + +/* MMU register offsets */ +#define VSI_MMU_FLUSH_BASE 0x184 +#define VSI_MMU_BIT_FLUSH BIT(4) + +#define VSI_MMU_PAGE_FAULT_ADDR 0x380 +#define VSI_MMU_STATUS_BASE 0x384 /* IRQ status */ + +#define VSI_MMU_BIT_ENABLE BIT(0) + +#define VSI_MMU_OUT_OF_BOUND BIT(28) +/* Irq mask */ +#define VSI_MMU_IRQ_MASK 0x7 + +#define VSI_DTE_PT_ADDRESS_MASK 0xffffffc0 +#define VSI_DTE_PT_VALID BIT(0) + +#define VSI_PAGE_DESC_LO_MASK 0xfffff000 +#define VSI_PAGE_DESC_HI_MASK GENMASK_ULL(39, 32) +#define VSI_PAGE_DESC_HI_SHIFT (32 - 4) + +static inline phys_addr_t vsi_dte_pt_address(u32 dte) +{ + return (phys_addr_t)dte & VSI_DTE_PT_ADDRESS_MASK; +} + +static inline u32 vsi_mk_dte(u32 dte) +{ + return (phys_addr_t)dte | VSI_DTE_PT_VALID; +} + +#define VSI_PTE_PAGE_WRITABLE BIT(2) +#define VSI_PTE_PAGE_VALID BIT(0) + +static inline phys_addr_t vsi_pte_page_address(u64 pte) +{ + return ((pte << VSI_PAGE_DESC_HI_SHIFT) & VSI_PAGE_DESC_HI_MASK) | + (pte & VSI_PAGE_DESC_LO_MASK); +} + +static u32 vsi_mk_pte(phys_addr_t page, int prot) +{ + u32 flags = 0; + + flags |= (prot & IOMMU_WRITE) ? VSI_PTE_PAGE_WRITABLE : 0; + + page = (page & VSI_PAGE_DESC_LO_MASK) | + ((page & VSI_PAGE_DESC_HI_MASK) >> VSI_PAGE_DESC_HI_SHIFT); + + return page | flags | VSI_PTE_PAGE_VALID; +} + +#define VSI_DTE_PT_VALID BIT(0) + +static inline bool vsi_dte_is_pt_valid(u32 dte) +{ + return dte & VSI_DTE_PT_VALID; +} + +static inline bool vsi_pte_is_page_valid(u32 pte) +{ + return pte & VSI_PTE_PAGE_VALID; +} + +static u32 vsi_mk_pte_invalid(u32 pte) +{ + return pte & ~VSI_PTE_PAGE_VALID; +} + +#define VSI_MASTER_TLB_MASK GENMASK_ULL(31, 10) +/* mode 0 : 4k */ +#define VSI_PTA_4K_MODE 0 + +static u64 vsi_mk_pta(dma_addr_t dt_dma) +{ + u64 val = (dt_dma & VSI_MASTER_TLB_MASK) | VSI_PTA_4K_MODE; + + return val; +} + +static struct vsi_iommu_domain *to_vsi_domain(struct iommu_domain *dom) +{ + return container_of(dom, struct vsi_iommu_domain, domain); +} + +static inline void vsi_table_flush(struct vsi_iommu_domain *vsi_domain, dma_addr_t dma, + unsigned int count) +{ + size_t size = count * sizeof(u32); /* count of u32 entry */ + + dma_sync_single_for_device(vsi_domain->dev, dma, size, DMA_TO_DEVICE); +} + +#define VSI_IOVA_DTE_MASK 0xffc00000 +#define VSI_IOVA_DTE_SHIFT 22 +#define VSI_IOVA_PTE_MASK 0x003ff000 +#define VSI_IOVA_PTE_SHIFT 12 +#define VSI_IOVA_PAGE_MASK 0x00000fff +#define VSI_IOVA_PAGE_SHIFT 0 + +static u32 vsi_iova_dte_index(u32 iova) +{ + return (iova & VSI_IOVA_DTE_MASK) >> VSI_IOVA_DTE_SHIFT; +} + +static u32 vsi_iova_pte_index(u32 iova) +{ + return (iova & VSI_IOVA_PTE_MASK) >> VSI_IOVA_PTE_SHIFT; +} + +static u32 vsi_iova_page_offset(u32 iova) +{ + return (iova & VSI_IOVA_PAGE_MASK) >> VSI_IOVA_PAGE_SHIFT; +} + +static irqreturn_t vsi_iommu_irq(int irq, void *dev_id) +{ + struct vsi_iommu *iommu = dev_id; + unsigned long flags; + dma_addr_t iova; + u32 status; + + if (pm_runtime_resume_and_get(iommu->dev) < 0) + return IRQ_NONE; + + spin_lock_irqsave(&iommu->lock, flags); + + status = readl(iommu->regs + VSI_MMU_STATUS_BASE); + if (status & VSI_MMU_IRQ_MASK) { + dev_err(iommu->dev, "unexpected int_status=%08x\n", status); + iova = readl(iommu->regs + VSI_MMU_PAGE_FAULT_ADDR); + report_iommu_fault(iommu->domain, iommu->dev, iova, status); + } + writel(0, iommu->regs + VSI_MMU_STATUS_BASE); + + spin_unlock_irqrestore(&iommu->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + + return IRQ_HANDLED; +} + +static struct vsi_iommu *vsi_iommu_get_from_dev(struct device *dev) +{ + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + struct device *iommu_dev = bus_find_device_by_fwnode(&platform_bus_type, + fwspec->iommu_fwnode); + + put_device(iommu_dev); + + return iommu_dev ? dev_get_drvdata(iommu_dev) : NULL; +} + +static struct iommu_domain *vsi_iommu_domain_alloc_paging(struct device *dev) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain; + + vsi_domain = kzalloc(sizeof(*vsi_domain), GFP_KERNEL); + if (!vsi_domain) + return NULL; + + vsi_domain->dev = iommu->dev; + spin_lock_init(&vsi_domain->lock); + + /* + * iommu use a 2 level pagetable. + * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries. + * Allocate one 4 KiB page for each table. + */ + vsi_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, + SPAGE_SIZE); + if (!vsi_domain->dt) + goto err_free_domain; + + vsi_domain->dt_dma = dma_map_single(vsi_domain->dev, vsi_domain->dt, + SPAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, vsi_domain->dt_dma)) { + dev_err(dev, "DMA map error for DT\n"); + goto err_free_dt; + } + + vsi_domain->pta = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, + SPAGE_SIZE); + if (!vsi_domain->pta) + goto err_unmap_dt; + + vsi_domain->pta[0] = vsi_mk_pta(vsi_domain->dt_dma); + vsi_domain->pta_dma = dma_map_single(vsi_domain->dev, vsi_domain->pta, + SPAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, vsi_domain->pta_dma)) { + dev_err(dev, "DMA map error for PTA\n"); + goto err_free_pta; + } + + INIT_LIST_HEAD(&vsi_domain->iommus); + + vsi_domain->domain.geometry.aperture_start = 0; + vsi_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32); + vsi_domain->domain.geometry.force_aperture = true; + vsi_domain->domain.pgsize_bitmap = SZ_4K; + + return &vsi_domain->domain; + +err_free_pta: + iommu_free_pages(vsi_domain->pta); +err_unmap_dt: + dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, + SPAGE_SIZE, DMA_TO_DEVICE); +err_free_dt: + iommu_free_pages(vsi_domain->dt); +err_free_domain: + kfree(vsi_domain); + + return NULL; +} + +static phys_addr_t vsi_iommu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + phys_addr_t pt_phys, phys = 0; + unsigned long flags; + u32 dte, pte; + u32 *page_table; + + spin_lock_irqsave(&vsi_domain->lock, flags); + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + if (!vsi_dte_is_pt_valid(dte)) + goto unlock; + + pt_phys = vsi_dte_pt_address(dte); + page_table = (u32 *)phys_to_virt(pt_phys); + pte = page_table[vsi_iova_pte_index(iova)]; + if (!vsi_pte_is_page_valid(pte)) + goto unlock; + + phys = vsi_pte_page_address(pte) + vsi_iova_page_offset(iova); + +unlock: + spin_unlock_irqrestore(&vsi_domain->lock, flags); + return phys; +} + +static size_t vsi_iommu_unmap_iova(struct vsi_iommu_domain *vsi_domain, + u32 *pte_addr, dma_addr_t pte_dma, + size_t size) +{ + unsigned int pte_count; + unsigned int pte_total = size / SPAGE_SIZE; + + for (pte_count = 0; + pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { + u32 pte = pte_addr[pte_count]; + + if (!vsi_pte_is_page_valid(pte)) + break; + + pte_addr[pte_count] = vsi_mk_pte_invalid(pte); + } + + vsi_table_flush(vsi_domain, pte_dma, pte_total); + + return pte_count * SPAGE_SIZE; +} + +static int vsi_iommu_map_iova(struct vsi_iommu_domain *vsi_domain, u32 *pte_addr, + dma_addr_t pte_dma, dma_addr_t iova, + phys_addr_t paddr, size_t size, int prot) +{ + unsigned int pte_count; + unsigned int pte_total = size / SPAGE_SIZE; + + for (pte_count = 0; + pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { + u32 pte = pte_addr[pte_count]; + + if (vsi_pte_is_page_valid(pte)) + return (pte_count - 1) * SPAGE_SIZE; + + pte_addr[pte_count] = vsi_mk_pte(paddr, prot); + + paddr += SPAGE_SIZE; + } + + vsi_table_flush(vsi_domain, pte_dma, pte_total); + + return 0; +} + +static void vsi_iommu_flush_tlb(struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + struct list_head *pos; + + list_for_each(pos, &vsi_domain->iommus) { + struct vsi_iommu *iommu; + + iommu = list_entry(pos, struct vsi_iommu, node); + if (!iommu) + continue; + + if (pm_runtime_get(iommu->dev) < 0) + continue; + + spin_lock(&iommu->lock); + + if (iommu->enable) { + writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); + writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); + } + + spin_unlock(&iommu->lock); + + pm_runtime_put_autosuspend(iommu->dev); + } +} + +static size_t vsi_iommu_unmap(struct iommu_domain *domain, unsigned long _iova, + size_t size, size_t count, struct iommu_iotlb_gather *gather) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + dma_addr_t pte_dma, iova = (dma_addr_t)_iova; + unsigned long flags; + phys_addr_t pt_phys; + u32 dte; + u32 *pte_addr; + size_t unmap_size = 0; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + /* Just return 0 if iova is unmapped */ + if (!vsi_dte_is_pt_valid(dte)) + goto unlock; + + pt_phys = vsi_dte_pt_address(dte); + pte_addr = (u32 *)phys_to_virt(pt_phys) + vsi_iova_pte_index(iova); + pte_dma = pt_phys + vsi_iova_pte_index(iova) * sizeof(u32); + unmap_size = vsi_iommu_unmap_iova(vsi_domain, pte_addr, pte_dma, size); + if (!unmap_size) + goto unlock; + + vsi_iommu_flush_tlb(domain); +unlock: + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + return unmap_size; +} + +static u32 *vsi_dte_get_page_table(struct vsi_iommu_domain *vsi_domain, + dma_addr_t iova, gfp_t gfp) +{ + u32 *page_table, *dte_addr; + u32 dte_index, dte; + phys_addr_t pt_phys; + dma_addr_t pt_dma; + gfp_t flags; + + dte_index = vsi_iova_dte_index(iova); + dte_addr = &vsi_domain->dt[dte_index]; + dte = *dte_addr; + if (vsi_dte_is_pt_valid(dte)) + goto done; + + /* Do not allow to sleep while allocating the buffer */ + flags = (gfp & ~GFP_KERNEL) | GFP_ATOMIC | GFP_DMA32; + page_table = iommu_alloc_pages_sz(flags, PAGE_SIZE); + if (!page_table) + return ERR_PTR(-ENOMEM); + + pt_dma = dma_map_single(vsi_domain->dev, page_table, PAGE_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(vsi_domain->dev, pt_dma)) { + dev_err(vsi_domain->dev, "DMA mapping error while allocating page table\n"); + iommu_free_pages(page_table); + return ERR_PTR(-ENOMEM); + } + + dte = vsi_mk_dte(pt_dma); + *dte_addr = dte; + + vsi_table_flush(vsi_domain, + vsi_domain->dt_dma + dte_index * sizeof(u32), 1); +done: + pt_phys = vsi_dte_pt_address(dte); + return (u32 *)phys_to_virt(pt_phys); +} + +static int vsi_iommu_map(struct iommu_domain *domain, unsigned long _iova, + phys_addr_t paddr, size_t size, size_t count, + int prot, gfp_t gfp, size_t *mapped) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + dma_addr_t pte_dma, iova = (dma_addr_t)_iova; + u32 *page_table, *pte_addr; + u32 dte, pte_index; + unsigned long flags; + int ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + page_table = vsi_dte_get_page_table(vsi_domain, iova, gfp); + if (IS_ERR(page_table)) { + spin_unlock_irqrestore(&vsi_domain->lock, flags); + return PTR_ERR(page_table); + } + + dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; + pte_index = vsi_iova_pte_index(iova); + pte_addr = &page_table[pte_index]; + pte_dma = vsi_dte_pt_address(dte) + pte_index * sizeof(u32); + ret = vsi_iommu_map_iova(vsi_domain, pte_addr, pte_dma, iova, + paddr, size, prot); + if (!ret) + *mapped = size; + + vsi_iommu_flush_tlb(domain); + + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + return ret; +} + +static void vsi_iommu_disable(struct vsi_iommu *iommu) +{ + writel(0, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); + iommu->enable = false; +} + +static int vsi_iommu_identity_attach(struct iommu_domain *domain, + struct device *dev, struct iommu_domain *old) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int ret; + + ret = pm_runtime_resume_and_get(iommu->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + if (iommu->domain == domain) + goto unlock; + + vsi_iommu_disable(iommu); + list_del_init(&iommu->node); + + iommu->domain = domain; + +unlock: + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + return 0; +} + +static const struct iommu_domain_ops vsi_identity_ops = { + .attach_dev = vsi_iommu_identity_attach, +}; + +static struct iommu_domain vsi_identity_domain = { + .type = IOMMU_DOMAIN_IDENTITY, + .ops = &vsi_identity_ops, +}; + +static void vsi_iommu_enable(struct vsi_iommu *iommu, struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + + if (domain == &vsi_identity_domain) + return; + + writel(vsi_domain->pta_dma, iommu->regs + VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE); + writel(VSI_MMU_OUT_OF_BOUND, iommu->regs + VSI_MMU_CONFIG1_BASE); + writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_EXCEPTION_BASE); + writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); + iommu->enable = true; +} + +static int vsi_iommu_attach_device(struct iommu_domain *domain, + struct device *dev, struct iommu_domain *old) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int ret = 0; + + ret = pm_runtime_resume_and_get(iommu->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + + vsi_iommu_enable(iommu, domain); + writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); + writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); + + list_del_init(&iommu->node); + list_add_tail(&iommu->node, &vsi_domain->iommus); + + iommu->domain = domain; + + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + pm_runtime_put_autosuspend(iommu->dev); + return ret; +} + +static void vsi_iommu_domain_free(struct iommu_domain *domain) +{ + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); + unsigned long flags; + int i; + + spin_lock_irqsave(&vsi_domain->lock, flags); + + WARN_ON(!list_empty(&vsi_domain->iommus)); + + for (i = 0; i < NUM_DT_ENTRIES; i++) { + u32 dte = vsi_domain->dt[i]; + + if (vsi_dte_is_pt_valid(dte)) { + phys_addr_t pt_phys = vsi_dte_pt_address(dte); + u32 *page_table = phys_to_virt(pt_phys); + + dma_unmap_single(vsi_domain->dev, pt_phys, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(page_table); + } + } + + dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(vsi_domain->dt); + + dma_unmap_single(vsi_domain->dev, vsi_domain->pta_dma, + SPAGE_SIZE, DMA_TO_DEVICE); + iommu_free_pages(vsi_domain->pta); + + spin_unlock_irqrestore(&vsi_domain->lock, flags); + + kfree(vsi_domain); +} + +static struct iommu_device *vsi_iommu_probe_device(struct device *dev) +{ + struct vsi_iommu *iommu = vsi_iommu_get_from_dev(dev); + struct device_link *link; + + link = device_link_add(dev, iommu->dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (!link) + dev_err(dev, "Unable to link %s\n", dev_name(iommu->dev)); + + dev_iommu_priv_set(dev, iommu); + return &iommu->iommu; +} + +static void vsi_iommu_release_device(struct device *dev) +{ + struct vsi_iommu *iommu = dev_iommu_priv_get(dev); + + device_link_remove(dev, iommu->dev); +} + +static int vsi_iommu_of_xlate(struct device *dev, const struct of_phandle_args *args) +{ + return iommu_fwspec_add_ids(dev, args->args, 1); +} + +static const struct iommu_ops vsi_iommu_ops = { + .identity_domain = &vsi_identity_domain, + .release_domain = &vsi_identity_domain, + .domain_alloc_paging = vsi_iommu_domain_alloc_paging, + .of_xlate = vsi_iommu_of_xlate, + .probe_device = vsi_iommu_probe_device, + .release_device = vsi_iommu_release_device, + .device_group = generic_single_device_group, + .owner = THIS_MODULE, + .default_domain_ops = &(const struct iommu_domain_ops) { + .attach_dev = vsi_iommu_attach_device, + .map_pages = vsi_iommu_map, + .unmap_pages = vsi_iommu_unmap, + .iova_to_phys = vsi_iommu_iova_to_phys, + .free = vsi_iommu_domain_free, + } +}; + +static const struct of_device_id vsi_iommu_dt_ids[] = { + { + .compatible = "verisilicon,iommu-1.2", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vsi_iommu_dt_ids); + +static int vsi_iommu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vsi_iommu *iommu; + int err; + + iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL); + if (!iommu) + return -ENOMEM; + + iommu->dev = dev; + spin_lock_init(&iommu->lock); + INIT_LIST_HEAD(&iommu->node); + + iommu->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(iommu->regs)) + return -ENOMEM; + + iommu->num_clocks = devm_clk_bulk_get_all(dev, &iommu->clocks); + if (iommu->num_clocks < 0) + return iommu->num_clocks; + + err = clk_bulk_prepare(iommu->num_clocks, iommu->clocks); + if (err) + return err; + + iommu->irq = platform_get_irq(pdev, 0); + if (iommu->irq < 0) + return iommu->irq; + + err = devm_request_irq(iommu->dev, iommu->irq, vsi_iommu_irq, + IRQF_SHARED, dev_name(dev), iommu); + if (err) + goto err_unprepare_clocks; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + platform_set_drvdata(pdev, iommu); + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL, dev_name(dev)); + if (err) + goto err_runtime_disable; + + err = iommu_device_register(&iommu->iommu, &vsi_iommu_ops, dev); + if (err) + goto err_remove_sysfs; + + return 0; + +err_remove_sysfs: + iommu_device_sysfs_remove(&iommu->iommu); +err_runtime_disable: + pm_runtime_disable(dev); +err_unprepare_clocks: + clk_bulk_unprepare(iommu->num_clocks, iommu->clocks); + return err; +} + +static void vsi_iommu_shutdown(struct platform_device *pdev) +{ + struct vsi_iommu *iommu = platform_get_drvdata(pdev); + + disable_irq(iommu->irq); + pm_runtime_force_suspend(&pdev->dev); +} + +static int __maybe_unused vsi_iommu_suspend(struct device *dev) +{ + struct vsi_iommu *iommu = dev_get_drvdata(dev); + + vsi_iommu_disable(iommu); + + clk_bulk_disable(iommu->num_clocks, iommu->clocks); + + return 0; +} + +static int __maybe_unused vsi_iommu_resume(struct device *dev) +{ + struct vsi_iommu *iommu = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + ret = clk_bulk_enable(iommu->num_clocks, iommu->clocks); + if (ret) + return ret; + + if (iommu->domain) { + struct vsi_iommu_domain *vsi_domain = to_vsi_domain(iommu->domain); + + spin_lock_irqsave(&vsi_domain->lock, flags); + spin_lock(&iommu->lock); + vsi_iommu_enable(iommu, iommu->domain); + spin_unlock(&iommu->lock); + spin_unlock_irqrestore(&vsi_domain->lock, flags); + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vsi_iommu_pm_ops, + vsi_iommu_suspend, vsi_iommu_resume, + NULL); + +static struct platform_driver rockchip_vsi_iommu_driver = { + .probe = vsi_iommu_probe, + .shutdown = vsi_iommu_shutdown, + .driver = { + .name = "vsi_iommu", + .of_match_table = vsi_iommu_dt_ids, + .pm = pm_sleep_ptr(&vsi_iommu_pm_ops), + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(rockchip_vsi_iommu_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Gaignard "); +MODULE_DESCRIPTION("Verisilicon IOMMU driver"); From 660ccf523b3a1ca70290c749327b958f26fefb5b Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 15 Apr 2026 09:23:40 +0200 Subject: [PATCH 065/208] arm64: dts: rockchip: Add verisilicon IOMMU node on RK3588 Add the device tree node for the Verisilicon IOMMU present in the RK3588 SoC. This IOMMU handles address translation for the VPU hardware blocks. Signed-off-by: Benjamin Gaignard Link: https://lore.kernel.org/r/20260415072349.44237-5-benjamin.gaignard@collabora.com Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index 7fd45ace52590d..e8855b26267d88 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1428,6 +1428,17 @@ clock-names = "aclk", "hclk"; power-domains = <&power RK3588_PD_AV1>; resets = <&cru SRST_A_AV1>, <&cru SRST_P_AV1>, <&cru SRST_A_AV1_BIU>, <&cru SRST_P_AV1_BIU>; + iommus = <&av1d_mmu>; + }; + + av1d_mmu: iommu@fdca0000 { + compatible = "rockchip,rk3588-av1-iommu", "verisilicon,iommu-1.2"; + reg = <0x0 0xfdca0000 0x0 0x600>; + interrupts = ; + clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>; + clock-names = "core", "iface"; + #iommu-cells = <0>; + power-domains = <&power RK3588_PD_AV1>; }; vicap: video-capture@fdce0000 { From 2c7b348c21d928378eabf4c77946ad2275006c6c Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 15 Apr 2026 09:23:41 +0200 Subject: [PATCH 066/208] arm64: defconfig: enable Verisilicon IOMMU for Rockchip RK3588 Enable Verisilicon IOMMU used by Rockchip RK3588 AV1 hardware codec. This hardware block could be found in Radxa ROCK 5B board. Signed-off-by: Benjamin Gaignard Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260415072349.44237-6-benjamin.gaignard@collabora.com Signed-off-by: Sebastian Reichel --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 807b40374bd45a..ba49c6abec9fa2 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1626,6 +1626,7 @@ CONFIG_ARM_SMMU_V3=y CONFIG_MTK_IOMMU=y CONFIG_QCOM_IOMMU=y CONFIG_APPLE_DART=m +CONFIG_VSI_IOMMU=m CONFIG_REMOTEPROC=y CONFIG_IMX_REMOTEPROC=y CONFIG_MTK_SCP=m From fb911d31c1e015a468e5b9b84f8502a80aa4957c Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 9 Jan 2026 18:01:19 +0800 Subject: [PATCH 067/208] [WIP] arm64: dts: rockchip: add USB-C DP AltMode for ArmSom Sige5 Enable USB-C DP AltMode for the ArmSom Sige5. Signed-off-by: Andy Yan This has two issues: 1. The USB-C hotplug detection does not yet work properly. I.e. even after plugging in a device with DP AltMode, /sys/class/drm/card0-DP-1 stays at 'disconnected'. This is a problem I've already seen on the Rock 5B, which needs further investigation. 2. The DT binding does not yet look good for upstream. On the plus side there are no regressions and this results in probing the DP driver, which means it is already good for regression testing. Thus I am already adding this to our development branch. Signed-off-by: Sebastian Reichel --- .../boot/dts/rockchip/rk3576-armsom-sige5.dts | 77 ++++++++++++++++--- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index 1c100ffd151869..9aca9eadf18008 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -272,6 +272,22 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + &gmac0 { phy-mode = "rgmii-id"; clock_in_out = "output"; @@ -719,20 +735,22 @@ port@0 { reg = <0>; - usbc0_hs_ep: endpoint { + usbc0_hs: endpoint { remote-endpoint = <&usb_drd0_hs_ep>; }; }; + port@1 { reg = <1>; - usbc0_ss_ep: endpoint { - remote-endpoint = <&usb_drd0_ss_ep>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; }; }; + port@2 { reg = <2>; - usbc0_dp_ep: endpoint { - remote-endpoint = <&usbdp_phy_ep>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_dp_out>; }; }; }; @@ -988,14 +1006,14 @@ port@0 { reg = <0>; usb_drd0_hs_ep: endpoint { - remote-endpoint = <&usbc0_hs_ep>; + remote-endpoint = <&usbc0_hs>; }; }; port@1 { reg = <1>; usb_drd0_ss_ep: endpoint { - remote-endpoint = <&usbc0_ss_ep>; + remote-endpoint = <&usbdp_phy_ss_in>; }; }; }; @@ -1015,9 +1033,41 @@ sbu2-dc-gpios = <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>; status = "okay"; - port { - usbdp_phy_ep: endpoint { - remote-endpoint = <&usbc0_dp_ep>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; }; }; }; @@ -1036,3 +1086,10 @@ remote-endpoint = <&hdmi_in_vp0>; }; }; + +&vp1 { + vp1_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; From 0624fab9b0fdc509d7bf72d9a21d49f2a34ad17f Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 15:10:06 +0800 Subject: [PATCH 068/208] PCI: dw-rockchip: Add phy_calibrate() to check PHY lock status Current we keep controller in reset state when initializing PHY which is the right thing to do. But this case, the PHY is also reset because it refers to a signal from controller. Now we check PHY lock status inside .phy_init() callback which may be bogus for certain type of PHY, because of the fact above. Add phy_calibrate() to better check PHY lock status if provided. Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1766560210-100883-2-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sebastian Reichel --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 65e85c7eab0587..74598da98a7343 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -862,6 +862,12 @@ static int rockchip_pcie_probe(struct platform_device *pdev) if (ret) goto deinit_phy; + ret = phy_calibrate(rockchip->phy); + if (ret) { + dev_err(dev, "phy lock failed\n"); + goto assert_controller; + } + ret = rockchip_pcie_clk_init(rockchip); if (ret) goto deinit_phy; @@ -884,7 +890,8 @@ static int rockchip_pcie_probe(struct platform_device *pdev) } return 0; - +assert_controller: + reset_control_assert(rockchip->rst); deinit_clk: clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); deinit_phy: From 70805cf594ceda7b1a911a8b61c71d0117e3d45f Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 15:10:07 +0800 Subject: [PATCH 069/208] phy: rockchip-snps-pcie3: Add phy_calibrate() support Move calibration from phy_init() to phy_calibrate(). Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1766560210-100883-3-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-snps-pcie3.c | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c index 4e8ffd173096a4..9933cda0b08ef8 100644 --- a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -71,6 +71,7 @@ struct rockchip_p3phy_priv { struct rockchip_p3phy_ops { int (*phy_init)(struct rockchip_p3phy_priv *priv); + int (*phy_calibrate)(struct rockchip_p3phy_priv *priv); }; static int rockchip_p3phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) @@ -97,8 +98,6 @@ static int rockchip_p3phy_rk3568_init(struct rockchip_p3phy_priv *priv) { struct phy *phy = priv->phy; bool bifurcation = false; - int ret; - u32 reg; /* Deassert PCIe PMA output clamp mode */ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON9, GRF_PCIE30PHY_DA_OCM); @@ -124,25 +123,34 @@ static int rockchip_p3phy_rk3568_init(struct rockchip_p3phy_priv *priv) reset_control_deassert(priv->p30phy); + return 0; +} + +static int rockchip_p3phy_rk3568_calibrate(struct rockchip_p3phy_priv *priv) +{ + int ret; + u32 reg; + ret = regmap_read_poll_timeout(priv->phy_grf, GRF_PCIE30PHY_STATUS0, reg, SRAM_INIT_DONE(reg), 0, 500); if (ret) - dev_err(&priv->phy->dev, "%s: lock failed 0x%x, check input refclk and power supply\n", - __func__, reg); + dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", + reg); + return ret; } static const struct rockchip_p3phy_ops rk3568_ops = { .phy_init = rockchip_p3phy_rk3568_init, + .phy_calibrate = rockchip_p3phy_rk3568_calibrate, }; static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) { u32 reg = 0; u8 mode = RK3588_LANE_AGGREGATION; /* default */ - int ret; regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_LN0_CON1, priv->rx_cmn_refclk_mode[0] ? RK3588_RX_CMN_REFCLK_MODE_EN : @@ -184,6 +192,14 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) reset_control_deassert(priv->p30phy); + return 0; +} + +static int rockchip_p3phy_rk3588_calibrate(struct rockchip_p3phy_priv *priv) +{ + int ret; + u32 reg; + ret = regmap_read_poll_timeout(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_STATUS1, reg, RK3588_SRAM_INIT_DONE(reg), @@ -200,6 +216,7 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) static const struct rockchip_p3phy_ops rk3588_ops = { .phy_init = rockchip_p3phy_rk3588_init, + .phy_calibrate = rockchip_p3phy_rk3588_calibrate, }; static int rockchip_p3phy_init(struct phy *phy) @@ -234,10 +251,22 @@ static int rockchip_p3phy_exit(struct phy *phy) return 0; } +static int rockchip_p3phy_calibrate(struct phy *phy) +{ + struct rockchip_p3phy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->ops->phy_calibrate) + ret = priv->ops->phy_calibrate(priv); + + return ret; +} + static const struct phy_ops rockchip_p3phy_ops = { .init = rockchip_p3phy_init, .exit = rockchip_p3phy_exit, .set_mode = rockchip_p3phy_set_mode, + .calibrate = rockchip_p3phy_calibrate, .owner = THIS_MODULE, }; From 5a16745f343d374c47d49d3699bcab85bbf3f074 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 15:10:08 +0800 Subject: [PATCH 070/208] phy: rockchip-snps-pcie3: Increase sram init timeout Per massive test, 500us is not enough for all chips, increase it to 20000us for worse case recommended. Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1766560210-100883-4-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-snps-pcie3.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c index 9933cda0b08ef8..f5a5d0af3f0cbb 100644 --- a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -19,6 +19,9 @@ #include #include +/* Common definition */ +#define RK_SRAM_INIT_TIMEOUT_US 20000 + /* Register for RK3568 */ #define GRF_PCIE30PHY_CON1 0x4 #define GRF_PCIE30PHY_CON6 0x18 @@ -28,6 +31,7 @@ #define GRF_PCIE30PHY_WR_EN (0xf << 16) #define SRAM_INIT_DONE(reg) (reg & BIT(14)) + #define RK3568_BIFURCATION_LANE_0_1 BIT(0) /* Register for RK3588 */ @@ -134,7 +138,7 @@ static int rockchip_p3phy_rk3568_calibrate(struct rockchip_p3phy_priv *priv) ret = regmap_read_poll_timeout(priv->phy_grf, GRF_PCIE30PHY_STATUS0, reg, SRAM_INIT_DONE(reg), - 0, 500); + 0, RK_SRAM_INIT_TIMEOUT_US); if (ret) dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", reg); @@ -203,11 +207,11 @@ static int rockchip_p3phy_rk3588_calibrate(struct rockchip_p3phy_priv *priv) ret = regmap_read_poll_timeout(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_STATUS1, reg, RK3588_SRAM_INIT_DONE(reg), - 0, 500); + 0, RK_SRAM_INIT_TIMEOUT_US); ret |= regmap_read_poll_timeout(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY1_STATUS1, reg, RK3588_SRAM_INIT_DONE(reg), - 0, 500); + 0, RK_SRAM_INIT_TIMEOUT_US); if (ret) dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", reg); From 455392ff16c83660ad3390d1ba5e3f98c2acbb62 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 15:10:09 +0800 Subject: [PATCH 071/208] phy: rockchip-snps-pcie3: Check more sram init status for RK3588 All the lower 4 bits should be checked which shows the mpllx_state. Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1766560210-100883-5-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-snps-pcie3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c index f5a5d0af3f0cbb..6cc38e36a90612 100644 --- a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -43,7 +43,7 @@ #define RK3588_PCIE3PHY_GRF_PHY0_LN1_CON1 0x1104 #define RK3588_PCIE3PHY_GRF_PHY1_LN0_CON1 0x2004 #define RK3588_PCIE3PHY_GRF_PHY1_LN1_CON1 0x2104 -#define RK3588_SRAM_INIT_DONE(reg) (reg & BIT(0)) +#define RK3588_SRAM_INIT_DONE(reg) ((reg & 0xf) == 0xf) #define RK3588_BIFURCATION_LANE_0_1 BIT(0) #define RK3588_BIFURCATION_LANE_2_3 BIT(1) From d4fa635a342ef111de4af22d95ca8a4f7a3c4d6a Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 15:10:10 +0800 Subject: [PATCH 072/208] phy: rockchip-snps-pcie3: Only check PHY1 status when using it RK3588_LANE_AGGREGATION and RK3588_BIFURCATION_LANE_2_3 should be used to check if it need to check PHY1 status. Because in other cases, only PHY0 could show locked status. Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1766560210-100883-6-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-snps-pcie3.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c index 6cc38e36a90612..36b2142ec913ac 100644 --- a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -183,6 +183,7 @@ static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv) } reg = mode; + priv->pcie30_phymode = mode; regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_CMN_CON0, RK3588_PCIE30_PHY_MODE_EN | reg); @@ -208,10 +209,13 @@ static int rockchip_p3phy_rk3588_calibrate(struct rockchip_p3phy_priv *priv) RK3588_PCIE3PHY_GRF_PHY0_STATUS1, reg, RK3588_SRAM_INIT_DONE(reg), 0, RK_SRAM_INIT_TIMEOUT_US); - ret |= regmap_read_poll_timeout(priv->phy_grf, - RK3588_PCIE3PHY_GRF_PHY1_STATUS1, - reg, RK3588_SRAM_INIT_DONE(reg), - 0, RK_SRAM_INIT_TIMEOUT_US); + if (priv->pcie30_phymode & (RK3588_LANE_AGGREGATION | RK3588_BIFURCATION_LANE_2_3)) { + ret |= regmap_read_poll_timeout(priv->phy_grf, + RK3588_PCIE3PHY_GRF_PHY1_STATUS1, + reg, RK3588_SRAM_INIT_DONE(reg), + 0, RK_SRAM_INIT_TIMEOUT_US); + } + if (ret) dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n", reg); From 3acabda0c7a9d8816b37e328d18b6d88bedc5c2b Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 19 Jan 2026 13:11:40 +0100 Subject: [PATCH 073/208] mmc: sdhci-of-dwcmshc: Reduce clock reduction print level The system gracefully handles not being able to reduce the clock frequencies, so there is nothing to be fixed. Reduce the error print to debug level to keep the error log nice and short with important messages. Signed-off-by: Sebastian Reichel --- drivers/mmc/host/sdhci-of-dwcmshc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index b9ecd91f44ad4a..0f9576b790602a 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -790,7 +790,7 @@ static void dwcmshc_rk3568_set_clock(struct sdhci_host *host, unsigned int clock if (clock <= 52000000) { if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 || host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { - dev_err(mmc_dev(host->mmc), + dev_dbg(mmc_dev(host->mmc), "Can't reduce the clock below 52MHz in HS200/HS400 mode"); goto enable_clk; } From ed681ea287bb6cc5c61efc87510436c9ba365445 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 21 Jan 2026 12:29:32 +0200 Subject: [PATCH 074/208] drm/rockchip: dw_dp: Switch to drmm_kzalloc() Driver makes use of drmm_encoder_init() to initialize the encoder and automatically handle the cleanup by registering drm_encoder_cleanup() with drmm_add_action(). However, the internal structure containing the encoder part gets allocated with devm_kzalloc(), which happens while component_bind_all() is being called from Rockchip DRM driver. The component framework further ensures it is deallocated as part of releasing all the resources claimed during bind, which is triggered from component_unbind_all(). When the reference to the DRM device gets eventually dropped via drm_dev_put() in rockchip_drm_unbind(), drmm_encoder_alloc_release() attempts to access the now released encoder structure, leading to use-after-free. Ensure driver's internal structure is still reachable on encoder cleanup by switching from a device-managed allocation to a drm-managed one. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index dac3d202971eda..532af476d250a5 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -82,7 +83,7 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * struct drm_connector *connector; int ret; - dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL); if (!dp) return -ENOMEM; From 47a339e55cb08f17b6a51787f4ecd197f0c71ef1 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 21 Jan 2026 14:02:15 +0200 Subject: [PATCH 075/208] drm/rockchip: dw_dp: Fix null-ptr-deref in dw_dp_remove() Attempting to access driver data in the platform driver ->remove() callback may lead to a null pointer dereference since there is no guaranty that the component ->bind() callback invoking platform_set_drvdata() was executed. A common scenario is when Rockchip DRM driver didn't manage to run component_bind_all() because of an (unrelated) error causing early return from rockchip_drm_bind(). Drop the unnecessary call to platform_get_drvdata() and, instead, reference the target device structure via platform_device. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 532af476d250a5..8945a245398ca4 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -133,9 +133,7 @@ static int dw_dp_probe(struct platform_device *pdev) static void dw_dp_remove(struct platform_device *pdev) { - struct rockchip_dw_dp *dp = platform_get_drvdata(pdev); - - component_del(dp->dev, &dw_dp_rockchip_component_ops); + component_del(&pdev->dev, &dw_dp_rockchip_component_ops); } static const struct dw_dp_plat_data rk3588_dp_plat_data = { From a8e475d779bfa549cfbd8a38f83412f2b89dd988 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 20 Jan 2026 21:44:26 +0200 Subject: [PATCH 076/208] drm/rockchip: dw_hdmi_qp: Switch to drmm_encoder_init() Simplify encoder initialization and cleanup by making use of drmm_encoder_init(), which takes care of registering drm_encoder_cleanup() with drmm_add_action(). Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 1a09bcc96c3ece..c78db7f8ab6c76 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -477,7 +478,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, if (!pdev->dev.of_node) return -ENODEV; - hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; @@ -586,16 +587,16 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return ret; drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return dev_err_probe(hdmi->dev, ret, "Failed to init encoder"); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data); - if (IS_ERR(hdmi->hdmi)) { - drm_encoder_cleanup(encoder); + if (IS_ERR(hdmi->hdmi)) return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmi), "Failed to bind dw-hdmi-qp"); - } connector = drm_bridge_connector_init(drm, encoder); if (IS_ERR(connector)) @@ -612,8 +613,6 @@ static void dw_hdmi_qp_rockchip_unbind(struct device *dev, struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev); cancel_delayed_work_sync(&hdmi->hpd_work); - - drm_encoder_cleanup(&hdmi->encoder.encoder); } static const struct component_ops dw_hdmi_qp_rockchip_ops = { From b91981c1e008179e360ca698092e6fd51e668175 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 22 Jan 2026 14:57:54 +0100 Subject: [PATCH 077/208] thermal/drivers/rockchip: Shut up eFuse prober defer warning Shut up the following message printed at error level on ArmSom Sige5: rockchip-thermal 2ae70000.tsadc: failed reading trim of sensor 0: -EPROBE_DEFER Fixes: ae332ec0009d ("thermal/drivers/rockchip: Support reading trim values from OTP") Signed-off-by: Sebastian Reichel --- drivers/thermal/rockchip_thermal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index c49ddf70f86e7b..2a40339de0d0ef 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1638,8 +1638,7 @@ rockchip_thermal_register_sensor(struct platform_device *pdev, if (tsadc->get_trim_code && sensor->of_node) { error = rockchip_get_efuse_value(sensor->of_node, "trim", &trim); if (error < 0 && error != -ENOENT) { - dev_err(dev, "failed reading trim of sensor %d: %pe\n", - id, ERR_PTR(error)); + dev_err_probe(dev, error, "failed reading trim of sensor %d\n", id); return error; } if (trim) { From ac676c079f578f04ebb365863e6f8ae611ab8cda Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Wed, 18 Mar 2026 22:26:19 +0300 Subject: [PATCH 078/208] media: synopsys: hdmirx: Fix HPD hold time Increase time of holding HPD pin low by 50ms. This fixes EDID change not detected by sink/display side. Fixes: 7b59b132ad43 ("media: platform: synopsys: Add support for HDMI input driver") Reported-by: Ross Cawston Closes: https://lore.kernel.org/linux-rockchip/20260209061654.54757-1-ross@r-sc.ca/ Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20260318192619.3910060-1-dmitry.osipenko@collabora.com Signed-off-by: Sebastian Reichel --- drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 61ad20b18b8d65..4c8957505a50dc 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -506,9 +506,9 @@ static void hdmirx_hpd_ctrl(struct snps_hdmirx_dev *hdmirx_dev, bool en) hdmirx_writel(hdmirx_dev, CORE_CONFIG, hdmirx_dev->hpd_trigger_level_high ? en : !en); - /* 100ms delay as per HDMI spec */ + /* 100ms delay as per HDMI spec + extra 50ms to cover internal delay */ if (!en) - msleep(100); + msleep(100 + 50); } static void hdmirx_write_edid_data(struct snps_hdmirx_dev *hdmirx_dev, From 7a4e22fb52e250afba29a866fb28141a8679e383 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 29 Jan 2026 23:57:28 +0100 Subject: [PATCH 079/208] [RFC] drm/rockchip: vop2: Add clock rate mode check The display might offer modes, which exceed the maximum clock rate of a video output. This usually happens for displays that offer refresh rates above 60 Hz. This results in no picture (or a broken one) being displayed without manual intervention. Fix this by teaching the driver about the maximum achievable clock rates for each video port. The information about the maximum clock rates for each video channel and the tip about multiple pixels being processed per clock were provided by Andy Yan and roughly checked against the information available in the datasheet (which specifies limits like "2560x1600@60Hz with 10-bit" instead of a specific pixel rate). For the video ports supporting a 600 MHz input clock, there is some logic to handle up to 4 pixels in parallel when needed resulting in the extra multiplier. Suggested-by: Andy Yan Link: https://lore.kernel.org/linux-rockchip/1528d788.186b.19d08ed974c.Coremail.andyshrk@163.com/ Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 3 +++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 1 + drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 2601446f9f0748..1ad6369336d2fb 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1442,6 +1442,9 @@ static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc, if (mode->hdisplay > vp->data->max_output.width) return MODE_BAD_HVALUE; + if (mode->clock > vp->data->max_pixel_clock_rate / 1000) + return MODE_CLOCK_HIGH; + return MODE_OK; } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index b8198714c1c912..0296e35579a921 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -232,6 +232,7 @@ struct vop2_video_port_data { u16 gamma_lut_len; u16 cubic_lut_len; struct vop_rect max_output; + u32 max_pixel_clock_rate; const u8 pre_scan_max_dly[4]; unsigned int offset; /** diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 02a788a4dfddaa..853e13977b57b5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -559,18 +559,21 @@ static const struct vop2_video_port_data rk3568_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U, .pre_scan_max_dly = { 69, 53, 53, 42 }, .offset = 0xc00, }, { .id = 1, .gamma_lut_len = 1024, .max_output = { 2048, 1536 }, + .max_pixel_clock_rate = 200000000U, .pre_scan_max_dly = { 40, 40, 40, 40 }, .offset = 0xd00, }, { .id = 2, .gamma_lut_len = 1024, .max_output = { 1920, 1080 }, + .max_pixel_clock_rate = 150000000U, .pre_scan_max_dly = { 40, 40, 40, 40 }, .offset = 0xe00, }, @@ -775,6 +778,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 2, /* 2 for pixel_rate */ /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 8, 2, 0 }, .offset = 0xc00, @@ -785,6 +789,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 729, /* 9x9x9 */ .max_output = { 2560, 1600 }, + .max_pixel_clock_rate = 300000000U, /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 6, 0, 0 }, .offset = 0xd00, @@ -793,6 +798,7 @@ static const struct vop2_video_port_data rk3576_vop_video_ports[] = { .id = 2, .gamma_lut_len = 1024, .max_output = { 1920, 1080 }, + .max_pixel_clock_rate = 150000000U, /* win layer_mix hdr */ .pre_scan_max_dly = { 10, 6, 0, 0 }, .offset = 0xe00, @@ -1061,6 +1067,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, /* hdr2sdr sdr2hdr hdr2hdr sdr2sdr */ .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xc00, @@ -1070,6 +1077,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 729, /* 9x9x9 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xd00, }, { @@ -1078,12 +1086,14 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { .gamma_lut_len = 1024, .cubic_lut_len = 17 * 17 * 17, /* 17x17x17 */ .max_output = { 4096, 2304 }, + .max_pixel_clock_rate = 600000000U * 4, .pre_scan_max_dly = { 52, 52, 52, 52 }, .offset = 0xe00, }, { .id = 3, .gamma_lut_len = 1024, .max_output = { 2048, 1536 }, + .max_pixel_clock_rate = 150000000U, .pre_scan_max_dly = { 52, 52, 52, 52 }, .offset = 0xf00, }, From d29a4c220b8388080204d0a5485f34e31dc8e521 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 17 Mar 2026 15:44:57 +0100 Subject: [PATCH 080/208] [WIP] arm64: dts: rockchip: disable command queue engine for Sige5 The Sige5 board often runs into CQE errors, which make the boot unreliable; disable support for it to get consistent results until somebody found time to analyze the root cause. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index 9aca9eadf18008..411253b6eff038 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -931,6 +931,7 @@ no-sdio; no-sd; non-removable; + /delete-property/ supports-cqe; status = "okay"; }; From 374883ef660bb9b1ea237adf2fd643ca7775dff7 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 2 Mar 2026 22:37:40 +0100 Subject: [PATCH 081/208] usb: typec: tcpm: Also use SNK_WAIT_CAPABILITIES_TIMEOUT for vbus_never_low Most USB-C ports on Linux devices are self-powered (i.e. because the device offering a port has a battery or is powered from Mains using a dedicated power-supply). If a port instead is the only power source for a device things become fragile. SNK_WAIT_CAPABILITIES_TIMEOUT has been introduced to better handle these devices as it tries to avoid sending a hard reset. This further improves things by also trying to avoid the soft reset as that is simply escalated into a hard reset with some USB PD sources. This (hopefully, testing is still ongoing) fixes the following issue, which sometimes happens with the ROCK 5B from the Collabora LAVA lab (used for the KernelCI project): [ 16.330582] typec_fusb302 4-0022: pending state change SNK_WAIT_CAPABILITIES -> SNK_SOFT_RESET @ 310 ms [rev2 NONE_AMS] [ 16.641651] typec_fusb302 4-0022: state change SNK_WAIT_CAPABILITIES -> SNK_SOFT_RESET [delayed 310 ms] [ 16.642488] typec_fusb302 4-0022: AMS SOFT_RESET_AMS start [ 16.642968] typec_fusb302 4-0022: state change SNK_SOFT_RESET -> AMS_START [rev2 SOFT_RESET_AMS] [ 16.643741] typec_fusb302 4-0022: state change AMS_START -> SOFT_RESET_SEND [rev2 SOFT_RESET_AMS] [ 16.644525] typec_fusb302 4-0022: PD TX, header: 0x4d [ 16.647659] typec_fusb302 4-0022: sending PD message header: 4d [ 16.648190] typec_fusb302 4-0022: sending PD message len: 0 [ 16.650521] typec_fusb302 4-0022: IRQ: 0x41, a: 0x00, b: 0x00, status0: 0x83 [ 16.651154] typec_fusb302 4-0022: IRQ: BC_LVL, handler pending [ 16.653494] typec_fusb302 4-0022: IRQ: 0x41, a: 0x00, b: 0x00, status0: 0x83 [ 16.654120] typec_fusb302 4-0022: IRQ: BC_LVL, handler pending [ 16.656454] typec_fusb302 4-0022: IRQ: 0x41, a: 0x10, b: 0x00, status0: 0x83 [ 16.657078] typec_fusb302 4-0022: IRQ: BC_LVL, handler pending [ 16.657612] typec_fusb302 4-0022: IRQ: PD retry failed [ 16.658073] typec_fusb302 4-0022: PD TX complete, status: 2 [ 16.658583] typec_fusb302 4-0022: state change SOFT_RESET_SEND -> HARD_RESET_SEND [rev2 SOFT_RESET_AMS] [ 16.659411] typec_fusb302 4-0022: AMS SOFT_RESET_AMS finished [ 16.659920] typec_fusb302 4-0022: Initiating hard-reset, which might result in machine power-loss. [ 16.660705] typec_fusb302 4-0022: AMS HARD_RESET start [ 16.661162] typec_fusb302 4-0022: PD TX, type: 0x5 [ 16.664334] typec_fusb302 4-0022: IRQ: 0x41, a: 0x08, b: 0x00, status0: 0x83 [ 16.664965] typec_fusb302 4-0022: IRQ: BC_LVL, handler pending [ 16.665514] typec_fusb302 4-0022: IRQ: PD hardreset sent [ 16.666783] typec_fusb302 4-0022: PD TX complete, status: 0 [ 16.667305] typec_fusb302 4-0022: state change HARD_RESET_SEND -> HARD_RESET_START [rev2 HARD_RESET] [ 16.672730] typec_fusb302 4-0022: pd := off [ 16.673103] typec_fusb302 4-0022: state change HARD_RESET_START -> SNK_HARD_RESET_SINK_OFF [rev2 HARD_RESET] [ 16.673972] typec_fusb302 4-0022: vconn:=0 [ 16.674331] typec_fusb302 4-0022: vconn is already off [ 16.674787] typec_fusb302 4-0022: Requesting mux state 1, usb-role 2, orientation 1 --- board reset --- Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/tcpm.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index d4e656f8cf34c1..008e903fdc0c28 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -5624,20 +5624,25 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SNK_READY, 0); break; } + /* + * For non self-powered devices, first of all try explicitly + * requesting the source capabilities for better support of + * of non-compliant PD sources (a comment with more details is + * in the SNK_WAIT_CAPABILITIES_TIMEOUT state). + * * If VBUS has never been low, and we time out waiting * for source cap, try a soft reset first, in case we * were already in a stable contract before this boot. * Do this only once. */ - if (port->vbus_never_low) { + if (!port->self_powered) { + upcoming_state = SNK_WAIT_CAPABILITIES_TIMEOUT; + } else if (port->vbus_never_low) { port->vbus_never_low = false; upcoming_state = SNK_SOFT_RESET; } else { - if (!port->self_powered) - upcoming_state = SNK_WAIT_CAPABILITIES_TIMEOUT; - else - upcoming_state = hard_reset_state(port); + upcoming_state = hard_reset_state(port); } tcpm_set_state(port, upcoming_state, @@ -5659,10 +5664,17 @@ static void run_state_machine(struct tcpm_port *port) * and handled by all USB PD source and dual role devices * according to the specification. */ + if (port->vbus_never_low) { + port->vbus_never_low = false; + upcoming_state = SNK_SOFT_RESET; + } else { + upcoming_state = hard_reset_state(port); + } + if (tcpm_pd_send_control(port, PD_CTRL_GET_SOURCE_CAP, TCPC_TX_SOP)) - tcpm_set_state_cond(port, hard_reset_state(port), 0); + tcpm_set_state_cond(port, upcoming_state, 0); else - tcpm_set_state(port, hard_reset_state(port), + tcpm_set_state(port, upcoming_state, port->timings.sink_wait_cap_time); break; case SNK_NEGOTIATE_CAPABILITIES: From 7c6d8c321b800e960b3bcaea8b99c6068e29d6fb Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 24 Mar 2026 18:16:31 +0100 Subject: [PATCH 082/208] usb: typec: tcpm: log when VDM AMS state machine is cancelled Since commit 0bc3ee92880d ("usb: typec: tcpm: Properly interrupt VDM AMS") the VDM AMS state machine is cancelled when a non-VDM message arrives. Apparently Dell U2725QE displays potentially interrupt the discover identity AMS to try a power role swap: [ 4.341899] AMS POWER_NEGOTIATION finished [ 4.341906] cc:=4 [ 4.353165] AMS DISCOVER_IDENTITY start [ 4.353187] PD TX, header: 0x176f [ 4.365820] PD TX complete, status: 0 [ 4.365885] PD RX, header: 0x24a [1] [ 4.365899] AMS DISCOVER_IDENTITY finished [ 4.365903] cc:=4 [ 4.376612] PD TX, header: 0x964 [ 4.481320] PD RX, header: 0x444f [1] [ 4.481353] PD RX, header: 0x64a [1] [ 4.481374] PD TX, header: 0x964 [ 4.492423] PD TX complete, status: 0 At this point the log stops without the discover identity being completed successfully. Apparently what happens is: 1. TCPM negotiated power as PD source 2. TCPM continues with identity discovery 3. Display asks for a power swap 4. TCPM stops state machine for identity discovery 5. TCPM rejects the power swap 6. Display sends identity discovery response 7. Display sends the power swap request once more Note, that this does not always happen. There is a race condition and sometimes the power swap request arrives before TCPM switches into the DISCOVER_IDENTITY phase, which avoids the AMS interruption and then things work as expected. I tried adding logic to restart the discover identity state machine in this case, which should be fine according to the specification as far as I can tell. Unfortunately it does not help with the Dell U2725QE: Once it runs into the above issue, the display seems to have messed up its internal message buffer resulting in the display sending answers to the previous message instead of the current one. Obviously no sensible PD communication is sensible at this point. Improve the situation a bit by at least giving a decent log entry. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/tcpm.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 008e903fdc0c28..b8c36f9a880527 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1673,6 +1673,15 @@ static bool tcpm_ams_interruptible(struct tcpm_port *port) return true; } +static void tcpm_vdm_handle_ams_interruption(struct tcpm_port *port) +{ + tcpm_log(port, "VDM AMS state machine got interrupted"); + + port->vdm_state = VDM_STATE_ERR_BUSY; + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); +} + static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) { int ret = 0; @@ -3367,11 +3376,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, bool frs_enable; int ret; - if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) + tcpm_vdm_handle_ams_interruption(port); switch (type) { case PD_DATA_SOURCE_CAP: @@ -3568,11 +3574,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in * VDM AMS if waiting for VDM responses and will be handled later. */ - if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) + tcpm_vdm_handle_ams_interruption(port); switch (type) { case PD_CTRL_GOOD_CRC: @@ -3890,11 +3893,8 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); /* stopping VDM state machine if interrupted by other Messages */ - if (tcpm_vdm_ams(port)) { - port->vdm_state = VDM_STATE_ERR_BUSY; - tcpm_ams_finish(port); - mod_vdm_delayed_work(port, 0); - } + if (tcpm_vdm_ams(port)) + tcpm_vdm_handle_ams_interruption(port); if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); From 1d5b42721b272abf9f7f2797c9129b41381afb12 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:45 +0200 Subject: [PATCH 083/208] phy: rockchip: samsung-hdptx: Fix rate recalculation for high bpc The PHY PLL can been programmed by an external component, e.g. the bootloader, just before the recalc_rate() callback is invoked during devm_clk_hw_register() in the probe path. Therefore rk_hdptx_phy_clk_recalc_rate() finds the PLL enabled and attempts to compute the clock rate, while making use of the bpc value from the HDMI PHY configuration, which always defaults to 8 because phy_configure() was not run at that point. As a consequence, the (re)calculated rate is incorrect when the actual bpc was higher than 8. Do not rely on any of the hdmi_cfg members when computing the clock rate and, instead, read the required input data (i.e. bpc), directly from the hardware registers. Fixes: 3481fc04d969 ("phy: rockchip: samsung-hdptx: Compute clk rate from PLL config") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-1-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 2d973bc37f076f..7fb1c22318bbf6 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -2168,7 +2168,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) struct lcpll_config lcpll_hw; struct ropll_config ropll_hw; u64 fout, sdm; - u32 mode, val; + u32 mode, bpc, val; int ret, i; ret = regmap_read(hdptx->regmap, CMN_REG(0008), &mode); @@ -2266,6 +2266,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) if (ret) return 0; ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1; + bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8; fout = PLL_REF_CLK * ropll_hw.pms_mdiv; if (ropll_hw.sdm_en) { @@ -2280,7 +2281,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) fout = fout + sdm; } - return div_u64(fout * 2, ropll_hw.pms_sdiv * 10); + return div_u64(fout * 2 * 8, ropll_hw.pms_sdiv * 10 * bpc); } static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw, @@ -2288,19 +2289,13 @@ static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); u32 status; - u64 rate; int ret; ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status); if (ret || !(status & HDPTX_I_PLL_EN)) return 0; - rate = rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx); - - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - return rate; - - return DIV_ROUND_CLOSEST_ULL(rate * 8, hdptx->hdmi_cfg.bpc); + return rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx); } static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, From ce71153911e18de49f914204e16a85871cb91965 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:46 +0200 Subject: [PATCH 084/208] phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes Any changes to the PHY link rate and/or color depth done via the HDMI PHY configuration API are not immediately programmed into the hardware, but are delayed until the PHY usage count gets incremented from 0 to 1, that is when it is powered on or when the PLL clock exposed through the CCF API is prepared, whichever comes first. Since the clock might remain in prepared state after subsequent PHY config changes, the programming can also be triggered via clk_ops.set_rate(). However, from the clock consumer perspective (i.e. VOP2 display controller), the (pixel) clock rate doesn't vary with bpc, as that is handled internally by the PHY and reflected in the TDMS character rate only. As a consequence, changing the bpc while preserving the modeline may lead to out-of-sync issues between CCF and HDMI PHY config state, because the .set_rate() callback is not invoked when clock rate remains constant. This may also happen when the PHY PLL has been pre-programmed by an external entity, e.g. the bootloader, which is actually a regression introduced by the recent FRL patches. Introduce a pll_config_dirty flag to keep track of uncommitted PHY config changes and use it in clk_ops.determine_rate() to invalidate the current clock rate (as known by CCF) and, consequently, ensure those changes are programmed into hardware via clk_ops.set_rate(). Moreover, proceed with a similar fix in phy_ops.power_on() callback, to handle the scenario where the CCF API is not used due to operating in FRL mode, while the clock is still in a prepared state and thus preventing rk_hdptx_phy_consumer_get() to apply the updated PHY configuration. Fixes: de5dba833118 ("phy: rockchip: samsung-hdptx: Add HDMI 2.1 FRL support") Fixes: 9d0ec51d7c22 ("phy: rockchip: samsung-hdptx: Add high color depth management") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-2-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 86 ++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 7fb1c22318bbf6..14d266c8df5c82 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -413,6 +413,7 @@ struct rk_hdptx_phy { /* clk provider */ struct clk_hw hw; + bool pll_config_dirty; bool restrict_rate_change; atomic_t usage_count; @@ -1260,13 +1261,19 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) static int rk_hdptx_pll_cmn_config(struct rk_hdptx_phy *hdptx) { + int ret; + if (hdptx->hdmi_cfg.rate <= HDMI20_MAX_RATE) - return rk_hdptx_tmds_ropll_cmn_config(hdptx); + ret = rk_hdptx_tmds_ropll_cmn_config(hdptx); + else if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE) + ret = rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx); + else + ret = rk_hdptx_frl_lcpll_cmn_config(hdptx); - if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE) - return rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx); + if (!ret) + hdptx->pll_config_dirty = false; - return rk_hdptx_frl_lcpll_cmn_config(hdptx); + return ret; } static int rk_hdptx_frl_lcpll_mode_config(struct rk_hdptx_phy *hdptx) @@ -1347,25 +1354,17 @@ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx) return 0; ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status); - if (ret) - goto dec_usage; - - if (status & HDPTX_O_PLL_LOCK_DONE) - dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n"); + if (ret) { + atomic_dec(&hdptx->usage_count); + return ret; + } - if (mode == PHY_MODE_DP) { + if (mode == PHY_MODE_DP) rk_hdptx_dp_reset(hdptx); - } else { - ret = rk_hdptx_pll_cmn_config(hdptx); - if (ret) - goto dec_usage; - } + else + rk_hdptx_pll_cmn_config(hdptx); return 0; - -dec_usage: - atomic_dec(&hdptx->usage_count); - return ret; } static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force) @@ -1700,16 +1699,20 @@ static int rk_hdptx_phy_power_on(struct phy *phy) if (ret) rk_hdptx_phy_consumer_put(hdptx, true); } else { - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0)); + if (hdptx->pll_config_dirty) + ret = rk_hdptx_pll_cmn_config(hdptx); - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - ret = rk_hdptx_frl_lcpll_mode_config(hdptx); - else - ret = rk_hdptx_tmds_ropll_mode_config(hdptx); + if (!ret) { + regmap_write(hdptx->grf, GRF_HDPTX_CON0, + HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0)); - if (ret) + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) + ret = rk_hdptx_frl_lcpll_mode_config(hdptx); + else + ret = rk_hdptx_tmds_ropll_mode_config(hdptx); + } else { rk_hdptx_phy_consumer_put(hdptx, true); + } } return ret; @@ -2081,7 +2084,10 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt dev_err(hdptx->dev, "invalid hdmi params for phy configure\n"); } else { hdptx->restrict_rate_change = true; - dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, + hdptx->pll_config_dirty = true; + + dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); } @@ -2303,8 +2309,19 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) - return hdptx->hdmi_cfg.rate; + /* + * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate() + * will be invoked to commit PLL configuration. + */ + if (hdptx->pll_config_dirty) { + req->rate = 0; + return 0; + } + + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) { + req->rate = hdptx->hdmi_cfg.rate; + return 0; + } /* * FIXME: Temporarily allow altering TMDS char rate via CCF. @@ -2336,17 +2353,6 @@ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); - unsigned long long link_rate = rate; - - if (hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) - link_rate = DIV_ROUND_CLOSEST_ULL(rate * hdptx->hdmi_cfg.bpc, 8); - - /* Revert any unlikely link rate change since determine_rate() */ - if (hdptx->hdmi_cfg.rate != link_rate) { - dev_warn(hdptx->dev, "Reverting unexpected rate change from %llu to %llu\n", - link_rate, hdptx->hdmi_cfg.rate); - hdptx->hdmi_cfg.rate = link_rate; - } /* * The link rate would be normally programmed in HW during From f4e1730e0a84858cebf229149396c2748a30f320 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:47 +0200 Subject: [PATCH 085/208] phy: rockchip: samsung-hdptx: Drop TMDS rate setup workaround Since commit ba9c2fe18c17 ("drm/rockchip: dw_hdmi_qp: Switch to phy_configure()") the TMDS rate setup doesn't rely anymore on the unconventional usage of the bus width, instead it is managed exclusively through the HDMI PHY configuration API. Drop the now obsolete workaround to retrieve the TMDS character rate via phy_get_bus_width() during power_on(). While at it, get rid of the extra call to rk_hdptx_phy_consumer_put() by moving the statement at the end of the function. Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-3-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 14d266c8df5c82..b56360b5df78a3 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -1655,22 +1655,6 @@ static int rk_hdptx_phy_power_on(struct phy *phy) enum phy_mode mode = phy_get_mode(phy); int ret, lane; - if (mode != PHY_MODE_DP) { - if (!hdptx->hdmi_cfg.rate && hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) { - /* - * FIXME: Temporary workaround to setup TMDS char rate - * from the RK DW HDMI QP bridge driver. - * Will be removed as soon the switch to the HDMI PHY - * configuration API has been completed on both ends. - */ - hdptx->hdmi_cfg.rate = phy_get_bus_width(hdptx->phy) & 0xfffffff; - hdptx->hdmi_cfg.rate *= 100; - } - - dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, - hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); - } - ret = rk_hdptx_phy_consumer_get(hdptx); if (ret) return ret; @@ -1696,9 +1680,10 @@ static int rk_hdptx_phy_power_on(struct phy *phy) rk_hdptx_dp_pll_init(hdptx); ret = rk_hdptx_dp_aux_init(hdptx); - if (ret) - rk_hdptx_phy_consumer_put(hdptx, true); } else { + dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + if (hdptx->pll_config_dirty) ret = rk_hdptx_pll_cmn_config(hdptx); @@ -1710,11 +1695,12 @@ static int rk_hdptx_phy_power_on(struct phy *phy) ret = rk_hdptx_frl_lcpll_mode_config(hdptx); else ret = rk_hdptx_tmds_ropll_mode_config(hdptx); - } else { - rk_hdptx_phy_consumer_put(hdptx, true); } } + if (ret) + rk_hdptx_phy_consumer_put(hdptx, true); + return ret; } From 97aa4233010103fe65a11088ffeb287f48762332 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:48 +0200 Subject: [PATCH 086/208] phy: rockchip: samsung-hdptx: Drop restrict_rate_change handling Since commit 6efbd0f46dd8 ("phy: rockchip: samsung-hdptx: Restrict altering TMDS char rate via CCF"), adjusting the rate via the Common Clock Framework API has been disallowed. To avoid breaking existing users until switching to the PHY config API, it introduced a temporary exception to the rule, controlled via the 'restrict_rate_change' flag. As the API transition completed, remove the now deprecated exception logic. Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-4-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index b56360b5df78a3..e5c8bc95a416ec 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -414,7 +414,6 @@ struct rk_hdptx_phy { /* clk provider */ struct clk_hw hw; bool pll_config_dirty; - bool restrict_rate_change; atomic_t usage_count; @@ -2069,7 +2068,6 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt if (ret) { dev_err(hdptx->dev, "invalid hdmi params for phy configure\n"); } else { - hdptx->restrict_rate_change = true; hdptx->pll_config_dirty = true; dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, @@ -2296,41 +2294,17 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); /* - * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate() - * will be invoked to commit PLL configuration. + * For uncommitted PLL configuration, invalidate the current clock rate + * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked. + * Otherwise, restrict the rate according to the PHY link setup. */ - if (hdptx->pll_config_dirty) { + if (hdptx->pll_config_dirty) req->rate = 0; - return 0; - } - - if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) { + else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) req->rate = hdptx->hdmi_cfg.rate; - return 0; - } - - /* - * FIXME: Temporarily allow altering TMDS char rate via CCF. - * To be dropped as soon as the RK DW HDMI QP bridge driver - * switches to make use of phy_configure(). - */ - if (!hdptx->restrict_rate_change && req->rate != hdptx->hdmi_cfg.rate) { - struct phy_configure_opts_hdmi hdmi = { - .tmds_char_rate = req->rate, - }; - - int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi, &hdptx->hdmi_cfg); - - if (ret) - return ret; - } - - /* - * The TMDS char rate shall be adjusted via phy_configure() only, - * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with - * a different rate argument. - */ - req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc); + else + req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, + hdptx->hdmi_cfg.bpc); return 0; } From 4395258c52d06c0ceeb0cdf0829ec693bf139134 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:49 +0200 Subject: [PATCH 087/208] phy: rockchip: samsung-hdptx: Simplify GRF access with FIELD_PREP_WM16() The 16 most significant bits of the general-purpose register (GRF) are used as a write-enable mask for the remaining 16 bits. Make use of the recently introduced FIELD_PREP_WM16() macro to avoid open-coding the bit shift operations and improve code readability. Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-5-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index e5c8bc95a416ec..baa6916331bed0 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -949,7 +950,9 @@ static void rk_hdptx_pre_power_up(struct rk_hdptx_phy *hdptx) reset_control_assert(hdptx->rsts[RST_CMN].rstc); reset_control_assert(hdptx->rsts[RST_INIT].rstc); - val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); } @@ -960,8 +963,8 @@ static int rk_hdptx_post_enable_lane(struct rk_hdptx_phy *hdptx) reset_control_deassert(hdptx->rsts[RST_LANE].rstc); - val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | - HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN; + val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); /* 3 lanes FRL mode */ @@ -990,16 +993,15 @@ static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx) u32 val; int ret; - val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 | - HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN; + val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); usleep_range(10, 15); reset_control_deassert(hdptx->rsts[RST_INIT].rstc); usleep_range(10, 15); - val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN; - regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1)); usleep_range(10, 15); reset_control_deassert(hdptx->rsts[RST_CMN].rstc); @@ -1037,7 +1039,9 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx) reset_control_assert(hdptx->rsts[RST_CMN].rstc); reset_control_assert(hdptx->rsts[RST_INIT].rstc); - val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16; + val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, val); } @@ -1135,7 +1139,7 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_cmn_init_seq); @@ -1178,8 +1182,7 @@ static int rk_hdptx_frl_lcpll_ropll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); /* ROPLL input reference clock from LCPLL (cascade mode) */ - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - (LC_REF_CLK_SEL << 16) | LC_REF_CLK_SEL); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 1)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_ropll_cmn_init_seq); @@ -1218,7 +1221,7 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) rk_hdptx_pre_power_up(hdptx); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0)); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq); rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_cmn_init_seq); @@ -1336,11 +1339,9 @@ static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx) FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x0)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0)); + FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) | + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0)); } static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx) @@ -1611,9 +1612,8 @@ static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx) FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1)); regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x1)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x1)); + FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1) | + FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1)); usleep_range(20, 25); reset_control_deassert(hdptx->rsts[RST_INIT].rstc); @@ -1660,7 +1660,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy) if (mode == PHY_MODE_DP) { regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x1)); + FIELD_PREP_WM16(HDPTX_MODE_SEL, 1)); for (lane = 0; lane < 4; lane++) { regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane, @@ -1688,7 +1688,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy) if (!ret) { regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0)); + FIELD_PREP_WM16(HDPTX_MODE_SEL, 0)); if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) ret = rk_hdptx_frl_lcpll_mode_config(hdptx); @@ -1823,8 +1823,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx, u32 bw, status; int ret; - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0)); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0)); switch (dp->link_rate) { case 1620: @@ -1880,8 +1879,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx, regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK, FIELD_PREP(DP_TX_LINK_BW_MASK, bw)); - regmap_write(hdptx->grf, GRF_HDPTX_CON0, - HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x1)); + regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1)); ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status), From 94db56cecf9a31b827305ff14730a5b03277c476 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Feb 2026 22:48:50 +0200 Subject: [PATCH 088/208] phy: rockchip: samsung-hdptx: Consistently use bitfield macros Make the code more robust and improve readability by using the available bitfield macros (e.g. FIELD_PREP, FIELD_GET) whenever possible, instead of open coding the related bit operations. Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-6-f998f2762d0f@collabora.com Signed-off-by: Sebastian Reichel --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index baa6916331bed0..3bde7fbb34b1cf 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -53,6 +53,12 @@ /* CMN_REG(001e) */ #define LCPLL_PI_EN_MASK BIT(5) #define LCPLL_100M_CLK_EN_MASK BIT(0) +/* CMN_REG(0022) */ +#define ANA_LCPLL_PMS_PDIV_MASK GENMASK(7, 4) +#define ANA_LCPLL_PMS_REFDIV_MASK GENMASK(3, 0) +/* CMN_REG(0023) */ +#define LCPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4) +#define LCPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0) /* CMN_REG(0025) */ #define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4) /* CMN_REG(0028) */ @@ -1157,9 +1163,11 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx) regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv); regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc); regmap_write(hdptx->regmap, CMN_REG(0022), - (cfg->pms_pdiv << 4) | cfg->pms_refdiv); + FIELD_PREP(ANA_LCPLL_PMS_PDIV_MASK, cfg->pms_pdiv) | + FIELD_PREP(ANA_LCPLL_PMS_REFDIV_MASK, cfg->pms_refdiv)); regmap_write(hdptx->regmap, CMN_REG(0023), - (cfg->pms_sdiv << 4) | cfg->pms_sdiv); + FIELD_PREP(LCPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv) | + FIELD_PREP(LCPLL_PMS_SDIV_HBR_MASK, cfg->pms_sdiv)); regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno); regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign); regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num); @@ -1229,8 +1237,10 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx) regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv); regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc); regmap_write(hdptx->regmap, CMN_REG(0059), - (cfg->pms_pdiv << 4) | cfg->pms_refdiv); - regmap_write(hdptx->regmap, CMN_REG(005a), cfg->pms_sdiv << 4); + FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, cfg->pms_pdiv) | + FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, cfg->pms_refdiv)); + regmap_write(hdptx->regmap, CMN_REG(005a), + FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv)); regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK, FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en)); @@ -2192,7 +2202,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) ret = regmap_read(hdptx->regmap, CMN_REG(002D), &val); if (ret) return 0; - lcpll_hw.sdc_n = (val & LCPLL_SDC_N_MASK) >> 1; + lcpll_hw.sdc_n = FIELD_GET(LCPLL_SDC_N_MASK, val); for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) { const struct lcpll_config *cfg = &rk_hdptx_frl_lcpll_cfg[i]; @@ -2253,7 +2263,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx) ret = regmap_read(hdptx->regmap, CMN_REG(0086), &val); if (ret) return 0; - ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1; + ropll_hw.pms_sdiv = FIELD_GET(PLL_PCG_POSTDIV_SEL_MASK, val) + 1; bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8; fout = PLL_REF_CLK * ropll_hw.pms_mdiv; From 70414f9cf71aeb798d7a4b409448581cdcf79b78 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 17 Feb 2026 20:36:26 +0200 Subject: [PATCH 089/208] [DEBUG] phy: rockchip: samsung-hdptx: Add verbose logging --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 3bde7fbb34b1cf..0684e1a0d1a17d 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -1028,6 +1028,8 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx) { u32 val; + dev_dbg(hdptx->dev, "PHY disable\n"); + reset_control_assert(hdptx->rsts[RST_APB].rstc); usleep_range(20, 30); reset_control_deassert(hdptx->rsts[RST_APB].rstc); @@ -1717,6 +1719,8 @@ static int rk_hdptx_phy_power_off(struct phy *phy) { struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy); + dev_dbg(hdptx->dev, "power_off\n"); + return rk_hdptx_phy_consumer_put(hdptx, false); } @@ -2149,6 +2153,8 @@ static int rk_hdptx_phy_clk_prepare(struct clk_hw *hw) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "clk_prepare\n"); + return rk_hdptx_phy_consumer_get(hdptx); } @@ -2156,6 +2162,8 @@ static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "clk_unprepare\n"); + rk_hdptx_phy_consumer_put(hdptx, true); } @@ -2287,13 +2295,20 @@ static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); u32 status; + u64 rate; int ret; ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status); - if (ret || !(status & HDPTX_I_PLL_EN)) + if (ret || !(status & HDPTX_I_PLL_EN)) { + dev_dbg(hdptx->dev, "%s ret=%d status=%x\n", __func__, ret, status); return 0; + } - return rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx); + rate = rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx); + + dev_dbg(hdptx->dev, "%s from_pll=%llu\n", __func__, rate); + + return rate; } static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, @@ -2301,6 +2316,8 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "%s req=%lu par=%lu cfg=%llu dirty=%d\n", __func__, + req->rate, req->best_parent_rate, hdptx->hdmi_cfg.rate, hdptx->pll_config_dirty); /* * For uncommitted PLL configuration, invalidate the current clock rate * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked. @@ -2322,6 +2339,8 @@ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + dev_dbg(hdptx->dev, "%s req=%lu par=%lu cfg=%llu dirty=%d\n", __func__, + rate, parent_rate, hdptx->hdmi_cfg.rate, hdptx->pll_config_dirty); /* * The link rate would be normally programmed in HW during * phy_ops.power_on() or clk_ops.prepare() callbacks, but it might @@ -2373,6 +2392,8 @@ static int rk_hdptx_phy_runtime_suspend(struct device *dev) { struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev); + dev_dbg(hdptx->dev, "suspend\n"); + clk_bulk_disable_unprepare(hdptx->nr_clks, hdptx->clks); return 0; @@ -2383,6 +2404,8 @@ static int rk_hdptx_phy_runtime_resume(struct device *dev) struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev); int ret; + dev_dbg(hdptx->dev, "resume\n"); + ret = clk_bulk_prepare_enable(hdptx->nr_clks, hdptx->clks); if (ret) dev_err(hdptx->dev, "Failed to enable clocks: %d\n", ret); From 7f575d2df502ec5f874abffb7cee285ff093317e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 21 Jan 2026 13:50:42 +0200 Subject: [PATCH 090/208] drm/rockchip: dw_dp: Simplify error handling Make the code a bit more compact by getting rid of the superfluous assignments around PTR_ERR(). While at it, also drop dev assignment in dw_dp_probe(). Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 8945a245398ca4..fafefee8370d6d 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -104,20 +104,15 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs); dp->base = dw_dp_bind(dev, encoder, plat_data); - if (IS_ERR(dp->base)) { - ret = PTR_ERR(dp->base); - return ret; - } + if (IS_ERR(dp->base)) + return PTR_ERR(dp->base); connector = drm_bridge_connector_init(drm_dev, encoder); - if (IS_ERR(connector)) { - ret = PTR_ERR(connector); - return dev_err_probe(dev, ret, "Failed to init bridge connector"); - } + if (IS_ERR(connector)) + return dev_err_probe(dev, PTR_ERR(connector), + "Failed to init bridge connector"); - drm_connector_attach_encoder(connector, encoder); - - return 0; + return drm_connector_attach_encoder(connector, encoder); } static const struct component_ops dw_dp_rockchip_component_ops = { @@ -126,9 +121,7 @@ static const struct component_ops dw_dp_rockchip_component_ops = { static int dw_dp_probe(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - - return component_add(dev, &dw_dp_rockchip_component_ops); + return component_add(&pdev->dev, &dw_dp_rockchip_component_ops); } static void dw_dp_remove(struct platform_device *pdev) From b80069ff09a0b796ded0b8a7503b62ec88ec06bc Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Mar 2026 02:55:07 +0200 Subject: [PATCH 091/208] drm/bridge: synopsys: dw-dp: Support unregistering the AUX channel The DisplayPort AUX channel gets initialized and registered during dw_dp_bind(), but it is never unregistered, which may lead to resource leaks and/or use-after-free. Add the missing dw_dp_unbind() function to allow the users of the library to handle the required cleanup, i.e. unregister the AUX adapter. Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260327-drm-rk-fixes-v3-1-fd2e6900c08c@collabora.com Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 6 ++++++ include/drm/bridge/dw_dp.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 45b37885d719dc..164da0dfee9a4e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -2097,6 +2097,12 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } EXPORT_SYMBOL_GPL(dw_dp_bind); +void dw_dp_unbind(struct dw_dp *dp) +{ + drm_dp_aux_unregister(&dp->aux); +} +EXPORT_SYMBOL_GPL(dw_dp_unbind); + MODULE_AUTHOR("Andy Yan "); MODULE_DESCRIPTION("DW DP Core Library"); MODULE_LICENSE("GPL"); diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h index 25363541e69d51..22105c3e8e4d66 100644 --- a/include/drm/bridge/dw_dp.h +++ b/include/drm/bridge/dw_dp.h @@ -24,4 +24,5 @@ struct dw_dp_plat_data { struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data); +void dw_dp_unbind(struct dw_dp *dp); #endif /* __DW_DP__ */ From 9fc003f8515092bf2c630566e9b6ee34a87905b6 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 27 Mar 2026 02:55:08 +0200 Subject: [PATCH 092/208] drm/rockchip: dw_dp: Release core resources Core resources such as the DisplayPort AUX channel get initialized and registered during dw_dp_bind(), but are never unregistered, which may lead to memory leaks and/or use-after-free: [ 224.661371] BUG: KASAN: slab-use-after-free in device_is_dependent+0xe0/0x2b0 [ 224.662015] Read of size 8 at addr ffff00011aee8550 by task modprobe/658 [ 224.662612] [ 224.662752] CPU: 7 UID: 0 PID: 658 Comm: modprobe Not tainted 7.0.0-rc2-next-20260305 #14 PREEMPT [ 224.662759] Hardware name: Radxa ROCK 5B (DT) [ 224.662762] Call trace: [ 224.662764] show_stack+0x20/0x38 (C) [ 224.662772] dump_stack_lvl+0x6c/0x98 [ 224.662777] print_report+0x160/0x4b8 [ 224.662783] kasan_report+0xb4/0xe0 [ 224.662790] __asan_report_load8_noabort+0x20/0x30 [ 224.662796] device_is_dependent+0xe0/0x2b0 [ 224.662802] device_is_dependent+0x108/0x2b0 [ 224.662808] device_link_add+0x1f8/0x10b0 [ 224.662813] devm_of_phy_get_by_index+0x120/0x200 [ 224.662819] dw_dp_bind+0x34c/0xb10 [dw_dp] [ 224.662830] dw_dp_rockchip_bind+0x194/0x250 [rockchipdrm] [ 224.662864] component_bind_all+0x3a8/0x720 [ 224.662869] rockchip_drm_bind+0x120/0x390 [rockchipdrm] [ 224.662899] try_to_bring_up_aggregate_device+0x76c/0x838 [ 224.662904] component_master_add_with_match+0x1f4/0x230 [ 224.662909] rockchip_drm_platform_probe+0x420/0x538 [rockchipdrm] [ 224.662939] platform_probe+0xe8/0x168 [ 224.662945] really_probe+0x340/0x828 [ 224.662950] __driver_probe_device+0x2e0/0x350 [ 224.662954] driver_probe_device+0x80/0x140 [ 224.662959] __driver_attach+0x398/0x460 [ 224.662964] bus_for_each_dev+0xe0/0x198 [ 224.662968] driver_attach+0x50/0x68 [ 224.662972] bus_add_driver+0x2a0/0x4c0 [ 224.662977] driver_register+0x294/0x360 [ 224.662982] __platform_driver_register+0x7c/0x98 [ 224.662987] rockchip_drm_init+0xc4/0xff8 [rockchipdrm] Since a previous commit exported dw_dp_unbind() function in DW DP core library to take care of the necessary cleanup, use this in the component's unbind() callback, as well as in its bind() error path. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20260327-drm-rk-fixes-v3-2-fd2e6900c08c@collabora.com Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index fafefee8370d6d..94c1036d9d9f44 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -109,14 +109,28 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) - return dev_err_probe(dev, PTR_ERR(connector), - "Failed to init bridge connector"); + ret = dev_err_probe(dev, PTR_ERR(connector), + "Failed to init bridge connector"); + else + ret = drm_connector_attach_encoder(connector, encoder); - return drm_connector_attach_encoder(connector, encoder); + if (ret) + dw_dp_unbind(dp->base); + + return ret; +} + +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + + dw_dp_unbind(dp->base); } static const struct component_ops dw_dp_rockchip_component_ops = { .bind = dw_dp_rockchip_bind, + .unbind = dw_dp_rockchip_unbind, }; static int dw_dp_probe(struct platform_device *pdev) From 7f1393714c8080b61013025c6cd2d3094808def0 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 9 Mar 2026 21:12:33 +0200 Subject: [PATCH 093/208] drm/bridge: synopsys: dw-dp: Drop useless memory allocation The bridge gets allocated and initialized implicitly via the devm_drm_bridge_alloc() helper in dw_dp_bind(). However, this is preceded by an explicit allocation for the same dw_dp struct, which is never used anywhere as the return from devm_kzalloc() gets immediately overwritten by the aforementioned helper. Get rid of the unnecessary and confusing memory allocation. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 164da0dfee9a4e..6211ba6ba7bd6b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1970,10 +1970,6 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, void __iomem *res; int ret; - dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); - if (!dp) - return ERR_PTR(-ENOMEM); - dp = devm_drm_bridge_alloc(dev, struct dw_dp, bridge, &dw_dp_bridge_funcs); if (IS_ERR(dp)) return ERR_CAST(dp); From 0e4b3601d74e1c0542d34d2c3718c1907bd85341 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 17 Mar 2026 15:43:25 +0100 Subject: [PATCH 094/208] [DEBUG] usb: typec: altmode/displayport: print message on probe error Print an error message when the DisplayPort AltMode driver probe failed due to incorrect pin assignment or wrong data-role to ease debugging issues. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/altmodes/displayport.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 263a89c5f32433..3ff0882f3d97c1 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -766,8 +766,10 @@ int dp_altmode_probe(struct typec_altmode *alt) struct dp_altmode *dp; /* Port can only be DFP_U. */ - if (typec_altmode_get_data_role(alt) != TYPEC_HOST) + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) { + dev_err(alt->dev.parent->parent, "Cannot probe DP AltMode as data-role is not HOST\n"); return -EPROTO; + } /* Make sure we have compatible pin configurations */ if (!(DP_CAP_PIN_ASSIGN_DFP_D(port->vdo) & @@ -775,6 +777,7 @@ int dp_altmode_probe(struct typec_altmode *alt) !(DP_CAP_PIN_ASSIGN_UFP_D(port->vdo) & DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) { typec_altmode_put_plug(plug); + dev_err(alt->dev.parent->parent, "Cannot probe DP AltMode due to incorrect pin assignment\n"); return -ENODEV; } From f5d66a62833640629593ba5e96ab7cd6000e3aef Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 19 Feb 2026 15:55:04 +0100 Subject: [PATCH 095/208] drm/bridge: synopsys: dw-dp: Simplify driver data setting There is no need to get the platform device just for setting up the driver data. Simplify the logic. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 94c1036d9d9f44..56658a45b77327 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -75,7 +75,6 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) { - struct platform_device *pdev = to_platform_device(dev); const struct dw_dp_plat_data *plat_data; struct drm_device *drm_dev = data; struct rockchip_dw_dp *dp; @@ -88,7 +87,7 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * return -ENOMEM; dp->dev = dev; - platform_set_drvdata(pdev, dp); + dev_set_drvdata(dev, dp); plat_data = of_device_get_match_data(dev); if (!plat_data) From 7694da698e6ce84b257823ef73b54a4e79bf7bdd Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 6 Mar 2026 14:26:19 +0100 Subject: [PATCH 096/208] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED Add support for MEDIA_BUS_FMT_FIXED, which is e.g. requested for USB-C DP chains as the last bridge in the chain (aux-hpd-bridge) does not implement atomic_get_output_bus_fmts(), which results in the generic drm_atomic_bridge_chain_select_bus_fmts() code using MEDIA_BUS_FMT_FIXED instead. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 6211ba6ba7bd6b..fc4ddb7792d70f 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1528,6 +1528,7 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge, struct drm_connector_state *conn_state) { struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + unsigned int out_bus_format = bridge_state->output_bus_cfg.format; struct dw_dp *dp = bridge_to_dp(bridge); struct dw_dp_bridge_state *state; const struct dw_dp_output_format *fmt; @@ -1538,7 +1539,10 @@ static int dw_dp_bridge_atomic_check(struct drm_bridge *bridge, state = to_dw_dp_bridge_state(bridge_state); mode = &state->mode; - fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); + if (out_bus_format == MEDIA_BUS_FMT_FIXED) + out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + fmt = dw_dp_get_output_format(out_bus_format); if (!fmt) return -EINVAL; From 3e9e728497a5b85e5f52630def542cdd0e8fd333 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 5 Mar 2026 18:12:53 +0100 Subject: [PATCH 097/208] drm/bridge: synopsys: dw-dp: Add follow-up bridge support Add support to use USB-C connectors with the DP altmode helper code on devicetree based platforms. To get this working there must be a DRM bridge chain from the DisplayPort controller to the USB-C connector. E.g. on Rockchip RK3576: root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges bridge[0]: dw_dp_bridge_funcs refcount: 7 type: [10] DP OF: /soc/dp@27e40000:rockchip,rk3576-dp ops: [0x47] detect edid hpd bridge[1]: drm_aux_bridge_funcs refcount: 4 type: [0] Unknown OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy ops: [0x0] bridge[2]: drm_aux_hpd_bridge_funcs refcount: 5 type: [10] DP OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector ops: [0x4] hpd Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index fc4ddb7792d70f..ccc55e40e81ca1 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1970,7 +1970,7 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, { struct platform_device *pdev = to_platform_device(dev); struct dw_dp *dp; - struct drm_bridge *bridge; + struct drm_bridge *bridge, *next_bridge; void __iomem *res; int ret; @@ -2064,6 +2064,20 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, goto unregister_aux; } + next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + if (IS_ERR(next_bridge)) { + ret = PTR_ERR(next_bridge); + dev_err_probe(dev, ret, "failed to get follow-up bridge.\n"); + goto unregister_aux; + } + + ret = drm_bridge_attach(encoder, next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err_probe(dev, ret, "Failed to attach next bridge\n"); + goto unregister_aux; + } + dw_dp_init_hw(dp); ret = phy_init(dp->phy); From f352c165a2b16ab80f76b5a73584b2bb9c3243ec Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 26 Feb 2026 16:42:44 +0100 Subject: [PATCH 098/208] drm/bridge: Add out-of-band HPD notify handler For DP bridges, that can be used for DP AltMode, it might be necessary to enforce HPD status. There is an existing ->oob_hotplug_event() on the DRM connector, but it currently just calls into hpd_notify(). As DP bridge drivers usually also implement .detect and that also generates calls into hpd_notify, this is a bad place to force the HPD status as the follow-up detect call might force it off again resulting in all follow-up calls to the detection routine also failing. Avoid this by having a dedicated function for OOB events. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/display/drm_bridge_connector.c | 6 ++++++ include/drm/drm_bridge.h | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 39cc18f78eda11..4333cc66073ecf 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -180,6 +180,12 @@ static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connect struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); + /* Notify all bridges in the pipeline of hotplug events. */ + drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { + if (bridge->funcs->oob_notify) + bridge->funcs->oob_notify(bridge, connector, status); + } + drm_bridge_connector_handle_hpd(bridge_connector, status); } diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index a8d67bd9ee505f..1ad9ae50c829cc 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -646,6 +646,20 @@ struct drm_bridge_funcs { */ void (*hpd_disable)(struct drm_bridge *bridge); + /** + * @oob_notify: + * + * Notify the bridge of out of band hot plug detection. + * + * This callback is optional, it may be implemented by bridges that + * need to be notified of display connection or disconnection for + * internal reasons. One use case is to force the DP controllers HPD + * signal for USB-C DP AltMode. + */ + void (*oob_notify)(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status); + /** * @hdmi_tmds_char_rate_valid: * From 452c6dc5c8d6b40c85ba81af148ab6f59ba754e0 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 4 Mar 2026 15:29:45 +0100 Subject: [PATCH 099/208] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD Add support for USB-C DP AltMode out-of-band hotplug handling. The handling itself is implemented in the platform specific driver as the registers to force HPD state are not part of the Designware DisplayPort IP itself. Instead the platform integration might provide the necessary functionality to mux the HPD signal. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 38 ++++++++++++++++++++++++- include/drm/bridge/dw_dp.h | 3 ++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index ccc55e40e81ca1..7ade88f7446659 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1817,6 +1817,19 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b return &state->base; } +static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + bool hpd_high = status != connector_status_disconnected; + struct dw_dp *dp = bridge_to_dp(bridge); + + if (dp->plat_data.hpd_sw_cfg) + dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high); + else + dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n"); +} + static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -1829,6 +1842,7 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .atomic_disable = dw_dp_bridge_atomic_disable, .detect = dw_dp_bridge_detect, .edid_read = dw_dp_bridge_edid_read, + .oob_notify = dw_dp_bridge_oob_notify, }; static int dw_dp_link_retrain(struct dw_dp *dp) @@ -1965,6 +1979,19 @@ static void dw_dp_phy_exit(void *data) phy_exit(dp->phy); } +static bool dw_dp_is_routed_to_usb_c(struct drm_encoder *encoder) +{ + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL; + struct fwnode_handle *fwnode; + + last_bridge = drm_bridge_chain_get_last_bridge(encoder); + if (!last_bridge) + return false; + + fwnode = of_fwnode_handle(last_bridge->of_node); + return fwnode_device_is_compatible(fwnode, "usb-c-connector"); +} + struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data) { @@ -1980,7 +2007,9 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, dp->dev = dev; dp->pixel_mode = plat_data->pixel_mode; - + dp->plat_data.hpd_sw_sel = plat_data->hpd_sw_sel; + dp->plat_data.hpd_sw_cfg = plat_data->hpd_sw_cfg; + dp->plat_data.data = plat_data->data; dp->plat_data.max_link_rate = plat_data->max_link_rate; bridge = &dp->bridge; mutex_init(&dp->irq_lock); @@ -2078,6 +2107,13 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, goto unregister_aux; } + if (dw_dp_is_routed_to_usb_c(encoder)) { + dev_dbg(dev, "USB-C mode\n"); + + if (dp->plat_data.hpd_sw_sel) + dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1); + } + dw_dp_init_hw(dp); ret = phy_init(dp->phy); diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h index 22105c3e8e4d66..2127afa26b2cfd 100644 --- a/include/drm/bridge/dw_dp.h +++ b/include/drm/bridge/dw_dp.h @@ -20,6 +20,9 @@ enum { struct dw_dp_plat_data { u32 max_link_rate; u8 pixel_mode; + void *data; + void (*hpd_sw_sel)(void *data, bool hpd); + void (*hpd_sw_cfg)(void *data, bool hpd); }; struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, From ed5ee1e5936b3beda8f583d8abb28e8a7501e196 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 4 Mar 2026 15:34:43 +0100 Subject: [PATCH 100/208] drm/rockchip: dw_dp: Implement out-of-band HPD handling Implement out-of-band hotplug handling, which will be used to receive external hotplug information from the USB-C state machine. This is currently handled by the USBDP PHY, which brings quite some trouble as the register being accessed requires the power-domain from the DP controller and also requires custom TypeC HPD info parsing in the USBDP PHY driver. In contrast to the USBDP PHY this does not just enable the hotplug signal when a DP AltMode capable adapter is plugged in, but instead properly detects if a cable is plugged in for things like USB-C to HDMI adapters. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 126 ++++++++++++++++++++-- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 56658a45b77327..9a1ae93f13307b 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -7,8 +7,11 @@ */ #include +#include +#include #include #include +#include #include #include #include @@ -25,12 +28,54 @@ #include "rockchip_drm_drv.h" #include "rockchip_drm_vop.h" +#define ROCKCHIP_MAX_CTRLS 2 + +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10) +#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11) + +struct rockchip_dw_dp_plat_data { + u8 num_ctrls; + u32 ctrl_ids[ROCKCHIP_MAX_CTRLS]; + u32 max_link_rate; + u8 pixel_mode; + u32 hpd_reg[ROCKCHIP_MAX_CTRLS]; +}; + struct rockchip_dw_dp { struct dw_dp *base; struct device *dev; + const struct rockchip_dw_dp_plat_data *pdata; + struct regmap *vo_grf; struct rockchip_encoder encoder; + int id; + bool hpd_sel; + bool hpd_cfg; }; +static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw) +{ + struct rockchip_dw_dp *dp = data; + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + + dp->hpd_sel = force_hpd_from_sw; + + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel)); +} + +static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd) +{ + struct rockchip_dw_dp *dp = data; + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + + dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd)); + + dp->hpd_cfg = hpd; + + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg)); +} + static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) @@ -73,14 +118,49 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { .atomic_check = dw_dp_encoder_atomic_check, }; +static struct regmap *dp_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp) +{ + struct device_node *np = dev_of_node(dp->dev); + struct of_phandle_args args; + struct regmap *regmap; + int ret; + + ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args); + if (ret) + return ERR_PTR(-ENODEV); + + /* + * Limit this workaround to RK3576 and RK3588, new platforms should + * add a VO GRF phandle in the DisplayPort DT node. + */ + if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") && + !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) { + regmap = ERR_PTR(-ENODEV); + goto out_put_node; + } + + regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf"); + +out_put_node: + of_node_put(args.np); + return regmap; +} + static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) { - const struct dw_dp_plat_data *plat_data; + const struct rockchip_dw_dp_plat_data *plat_data_const; + struct platform_device *pdev = to_platform_device(dev); + struct dw_dp_plat_data *plat_data; struct drm_device *drm_dev = data; struct rockchip_dw_dp *dp; struct drm_encoder *encoder; struct drm_connector *connector; - int ret; + struct resource *res; + int ret, id; + + plat_data = drmm_kzalloc(drm_dev, sizeof(*plat_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL); if (!dp) @@ -89,10 +169,38 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * dp->dev = dev; dev_set_drvdata(dev, dp); - plat_data = of_device_get_match_data(dev); - if (!plat_data) + plat_data_const = device_get_match_data(dev); + if (!plat_data_const) return -ENODEV; + dp->pdata = plat_data_const; + + res = platform_get_mem_or_io(pdev, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + + /* find the DisplayPort ID from the io address */ + dp->id = -ENODEV; + for (id = 0; id < plat_data_const->num_ctrls; id++) { + if (res->start == plat_data_const->ctrl_ids[id]) { + dp->id = id; + break; + } + } + + if (dp->id < 0) + return dp->id; + + dp->vo_grf = dp_dp_rockchip_get_vo_grf(dp); + if (IS_ERR(dp->vo_grf)) + return PTR_ERR(dp->vo_grf); + + plat_data->max_link_rate = plat_data_const->max_link_rate; + plat_data->pixel_mode = plat_data_const->pixel_mode; + plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel; + plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg; + plat_data->data = dp; + encoder = &dp->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0); @@ -142,14 +250,20 @@ static void dw_dp_remove(struct platform_device *pdev) component_del(&pdev->dev, &dw_dp_rockchip_component_ops); } -static const struct dw_dp_plat_data rk3588_dp_plat_data = { +static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = { + .num_ctrls = 2, + .ctrl_ids = {0xfde50000, 0xfde60000}, .max_link_rate = 810000, .pixel_mode = DW_DP_MP_QUAD_PIXEL, + .hpd_reg = {0x0000, 0x0008}, }; -static const struct dw_dp_plat_data rk3576_dp_plat_data = { +static const struct rockchip_dw_dp_plat_data rk3576_dp_plat_data = { + .num_ctrls = 1, + .ctrl_ids = {0x27e40000}, .max_link_rate = 810000, .pixel_mode = DW_DP_MP_DUAL_PIXEL, + .hpd_reg = {0x0000}, }; static const struct of_device_id dw_dp_of_match[] = { From 15e6fd04b5d583a775e6cec7ad088a9edf080d06 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 21 Jan 2026 05:22:05 +0100 Subject: [PATCH 101/208] drm/bridge: synopsys: dw-dp: Add audio support Implement audio support for the Synopsys DesignWare DisplayPort controller. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 221 +++++++++++++++++++++++- 1 file changed, 220 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 7ade88f7446659..550bdf37fae5cb 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -23,17 +23,21 @@ #include #include #include +#include #include #include #include #include #include +#include + #define DW_DP_VERSION_NUMBER 0x0000 #define DW_DP_VERSION_TYPE 0x0004 #define DW_DP_ID 0x0008 #define DW_DP_CONFIG_REG1 0x0100 +#define AUDIO_SELECT GENMASK(2, 1) #define DW_DP_CONFIG_REG2 0x0104 #define DW_DP_CONFIG_REG3 0x0108 @@ -110,6 +114,10 @@ #define HBR_MODE_ENABLE BIT(10) #define AUDIO_DATA_WIDTH GENMASK(9, 5) #define AUDIO_DATA_IN_EN GENMASK(4, 1) +#define AUDIO_DATA_IN_EN_CHANNEL12 BIT(0) +#define AUDIO_DATA_IN_EN_CHANNEL34 BIT(1) +#define AUDIO_DATA_IN_EN_CHANNEL56 BIT(2) +#define AUDIO_DATA_IN_EN_CHANNEL78 BIT(3) #define AUDIO_INF_SELECT BIT(0) #define DW_DP_SDP_VERTICAL_CTRL 0x0500 @@ -253,6 +261,8 @@ #define SDP_REG_BANK_SIZE 16 +#define DW_DP_SDP_VERSION 0x12 + struct dw_dp_link_caps { bool enhanced_framing; bool tps3_supported; @@ -305,6 +315,19 @@ struct dw_dp_hotplug { bool long_hpd; }; +enum dw_dp_audio_interface_support { + DW_DP_AUDIO_I2S_ONLY = 0, + DW_DP_AUDIO_SPDIF_ONLY = 1, + DW_DP_AUDIO_I2S_AND_SPDIF = 2, + DW_DP_AUDIO_NONE = 3, +}; + +enum dw_dp_audio_interface { + DW_DP_AUDIO_I2S = 0, + DW_DP_AUDIO_SPDIF = 1, + DW_DP_AUDIO_UNUSED, +}; + struct dw_dp { struct drm_bridge bridge; struct device *dev; @@ -320,6 +343,8 @@ struct dw_dp { int irq; struct work_struct hpd_work; struct dw_dp_hotplug hotplug; + enum dw_dp_audio_interface audio_interface; + int audio_channels; /* Serialize hpd status access */ struct mutex irq_lock; @@ -1830,6 +1855,186 @@ static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge, dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n"); } +static int dw_dp_audio_infoframe_send(struct dw_dp *dp) +{ + struct hdmi_audio_infoframe frame; + struct dw_dp_sdp sdp; + int ret; + + ret = hdmi_audio_infoframe_init(&frame); + if (ret < 0) + return ret; + + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + frame.channels = dp->audio_channels; + + ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION); + if (ret < 0) + return ret; + + sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL; + + return dw_dp_send_sdp(dp, &sdp); +} + +static int dw_dp_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio startup\n"); + pm_runtime_get_sync(dp->dev); + + return 0; +} + +static void dw_dp_audio_unprepare(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + /* Disable all audio streams */ + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN, + FIELD_PREP(AUDIO_DATA_IN_EN, 0)); + + if (dp->audio_interface == DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface == DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->i2s_clk); + + dp->audio_interface = DW_DP_AUDIO_UNUSED; +} + +static int dw_dp_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + u8 audio_data_in_en, supported_audio_interfaces; + u32 cfg1; + int ret; + + /* + * prepare might be called multiple times, so release the clocks + * from previous calls to keep the calls in balance. + */ + if (dp->audio_interface != DW_DP_AUDIO_UNUSED) + dw_dp_audio_unprepare(bridge, connector); + + dp->audio_channels = params->cea.channels; + switch (params->cea.channels) { + case 1: + case 2: + audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12; + break; + case 8: + audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12 | + AUDIO_DATA_IN_EN_CHANNEL34 | + AUDIO_DATA_IN_EN_CHANNEL56 | + AUDIO_DATA_IN_EN_CHANNEL78; + break; + default: + dev_err(dp->dev, "invalid audio channels %d\n", dp->audio_channels); + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_SPDIF: + dp->audio_interface = DW_DP_AUDIO_SPDIF; + break; + case HDMI_I2S: + /* + * It is recommended to use SPDIF instead of I2S, since I2S mode requires + * manually inserting PCUV control bits from userspace and this is done + * automatically in hardware for SPDIF mode. + */ + dp->audio_interface = DW_DP_AUDIO_I2S; + break; + default: + dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt); + return -EINVAL; + } + + regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1); + supported_audio_interfaces = FIELD_GET(AUDIO_SELECT, cfg1); + + if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF && + supported_audio_interfaces != dp->audio_interface) { + dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt); + return -EINVAL; + } + + clk_prepare_enable(dp->spdif_clk); + clk_prepare_enable(dp->i2s_clk); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, + AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH | + AUDIO_INF_SELECT | HBR_MODE_ENABLE, + FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) | + FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) | + FIELD_PREP(AUDIO_DATA_WIDTH, params->sample_width) | + FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) | + FIELD_PREP(HBR_MODE_ENABLE, 0)); + + /* Wait for inf switch */ + usleep_range(20, 40); + + if (dp->audio_interface == DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface == DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->i2s_clk); + + /* + * Send audio stream during vertical and horizontal blanking periods. + * Send out audio timestamp SDP once per video frame during the vertical + * blanking period + */ + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, + EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) | + FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1)); + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, + EN_AUDIO_STREAM_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1)); + + ret = dw_dp_audio_infoframe_send(dp); + if (ret < 0) + dev_err(dp->dev, "failed to send audio infoframe\n"); + + dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%d\n", + dp->audio_channels, dp->audio_interface); + + return 0; +} + +static void dw_dp_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio shutdown\n"); + + dw_dp_audio_unprepare(bridge, connector); + pm_runtime_put_autosuspend(dp->dev); +} + +static int dw_dp_audio_mute_stream(struct drm_bridge *bridge, + struct drm_connector *connector, + bool enable, int direction) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un"); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE, + FIELD_PREP(AUDIO_MUTE, enable)); + + return 0; +} + static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -1843,6 +2048,11 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = { .detect = dw_dp_bridge_detect, .edid_read = dw_dp_bridge_edid_read, .oob_notify = dw_dp_bridge_oob_notify, + + .dp_audio_startup = dw_dp_audio_startup, + .dp_audio_prepare = dw_dp_audio_prepare, + .dp_audio_shutdown = dw_dp_audio_shutdown, + .dp_audio_mute_stream = dw_dp_audio_mute_stream, }; static int dw_dp_link_retrain(struct dw_dp *dp) @@ -2069,10 +2279,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } bridge->of_node = dev->of_node; - bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + bridge->ops = DRM_BRIDGE_OP_DP_AUDIO | + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD; bridge->type = DRM_MODE_CONNECTOR_DisplayPort; bridge->ycbcr_420_allowed = true; + dp->audio_interface = DW_DP_AUDIO_UNUSED; + bridge->hdmi_audio_dev = dev; + bridge->hdmi_audio_max_i2s_playback_channels = 8; + bridge->hdmi_audio_dai_port = 1; + bridge->hdmi_audio_spdif_playback = true; + ret = devm_drm_bridge_add(dev, bridge); if (ret) return ERR_PTR(ret); From 5373308b18e7d8627baac3117733e1992a12d3ab Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 19 Mar 2026 16:40:50 +0100 Subject: [PATCH 102/208] drm/bridge: synopsys: dw-dp: Add Runtime PM support Add runtime PM stubs to the Synopsys DesignWare DisplayPort bridge driver. Support is not enabled automatically and must be hooked up in the vendor specific glue code. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 27 +++++++++++++++++++++++++ include/drm/bridge/dw_dp.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 550bdf37fae5cb..9553bad690d9bb 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1490,6 +1490,8 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, if (WARN_ON(msg->size > 16)) return -E2BIG; + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + switch (msg->request & ~DP_AUX_I2C_MOT) { case DP_AUX_NATIVE_WRITE: case DP_AUX_I2C_WRITE: @@ -1680,6 +1682,8 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_connector_state *conn_state; int ret; + pm_runtime_get_sync(dp->dev); + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); if (!connector) { dev_err(dp->dev, "failed to get connector\n"); @@ -1734,6 +1738,7 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, dw_dp_link_disable(dp); bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); dw_dp_reset(dp); + pm_runtime_put_autosuspend(dp->dev); } static bool dw_dp_hpd_detect_link(struct dw_dp *dp, struct drm_connector *connector) @@ -1754,6 +1759,8 @@ static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge, { struct dw_dp *dp = bridge_to_dp(bridge); + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + if (!dw_dp_hpd_detect(dp)) return connector_status_disconnected; @@ -2372,6 +2379,26 @@ void dw_dp_unbind(struct dw_dp *dp) } EXPORT_SYMBOL_GPL(dw_dp_unbind); +int dw_dp_runtime_suspend(struct dw_dp *dp) +{ + clk_disable_unprepare(dp->aux_clk); + clk_disable_unprepare(dp->apb_clk); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_dp_runtime_suspend); + +int dw_dp_runtime_resume(struct dw_dp *dp) +{ + clk_prepare_enable(dp->apb_clk); + clk_prepare_enable(dp->aux_clk); + + dw_dp_init_hw(dp); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_dp_runtime_resume); + MODULE_AUTHOR("Andy Yan "); MODULE_DESCRIPTION("DW DP Core Library"); MODULE_LICENSE("GPL"); diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h index 2127afa26b2cfd..3037e0290861eb 100644 --- a/include/drm/bridge/dw_dp.h +++ b/include/drm/bridge/dw_dp.h @@ -28,4 +28,7 @@ struct dw_dp_plat_data { struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data); void dw_dp_unbind(struct dw_dp *dp); + +int dw_dp_runtime_suspend(struct dw_dp *dp); +int dw_dp_runtime_resume(struct dw_dp *dp); #endif /* __DW_DP__ */ From 43f494e76ce9f68b16541499de3292754740346e Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 19 Mar 2026 16:44:22 +0100 Subject: [PATCH 103/208] drm/rockchip: dw_dp: Add runtime PM support Add support for runtime PM to the Rockchip RK3576/3588 Synopsys DesignWare DisplayPort driver. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 9a1ae93f13307b..1ea030db19748b 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,8 @@ static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw) dp->hpd_sel = force_hpd_from_sw; + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + regmap_write(dp->vo_grf, hpd_reg, FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel)); } @@ -72,6 +75,8 @@ static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd) dp->hpd_cfg = hpd; + ACQUIRE(pm_runtime_active_auto, pm)(dp->dev); + regmap_write(dp->vo_grf, hpd_reg, FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg)); } @@ -214,6 +219,12 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * if (IS_ERR(dp->base)) return PTR_ERR(dp->base); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); + connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) ret = dev_err_probe(dev, PTR_ERR(connector), @@ -250,6 +261,33 @@ static void dw_dp_remove(struct platform_device *pdev) component_del(&pdev->dev, &dw_dp_rockchip_component_ops); } +static int dw_dp_rockchip_runtime_suspend(struct device *dev) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + return dw_dp_runtime_suspend(dp->base); +} + +static int dw_dp_rockchip_runtime_resume(struct device *dev) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + u32 hpd_reg = dp->pdata->hpd_reg[dp->id]; + int ret; + + ret = dw_dp_runtime_resume(dp->base); + if (ret) + return ret; + + regmap_write(dp->vo_grf, hpd_reg, + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel) | + FIELD_PREP_WM16_CONST(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg)); + + return 0; +} + +static const struct dev_pm_ops dw_dp_pm_ops = { + RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL) +}; + static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = { .num_ctrls = 2, .ctrl_ids = {0xfde50000, 0xfde60000}, @@ -284,5 +322,6 @@ struct platform_driver dw_dp_driver = { .driver = { .name = "dw-dp", .of_match_table = dw_dp_of_match, + .pm = pm_ptr(&dw_dp_pm_ops), }, }; From 49f0c61ecd66092b196b75f41bd38f3cf423a072 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 23 Jan 2026 23:10:22 +0100 Subject: [PATCH 104/208] [RFC] dt-bindings: display: rockchip: dw-dp: fix sound DAI cells The RK3588 and RK3576 DesignWare DisplayPort controllers both have two possible DAI interfaces: I2S and S/PDIF. Thus it is needed to have an argument to select the right interface. In case of RK3576 this is not enough though. The RK3576 has the same IP as RK3588, but configured with Multi Stream Transport (MST) enabled for up to 3 displays and thus has a total of 6 DAI interfaces (I2S and S/PDIF for each possible stream. Meanwhile the RK3588 does not support MST and thus has only 2 DAI interfaces. The binding update right now only supports the simple single stream transport (SST) setup. To avoid further DT ABI breakage (or complicated bindings supporting different number of arguments), it's probably a good idea to take MST into account now even though the upstream Linux driver does not yet support it. I see two options: 1. Adding yet another cell, so that we have the following: <&dp_ctrl [display_stream] [i2s_or_spdif]>; potentially append extra input ports for MST video data to existing ports node (e.g. port@2). I would only handle the sound DAI part in my patch and basically use '0' for the display stream and just leave the option of using '1' or '2' once MST support is added. 2. The vendor kernel creates a sub-node for each supported display stream and puts the ports mapping as well as the DAI reference into that. This bundles all information for one display stream together, which creates a clean look but the subnode does not really describe any real thing in the hardware. As upstream MST support seems to be quite limited, I wish for some feedback about the preferred way to handle this. Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml index 2b0d9e23e9432f..1303d0e2145a97 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml @@ -83,7 +83,8 @@ properties: maxItems: 1 "#sound-dai-cells": - const: 0 + const: 1 + description: 0 for I2S, 1 for SPDIF required: - compatible @@ -144,7 +145,7 @@ examples: resets = <&cru SRST_DP0>; phys = <&usbdp_phy0 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; ports { #address-cells = <1>; From a3cd3f2d114debbb633e58bff3fb50fc37b38e6b Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 21 Jan 2026 06:01:21 +0100 Subject: [PATCH 105/208] arm64: dts: rockchip: Add DP sound support to RK3576 device tree Add support for enabling sound for the DisplayPort to the RK3576 DT. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576.dtsi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index 28175d8200d57c..f62a0d01fe0549 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -344,6 +344,21 @@ }; }; + dp0_sound: dp0-sound { + compatible = "simple-audio-card"; + simple-audio-card,mclk-fs = <512>; + simple-audio-card,name = "DP0"; + status = "disabled"; + + simple-audio-card,codec { + sound-dai = <&dp 1>; + }; + + simple-audio-card,cpu { + sound-dai = <&spdif_tx3>; + }; + }; + gpu_opp_table: opp-table-gpu { compatible = "operating-points-v2"; @@ -1508,6 +1523,7 @@ resets = <&cru SRST_DP0>; phys = <&usbdp_phy PHY_TYPE_DP>; power-domains = <&power RK3576_PD_VO1>; + #sound-dai-cells = <1>; status = "disabled"; ports { From 4f58ff371e36cf81940d3b5a9e8a9a3864b46eb9 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 21 Jan 2026 06:02:48 +0100 Subject: [PATCH 106/208] arm64: dts: rockchip: Add DP audio for ArmSom Sige5 Add audio support to the USB-C DisplayPort Alternate Mode. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index 411253b6eff038..b76b9c9fe0110b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -288,6 +288,10 @@ }; }; +&dp0_sound { + status = "okay"; +}; + &gmac0 { phy-mode = "rgmii-id"; clock_in_out = "output"; @@ -966,6 +970,10 @@ status = "okay"; }; +&spdif_tx3 { + status = "okay"; +}; + &u2phy0 { status = "okay"; }; From 05f7ad36b03e8b8ab2d345e887a8f4a99851eabb Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 12 Feb 2026 22:22:54 +0100 Subject: [PATCH 107/208] arm64: dts: rockchip: Fix DP sound-dai-cells on RK3588 The DP controller has a I2S and SPDIF interface and thus needs one cell to specify the interface being used. Signed-off-by: Sebastian Reichel --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 2 +- arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index e8855b26267d88..43c81727354fab 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1946,7 +1946,7 @@ phys = <&usbdp_phy0 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; resets = <&cru SRST_DP0>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; status = "disabled"; ports { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi index a2640014ee0421..b4d7843918cce7 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi @@ -223,7 +223,7 @@ phys = <&usbdp_phy1 PHY_TYPE_DP>; power-domains = <&power RK3588_PD_VO0>; resets = <&cru SRST_DP1>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; status = "disabled"; ports { From 93dbccb4ff9c39a678d8965d51b914e90f3077d3 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 16:23:15 +0200 Subject: [PATCH 108/208] usb: typec: tcpm: add device managed port registration A few drivers would benefit from having device managed TCPM port registration, so that their probe routine does not mix device managed and non-device managed calls. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/tcpm.c | 24 ++++++++++++++++++++++++ include/linux/usb/tcpm.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index b8c36f9a880527..0ab22eafaf35f0 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -8659,6 +8659,30 @@ void tcpm_unregister_port(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_unregister_port); +static void devm_tcpm_unregister_port(void *data) +{ + struct tcpm_port *port = data; + tcpm_unregister_port(port); +} + +struct tcpm_port *devm_tcpm_register_port(struct device *dev, + struct tcpc_dev *tcpc) +{ + struct tcpm_port *result; + int ret; + + result = tcpm_register_port(dev, tcpc); + if (IS_ERR(result)) + return result; + + ret = devm_add_action_or_reset(dev, devm_tcpm_unregister_port, result); + if (ret < 0) + return ERR_PTR(ret); + + return result; +} +EXPORT_SYMBOL_GPL(devm_tcpm_register_port); + MODULE_AUTHOR("Guenter Roeck "); MODULE_DESCRIPTION("USB Type-C Port Manager"); MODULE_LICENSE("GPL"); diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 93079450bba01f..fc60980f9f41e8 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -177,6 +177,9 @@ struct tcpm_port; struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc); void tcpm_unregister_port(struct tcpm_port *port); +struct tcpm_port *devm_tcpm_register_port(struct device *dev, + struct tcpc_dev *tcpc); + void tcpm_vbus_change(struct tcpm_port *port); void tcpm_cc_change(struct tcpm_port *port); void tcpm_sink_frs(struct tcpm_port *port); From e31237d411a545fd8cac85156e19a737f3906e93 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 17:41:17 +0200 Subject: [PATCH 109/208] usb: typec: fusb302: Switch to device managed resources The driver's probe routine is currently mixing registration calls for device managed resources with unmananged ones. This results in issues such as resource leaks or use-after-free. Fix this up by fully converting the driver to device managed resources. Resource acquisition has been reordered a bit, so that the work is initialized after the TCPM port is registered as the work uses the TCPM devices. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 112 +++++++++++++++++-------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 34354c47c49547..69b1af1358d11f 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -212,20 +213,28 @@ static int fusb302_debug_show(struct seq_file *s, void *v) } DEFINE_SHOW_ATTRIBUTE(fusb302_debug); -static void fusb302_debugfs_init(struct fusb302_chip *chip) +static void fusb302_debugfs_exit(void *data) +{ + struct fusb302_chip *chip = data; + + debugfs_remove(chip->dentry); +} + +static int fusb302_debugfs_init(struct fusb302_chip *chip) { char name[NAME_MAX]; + int ret; + + ret = devm_mutex_init(chip->dev, &chip->logbuffer_lock); + if (ret < 0) + return ret; - mutex_init(&chip->logbuffer_lock); snprintf(name, NAME_MAX, "fusb302-%s", dev_name(chip->dev)); chip->dentry = debugfs_create_dir(name, usb_debug_root); debugfs_create_file("log", S_IFREG | 0444, chip->dentry, chip, &fusb302_debug_fops); -} -static void fusb302_debugfs_exit(struct fusb302_chip *chip) -{ - debugfs_remove(chip->dentry); + return devm_add_action_or_reset(chip->dev, fusb302_debugfs_exit, chip); } #else @@ -233,7 +242,6 @@ static void fusb302_debugfs_exit(struct fusb302_chip *chip) static void fusb302_log(const struct fusb302_chip *chip, const char *fmt, ...) { } static void fusb302_debugfs_init(const struct fusb302_chip *chip) { } -static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } #endif @@ -1688,6 +1696,13 @@ static struct fwnode_handle *fusb302_fwnode_get(struct device *dev) return fwnode; } +static void fusb302_fwnode_put(void *data) +{ + struct fusb302_chip *chip = data; + + fwnode_handle_put(chip->tcpc_dev.fwnode); +} + static int fusb302_probe(struct i2c_client *client) { struct fusb302_chip *chip; @@ -1708,7 +1723,10 @@ static int fusb302_probe(struct i2c_client *client) chip->i2c_client = client; chip->dev = &client->dev; - mutex_init(&chip->lock); + + ret = devm_mutex_init(dev, &chip->lock); + if (ret < 0) + return ret; /* * Devicetree platforms should get extcon via phandle (not yet @@ -1727,67 +1745,68 @@ static int fusb302_probe(struct i2c_client *client) if (IS_ERR(chip->vbus)) return PTR_ERR(chip->vbus); - chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); + chip->wq = devm_alloc_ordered_workqueue(dev, dev_name(dev), 0); if (!chip->wq) return -ENOMEM; spin_lock_init(&chip->irq_lock); - INIT_WORK(&chip->irq_work, fusb302_irq_work); - INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); init_tcpc_dev(&chip->tcpc_dev); - fusb302_debugfs_init(chip); + + ret = fusb302_debugfs_init(chip); + if (ret < 0) + return ret; if (client->irq) { chip->gpio_int_n_irq = client->irq; } else { ret = init_gpio(chip); if (ret < 0) - goto destroy_workqueue; + return ret; } chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); if (IS_ERR(chip->tcpc_dev.fwnode)) { ret = PTR_ERR(chip->tcpc_dev.fwnode); - goto destroy_workqueue; + return ret; } + ret = devm_add_action_or_reset(dev, fusb302_fwnode_put, chip); + if (ret < 0) + return ret; + bridge_dev = devm_drm_dp_hpd_bridge_alloc(chip->dev, to_of_node(chip->tcpc_dev.fwnode)); - if (IS_ERR(bridge_dev)) { - ret = dev_err_probe(chip->dev, PTR_ERR(bridge_dev), - "failed to alloc bridge\n"); - goto fwnode_put; - } + if (IS_ERR(bridge_dev)) + return dev_err_probe(chip->dev, PTR_ERR(bridge_dev), + "failed to alloc bridge\n"); - chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); - if (IS_ERR(chip->tcpm_port)) { - ret = dev_err_probe(dev, PTR_ERR(chip->tcpm_port), + chip->tcpm_port = devm_tcpm_register_port(&client->dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) + return dev_err_probe(dev, PTR_ERR(chip->tcpm_port), "cannot register tcpm port\n"); - goto fwnode_put; - } - ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); - if (ret) - goto tcpm_unregister_port; + ret = devm_delayed_work_autocancel(dev, &chip->bc_lvl_handler, + fusb302_bc_lvl_handler_work); + if (ret < 0) + return ret; + + ret = devm_work_autocancel(dev, &chip->irq_work, fusb302_irq_work); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(dev, chip->gpio_int_n_irq, NULL, + fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to request IRQ"); - ret = request_threaded_irq(chip->gpio_int_n_irq, NULL, fusb302_irq_intn, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "fsc_interrupt_int_n", chip); - if (ret < 0) { - dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); - goto tcpm_unregister_port; - } - enable_irq_wake(chip->gpio_int_n_irq); i2c_set_clientdata(client, chip); - return 0; + ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); + if (ret) + return ret; -tcpm_unregister_port: - tcpm_unregister_port(chip->tcpm_port); -fwnode_put: - fwnode_handle_put(chip->tcpc_dev.fwnode); -destroy_workqueue: - fusb302_debugfs_exit(chip); - destroy_workqueue(chip->wq); + enable_irq_wake(chip->gpio_int_n_irq); return ret; } @@ -1797,13 +1816,6 @@ static void fusb302_remove(struct i2c_client *client) struct fusb302_chip *chip = i2c_get_clientdata(client); disable_irq_wake(chip->gpio_int_n_irq); - free_irq(chip->gpio_int_n_irq, chip); - cancel_work_sync(&chip->irq_work); - cancel_delayed_work_sync(&chip->bc_lvl_handler); - tcpm_unregister_port(chip->tcpm_port); - fwnode_handle_put(chip->tcpc_dev.fwnode); - destroy_workqueue(chip->wq); - fusb302_debugfs_exit(chip); } static int fusb302_pm_suspend(struct device *dev) From fc9139a871245f6b62e7fc8d7401ea277e21aaf7 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 18:01:37 +0200 Subject: [PATCH 110/208] usb: typec: fusb302: rename init_gpio into fusb302_init_irq Add the fusb302_ prefix to init_gpio, since it is used by all the other functions and allows easy figuring out that it is a local function. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 69b1af1358d11f..6959c3459d6abd 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1644,7 +1644,7 @@ static void fusb302_irq_work(struct work_struct *work) enable_irq(chip->gpio_int_n_irq); } -static int init_gpio(struct fusb302_chip *chip) +static int fusb302_init_irq(struct fusb302_chip *chip) { struct device *dev = chip->dev; int ret = 0; @@ -1759,7 +1759,7 @@ static int fusb302_probe(struct i2c_client *client) if (client->irq) { chip->gpio_int_n_irq = client->irq; } else { - ret = init_gpio(chip); + ret = fusb302_init_irq(chip); if (ret < 0) return ret; } From 2ded126584163dfc7f16f235bf96091aa6d6c840 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 18:03:15 +0200 Subject: [PATCH 111/208] usb: typec: fusb302: move gpio_int_n into function local scope The driver only cares about the interrupt and the GPIO itself is device managed, so drop the reference from the device structure and only keep it in the function scope. While at it cleanup the error handling by using dev_err_probe. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 6959c3459d6abd..6334d5e3fd81c8 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -85,7 +85,6 @@ struct fusb302_chip { struct work_struct irq_work; bool irq_suspended; bool irq_while_suspended; - struct gpio_desc *gpio_int_n; int gpio_int_n_irq; struct extcon_dev *extcon; @@ -1647,19 +1646,17 @@ static void fusb302_irq_work(struct work_struct *work) static int fusb302_init_irq(struct fusb302_chip *chip) { struct device *dev = chip->dev; + struct gpio_desc *int_gpio; int ret = 0; - chip->gpio_int_n = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); - if (IS_ERR(chip->gpio_int_n)) { - dev_err(dev, "failed to request gpio_int_n\n"); - return PTR_ERR(chip->gpio_int_n); - } - ret = gpiod_to_irq(chip->gpio_int_n); - if (ret < 0) { - dev_err(dev, - "cannot request IRQ for GPIO Int_N, ret=%d", ret); - return ret; - } + int_gpio = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); + if (IS_ERR(int_gpio)) + return dev_err_probe(dev, PTR_ERR(int_gpio), "failed to request interrupt GPIO\n"); + + ret = gpiod_to_irq(int_gpio); + if (ret < 0) + return dev_err_probe(dev, ret, "cannot request IRQ for GPIO\n"); + chip->gpio_int_n_irq = ret; return 0; } From 93ce71853bb9023d318ed15c7ea651ac367a5597 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 18:10:26 +0200 Subject: [PATCH 112/208] usb: typec: fusb302: rework gpio_int_n_irq handling Improve the code readability, so that one does not need to jump into fusb302_init_irq() to understand that chip->gpio_int_n_irq is guranteed to be initialized. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 6334d5e3fd81c8..f1de6d9cff1ce6 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1657,8 +1657,7 @@ static int fusb302_init_irq(struct fusb302_chip *chip) if (ret < 0) return dev_err_probe(dev, ret, "cannot request IRQ for GPIO\n"); - chip->gpio_int_n_irq = ret; - return 0; + return ret; } #define PDO_FIXED_FLAGS \ @@ -1753,13 +1752,13 @@ static int fusb302_probe(struct i2c_client *client) if (ret < 0) return ret; - if (client->irq) { + if (client->irq) chip->gpio_int_n_irq = client->irq; - } else { - ret = fusb302_init_irq(chip); - if (ret < 0) - return ret; - } + else + chip->gpio_int_n_irq = fusb302_init_irq(chip); + + if (chip->gpio_int_n_irq < 0) + return chip->gpio_int_n_irq; chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); if (IS_ERR(chip->tcpc_dev.fwnode)) { From 7ad03b800a6320cc21cfc182ecb3809e00e1f39d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 21 Apr 2026 18:17:02 +0200 Subject: [PATCH 113/208] usb: typec: fusb302: drop custom gpio interrupt logic When the driver was upstreamed it contained the logic to fetch a "fcs,int_n" GPIO from device-tree, convert it into an interrupt and use it. This was never part of the binding and there was only a single upstream user, which got converted to follow the proper bindings in 2020. Drop the custom logic and only allow the properly documented ABI. Signed-off-by: Sebastian Reichel --- drivers/usb/typec/tcpm/fusb302.c | 41 ++++++++------------------------ 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index f1de6d9cff1ce6..5a166caff54676 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -85,7 +84,7 @@ struct fusb302_chip { struct work_struct irq_work; bool irq_suspended; bool irq_while_suspended; - int gpio_int_n_irq; + int irq; struct extcon_dev *extcon; struct workqueue_struct *wq; @@ -1496,7 +1495,7 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) unsigned long flags; /* Disable our level triggered IRQ until our irq_work has cleared it */ - disable_irq_nosync(chip->gpio_int_n_irq); + disable_irq_nosync(chip->irq); spin_lock_irqsave(&chip->irq_lock, flags); if (chip->irq_suspended) @@ -1640,24 +1639,7 @@ static void fusb302_irq_work(struct work_struct *work) } done: mutex_unlock(&chip->lock); - enable_irq(chip->gpio_int_n_irq); -} - -static int fusb302_init_irq(struct fusb302_chip *chip) -{ - struct device *dev = chip->dev; - struct gpio_desc *int_gpio; - int ret = 0; - - int_gpio = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); - if (IS_ERR(int_gpio)) - return dev_err_probe(dev, PTR_ERR(int_gpio), "failed to request interrupt GPIO\n"); - - ret = gpiod_to_irq(int_gpio); - if (ret < 0) - return dev_err_probe(dev, ret, "cannot request IRQ for GPIO\n"); - - return ret; + enable_irq(chip->irq); } #define PDO_FIXED_FLAGS \ @@ -1752,13 +1734,10 @@ static int fusb302_probe(struct i2c_client *client) if (ret < 0) return ret; - if (client->irq) - chip->gpio_int_n_irq = client->irq; - else - chip->gpio_int_n_irq = fusb302_init_irq(chip); - - if (chip->gpio_int_n_irq < 0) - return chip->gpio_int_n_irq; + chip->irq = client->irq; + if (!chip->irq) + return dev_err_probe(chip->dev, -ENXIO, + "missing interrupt\n"); chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); if (IS_ERR(chip->tcpc_dev.fwnode)) { @@ -1789,7 +1768,7 @@ static int fusb302_probe(struct i2c_client *client) if (ret < 0) return ret; - ret = devm_request_threaded_irq(dev, chip->gpio_int_n_irq, NULL, + ret = devm_request_threaded_irq(dev, chip->irq, NULL, fusb302_irq_intn, IRQF_ONESHOT | IRQF_TRIGGER_LOW, "fsc_interrupt_int_n", chip); @@ -1802,7 +1781,7 @@ static int fusb302_probe(struct i2c_client *client) if (ret) return ret; - enable_irq_wake(chip->gpio_int_n_irq); + enable_irq_wake(chip->irq); return ret; } @@ -1811,7 +1790,7 @@ static void fusb302_remove(struct i2c_client *client) { struct fusb302_chip *chip = i2c_get_clientdata(client); - disable_irq_wake(chip->gpio_int_n_irq); + disable_irq_wake(chip->irq); } static int fusb302_pm_suspend(struct device *dev) From 6326472fb0bd11e23378dd9ce3ee7ca0eebd2d97 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 18 Mar 2026 01:17:57 +0200 Subject: [PATCH 114/208] phy: hdmi: Add optional FRL TxFFE config options During HDMI 2.1 FRL link training, the source and sink can negotiate a Transmitter Feed-Forward Equalization (TxFFE) level to improve the signal quality. Starting from zero, the source may increment the TxFFE level up to a maximum agreed during the LTS3 stage if the sink keeps reporting FLT failures. TxFFE adjustment is optional and only attempted when both the source and the connected sink support it. Since the existing HDMI PHY configuration API covers the FRL rate/lane selection only, provide the following fields to the frl sub-struct of phy_configure_opts_hdmi: * ffe_level: the TxFFE level to apply, only meaningful when set_ffe_level is set. * set_ffe_level: a 1-bit flag that changes the semantics of the phy_configure() call, i.e. when set, the PHY driver must apply the new ffe_level and ignore the other frl related fields. The flag-based approach reflects an important invariant in the link training process: whenever the FRL rate or lane count changes, the TxFFE level must be reset to zero. A separate phy_configure() call with set_ffe_level can only follow after the rate has been established, making the two operations deliberately distinct. Signed-off-by: Cristian Ciocaltea --- include/linux/phy/phy-hdmi.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/phy/phy-hdmi.h b/include/linux/phy/phy-hdmi.h index d4cf4430ee8f3b..1d4b6247507979 100644 --- a/include/linux/phy/phy-hdmi.h +++ b/include/linux/phy/phy-hdmi.h @@ -19,6 +19,10 @@ enum phy_hdmi_mode { * @tmds_char_rate: HDMI TMDS Character Rate in Hertz. * @frl.rate_per_lane: HDMI FRL Rate per Lane in Gbps. * @frl.lanes: HDMI FRL lanes count. + * @frl.ffe_level: Transmitter Feed Forward Equalizer Level. + * Optional, only meaningful when set_ffe_level flag is on. + * @frl.set_ffe_level: Flag indicating whether or not to reconfigure ffe_level. + * All the other struct fields must be ignored when this is used. * * This structure is used to represent the configuration state of a HDMI phy. */ @@ -29,6 +33,8 @@ struct phy_configure_opts_hdmi { struct { u8 rate_per_lane; u8 lanes; + u8 ffe_level; + u8 set_ffe_level : 1; } frl; }; }; From 9a3f154b423143bf01416ebe6e8a22bd6df3b6b6 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 18 Mar 2026 01:43:24 +0200 Subject: [PATCH 115/208] phy: rockchip: samsung-hdptx: Add support for FRL TxFFE level control During HDMI 2.1 FRL link training, the source may need to incrementally raise the TxFFE level in response to persistent link failures reported by the sink during LTS3. The phy_configure_opts_hdmi struct now carries ffe_level and set_ffe_level fields to convey such an update independently of a full rate reconfiguration. Wire up the optional TxFFE control in the Samsung HDPTX PHY driver. Signed-off-by: Cristian Ciocaltea --- .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 74 +++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 0684e1a0d1a17d..64787d47025f93 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -333,6 +333,7 @@ #define FRL_3G3L_RATE 900000000 #define FRL_6G3L_RATE 1800000000 #define FRL_8G4L_RATE 3200000000 +#define FRL_FFE_MAX_LEVEL 3 enum dp_link_rate { DP_BW_RBR, @@ -466,6 +467,16 @@ static const struct ropll_config rk_hdptx_tmds_ropll_cfg[] = { { 25175000ULL, 84, 84, 1, 1, 15, 1, 168, 1, 16, 4, 1, 1, }, }; +static const struct ffe_config { + u8 pre_shoot; + u8 de_emphasis; +} rk_hdptx_frl_ffe_cfg[FRL_FFE_MAX_LEVEL + 1] = { + { 0x3, 0x4 }, + { 0x3, 0x6 }, + { 0x3, 0x8 }, + { 0x3, 0x9 }, +}; + static const struct reg_sequence rk_hdptx_common_cmn_init_seq[] = { REG_SEQ0(CMN_REG(0009), 0x0c), REG_SEQ0(CMN_REG(000a), 0x83), @@ -1323,6 +1334,45 @@ static int rk_hdptx_tmds_ropll_mode_config(struct rk_hdptx_phy *hdptx) return rk_hdptx_post_enable_lane(hdptx); } +static int rk_hdptx_frl_ffe_config(struct rk_hdptx_phy *hdptx, u8 ffe_level) +{ + u8 val; + + if (ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + val = rk_hdptx_frl_ffe_cfg[ffe_level].pre_shoot; + + regmap_update_bits(hdptx->regmap, LANE_REG(0305), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0405), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0505), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0605), + LN_TX_DRV_PRE_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val)); + + val = rk_hdptx_frl_ffe_cfg[ffe_level].de_emphasis; + + regmap_update_bits(hdptx->regmap, LANE_REG(0304), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0404), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0504), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + regmap_update_bits(hdptx->regmap, LANE_REG(0604), + LN_TX_DRV_POST_LVL_CTRL_MASK, + FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val)); + return 0; +} + static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx) { reset_control_assert(hdptx->rsts[RST_LANE].rstc); @@ -1734,6 +1784,13 @@ static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx, unsigned long long frl_rate = 100000000ULL * hdmi_in->frl.lanes * hdmi_in->frl.rate_per_lane; + if (hdmi_in->frl.set_ffe_level) { + if (hdmi_in->frl.ffe_level > FRL_FFE_MAX_LEVEL) + return -EINVAL; + + return 0; + } + switch (hdmi_in->frl.rate_per_lane) { case 3: case 6: @@ -2080,11 +2137,18 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt if (ret) { dev_err(hdptx->dev, "invalid hdmi params for phy configure\n"); } else { - hdptx->pll_config_dirty = true; - - dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, - hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", - hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL && + opts->hdmi.frl.set_ffe_level) { + dev_dbg(hdptx->dev, "%s ffe_level=%u\n", __func__, + opts->hdmi.frl.ffe_level); + ret = rk_hdptx_frl_ffe_config(hdptx, opts->hdmi.frl.ffe_level); + } else { + hdptx->pll_config_dirty = true; + + dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__, + hdptx->hdmi_cfg.mode ? "FRL" : "TMDS", + hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc); + } } return ret; From 87be3cab176df20ff9e5587667e340442bfc5d8d Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 10 Apr 2026 04:19:28 +0300 Subject: [PATCH 116/208] [WIP-update-fixes] phy: rockchip: samsung-hdptx: Handle PHY config after module reload The pll_config_dirty mechanism introduced in commit e63ea089a8ab ("phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes") invalidates the clock rate in determine_rate() by resetting req->rate to zero, ensuring CCF will invoke set_rate() to program pending PLL configuration changes into hardware. However, after a module reload cycle the PHY PLL clock gets re-registered with CCF, which causes the framework's cached rate to also be zero. Setting req->rate to zero then has no effect, since CCF sees no difference between the requested and current rates and skips calling set_rate(), leaving the PLL unconfigured. Address this by first computing the actual target rate from the HDMI link configuration, and only then invalidating it when it matches the CCF cached rate. Fixes: e63ea089a8ab ("phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes") Signed-off-by: Cristian Ciocaltea --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 64787d47025f93..7e7e51f8e7e54f 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -2387,14 +2387,16 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw, * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked. * Otherwise, restrict the rate according to the PHY link setup. */ - if (hdptx->pll_config_dirty) - req->rate = 0; - else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) + + if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) req->rate = hdptx->hdmi_cfg.rate; else req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc); + if (hdptx->pll_config_dirty && req->rate == clk_hw_get_rate(hw)) + req->rate = 0; + return 0; } From 45537984fc9e4d750e681d21f72257db64274b87 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sun, 2 Mar 2025 19:47:18 +0200 Subject: [PATCH 117/208] [DEBUG] drm/rockchip: vop2: Log DCLK setup Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 1ad6369336d2fb..9f30d2c7a2b400 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -958,8 +958,10 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); - if (vp->dclk_src) + if (vp->dclk_src) { + dev_info(vop2->dev, "reseting dclk parent\n"); clk_set_parent(vp->dclk, vp->dclk_src); + } clk_disable_unprepare(vp->dclk); @@ -1792,6 +1794,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, if (!vop2->pll_hdmiphy0) break; + dev_info(vop2->dev, "reparenting HDMI0 dclk\n"); if (!vp->dclk_src) vp->dclk_src = clk_get_parent(vp->dclk); @@ -1807,6 +1810,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, if (!vop2->pll_hdmiphy1) break; + dev_info(vop2->dev, "reparenting HDMI1 dclk\n"); if (!vp->dclk_src) vp->dclk_src = clk_get_parent(vp->dclk); @@ -1821,6 +1825,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, } } + dev_info(vop2->dev, "setting dclk rate=%lu (crt=%lu)\n", clock, clk_get_rate(vp->dclk)); clk_set_rate(vp->dclk, clock); vop2_post_config(crtc); From 5eacf36c3c2209e9efd6b7744f8c8b3ce697fc68 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 21 Apr 2026 23:51:17 +0300 Subject: [PATCH 118/208] drm/bridge: Remove redundant error check in drm_bridge_helper_reset_crtc() Remove the no-op error check after drm_atomic_helper_reset_crtc() since the goto target is the immediately following label and the return value is already propagated correctly without it. Reviewed-by: Dmitry Baryshkov Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_bridge_helper.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c index 420f29cf3e5435..0a3c8fee66b325 100644 --- a/drivers/gpu/drm/drm_bridge_helper.c +++ b/drivers/gpu/drm/drm_bridge_helper.c @@ -50,8 +50,6 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge, crtc = connector->state->crtc; ret = drm_atomic_helper_reset_crtc(crtc, ctx); - if (ret) - goto out; out: drm_modeset_unlock(&dev->mode_config.connection_mutex); From 566ddb37865531b1e36e05a5d5f7033af61b3a0c Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 10 Jan 2025 22:48:01 +0200 Subject: [PATCH 119/208] drm/bridge: Add detect_ctx hook and drm_bridge_detect_ctx() helper Add an atomic-aware .detect_ctx() callback to drm_bridge_funcs and a drm_bridge_detect_ctx() helper that accepts an optional drm_modeset_acquire_ctx. This enables bridge drivers to perform operations requiring modeset locking during connector detection, such as SCDC management for HDMI 2.0. When both ->detect_ctx and ->detect are defined, the former takes precedence. When ctx is NULL, locking is managed internally with EDEADLK retry. Tested-by: Diederik de Haas Tested-by: Maud Spierings Acked-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_bridge.c | 66 +++++++++++++++++++++++++++++++++--- include/drm/drm_bridge.h | 43 +++++++++++++++++++---- 2 files changed, 98 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index d6f512b733896d..62c3393cb6bcbd 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1360,9 +1360,9 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_check); * @connector: attached connector * * If the bridge supports output detection, as reported by the - * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect for the - * bridge and return the connection status. Otherwise return - * connector_status_unknown. + * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect_ctx + * or &drm_bridge_funcs.detect for the bridge and return the connection status. + * Otherwise return connector_status_unknown. * * RETURNS: * The detection status on success, or connector_status_unknown if the bridge @@ -1371,12 +1371,68 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_check); enum drm_connector_status drm_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) { + return drm_bridge_detect_ctx(bridge, connector, NULL); +} +EXPORT_SYMBOL_GPL(drm_bridge_detect); + +/** + * drm_bridge_detect_ctx - check if anything is attached to the bridge output + * @bridge: bridge control structure + * @connector: attached connector + * @ctx: acquire_ctx, or NULL to let this function handle locking + * + * If the bridge supports output detection, as reported by the + * DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect_ctx + * or &drm_bridge_funcs.detect for the bridge and return the connection status. + * Otherwise return connector_status_unknown. + * + * RETURNS: + * The detection status on success, or connector_status_unknown if the bridge + * doesn't support output detection. + * If @ctx is set, it might also return -EDEADLK. + */ +int drm_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_modeset_acquire_ctx br_ctx; + int ret; + if (!(bridge->ops & DRM_BRIDGE_OP_DETECT)) return connector_status_unknown; - return bridge->funcs->detect(bridge, connector); + if (!bridge->funcs->detect_ctx) + return bridge->funcs->detect(bridge, connector); + + if (ctx) { + ret = bridge->funcs->detect_ctx(bridge, connector, ctx); + if (ret == -EDEADLK) + return ret; + + goto out; + } + + drm_modeset_acquire_init(&br_ctx, 0); +retry: + ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, + &br_ctx); + if (!ret) + ret = bridge->funcs->detect_ctx(bridge, connector, &br_ctx); + + if (ret == -EDEADLK) { + drm_modeset_backoff(&br_ctx); + goto retry; + } + + drm_modeset_drop_locks(&br_ctx); + drm_modeset_acquire_fini(&br_ctx); +out: + if (WARN_ON(ret < 0)) + ret = connector_status_unknown; + + return ret; } -EXPORT_SYMBOL_GPL(drm_bridge_detect); +EXPORT_SYMBOL_GPL(drm_bridge_detect_ctx); /** * drm_bridge_get_modes - fill all modes currently valid for the sink into the diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 1ad9ae50c829cc..6626c1afbd5940 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -535,10 +535,12 @@ struct drm_bridge_funcs { * * Check if anything is attached to the bridge output. * - * This callback is optional, if not implemented the bridge will be - * considered as always having a component attached to its output. - * Bridges that implement this callback shall set the - * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops. + * This is the non-atomic version of detect_ctx() callback, and is + * optional. If both are implemented, it is ignored. If none is + * implemented, the bridge will be considered as always having a + * component attached to its output. Bridges that implement this + * callback shall set the DRM_BRIDGE_OP_DETECT flag in their + * &drm_bridge->ops. * * RETURNS: * @@ -547,6 +549,32 @@ struct drm_bridge_funcs { enum drm_connector_status (*detect)(struct drm_bridge *bridge, struct drm_connector *connector); + /** + * @detect_ctx: + * + * Check if anything is attached to the bridge output. + * + * This is the atomic version of detect() callback, and is optional. + * If both are implemented, it takes precedence. If none is implemented, + * the bridge will be considered as always having a component attached + * to its output. Bridges that implement this callback shall set the + * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops. + * + * To avoid races against concurrent connector state updates, the + * helper libraries always call this with ctx set to a valid context, + * and &drm_mode_config.connection_mutex will always be locked with + * the ctx parameter set to this ctx. This allows taking additional + * locks as required. + * + * RETURNS: + * + * &drm_connector_status indicating the bridge output status, + * or the error code returned by drm_modeset_lock(), -EDEADLK. + */ + int (*detect_ctx)(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); + /** * @get_modes: * @@ -1016,8 +1044,8 @@ struct drm_bridge_timings { enum drm_bridge_ops { /** * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to - * its output. Bridges that set this flag shall implement the - * &drm_bridge_funcs->detect callback. + * its output. Bridges that set this flag shall implement either the + * &drm_bridge_funcs->detect or &drm_bridge_funcs->detect_ctx callbacks. */ DRM_BRIDGE_OP_DETECT = BIT(0), /** @@ -1558,6 +1586,9 @@ drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge, enum drm_connector_status drm_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); +int drm_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); int drm_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector); const struct drm_edid *drm_bridge_edid_read(struct drm_bridge *bridge, From adae1e6ccefbc53bfbca1fe7a5fa3ae8f954b8d5 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 22 Apr 2026 01:26:32 +0300 Subject: [PATCH 120/208] drm/bridge-connector: Use cached connector status in .get_modes() Replace the active drm_bridge_connector_detect() call in get_modes() with a read of the already-cached connector->status. The .get_modes() callback is only invoked from drm_helper_probe_single_connector_modes(), which has already retrieved the connector status. Calling detect again is redundant and triggers a duplicate hotplug event. This is also a prerequisite for switching to the .detect_ctx() hook, which requires a drm_modeset_acquire_ctx not available in the .get_modes() path. Reviewed-by: Dmitry Baryshkov Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_bridge_connector.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 4333cc66073ecf..2b74373cac5d53 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -300,12 +300,10 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = { static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { - enum drm_connector_status status; const struct drm_edid *drm_edid; int n; - status = drm_bridge_connector_detect(connector, false); - if (status != connector_status_connected) + if (connector->status != connector_status_connected) goto no_edid; drm_edid = drm_bridge_edid_read(bridge, connector); From c85969ba9b9d51a78da6b75a1c8bb7291be0a11b Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 10 Jan 2025 23:04:23 +0200 Subject: [PATCH 121/208] drm/bridge-connector: Switch to .detect_ctx() for connector detection Use the atomic .detect_ctx() connector helper hook and invoke drm_bridge_detect_ctx() to propagate the modeset acquire context to bridge drivers. This enables bridge drivers to perform modeset operations during detection, which is needed for managing SCDC state lost on sink disconnects in HDMI 2.0 scenarios. Tested-by: Diederik de Haas Tested-by: Maud Spierings Reviewed-by: Dmitry Baryshkov Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 2b74373cac5d53..d3aaa81838320e 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -214,39 +214,6 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector) * Bridge Connector Functions */ -static enum drm_connector_status -drm_bridge_connector_detect(struct drm_connector *connector, bool force) -{ - struct drm_bridge_connector *bridge_connector = - to_drm_bridge_connector(connector); - struct drm_bridge *detect = bridge_connector->bridge_detect; - struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; - enum drm_connector_status status; - - if (detect) { - status = detect->funcs->detect(detect, connector); - - if (hdmi) - drm_atomic_helper_connector_hdmi_hotplug(connector, status); - - drm_bridge_connector_hpd_notify(connector, status); - } else { - switch (connector->connector_type) { - case DRM_MODE_CONNECTOR_DPI: - case DRM_MODE_CONNECTOR_LVDS: - case DRM_MODE_CONNECTOR_DSI: - case DRM_MODE_CONNECTOR_eDP: - status = connector_status_connected; - break; - default: - status = connector_status_unknown; - break; - } - } - - return status; -} - static void drm_bridge_connector_force(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = @@ -284,7 +251,6 @@ static void drm_bridge_connector_reset(struct drm_connector *connector) static const struct drm_connector_funcs drm_bridge_connector_funcs = { .reset = drm_bridge_connector_reset, - .detect = drm_bridge_connector_detect, .force = drm_bridge_connector_force, .fill_modes = drm_helper_probe_single_connector_modes, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -297,6 +263,42 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = { * Bridge Connector Helper Functions */ +static int drm_bridge_connector_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *detect = bridge_connector->bridge_detect; + struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; + int ret; + + if (detect) { + ret = drm_bridge_detect_ctx(detect, connector, ctx); + if (ret < 0) + return ret; + + if (hdmi) + drm_atomic_helper_connector_hdmi_hotplug(connector, ret); + + drm_bridge_connector_hpd_notify(connector, ret); + } else { + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DPI: + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_DSI: + case DRM_MODE_CONNECTOR_eDP: + ret = connector_status_connected; + break; + default: + ret = connector_status_unknown; + break; + } + } + + return ret; +} + static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { @@ -388,6 +390,7 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector, static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = { .get_modes = drm_bridge_connector_get_modes, + .detect_ctx = drm_bridge_connector_detect_ctx, .mode_valid = drm_bridge_connector_mode_valid, .enable_hpd = drm_bridge_connector_enable_hpd, .disable_hpd = drm_bridge_connector_disable_hpd, From c2a5b26fa8d00b50bfcc1268a505229f3ddd5c64 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Fri, 13 Sep 2024 17:30:35 +0300 Subject: [PATCH 122/208] drm/bridge: dw-hdmi-qp: Add HDMI 2.0 SCDC scrambling and high TMDS clock ratio support Enable HDMI 2.0 display modes (e.g. 4K@60Hz) by adding SCDC management for the high TMDS clock ratio and scrambling, required when the TMDS character rate exceeds the 340 MHz HDMI 1.4b limit. A periodic work item monitors the sink's scrambling status to recover from sink-side resets. On hotplug detect, if SCDC scrambling state is out of sync with the driver, trigger a CRTC reset to re-establish the link. Reject modes requiring TMDS rates above 600 MHz, as those fall in the HDMI 2.1 FRL domain which is not supported. In no_hpd configurations, further restrict to 340 MHz since SCDC requires a connected sink. Tested-by: Diederik de Haas Tested-by: Maud Spierings Acked-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 188 +++++++++++++++++-- 1 file changed, 172 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index d649a1cf07f5cf..c482a8e7da259e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2025 Amazon.com, Inc. or its affiliates. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -21,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -39,7 +42,9 @@ #define DDC_SEGMENT_ADDR 0x30 #define HDMI14_MAX_TMDSCLK 340000000 +#define HDMI20_MAX_TMDSRATE 600000000 +#define SCDC_MAX_SOURCE_VERSION 0x1 #define SCRAMB_POLL_DELAY_MS 3000 /* @@ -164,6 +169,11 @@ struct dw_hdmi_qp { } phy; unsigned long ref_clk_rate; + + struct drm_connector *curr_conn; + struct delayed_work scramb_work; + bool scramb_enabled; + struct regmap *regm; int main_irq; @@ -749,28 +759,124 @@ static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi) return adap; } +static bool dw_hdmi_qp_supports_scrambling(struct drm_display_info *display) +{ + if (!display->is_hdmi) + return false; + + return display->hdmi.scdc.supported && + display->hdmi.scdc.scrambling.supported; +} + +static int dw_hdmi_qp_set_scramb(struct dw_hdmi_qp *hdmi) +{ + bool done; + + dev_dbg(hdmi->dev, "set scrambling\n"); + + done = drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, true); + if (!done) + return -EIO; + + done = drm_scdc_set_scrambling(hdmi->curr_conn, true); + if (!done) { + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + return -EIO; + } + + schedule_delayed_work(&hdmi->scramb_work, + msecs_to_jiffies(SCRAMB_POLL_DELAY_MS)); + return 0; +} + +static void dw_hdmi_qp_scramb_work(struct work_struct *work) +{ + struct dw_hdmi_qp *hdmi = container_of(to_delayed_work(work), + struct dw_hdmi_qp, + scramb_work); + if (READ_ONCE(hdmi->scramb_enabled) && + !drm_scdc_get_scrambling_status(hdmi->curr_conn)) + dw_hdmi_qp_set_scramb(hdmi); +} + +static void dw_hdmi_qp_enable_scramb(struct dw_hdmi_qp *hdmi) +{ + int ret; + u8 ver; + + if (!dw_hdmi_qp_supports_scrambling(&hdmi->curr_conn->display_info)) + return; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &ver); + if (ret) { + dev_err(hdmi->dev, "Failed to read SCDC_SINK_VERSION: %d\n", ret); + return; + } + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, + min_t(u8, ver, SCDC_MAX_SOURCE_VERSION)); + if (ret) { + dev_err(hdmi->dev, "Failed to write SCDC_SOURCE_VERSION: %d\n", ret); + return; + } + + WRITE_ONCE(hdmi->scramb_enabled, true); + + ret = dw_hdmi_qp_set_scramb(hdmi); + if (ret) { + hdmi->scramb_enabled = false; + return; + } + + dw_hdmi_qp_write(hdmi, 1, SCRAMB_CONFIG0); + + /* Wait at least 1 ms before resuming TMDS transmission */ + usleep_range(1000, 5000); +} + +static void dw_hdmi_qp_disable_scramb(struct dw_hdmi_qp *hdmi) +{ + if (!hdmi->scramb_enabled) + return; + + dev_dbg(hdmi->dev, "disable scrambling\n"); + + WRITE_ONCE(hdmi->scramb_enabled, false); + cancel_delayed_work_sync(&hdmi->scramb_work); + + dw_hdmi_qp_write(hdmi, 0, SCRAMB_CONFIG0); + + if (hdmi->curr_conn->status == connector_status_connected) { + drm_scdc_set_scrambling(hdmi->curr_conn, false); + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + } +} + static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; struct drm_connector_state *conn_state; - struct drm_connector *connector; unsigned int op_mode; - connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); - if (WARN_ON(!connector)) + hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (WARN_ON(!hdmi->curr_conn)) return; - conn_state = drm_atomic_get_new_connector_state(state, connector); + conn_state = drm_atomic_get_new_connector_state(state, hdmi->curr_conn); if (WARN_ON(!conn_state)) return; - if (connector->display_info.is_hdmi) { + if (hdmi->curr_conn->display_info.is_hdmi) { dev_dbg(hdmi->dev, "%s mode=HDMI %s rate=%llu bpc=%u\n", __func__, drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format), conn_state->hdmi.tmds_char_rate, conn_state->hdmi.output_bpc); op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + if (conn_state->hdmi.tmds_char_rate > HDMI14_MAX_TMDSCLK) + dw_hdmi_qp_enable_scramb(hdmi); } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); op_mode = OPMODE_DVI; @@ -781,7 +887,7 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0); - drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + drm_atomic_helper_connector_hdmi_update_infoframes(hdmi->curr_conn, state); } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, @@ -791,14 +897,49 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, hdmi->tmds_char_rate = 0; + dw_hdmi_qp_disable_scramb(hdmi); + + hdmi->curr_conn = NULL; hdmi->phy.ops->disable(hdmi, hdmi->phy.data); } -static enum drm_connector_status -dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +static int dw_hdmi_qp_reset_crtc(struct dw_hdmi_qp *hdmi, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + u8 config; + int ret; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_TMDS_CONFIG, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read TMDS config: %d\n", ret); + return ret; + } + + if (!!(config & SCDC_SCRAMBLING_ENABLE) == hdmi->scramb_enabled) + return 0; + + drm_atomic_helper_connector_hdmi_hotplug(connector, + connector_status_connected); + /* + * Conform to HDMI 2.0 spec by ensuring scrambled data is not sent + * before configuring the sink scrambling, as well as suspending any + * TMDS transmission while changing the TMDS clock rate in the sink. + */ + + dev_dbg(hdmi->dev, "resetting crtc\n"); + + return drm_bridge_helper_reset_crtc(&hdmi->bridge, ctx); +} + +static int dw_hdmi_qp_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) { struct dw_hdmi_qp *hdmi = bridge->driver_private; + enum drm_connector_status status; const struct drm_edid *drm_edid; + int ret; if (hdmi->no_hpd) { drm_edid = drm_edid_read_ddc(connector, bridge->ddc); @@ -808,7 +949,20 @@ dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connec return connector_status_disconnected; } - return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + dev_dbg(hdmi->dev, "%s status=%d scramb=%d\n", __func__, + status, hdmi->scramb_enabled); + + if (status == connector_status_connected && hdmi->scramb_enabled) { + ret = dw_hdmi_qp_reset_crtc(hdmi, connector, ctx); + if (ret == -EDEADLK) + return ret; + if (ret < 0) + status = connector_status_unknown; + } + + return status; } static const struct drm_edid * @@ -832,12 +986,12 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; - /* - * TODO: when hdmi->no_hpd is 1 we must not support modes that - * require scrambling, including every mode with a clock above - * HDMI14_MAX_TMDSCLK. - */ - if (rate > HDMI14_MAX_TMDSCLK) { + if (hdmi->no_hpd && rate > HDMI14_MAX_TMDSCLK) { + dev_dbg(hdmi->dev, "Unsupported TMDS char rate in no_hpd mode: %lld\n", rate); + return MODE_CLOCK_HIGH; + } + + if (rate > HDMI20_MAX_TMDSRATE) { dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate); return MODE_CLOCK_HIGH; } @@ -1197,7 +1351,7 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, - .detect = dw_hdmi_qp_bridge_detect, + .detect_ctx = dw_hdmi_qp_bridge_detect_ctx, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, .hdmi_clear_avi_infoframe = dw_hdmi_qp_bridge_clear_avi_infoframe, @@ -1287,6 +1441,8 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi)) return ERR_CAST(hdmi); + INIT_DELAYED_WORK(&hdmi->scramb_work, dw_hdmi_qp_scramb_work); + hdmi->dev = dev; regs = devm_platform_ioremap_resource(pdev, 0); From 545ca9852a549c42b159a4ff9240be446d70001e Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:38 +0200 Subject: [PATCH 123/208] dt-bindings: pwm: Add a new binding for rockchip,rk3576-pwm The Rockchip RK3576 SoC has a newer PWM controller IP revision than previous Rockchip SoCs. This IP, called "PWMv4" by Rockchip, introduces several new features, and consequently differs in its bindings. Instead of expanding the ever-growing rockchip-pwm binding that already has an if-condition, add an entirely new binding to handle this. There are two additional clocks, "osc" and "rc". These are available for every PWM instance, and the PWM hardware can switch between the "pwm", "osc" and "rc" clock at runtime. The PWM controller also comes with an interrupt now. This interrupt is used to signal various conditions. Reviewed-by: Conor Dooley Reviewed-by: Rob Herring (Arm) Signed-off-by: Nicolas Frattaroli --- .../bindings/pwm/rockchip,rk3576-pwm.yaml | 77 +++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml new file mode 100644 index 00000000000000..48d5055c8b069f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/rockchip,rk3576-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip PWMv4 controller + +maintainers: + - Nicolas Frattaroli + +description: | + The Rockchip PWMv4 controller is a PWM controller found on several Rockchip + SoCs, such as the RK3576. + + It supports both generating and capturing PWM signals. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + items: + - const: rockchip,rk3576-pwm + + reg: + maxItems: 1 + + clocks: + items: + - description: Used to derive the PWM signal. + - description: Used as the APB bus clock. + - description: Used as an alternative to derive the PWM signal. + - description: Used as another alternative to derive the PWM signal. + + clock-names: + items: + - const: pwm + - const: pclk + - const: osc + - const: rc + + interrupts: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + pwm@2add0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add0000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, <&cru CLK_OSC_PWM1>, + <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + #pwm-cells = <3>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 046d2572ceefd8..d77c3fe8a8c443 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23154,6 +23154,13 @@ F: Documentation/userspace-api/media/v4l/metafmt-rkisp1.rst F: drivers/media/platform/rockchip/rkisp1 F: include/uapi/linux/rkisp1-config.h +ROCKCHIP MFPWM +M: Nicolas Frattaroli +L: linux-rockchip@lists.infradead.org +L: linux-pwm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml + ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT M: Daniel Golle M: Aurelien Jarno From 06ed0b3f80754d017004310a0076430805d8f3ca Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:39 +0200 Subject: [PATCH 124/208] mfd: Add Rockchip mfpwm driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the Rockchip RK3576, the PWM IP used by Rockchip has changed substantially. Looking at both the downstream pwm-rockchip driver as well as the mainline pwm-rockchip driver made it clear that with all its additional features and its differences from previous IP revisions, it is best supported in a new driver. This brings us to the question as to what such a new driver should be. To me, it soon became clear that it should actually be several new drivers, most prominently when Uwe Kleine-König let me know that I should not implement the pwm subsystem's capture callback, but instead write a counter driver for this functionality. Combined with the other as-of-yet unimplemented functionality of this new IP, it became apparent that it needs to be spread across several subsystems. For this reason, we add a new MFD core driver, called mfpwm (short for "Multi-function PWM"). This "parent" driver makes sure that only one device function driver is using the device at a time, and is in charge of registering the MFD cell devices for the individual device functions offered by the device. An acquire/release pattern is used to guarantee that device function drivers don't step on each other's toes. Signed-off-by: Nicolas Frattaroli --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 16 + drivers/mfd/Makefile | 1 + drivers/mfd/rockchip-mfpwm.c | 357 ++++++++++++++++++++++ include/linux/mfd/rockchip-mfpwm.h | 470 +++++++++++++++++++++++++++++ 5 files changed, 846 insertions(+) create mode 100644 drivers/mfd/rockchip-mfpwm.c create mode 100644 include/linux/mfd/rockchip-mfpwm.h diff --git a/MAINTAINERS b/MAINTAINERS index d77c3fe8a8c443..351ee2bb98d218 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23160,6 +23160,8 @@ L: linux-rockchip@lists.infradead.org L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml +F: drivers/mfd/rockchip-mfpwm.c +F: include/linux/mfd/rockchip-mfpwm.h ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT M: Daniel Golle diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 7192c9d1d268e9..80b4e82c4937fd 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1378,6 +1378,22 @@ config MFD_RC5T583 Additional drivers must be enabled in order to use the different functionality of the device. +config MFD_ROCKCHIP_MFPWM + tristate "Rockchip multi-function PWM controller" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on OF + depends on HAS_IOMEM + depends on COMMON_CLK + select MFD_CORE + help + Some Rockchip SoCs, such as the RK3576, use a PWM controller that has + several different functions, such as generating PWM waveforms but also + counting waveforms. + + This driver manages the overall device, and selects between different + functionalities at runtime as needed. Drivers for them are implemented + in their respective subsystems. + config MFD_RK8XX tristate select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index e75e8045c28afa..ebadbaea9e4a09 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -231,6 +231,7 @@ obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o obj-$(CONFIG_MFD_NTXEC) += ntxec.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o +obj-$(CONFIG_MFD_ROCKCHIP_MFPWM) += rockchip-mfpwm.o obj-$(CONFIG_MFD_RK8XX) += rk8xx-core.o obj-$(CONFIG_MFD_RK8XX_I2C) += rk8xx-i2c.o obj-$(CONFIG_MFD_RK8XX_SPI) += rk8xx-spi.o diff --git a/drivers/mfd/rockchip-mfpwm.c b/drivers/mfd/rockchip-mfpwm.c new file mode 100644 index 00000000000000..72d04982b9615e --- /dev/null +++ b/drivers/mfd/rockchip-mfpwm.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A driver to manage all the different functionalities exposed by Rockchip's + * PWMv4 hardware. + * + * This driver is chiefly focused on guaranteeing non-concurrent operation + * between the different device functions, as well as setting the clocks. + * It registers the device function platform devices, e.g. PWM output or + * PWM capture. + * + * Authors: + * Nicolas Frattaroli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct rockchip_mfpwm - private mfpwm driver instance state struct + * @pdev: pointer to this instance's &struct platform_device + * @base: pointer to the memory mapped registers of this device + * @pwm_clk: pointer to the PLL clock the PWM signal may be derived from + * @osc_clk: pointer to the fixed crystal the PWM signal may be derived from + * @rc_clk: pointer to the RC oscillator the PWM signal may be derived from + * @chosen_clk: a clk-mux of pwm_clk, osc_clk and rc_clk + * @pclk: pointer to the APB bus clock needed for mmio register access + * @active_func: pointer to the currently active device function, or %NULL if no + * device function is currently actively using any of the shared + * resources. May only be checked/modified with @state_lock held. + * @acquire_cnt: number of times @active_func has currently mfpwm_acquire()'d + * it. Must only be checked or modified while holding @state_lock. + * @state_lock: this lock is held while either the active device function, the + * enable register, or the chosen clock is being changed. + * @irq: the IRQ number of this device + */ +struct rockchip_mfpwm { + struct platform_device *pdev; + void __iomem *base; + struct clk *pwm_clk; + struct clk *osc_clk; + struct clk *rc_clk; + struct clk *chosen_clk; + struct clk *pclk; + struct rockchip_mfpwm_func *active_func; + unsigned int acquire_cnt; + spinlock_t state_lock; + int irq; +}; + +static atomic_t subdev_id = ATOMIC_INIT(0); + +static inline struct rockchip_mfpwm *to_rockchip_mfpwm(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static int mfpwm_check_pwmf(const struct rockchip_mfpwm_func *pwmf, + const char *fname) +{ + struct device *dev = &pwmf->parent->pdev->dev; + + if (IS_ERR_OR_NULL(pwmf)) { + dev_warn(dev, "called %s with an erroneous handle, no effect\n", + fname); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(pwmf->parent)) { + dev_warn(dev, "called %s with an erroneous mfpwm_func parent, no effect\n", + fname); + return -EINVAL; + } + + return 0; +} + +__attribute__((nonnull)) +static int mfpwm_do_acquire(struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm = pwmf->parent; + unsigned int cnt; + + if (mfpwm->active_func && pwmf->id != mfpwm->active_func->id) + return -EBUSY; + + if (!mfpwm->active_func) + mfpwm->active_func = pwmf; + + if (!check_add_overflow(mfpwm->acquire_cnt, 1, &cnt)) { + mfpwm->acquire_cnt = cnt; + } else { + dev_warn(&mfpwm->pdev->dev, "prevented acquire counter overflow in %s\n", + __func__); + return -EOVERFLOW; + } + + dev_dbg(&mfpwm->pdev->dev, "%d acquired mfpwm, acquires now at %u\n", + pwmf->id, mfpwm->acquire_cnt); + + return clk_enable(mfpwm->pclk); +} + +int mfpwm_acquire(struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + unsigned long flags; + int ret = 0; + + ret = mfpwm_check_pwmf(pwmf, "mfpwm_acquire"); + if (ret) + return ret; + + mfpwm = pwmf->parent; + dev_dbg(&mfpwm->pdev->dev, "%d is attempting to acquire\n", pwmf->id); + + if (!spin_trylock_irqsave(&mfpwm->state_lock, flags)) + return -EBUSY; + + ret = mfpwm_do_acquire(pwmf); + + spin_unlock_irqrestore(&mfpwm->state_lock, flags); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(mfpwm_acquire, "ROCKCHIP_MFPWM"); + +__attribute__((nonnull)) +static void mfpwm_do_release(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm = pwmf->parent; + + if (!mfpwm->active_func) + return; + + if (mfpwm->active_func->id != pwmf->id) + return; + + /* + * No need to check_sub_overflow here, !mfpwm->active_func above catches + * this type of problem already. + */ + mfpwm->acquire_cnt--; + + if (!mfpwm->acquire_cnt) + mfpwm->active_func = NULL; + + clk_disable(mfpwm->pclk); +} + +void mfpwm_release(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + unsigned long flags; + + if (mfpwm_check_pwmf(pwmf, "mfpwm_release")) + return; + + mfpwm = pwmf->parent; + + spin_lock_irqsave(&mfpwm->state_lock, flags); + mfpwm_do_release(pwmf); + dev_dbg(&mfpwm->pdev->dev, "%d released mfpwm, acquires now at %u\n", + pwmf->id, mfpwm->acquire_cnt); + spin_unlock_irqrestore(&mfpwm->state_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(mfpwm_release, "ROCKCHIP_MFPWM"); + +int mfpwm_get_mode(const struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm; + int ret; + + ret = mfpwm_check_pwmf(pwmf, "mfpwm_acquire"); + if (ret) + return ret; + + mfpwm = pwmf->parent; + + guard(spinlock_irqsave)(&mfpwm->state_lock); + + if (!rockchip_pwm_v4_is_enabled(mfpwm_reg_read(mfpwm->base, PWMV4_REG_ENABLE))) + return -1; + + return mfpwm_reg_read(mfpwm->base, PWMV4_REG_CTRL) & PWMV4_MODE_MASK; +} +EXPORT_SYMBOL_NS_GPL(mfpwm_get_mode, "ROCKCHIP_MFPWM"); + +/** + * mfpwm_register_subdev - register a single mfpwm_func + * @mfpwm: pointer to the parent &struct rockchip_mfpwm + * @name: sub-device name string + * + * Allocate a single &struct mfpwm_func, fill its members with appropriate data, + * and register a new mfd cell. + * + * Returns: 0 on success, negative errno on error + */ +static int mfpwm_register_subdev(struct rockchip_mfpwm *mfpwm, + const char *name) +{ + struct rockchip_mfpwm_func *func; + struct mfd_cell cell = {}; + + func = devm_kzalloc(&mfpwm->pdev->dev, sizeof(*func), GFP_KERNEL); + if (IS_ERR(func)) + return PTR_ERR(func); + func->irq = mfpwm->irq; + func->parent = mfpwm; + func->id = atomic_inc_return(&subdev_id); + func->base = mfpwm->base; + func->core = mfpwm->chosen_clk; + cell.name = name; + cell.platform_data = func; + cell.pdata_size = sizeof(*func); + + return devm_mfd_add_devices(&mfpwm->pdev->dev, func->id, &cell, 1, NULL, + 0, NULL); +} + +static int mfpwm_register_subdevs(struct rockchip_mfpwm *mfpwm) +{ + int ret; + + ret = mfpwm_register_subdev(mfpwm, "rockchip-pwm-v4"); + if (ret) + return ret; + + ret = mfpwm_register_subdev(mfpwm, "rockchip-pwm-capture"); + if (ret) + return ret; + + return 0; +} + +static int rockchip_mfpwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_mfpwm *mfpwm; + char *clk_mux_name; + const char *mux_p_names[3]; + int ret = 0; + + mfpwm = devm_kzalloc(&pdev->dev, sizeof(*mfpwm), GFP_KERNEL); + if (IS_ERR(mfpwm)) + return PTR_ERR(mfpwm); + + mfpwm->pdev = pdev; + + spin_lock_init(&mfpwm->state_lock); + + mfpwm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mfpwm->base)) + return dev_err_probe(dev, PTR_ERR(mfpwm->base), + "failed to ioremap address\n"); + + mfpwm->pclk = devm_clk_get_prepared(dev, "pclk"); + if (IS_ERR(mfpwm->pclk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->pclk), + "couldn't get and prepare 'pclk' clock\n"); + + mfpwm->irq = platform_get_irq(pdev, 0); + if (mfpwm->irq < 0) + return dev_err_probe(dev, mfpwm->irq, "couldn't get irq 0\n"); + + mfpwm->pwm_clk = devm_clk_get_prepared(dev, "pwm"); + if (IS_ERR(mfpwm->pwm_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->pwm_clk), + "couldn't get and prepare 'pwm' clock\n"); + + mfpwm->osc_clk = devm_clk_get_prepared(dev, "osc"); + if (IS_ERR(mfpwm->osc_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->osc_clk), + "couldn't get and prepare 'osc' clock\n"); + + mfpwm->rc_clk = devm_clk_get_prepared(dev, "rc"); + if (IS_ERR(mfpwm->rc_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->rc_clk), + "couldn't get and prepare 'rc' clock\n"); + + clk_mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s_chosen", dev_name(dev)); + if (!clk_mux_name) + return -ENOMEM; + + mux_p_names[0] = __clk_get_name(mfpwm->pwm_clk); + mux_p_names[1] = __clk_get_name(mfpwm->osc_clk); + mux_p_names[2] = __clk_get_name(mfpwm->rc_clk); + mfpwm->chosen_clk = clk_register_mux(dev, clk_mux_name, mux_p_names, + ARRAY_SIZE(mux_p_names), + CLK_SET_RATE_PARENT, + mfpwm->base + PWMV4_REG_CLK_CTRL, + PWMV4_CLK_SRC_SHIFT, PWMV4_CLK_SRC_WIDTH, + CLK_MUX_HIWORD_MASK, NULL); + ret = clk_prepare(mfpwm->chosen_clk); + if (ret) { + dev_err(dev, "failed to prepare PWM clock mux: %pe\n", + ERR_PTR(ret)); + return ret; + } + + platform_set_drvdata(pdev, mfpwm); + + ret = mfpwm_register_subdevs(mfpwm); + if (ret) { + dev_err(dev, "failed to register sub-devices: %pe\n", + ERR_PTR(ret)); + return ret; + } + + return ret; +} + +static void rockchip_mfpwm_remove(struct platform_device *pdev) +{ + struct rockchip_mfpwm *mfpwm = to_rockchip_mfpwm(pdev); + unsigned long flags; + + spin_lock_irqsave(&mfpwm->state_lock, flags); + + if (mfpwm->chosen_clk) { + clk_unprepare(mfpwm->chosen_clk); + clk_unregister_mux(mfpwm->chosen_clk); + } + + spin_unlock_irqrestore(&mfpwm->state_lock, flags); +} + +static const struct of_device_id rockchip_mfpwm_of_match[] = { + { + .compatible = "rockchip,rk3576-pwm", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_mfpwm_of_match); + +static struct platform_driver rockchip_mfpwm_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = rockchip_mfpwm_of_match, + }, + .probe = rockchip_mfpwm_probe, + .remove = rockchip_mfpwm_remove, +}; +module_platform_driver(rockchip_mfpwm_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip MFPWM Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/rockchip-mfpwm.h b/include/linux/mfd/rockchip-mfpwm.h new file mode 100644 index 00000000000000..dbf1588a438295 --- /dev/null +++ b/include/linux/mfd/rockchip-mfpwm.h @@ -0,0 +1,470 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 Collabora Ltd. + * + * Common header file for all the Rockchip Multi-function PWM controller + * drivers that are spread across subsystems. + * + * Authors: + * Nicolas Frattaroli + */ + +#ifndef __SOC_ROCKCHIP_MFPWM_H__ +#define __SOC_ROCKCHIP_MFPWM_H__ + +#include +#include +#include +#include +#include + +struct rockchip_mfpwm; + +/** + * struct rockchip_mfpwm_func - struct representing a single function driver + * + * @id: unique id for this function driver instance + * @base: pointer to start of MMIO registers + * @parent: a pointer to the parent mfpwm struct + * @irq: the shared IRQ gotten from the parent mfpwm device + * @core: a pointer to the clk mux that drives this channel's PWM + */ +struct rockchip_mfpwm_func { + int id; + void __iomem *base; + struct rockchip_mfpwm *parent; + int irq; + struct clk *core; +}; + +/* + * PWMV4 Register Definitions + * -------------------------- + * + * Attributes: + * RW - Read-Write + * RO - Read-Only + * WO - Write-Only + * W1T - Write high, Self-clearing + * W1C - Write high to clear interrupt + * + * Bit ranges to be understood with Verilog-like semantics, + * e.g. [03:00] is 4 bits: 0, 1, 2 and 3. + * + * All registers must be accessed with 32-bit width accesses only + */ + +#define PWMV4_REG_VERSION 0x000 +/* + * VERSION Register Description + * [31:24] RO | Hardware Major Version + * [23:16] RO | Hardware Minor Version + * [15:15] RO | Reserved + * [14:14] RO | Hardware supports biphasic counters + * [13:13] RO | Hardware supports filters + * [12:12] RO | Hardware supports waveform generation + * [11:11] RO | Hardware supports counter + * [10:10] RO | Hardware supports frequency metering + * [09:09] RO | Hardware supports power key functionality + * [08:08] RO | Hardware supports infrared transmissions + * [07:04] RO | Channel index of this instance + * [03:00] RO | Number of channels the base instance supports + */ + +#define PWMV4_REG_ENABLE 0x004 +/* + * ENABLE Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:06] RO | Reserved + * [05:05] RW | PWM Channel Counter Read Enable, 1 = enabled + */ +#define PWMV4_CHN_CNT_RD_EN(v) FIELD_PREP_WM16(BIT(5), (v)) +/* + * [04:04] W1T | PWM Globally Joined Control Enable + * 1 = this PWM channel will be enabled by a global pwm enable + * bit instead of the PWM Enable bit. + */ +#define PWMV4_GLOBAL_CTRL_EN(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Force Clock Enable + * 0 = disabled, if the PWM channel is inactive then so is the + * clock prescale module + */ +#define PWMV4_FORCE_CLK_EN(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] W1T | PWM Control Update Enable + * 1 = enabled, commits modifications of _CTRL, _PERIOD, _DUTY and + * _OFFSET registers once 1 is written to it + */ +#define PWMV4_CTRL_UPDATE_EN FIELD_PREP_WM16_CONST(BIT(2), 1) +/* + * [01:01] RW | PWM Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ +#define PWMV4_EN_MASK BIT(1) +#define PWMV4_EN(v) FIELD_PREP_WM16(PWMV4_EN_MASK, \ + ((v) ? 1 : 0)) +/* + * [00:00] RW | PWM Clock Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ +#define PWMV4_CLK_EN_MASK BIT(0) +#define PWMV4_CLK_EN(v) FIELD_PREP_WM16(PWMV4_CLK_EN_MASK, \ + ((v) ? 1 : 0)) +#define PWMV4_EN_BOTH_MASK (PWMV4_EN_MASK | PWMV4_CLK_EN_MASK) +static inline __pure bool rockchip_pwm_v4_is_enabled(unsigned int val) +{ + return (val & PWMV4_EN_BOTH_MASK); +} + +#define PWMV4_REG_CLK_CTRL 0x008 +/* + * CLK_CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:15] RW | Clock Global Selection + * 0 = current channel scale clock + * 1 = global channel scale clock + */ +#define PWMV4_CLK_GLOBAL(v) FIELD_PREP_WM16(BIT(15), (v)) +/* + * [14:13] RW | Clock Source Selection + * 0 = Clock from PLL, frequency can be configured + * 1 = Clock from crystal oscillator, frequency is fixed + * 2 = Clock from RC oscillator, frequency is fixed + * 3 = Reserved + * NOTE: The purpose for this clock-mux-outside-CRU construct is + * to let the SoC go into a sleep state with the PWM + * hardware still having a clock signal for IR input, which + * can then wake up the SoC. + */ +#define PWMV4_CLK_SRC_PLL 0x0U +#define PWMV4_CLK_SRC_CRYSTAL 0x1U +#define PWMV4_CLK_SRC_RC 0x2U +#define PWMV4_CLK_SRC_SHIFT 13 +#define PWMV4_CLK_SRC_WIDTH 2 +/* + * [12:04] RW | Scale Factor to apply to pre-scaled clock + * 1 <= v <= 256, v means clock divided by 2*v + */ +#define PWMV4_CLK_SCALE_F(v) FIELD_PREP_WM16(GENMASK(12, 4), (v)) +/* + * [03:03] RO | Reserved + * [02:00] RW | Prescale Factor + * v here means the input clock is divided by pow(2, v) + */ +#define PWMV4_CLK_PRESCALE_F(v) FIELD_PREP_WM16(GENMASK(2, 0), (v)) + +#define PWMV4_REG_CTRL 0x00C +/* + * CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:09] RO | Reserved + * [08:06] RW | PWM Input Channel Selection + * By default, the channel selects its own input, but writing v + * here selects PWM input from channel v instead. + */ +#define PWMV4_CTRL_IN_SEL(v) FIELD_PREP_WM16(GENMASK(8, 6), (v)) +/* [05:05] RW | Aligned Mode, 0 = Valid, 1 = Invalid */ +#define PWMV4_CTRL_UNALIGNED(v) FIELD_PREP_WM16(BIT(5), (v)) +/* [04:04] RW | Output Mode, 0 = Left Aligned, 1 = Centre Aligned */ +#define PWMV4_LEFT_ALIGNED 0x0U +#define PWMV4_CENTRE_ALIGNED 0x1U +#define PWMV4_CTRL_OUT_MODE(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Inactive Polarity for when the channel is either disabled or + * has completed outputting the entire waveform in one-shot mode. + * 0 = Negative, 1 = Positive + */ +#define PWMV4_POLARITY_N 0x0U +#define PWMV4_POLARITY_P 0x1U +#define PWMV4_INACTIVE_POL(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] RW | Duty Cycle Polarity to use at the start of the waveform. + * 0 = Negative, 1 = Positive + */ +#define PWMV4_DUTY_POL_SHIFT 2 +#define PWMV4_DUTY_POL_MASK BIT(PWMV4_DUTY_POL_SHIFT) +#define PWMV4_DUTY_POL(v) FIELD_PREP_WM16(PWMV4_DUTY_POL_MASK, \ + (v)) +/* + * [01:00] RW | PWM Mode + * 0 = One-shot mode, PWM generates waveform RPT times + * 1 = Continuous mode + * 2 = Capture mode, PWM measures cycles of input waveform + * 3 = Reserved + */ +#define PWMV4_MODE_ONESHOT 0x0U +#define PWMV4_MODE_CONT 0x1U +#define PWMV4_MODE_CAPTURE 0x2U +#define PWMV4_MODE_MASK GENMASK(1, 0) +#define PWMV4_MODE(v) FIELD_PREP_WM16(PWMV4_MODE_MASK, (v)) +#define PWMV4_CTRL_COM_FLAGS (PWMV4_INACTIVE_POL(PWMV4_POLARITY_N) | \ + PWMV4_DUTY_POL(PWMV4_POLARITY_P) | \ + PWMV4_CTRL_OUT_MODE(PWMV4_LEFT_ALIGNED) | \ + PWMV4_CTRL_UNALIGNED(true)) +#define PWMV4_CTRL_CONT_FLAGS (PWMV4_MODE(PWMV4_MODE_CONT) | \ + PWMV4_CTRL_COM_FLAGS) +#define PWMV4_CTRL_CAP_FLAGS (PWMV4_MODE(PWMV4_MODE_CAPTURE) | \ + PWMV4_CTRL_COM_FLAGS) + +#define PWMV4_REG_PERIOD 0x010 +/* + * PERIOD Register Description + * [31:00] RW | Period of the output waveform + * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED + */ + +#define PWMV4_REG_DUTY 0x014 +/* + * DUTY Register Description + * [31:00] RW | Duty cycle of the output waveform + * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED + */ + +#define PWMV4_REG_OFFSET 0x018 +/* + * OFFSET Register Description + * [31:00] RW | Offset of the output waveform, based on the PWM clock + * Constraints: 0 <= v <= (PERIOD - DUTY) + */ + +#define PWMV4_REG_RPT 0x01C +/* + * RPT Register Description + * [31:16] RW | Second dimensional of the effective number of waveform + * repetitions. Increases by one every first dimensional times. + * Value `n` means `n + 1` repetitions. The final number of + * repetitions of the waveform in one-shot mode is: + * `(first_dimensional + 1) * (second_dimensional + 1)` + * [15:00] RW | First dimensional of the effective number of waveform + * repetitions. Value `n` means `n + 1` repetitions. + */ + +#define PWMV4_REG_FILTER_CTRL 0x020 +/* + * FILTER_CTRL Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:04] RW | Filter window number + * [03:01] RO | Reserved + * [00:00] RW | Filter Enable, 0 = disabled, 1 = enabled + */ + +#define PWMV4_REG_CNT 0x024 +/* + * CNT Register Description + * [31:00] RO | Current value of the PWM Channel 0 counter in pwm clock cycles, + * 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_ENABLE_DELAY 0x028 +/* + * ENABLE_DELAY Register Description + * [31:16] RO | Reserved + * [15:00] RW | PWM enable delay, in an unknown unit but probably cycles + */ + +#define PWMV4_REG_HPC 0x02C +/* + * HPC Register Description + * [31:00] RW | Number of effective high polarity cycles of the input waveform + * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_LPC 0x030 +/* + * LPC Register Description + * [31:00] RW | Number of effective low polarity cycles of the input waveform + * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1 + */ + +#define PWMV4_REG_BIPHASIC_CNT_CTRL0 0x040 +/* + * BIPHASIC_CNT_CTRL0 Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Phase Edge Selection for mode 0, + * 0 = rising edge (posedge), 1 = falling edge (negedge) + * [08:08] RW | Biphasic Counter Clock force enable, 1 = force enable + * [07:07] W1T | Synchronous Enable + * [06:06] W1T | Mode Switch + * 0 = Normal Mode, 1 = Switch timer clock and measured clock + * Constraints: "Biphasic Counter Mode" must be 0 if this is 1 + * [05:03] RW | Biphasic Counter Mode + * 0x0 = Mode 0, 0x1 = Mode 1, 0x2 = Mode 2, 0x3 = Mode 3, + * 0x4 = Mode 4, 0x5 = Reserved + * [02:02] RW | Biphasic Counter Clock Selection + * 0 = clock is from PLL and frequency can be configured + * 1 = clock is from crystal oscillator and frequency is fixed + * [01:01] RW | Biphasic Counter Continuous Mode + * [00:00] W1T | Biphasic Counter Enable + */ + +#define PWMV4_REG_BIPHASIC_CNT_CTRL1 0x044 +/* + * BIPHASIC_CNT_CTRL1 Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:11] RO | Reserved + * [10:04] RW | Biphasic Counter Filter Window Number + * [03:01] RO | Reserved + * [00:00] RW | Biphasic Counter Filter Enable + */ + +#define PWMV4_REG_BIPHASIC_CNT_TIMER 0x048 +/* + * BIPHASIC_CNT_TIMER Register Description + * [31:00] RW | Biphasic Counter Timer Value, in number of biphasic counter + * timer clock cycles + */ + +#define PWMV4_REG_BIPHASIC_CNT_RES 0x04C +/* + * BIPHASIC_CNT_RES Register Description + * [31:00] RO | Biphasic Counter Result Value + * Constraints: Can only be read after INTSTS[9] is asserted + */ + +#define PWMV4_REG_BIPHASIC_CNT_RES_S 0x050 +/* + * BIPHASIC_CNT_RES_S Register Description + * [31:00] RO | Biphasic Counter Result Value with synchronised processing + * Can be read in real-time if BIPHASIC_CNT_CTRL0[7] was set to 1 + */ + +#define PWMV4_REG_INTSTS 0x070 +/* + * INTSTS Register Description + * [31:10] RO | Reserved + * [09:09] W1C | Biphasic Counter Interrupt Status, 1 = interrupt asserted + * [08:08] W1C | Waveform Middle Interrupt Status, 1 = interrupt asserted + * [07:07] W1C | Waveform Max Interrupt Status, 1 = interrupt asserted + * [06:06] W1C | IR Transmission End Interrupt Status, 1 = interrupt asserted + * [05:05] W1C | Power Key Match Interrupt Status, 1 = interrupt asserted + * [04:04] W1C | Frequency Meter Interrupt Status, 1 = interrupt asserted + * [03:03] W1C | Reload Interrupt Status, 1 = interrupt asserted + * [02:02] W1C | Oneshot End Interrupt Status, 1 = interrupt asserted + * [01:01] W1C | HPC Capture Interrupt Status, 1 = interrupt asserted + * [00:00] W1C | LPC Capture Interrupt Status, 1 = interrupt asserted + */ +#define PWMV4_INT_LPC BIT(0) +#define PWMV4_INT_HPC BIT(1) +#define PWMV4_INT_LPC_W(v) FIELD_PREP_WM16(PWMV4_INT_LPC, \ + ((v) ? 1 : 0)) +#define PWMV4_INT_HPC_W(v) FIELD_PREP_WM16(PWMV4_INT_HPC, \ + ((v) ? 1 : 0)) + +#define PWMV4_REG_INT_EN 0x074 +/* + * INT_EN Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Interrupt Enable, 1 = enabled + * [08:08] W1C | Waveform Middle Interrupt Enable, 1 = enabled + * [07:07] W1C | Waveform Max Interrupt Enable, 1 = enabled + * [06:06] W1C | IR Transmission End Interrupt Enable, 1 = enabled + * [05:05] W1C | Power Key Match Interrupt Enable, 1 = enabled + * [04:04] W1C | Frequency Meter Interrupt Enable, 1 = enabled + * [03:03] W1C | Reload Interrupt Enable, 1 = enabled + * [02:02] W1C | Oneshot End Interrupt Enable, 1 = enabled + * [01:01] W1C | HPC Capture Interrupt Enable, 1 = enabled + * [00:00] W1C | LPC Capture Interrupt Enable, 1 = enabled + */ + +#define PWMV4_REG_INT_MASK 0x078 +/* + * INT_MASK Register Description + * [31:16] WO | Write Enable Mask for the lower half of the register + * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in + * the same write operation + * [15:10] RO | Reserved + * [09:09] RW | Biphasic Counter Interrupt Masked, 1 = masked + * [08:08] W1C | Waveform Middle Interrupt Masked, 1 = masked + * [07:07] W1C | Waveform Max Interrupt Masked, 1 = masked + * [06:06] W1C | IR Transmission End Interrupt Masked, 1 = masked + * [05:05] W1C | Power Key Match Interrupt Masked, 1 = masked + * [04:04] W1C | Frequency Meter Interrupt Masked, 1 = masked + * [03:03] W1C | Reload Interrupt Masked, 1 = masked + * [02:02] W1C | Oneshot End Interrupt Masked, 1 = masked + * [01:01] W1C | HPC Capture Interrupt Masked, 1 = masked + * [00:00] W1C | LPC Capture Interrupt Masked, 1 = masked + */ + +static inline u32 mfpwm_reg_read(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void mfpwm_reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +/** + * mfpwm_acquire - try becoming the active mfpwm function device + * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func + * + * mfpwm device "function" drivers must call this function before doing anything + * that either modifies or relies on the parent device's state, such as clocks, + * enabling/disabling outputs, modifying shared regs etc. + * + * The return statues should always be checked. + * + * All mfpwm_acquire() calls must be balanced with corresponding mfpwm_release() + * calls once the device is no longer making changes that affect other devices, + * or stops producing user-visible effects that depend on the current device + * state being kept as-is. (e.g. after the PWM output signal is stopped) + * + * The same device function may mfpwm_acquire() multiple times while it already + * is active, i.e. it is re-entrant, though it needs to balance this with the + * same number of mfpwm_release() calls. + * + * Context: This function does not sleep. + * + * Return: + * * %0 - success + * * %-EBUSY - a different device function is active + * * %-EOVERFLOW - the acquire counter is at its maximum + */ +extern int __must_check mfpwm_acquire(struct rockchip_mfpwm_func *pwmf); + +/** + * mfpwm_release - drop usage of active mfpwm device function by 1 + * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func + * + * This is the balancing call to mfpwm_acquire(). If no users of the device + * function remain, set the mfpwm device to have no active device function, + * allowing other device functions to claim it. + */ +extern void mfpwm_release(const struct rockchip_mfpwm_func *pwmf); + +/** + * mfpwm_get_mode - get the current mode the hardware is in + * @pwmf: pointer to a &struct rockchip_mfpwm_func + * + * Check the hardware registers of the PWM hardware to determine which mode it + * is currently operating in, if any. + * + * Returns: + * - %-EINVAL if @pwmf is %NULL or an error pointer + * - %-1 if the PWM hardware is off, regardless of operating mode + * - %PWMV4_MODE_ONESHOT if PWM hardware is in one-shot output mode + * - %PWMV4_MODE_CONT if PWM hardware is in continuous output mode + * - %PWMV4_MODE_CAPTURE if PWM hardware is in capture mode + */ +extern int mfpwm_get_mode(const struct rockchip_mfpwm_func *pwmf); + +#endif /* __SOC_ROCKCHIP_MFPWM_H__ */ From 20eccbdb99c7c5c0bea370cfaadd8c1f7979317c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:40 +0200 Subject: [PATCH 125/208] pwm: Add rockchip PWMv4 driver The Rockchip RK3576 brings with it a new PWM IP, in downstream code referred to as "v4". This new IP is different enough from the previous Rockchip IP that I felt it necessary to add a new driver for it, instead of shoehorning it in the old one. Add this new driver, based on the PWM core's waveform APIs. Its platform device is registered by the parent mfpwm driver, from which it also receives a little platform data struct, so that mfpwm can guarantee that all the platform device drivers spread across different subsystems for this specific hardware IP do not interfere with each other. Signed-off-by: Nicolas Frattaroli --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 11 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-rockchip-v4.c | 383 ++++++++++++++++++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 drivers/pwm/pwm-rockchip-v4.c diff --git a/MAINTAINERS b/MAINTAINERS index 351ee2bb98d218..69347f456b8340 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23161,6 +23161,7 @@ L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml F: drivers/mfd/rockchip-mfpwm.c +F: drivers/pwm/pwm-rockchip-v4.c F: include/linux/mfd/rockchip-mfpwm.h ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376a0..3fe7993bf12ba7 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -625,6 +625,17 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_ROCKCHIP_V4 + tristate "Rockchip PWM v4 support" + depends on MFD_ROCKCHIP_MFPWM + help + Generic PWM framework driver for the PWM controller found on + later Rockchip SoCs such as the RK3576. + + Uses the Rockchip Multi-function PWM controller driver infrastructure + to guarantee fearlessly concurrent operation with other functions of + the same device implemented by drivers in other subsystems. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025db..a234027dbbc651 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_ROCKCHIP_V4) += pwm-rockchip-v4.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o diff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c new file mode 100644 index 00000000000000..b7de72c433c5bd --- /dev/null +++ b/drivers/pwm/pwm-rockchip-v4.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A Pulse-Width-Modulation (PWM) generator driver for the generators found in + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". Uses + * the MFPWM infrastructure to guarantee exclusive use over the device without + * other functions of the device from different drivers interfering with its + * operation while it's active. + * + * Technical Reference Manual: Chapter 31 of the RK3506 TRM Part 1, a SoC which + * uses the same PWM hardware and has a publicly available TRM. + * https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf + * + * Authors: + * Nicolas Frattaroli + * + * Limitations: + * - The hardware supports both completing the currently running period + * on disable (by switching to oneshot mode with a single repetition and + * only disable when the complete irq fires), and abrupt disable (freeze). + * Only the latter is implemented in the driver. + * - When the output is disabled, the pin will remain driven to whatever state + * it last had. + * - Adjustments to the duty cycle will only take effect during the next period. + * - Adjustments to the period length will only take effect during the next + * period. + * - The hardware only supports offsets in [0, period - duty_cycle] + */ + +#include +#include +#include +#include + +struct rockchip_pwm_v4 { + struct rockchip_mfpwm_func *pwmf; + struct pwm_chip chip; +}; + +struct __packed rockchip_pwm_v4_wf { + u32 period; + u32 duty; + u32 offset; + unsigned long rate; +}; + +static inline struct rockchip_pwm_v4 *to_rockchip_pwm_v4(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +/** + * rockchip_pwm_v4_round_single - convert a PWM parameter to hardware + * @rate: clock rate of the PWM clock, as per clk_get_rate + * Assumed to be <= 1GHz for overflow considerations + * @in_val: parameter in nanoseconds to convert + * + * Returns the rounded value, saturating at U32_MAX if too large + */ +static u32 rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val) +{ + u64 tmp; + + tmp = mul_u64_u64_div_u64(rate, in_val, NSEC_PER_SEC); + if (tmp > U32_MAX) + tmp = U32_MAX; + + return tmp; +} + +/** + * rockchip_pwm_v4_round_params - convert PWM parameters to hardware + * @rate: PWM clock rate to do the calculations at + * @wf: pointer to the generic &struct pwm_waveform input parameters + * @wfhw: pointer to the hardware-specific &struct rockchip_pwm_v4_wf output + * parameters that the results will be stored in + * + * Convert nanosecond-based duty/period/offset parameters to the PWM hardware's + * native rounded representation in number of cycles at clock rate @rate. Should + * any of the input parameters be out of range for the hardware, the + * corresponding output parameter is the maximum permissible value for said + * parameter with considerations to the others. + */ +static void rockchip_pwm_v4_round_params(unsigned long rate, + const struct pwm_waveform *wf, + struct rockchip_pwm_v4_wf *wfhw) +{ + wfhw->period = rockchip_pwm_v4_round_single(rate, wf->period_length_ns); + + wfhw->duty = rockchip_pwm_v4_round_single(rate, wf->duty_length_ns); + + /* As per TRM, PWM_OFFSET: "The value ranges from 0 to (period-duty)" */ + wfhw->offset = rockchip_pwm_v4_round_single(rate, wf->duty_offset_ns); + if (!wfhw->period) /* Don't underflow when pwm disabled */ + wfhw->offset = 0; + else if (wfhw->offset > wfhw->period - wfhw->duty) + wfhw->offset = wfhw->period - wfhw->duty; +} + +static int rockchip_pwm_v4_round_wf_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; + + rate = clk_get_rate(pc->pwmf->core); + + /* + * It's unlikely this code path is ever taken, as current hardware does + * not expose a clock that comes anywhere close to 1GHz. However, in + * order to avoid even a theoretical overflow in parameter rounding, + * error out if this ever happens to be the case. + */ + if (rate > NSEC_PER_SEC) + return -ERANGE; + + rockchip_pwm_v4_round_params(rate, wf, wfhw); + + if (wf->period_length_ns > 0) + wfhw->rate = rate; + else + wfhw->rate = 0; + + dev_dbg(&chip->dev, + "tohw: pwm#%u: %lld/%lld [+%lld] @%lu -> DUTY: %08x, PERIOD: %08x, OFFSET: %08x\n", + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + rate, wfhw->duty, wfhw->period, wfhw->offset); + + return 0; +} + +static int rockchip_pwm_v4_round_wf_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate = wfhw->rate; + + if (rate) { + wf->period_length_ns = DIV_ROUND_UP((u64)wfhw->period * NSEC_PER_SEC, rate); + wf->duty_length_ns = DIV_ROUND_UP((u64)wfhw->duty * NSEC_PER_SEC, rate); + wf->duty_offset_ns = DIV_ROUND_UP((u64)wfhw->offset * NSEC_PER_SEC, rate); + } else { + wf->period_length_ns = 0; + wf->duty_length_ns = 0; + wf->duty_offset_ns = 0; + } + + dev_dbg(&chip->dev, + "fromhw: pwm#%u: DUTY: %08x, PERIOD: %08x, OFFSET: %08x @%lu -> %lld/%lld [+%lld]\n", + pwm->hwpwm, wfhw->duty, wfhw->period, wfhw->offset, rate, + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); + + return 0; +} + +static int rockchip_pwm_v4_read_wf(struct pwm_chip *chip, struct pwm_device *pwm, + void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + rate = clk_get_rate(pc->pwmf->core); + + wfhw->period = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_PERIOD); + wfhw->duty = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_DUTY); + wfhw->offset = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_OFFSET); + if (rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_ENABLE))) + wfhw->rate = rate; + else + wfhw->rate = 0; + + mfpwm_release(pc->pwmf); + + return 0; +} + +static int rockchip_pwm_v4_write_wf(struct pwm_chip *chip, struct pwm_device *pwm, + const void *_wfhw) +{ + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + const struct rockchip_pwm_v4_wf *wfhw = _wfhw; + bool was_enabled; + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + was_enabled = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, + PWMV4_REG_ENABLE)); + + /* + * "But Nicolas", you ask with valid concerns, "why would you enable the + * PWM before setting all the parameter registers?" + * + * Excellent question, Mr. Reader M. Strawman! The RK3576 TRM Part 1 + * Section 34.6.3 specifies that this is the intended order of writes. + * Doing the PWM_EN and PWM_CLK_EN writes after the params but before + * the CTRL_UPDATE_EN, or even after the CTRL_UPDATE_EN, results in + * erratic behaviour where repeated turning on and off of the PWM may + * not turn it off under all circumstances. This is also why we don't + * use relaxed writes; it's not worth the footgun. + */ + if (wfhw->rate) + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + FIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, + PWMV4_EN_BOTH_MASK)); + else + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + FIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, 0)); + + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_PERIOD, wfhw->period); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_DUTY, wfhw->duty); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_OFFSET, wfhw->offset); + + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, PWMV4_CTRL_CONT_FLAGS); + + /* Commit new configuration to hardware output. */ + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_CTRL_UPDATE_EN); + + if (wfhw->rate) { + if (!was_enabled) { + dev_dbg(&chip->dev, "Enabling PWM output\n"); + ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_mfpwm_release; + ret = clk_set_rate_exclusive(pc->pwmf->core, wfhw->rate); + if (ret) { + clk_disable(pc->pwmf->core); + goto err_mfpwm_release; + } + + /* + * Output should be on now, acquire device to guarantee + * exclusion with other device functions while it's on. + * + * It's highly unlikely that this fails, as mfpwm has + * already been acquired before, and this is just a + * usage counter increase. Not worth the added + * complexity of clearing the PWMV4_REG_ENABLE again, + * especially considering the CTRL_UPDATE_EN behaviour. + */ + ret = mfpwm_acquire(pc->pwmf); + if (ret) { + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + goto err_mfpwm_release; + } + } + } else if (was_enabled) { + dev_dbg(&chip->dev, "Disabling PWM output\n"); + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + /* Output is off now, extra release to balance extra acquire */ + mfpwm_release(pc->pwmf); + } + +err_mfpwm_release: + mfpwm_release(pc->pwmf); + + return ret; +} + +static const struct pwm_ops rockchip_pwm_v4_ops = { + .sizeof_wfhw = sizeof(struct rockchip_pwm_v4_wf), + .round_waveform_tohw = rockchip_pwm_v4_round_wf_tohw, + .round_waveform_fromhw = rockchip_pwm_v4_round_wf_fromhw, + .read_waveform = rockchip_pwm_v4_read_wf, + .write_waveform = rockchip_pwm_v4_write_wf, +}; + +static bool rockchip_pwm_v4_on_and_continuous(struct rockchip_pwm_v4 *pc) +{ + bool en; + u32 val; + + en = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, + PWMV4_REG_ENABLE)); + val = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_CTRL); + + return en && ((val & PWMV4_MODE_MASK) == PWMV4_MODE_CONT); +} + +static int rockchip_pwm_v4_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); + struct rockchip_pwm_v4 *pc; + struct pwm_chip *chip; + struct device *dev = &pdev->dev; + int ret; + + /* + * For referencing the PWM in the DT to work, we need the parent MFD + * device's OF node. + */ + dev->of_node_reused = true; + device_set_node(dev, of_fwnode_handle(dev->parent->of_node)); + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pc = to_rockchip_pwm_v4(chip); + pc->pwmf = pwmf; + + ret = mfpwm_acquire(pwmf); + if (ret) + return dev_err_probe(dev, ret, "Couldn't acquire mfpwm in probe\n"); + + if (!rockchip_pwm_v4_on_and_continuous(pc)) + mfpwm_release(pwmf); + else { + dev_dbg(dev, "PWM was already on at probe time\n"); + ret = clk_enable(pwmf->core); + if (ret) { + dev_err_probe(dev, ret, "Enabling pwm clock failed\n"); + goto err_mfpwm_release; + } + ret = clk_rate_exclusive_get(pc->pwmf->core); + if (ret) { + dev_err_probe(dev, ret, "Protecting pwm clock failed\n"); + goto err_clk_disable; + } + } + + platform_set_drvdata(pdev, chip); + + chip->ops = &rockchip_pwm_v4_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret) { + dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + if (rockchip_pwm_v4_on_and_continuous(pc)) + goto err_rate_put; + + return ret; + } + + return 0; + +err_rate_put: + clk_rate_exclusive_put(pwmf->core); +err_clk_disable: + clk_disable(pwmf->core); +err_mfpwm_release: + mfpwm_release(pwmf); + + return ret; +} + +static const struct platform_device_id rockchip_pwm_v4_ids[] = { + { .name = "rockchip-pwm-v4", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, rockchip_pwm_v4_ids); + +static struct platform_driver rockchip_pwm_v4_driver = { + .probe = rockchip_pwm_v4_probe, + .driver = { + .name = "rockchip-pwm-v4", + }, + .id_table = rockchip_pwm_v4_ids, +}; +module_platform_driver(rockchip_pwm_v4_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip PWMv4 Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); +MODULE_ALIAS("platform:pwm-rockchip-v4"); From 101bd2554561c321f5a4db03b5a71d22be787299 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:41 +0200 Subject: [PATCH 126/208] counter: Add rockchip-pwm-capture driver Among many other things, Rockchip's new PWMv4 IP in the RK3576 supports PWM capture functionality. Add a basic driver for this that works to expose HPC/LPC counts and state change events to userspace through the counter framework. It's quite basic, but works well enough to demonstrate the device function exclusion stuff that mfpwm does, in order to eventually support all the functions of this device in drivers within their appropriate subsystems, without them interfering with each other. Signed-off-by: Nicolas Frattaroli --- MAINTAINERS | 1 + drivers/counter/Kconfig | 11 + drivers/counter/Makefile | 1 + drivers/counter/rockchip-pwm-capture.c | 307 +++++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 drivers/counter/rockchip-pwm-capture.c diff --git a/MAINTAINERS b/MAINTAINERS index 69347f456b8340..207ef803dbe7e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23160,6 +23160,7 @@ L: linux-rockchip@lists.infradead.org L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml +F: drivers/counter/rockchip-pwm-capture.c F: drivers/mfd/rockchip-mfpwm.c F: drivers/pwm/pwm-rockchip-v4.c F: include/linux/mfd/rockchip-mfpwm.h diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index d30d22dfe57741..85adeb41aeedad 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -90,6 +90,17 @@ config MICROCHIP_TCB_CAPTURE To compile this driver as a module, choose M here: the module will be called microchip-tcb-capture. +config ROCKCHIP_PWM_CAPTURE + tristate "Rockchip PWM Counter Capture driver" + depends on MFD_ROCKCHIP_MFPWM + help + Generic counter framework driver for the multi-function PWM on + Rockchip SoCs such as the RK3576. + + Uses the Rockchip Multi-function PWM controller driver infrastructure + to guarantee exclusive operation with other functions of the same + device implemented by drivers in other subsystems. + config RZ_MTU3_CNT tristate "Renesas RZ/G2L MTU3a counter driver" depends on RZ_MTU3 diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index fa3c1d08f70688..2bfcfc2c584bd1 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o obj-$(CONFIG_INTEL_QEP) += intel-qep.o obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o +obj-$(CONFIG_ROCKCHIP_PWM_CAPTURE) += rockchip-pwm-capture.o diff --git a/drivers/counter/rockchip-pwm-capture.c b/drivers/counter/rockchip-pwm-capture.c new file mode 100644 index 00000000000000..09a92f2bc40988 --- /dev/null +++ b/drivers/counter/rockchip-pwm-capture.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It + * allows for measuring the high cycles and low cycles of a PWM signal through + * the generic counter framework, while guaranteeing exclusive use over the + * MFPWM device while the counter is enabled. + * + * Authors: + * Nicolas Frattaroli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC) + +struct rockchip_pwm_capture { + struct rockchip_mfpwm_func *pwmf; + struct counter_device *counter; +}; + +static struct counter_signal rkpwmc_signals[] = { + { + .id = 0, + .name = "PWM Clock" + }, +}; + +static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = { + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, + COUNTER_SYNAPSE_ACTION_NONE, +}; + +static struct counter_synapse rkpwmc_pwm_synapses[] = { + { + .actions_list = rkpwmc_hpc_lpc_actions, + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), + .signal = &rkpwmc_signals[0] + }, +}; + +static const enum counter_function rkpwmc_functions[] = { + COUNTER_FUNCTION_INCREASE, +}; + +static inline bool rkpwmc_is_enabled(struct rockchip_mfpwm_func *pwmf) +{ + return mfpwm_get_mode(pwmf) == PWMV4_MODE_CAPTURE; +} + +static bool rkpwmc_acquire_if_enabled(struct rockchip_pwm_capture *pc) +{ + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret < 0) + return false; + + if (rkpwmc_is_enabled(pc->pwmf)) + return true; + + mfpwm_release(pc->pwmf); + + return false; +} + +static int rkpwmc_enable_read(struct counter_device *counter, + struct counter_count *count, + u8 *enable) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + + *enable = rkpwmc_is_enabled(pc->pwmf); + + return 0; +} + +static int rkpwmc_enable_write(struct counter_device *counter, + struct counter_count *count, + u8 enable) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + int ret; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + return ret; + + if (!!enable != rkpwmc_is_enabled(pc->pwmf)) { + if (enable) { + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(false)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, + PWMV4_CTRL_CAP_FLAGS); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, + PWMV4_INT_LPC_W(true) | + PWMV4_INT_HPC_W(true)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(true) | PWMV4_CLK_EN(true)); + + ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_release; + + ret = clk_rate_exclusive_get(pc->pwmf->core); + if (ret) + goto err_disable_pwm_clk; + + ret = mfpwm_acquire(pc->pwmf); + if (ret) + goto err_unprotect_pwm_clk; + } else { + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, + PWMV4_INT_LPC_W(false) | + PWMV4_INT_HPC_W(false)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(false) | PWMV4_CLK_EN(false)); + clk_rate_exclusive_put(pc->pwmf->core); + clk_disable(pc->pwmf->core); + mfpwm_release(pc->pwmf); + } + } + + mfpwm_release(pc->pwmf); + + return 0; + +err_unprotect_pwm_clk: + clk_rate_exclusive_put(pc->pwmf->core); +err_disable_pwm_clk: + clk_disable(pc->pwmf->core); +err_release: + mfpwm_release(pc->pwmf); + + return ret; +} + +static struct counter_comp rkpwmc_ext[] = { + COUNTER_COMP_ENABLE(rkpwmc_enable_read, rkpwmc_enable_write), +}; + +enum rkpwmc_count_id { + COUNT_LPC = 0, + COUNT_HPC = 1, +}; + +static struct counter_count rkpwmc_counts[] = { + { + .id = COUNT_LPC, + .name = "Low Polarity Capture", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, + .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses), + .ext = rkpwmc_ext, + .num_ext = ARRAY_SIZE(rkpwmc_ext), + }, + { + .id = COUNT_HPC, + .name = "High Polarity Capture", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, + .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses), + .ext = rkpwmc_ext, + .num_ext = ARRAY_SIZE(rkpwmc_ext), + }, +}; + +static int rkpwmc_count_read(struct counter_device *counter, + struct counter_count *count, u64 *value) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); + + switch (count->id) { + case COUNT_LPC: + if (rkpwmc_acquire_if_enabled(pc)) { + *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC); + mfpwm_release(pc->pwmf); + } else { + *value = 0; + } + return 0; + case COUNT_HPC: + if (rkpwmc_acquire_if_enabled(pc)) { + *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC); + mfpwm_release(pc->pwmf); + } else { + *value = 0; + } + return 0; + default: + return -EINVAL; + } +} + +static const struct counter_ops rkpwmc_ops = { + .count_read = rkpwmc_count_read, +}; + +static irqreturn_t rkpwmc_irq_handler(int irq, void *data) +{ + struct rockchip_pwm_capture *pc = data; + u32 intsts; + u32 clr = 0; + + intsts = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_INTSTS); + + if (!(intsts & RKPWMC_INT_MASK)) + return IRQ_NONE; + + if (intsts & PWMV4_INT_LPC) { + clr |= PWMV4_INT_LPC; + counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 0); + } + + if (intsts & PWMV4_INT_HPC) { + clr |= PWMV4_INT_HPC; + counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 1); + } + + if (clr) + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INTSTS, clr); + + /* If other interrupt status bits are set, they're not for this driver */ + if (intsts != clr) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int rockchip_pwm_capture_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); + struct rockchip_pwm_capture *pc; + struct counter_device *counter; + int ret; + + /* Set our (still unset) OF node to the parent MFD device's OF node */ + pdev->dev.parent->of_node_reused = true; + device_set_node(&pdev->dev, + of_fwnode_handle(no_free_ptr(pdev->dev.parent->of_node))); + + counter = devm_counter_alloc(&pdev->dev, sizeof(*pc)); + if (IS_ERR(counter)) + return PTR_ERR(counter); + + pc = counter_priv(counter); + pc->pwmf = pwmf; + + platform_set_drvdata(pdev, pc); + + /* If the counter is on at module probe, acquire it */ + rkpwmc_acquire_if_enabled(pc); + + counter->name = pdev->name; + counter->signals = rkpwmc_signals; + counter->num_signals = ARRAY_SIZE(rkpwmc_signals); + counter->ops = &rkpwmc_ops; + counter->counts = rkpwmc_counts; + counter->num_counts = ARRAY_SIZE(rkpwmc_counts); + + pc->counter = counter; + + ret = devm_counter_add(&pdev->dev, counter); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n"); + + ret = devm_request_irq(&pdev->dev, pwmf->irq, rkpwmc_irq_handler, + IRQF_SHARED, pdev->name, pc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed requesting IRQ\n"); + + return 0; +} + +static const struct platform_device_id rockchip_pwm_capture_id_table[] = { + { .name = "rockchip-pwm-capture", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, rockchip_pwm_capture_id_table); + +static struct platform_driver rockchip_pwm_capture_driver = { + .probe = rockchip_pwm_capture_probe, + .id_table = rockchip_pwm_capture_id_table, + .driver = { + .name = "rockchip-pwm-capture", + }, +}; +module_platform_driver(rockchip_pwm_capture_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("Rockchip PWM Counter Capture Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); +MODULE_IMPORT_NS("COUNTER"); +MODULE_ALIAS("platform:rockchip-pwm-capture"); From 82deda749fc72f9aae8b2eb882d3eb354cc7a7a3 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:42 +0200 Subject: [PATCH 127/208] arm64: dts: rockchip: add PWM nodes to RK3576 SoC dtsi The RK3576 SoC features three distinct PWM controllers, with variable numbers of channels. Add each channel as a separate node to the SoC's device tree, as they don't really overlap in register ranges. Signed-off-by: Nicolas Frattaroli --- arch/arm64/boot/dts/rockchip/rk3576.dtsi | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index f62a0d01fe0549..f98d7803dc3236 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -1047,6 +1047,32 @@ status = "disabled"; }; + pwm0_2ch_0: pwm@27330000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x27330000 0x0 0x1000>; + clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>, + <&cru CLK_PMU1PWM_OSC>, <&cru CLK_PMU1PWM_RC>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm0m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm0_2ch_1: pwm@27331000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x27331000 0x0 0x1000>; + clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>, + <&cru CLK_PMU1PWM_OSC>, <&cru CLK_PMU1PWM_RC>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm0m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + pmu: power-management@27380000 { compatible = "rockchip,rk3576-pmu", "syscon", "simple-mfd"; reg = <0x0 0x27380000 0x0 0x800>; @@ -2646,6 +2672,188 @@ status = "disabled"; }; + pwm1_6ch_0: pwm@2add0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add0000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_1: pwm@2add1000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add1000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_2: pwm@2add2000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add2000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch2>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_3: pwm@2add3000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add3000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch3>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_4: pwm@2add4000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add4000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch4>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1_6ch_5: pwm@2add5000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2add5000 0x0 0x1000>; + clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, + <&cru CLK_OSC_PWM1>, <&cru CLK_RC_PWM1>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm1m0_ch5>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_0: pwm@2ade0000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade0000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch0>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_1: pwm@2ade1000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade1000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch1>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_2: pwm@2ade2000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade2000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch2>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_3: pwm@2ade3000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade3000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch3>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_4: pwm@2ade4000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade4000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch4>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_5: pwm@2ade5000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade5000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch5>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_6: pwm@2ade6000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade6000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch6>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2_8ch_7: pwm@2ade7000 { + compatible = "rockchip,rk3576-pwm"; + reg = <0x0 0x2ade7000 0x0 0x1000>; + clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>, + <&cru CLK_OSC_PWM2>, <&cru CLK_RC_PWM2>; + clock-names = "pwm", "pclk", "osc", "rc"; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&pwm2m0_ch7>; + #pwm-cells = <3>; + status = "disabled"; + }; + saradc: adc@2ae00000 { compatible = "rockchip,rk3576-saradc", "rockchip,rk3588-saradc"; reg = <0x0 0x2ae00000 0x0 0x10000>; From 49b47824d041a01839e4a0363bf56d75bebbcc06 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 20 Apr 2026 15:52:43 +0200 Subject: [PATCH 128/208] arm64: dts: rockchip: Add cooling fan to ROCK 4D The ROCK 4D has a header to connect a small cooling fan. This fan is driven by one of the SoC's PWM outputs driving a transistor, that in turn controls the fan's power. With the introduction of PWM support, add a description of this cooling fan, as well as the additional trips and cooling-maps for it. Signed-off-by: Nicolas Frattaroli --- .../boot/dts/rockchip/rk3576-rock-4d.dts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index 90c4c251a9537e..43a8b2153a8b6a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -46,6 +46,14 @@ shutdown-gpios = <&gpio2 RK_PD1 GPIO_ACTIVE_HIGH>; }; + fan: pwm-fan { + compatible = "pwm-fan"; + cooling-levels = <0 180 205 230 255>; + fan-supply = <&vcc_5v0_sys>; + pwms = <&pwm2_8ch_5 0 60000 0>; + #cooling-cells = <2>; + }; + es8388_sound: es8388-sound { compatible = "simple-audio-card"; simple-audio-card,format = "i2s"; @@ -789,6 +797,36 @@ }; }; +&package_thermal { + polling-delay = <100>; + + trips { + package_fan0: package-fan0 { + temperature = <50000>; + hysteresis = <2000>; + type = "active"; + }; + + package_fan1: package-fan1 { + temperature = <60000>; + hysteresis = <2000>; + type = "active"; + }; + }; + + cooling-maps { + map1 { + trip = <&package_fan0>; + cooling-device = <&fan THERMAL_NO_LIMIT 1>; + }; + + map2 { + trip = <&package_fan1>; + cooling-device = <&fan 2 THERMAL_NO_LIMIT>; + }; + }; +}; + &pcie0 { pinctrl-names = "default"; pinctrl-0 = <&pcie_reset>; @@ -798,6 +836,13 @@ }; &pinctrl { + fan { + fan_pwm: fan-pwm { + rockchip,pins = + <4 RK_PC5 14 &pcfg_pull_down_drv_level_5>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; @@ -848,6 +893,11 @@ }; }; +&pwm2_8ch_5 { + pinctrl-0 = <&fan_pwm>; + status = "okay"; +}; + &sai1 { pinctrl-names = "default"; pinctrl-0 = <&sai1m0_lrck From 27a57b096016586ad77a6779286dfe6d8d04248c Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 21 Apr 2026 14:08:12 +0300 Subject: [PATCH 129/208] drm/bridge: dw-hdmi-qp: Rate limit i2c read error messages During EDID reads, repeated i2c errors can flood the kernel log: [ 25.361716] dwhdmiqp-rockchip fde80000.hdmi: i2c read error [ 25.363376] dwhdmiqp-rockchip fde80000.hdmi: i2c read error ... [ 25.368671] dwhdmiqp-rockchip fde80000.hdmi: i2c read error [ 25.369440] dwhdmiqp-rockchip fde80000.hdmi: failed to get edid Switch to dev_err_ratelimited() in dw_hdmi_qp_i2c_read() to reduce log spam while still reporting the condition. Reviewed-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index c482a8e7da259e..36fbd3831bc4d3 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -573,7 +573,7 @@ static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, dev_dbg_ratelimited(hdmi->dev, "i2c read timed out\n"); else - dev_err(hdmi->dev, "i2c read timed out\n"); + dev_err_ratelimited(hdmi->dev, "i2c read timed out\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } @@ -584,7 +584,7 @@ static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, dev_dbg_ratelimited(hdmi->dev, "i2c read error\n"); else - dev_err(hdmi->dev, "i2c read error\n"); + dev_err_ratelimited(hdmi->dev, "i2c read error\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } From 83672cc75fb883444ef75c75cc0ae7806f37e132 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 21 Apr 2026 14:21:36 +0300 Subject: [PATCH 130/208] drm/rockchip: dw_hdmi_qp: Add missing newlines in dev_err_probe() messages Add the missing trailing newlines to a couple of dev_err_probe() calls in dw_hdmi_qp_rockchip_bind(). Fixes: b6736a4ea3fa ("drm/rockchip: dw_hdmi_qp: Improve error handling with dev_err_probe()") Fixes: e1f7b7cbd74c ("drm/rockchip: dw_hdmi_qp: Switch to drmm_encoder_init()") Reviewed-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index c78db7f8ab6c76..d9333ad8996b8a 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -589,14 +589,14 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs); ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); if (ret) - return dev_err_probe(hdmi->dev, ret, "Failed to init encoder"); + return dev_err_probe(hdmi->dev, ret, "Failed to init encoder\n"); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data); if (IS_ERR(hdmi->hdmi)) return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmi), - "Failed to bind dw-hdmi-qp"); + "Failed to bind dw-hdmi-qp\n"); connector = drm_bridge_connector_init(drm, encoder); if (IS_ERR(connector)) From b57340d2bb2fb5d25c766889d8e3054a3b4e8e98 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 21 Apr 2026 14:38:26 +0300 Subject: [PATCH 131/208] drm/rockchip: dw_hdmi_qp: Use local dev variable consistently in bind() Replace indirect struct device accesses via hdmi->dev and pdev->dev with the local dev parameter already available in dw_hdmi_qp_rockchip_bind(), for consistency and readability. Reviewed-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index d9333ad8996b8a..618d2aaa5c7d8d 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -475,7 +475,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, struct clk *ref_clk; int ret, irq, i; - if (!pdev->dev.of_node) + if (!dev->of_node) return -ENODEV; hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL); @@ -495,7 +495,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, -ENODEV, "Missing platform ctrl ops\n"); hdmi->ctrl_ops = cfg->ctrl_ops; - hdmi->dev = &pdev->dev; + hdmi->dev = dev; hdmi->port_id = -ENODEV; /* Identify port ID by matching base IO address */ @@ -506,7 +506,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, } } if (hdmi->port_id < 0) - return dev_err_probe(hdmi->dev, hdmi->port_id, + return dev_err_probe(dev, hdmi->port_id, "Failed to match HDMI port ID\n"); plat_data.phy_ops = cfg->phy_ops; @@ -530,37 +530,36 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, hdmi->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); if (IS_ERR(hdmi->regmap)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->regmap), + return dev_err_probe(dev, PTR_ERR(hdmi->regmap), "Unable to get rockchip,grf\n"); hdmi->vo_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vo-grf"); if (IS_ERR(hdmi->vo_regmap)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->vo_regmap), + return dev_err_probe(dev, PTR_ERR(hdmi->vo_regmap), "Unable to get rockchip,vo-grf\n"); - ret = devm_clk_bulk_get_all_enabled(hdmi->dev, &clks); + ret = devm_clk_bulk_get_all_enabled(dev, &clks); if (ret < 0) - return dev_err_probe(hdmi->dev, ret, "Failed to get clocks\n"); + return dev_err_probe(dev, ret, "Failed to get clocks\n"); - ref_clk = clk_get(hdmi->dev, "ref"); + ref_clk = clk_get(dev, "ref"); if (IS_ERR(ref_clk)) - return dev_err_probe(hdmi->dev, PTR_ERR(ref_clk), + return dev_err_probe(dev, PTR_ERR(ref_clk), "Failed to get ref clock\n"); plat_data.ref_clk_rate = clk_get_rate(ref_clk); clk_put(ref_clk); - hdmi->frl_enable_gpio = devm_gpiod_get_optional(hdmi->dev, "frl-enable", + hdmi->frl_enable_gpio = devm_gpiod_get_optional(dev, "frl-enable", GPIOD_OUT_LOW); if (IS_ERR(hdmi->frl_enable_gpio)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->frl_enable_gpio), + return dev_err_probe(dev, PTR_ERR(hdmi->frl_enable_gpio), "Failed to request FRL enable GPIO\n"); hdmi->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); if (IS_ERR(hdmi->phy)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->phy), - "Failed to get phy\n"); + return dev_err_probe(dev, PTR_ERR(hdmi->phy), "Failed to get phy\n"); cfg->ctrl_ops->io_init(hdmi); @@ -578,7 +577,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, if (irq < 0) return irq; - ret = devm_request_threaded_irq(hdmi->dev, irq, + ret = devm_request_threaded_irq(dev, irq, cfg->ctrl_ops->hardirq_callback, cfg->ctrl_ops->irq_callback, IRQF_SHARED, "dw-hdmi-qp-hpd", @@ -589,18 +588,18 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs); ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); if (ret) - return dev_err_probe(hdmi->dev, ret, "Failed to init encoder\n"); + return dev_err_probe(dev, ret, "Failed to init encoder\n"); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data); if (IS_ERR(hdmi->hdmi)) - return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmi), + return dev_err_probe(dev, PTR_ERR(hdmi->hdmi), "Failed to bind dw-hdmi-qp\n"); connector = drm_bridge_connector_init(drm, encoder); if (IS_ERR(connector)) - return dev_err_probe(hdmi->dev, PTR_ERR(connector), + return dev_err_probe(dev, PTR_ERR(connector), "Failed to init bridge connector\n"); return drm_connector_attach_encoder(connector, encoder); From 67ef4dca2b939cc8aac324989cca062c81588e50 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 20 Apr 2026 23:55:39 +0300 Subject: [PATCH 132/208] drm/rockchip: dw_hdmi_qp: Register HPD IRQ after connector setup Move devm_request_threaded_irq() to the end of bind(), after drm_bridge_connector_init() and drm_connector_attach_encoder(), to ensure all DRM resources are ready before HPD interrupts can fire. While at it, add error handling for drm_connector_attach_encoder(). Tested-by: Diederik de Haas Tested-by: Maud Spierings Reviewed-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 618d2aaa5c7d8d..fbbe26f8730ca0 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -577,14 +577,6 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, if (irq < 0) return irq; - ret = devm_request_threaded_irq(dev, irq, - cfg->ctrl_ops->hardirq_callback, - cfg->ctrl_ops->irq_callback, - IRQF_SHARED, "dw-hdmi-qp-hpd", - hdmi); - if (ret) - return ret; - drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs); ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); if (ret) @@ -602,7 +594,15 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, PTR_ERR(connector), "Failed to init bridge connector\n"); - return drm_connector_attach_encoder(connector, encoder); + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return dev_err_probe(dev, ret, "Failed to attach connector\n"); + + return devm_request_threaded_irq(dev, irq, + cfg->ctrl_ops->hardirq_callback, + cfg->ctrl_ops->irq_callback, + IRQF_SHARED, "dw-hdmi-qp-hpd", + hdmi); } static void dw_hdmi_qp_rockchip_unbind(struct device *dev, From c7cb2ee2a469d6b3f150ea965f87e997eff1adb8 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 20 Apr 2026 22:42:12 +0300 Subject: [PATCH 133/208] drm/rockchip: dw_hdmi_qp: Restrict HPD event to the affected connector Switch from drm_helper_hpd_irq_event(), which polls all connectors, to drm_connector_helper_hpd_irq_event(), which runs the detect cycle only on the affected connector. This avoids unnecessary work and redundant detect calls on unrelated connectors. Tested-by: Diederik de Haas Tested-by: Maud Spierings Reviewed-by: Heiko Stuebner Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index fbbe26f8730ca0..d6338195a5b751 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -93,6 +93,7 @@ struct rockchip_hdmi_qp { struct regmap *regmap; struct regmap *vo_regmap; struct rockchip_encoder encoder; + struct drm_connector *connector; struct dw_hdmi_qp *hdmi; struct phy *phy; struct gpio_desc *frl_enable_gpio; @@ -252,11 +253,10 @@ static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) struct rockchip_hdmi_qp *hdmi = container_of(work, struct rockchip_hdmi_qp, hpd_work.work); - struct drm_device *drm = hdmi->encoder.encoder.dev; bool changed; - if (drm) { - changed = drm_helper_hpd_irq_event(drm); + if (hdmi->connector) { + changed = drm_connector_helper_hpd_irq_event(hdmi->connector); if (changed) dev_dbg(hdmi->dev, "connector status changed\n"); } @@ -467,7 +467,6 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, struct dw_hdmi_qp_plat_data plat_data = {}; const struct rockchip_hdmi_qp_cfg *cfg; struct drm_device *drm = data; - struct drm_connector *connector; struct drm_encoder *encoder; struct rockchip_hdmi_qp *hdmi; struct resource *res; @@ -589,12 +588,12 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, PTR_ERR(hdmi->hdmi), "Failed to bind dw-hdmi-qp\n"); - connector = drm_bridge_connector_init(drm, encoder); - if (IS_ERR(connector)) - return dev_err_probe(dev, PTR_ERR(connector), + hdmi->connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(hdmi->connector)) + return dev_err_probe(dev, PTR_ERR(hdmi->connector), "Failed to init bridge connector\n"); - ret = drm_connector_attach_encoder(connector, encoder); + ret = drm_connector_attach_encoder(hdmi->connector, encoder); if (ret) return dev_err_probe(dev, ret, "Failed to attach connector\n"); @@ -612,6 +611,8 @@ static void dw_hdmi_qp_rockchip_unbind(struct device *dev, struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev); cancel_delayed_work_sync(&hdmi->hpd_work); + + hdmi->connector = NULL; } static const struct component_ops dw_hdmi_qp_rockchip_ops = { @@ -646,8 +647,8 @@ static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev) dw_hdmi_qp_resume(dev, hdmi->hdmi); - if (hdmi->encoder.encoder.dev) - drm_helper_hpd_irq_event(hdmi->encoder.encoder.dev); + if (hdmi->connector) + drm_connector_helper_hpd_irq_event(hdmi->connector); return 0; } From 84abad52935bb0ed05dc69a6d1d53e3c4dd3cb24 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 12 Mar 2026 18:16:54 +0200 Subject: [PATCH 134/208] drm/display: scdc: Convert bit-field macros to use BIT() Make the code more robust and improve readability by replacing all open-coded bit-shift expressions with the BIT() macro. While at it, realign macro definitions so that the values line up consistently across the file. Moreover, SCDC_TMDS_BIT_CLOCK_RATIO_BY_10 is defined as (0 << 1), hence indicating a cleared bit. Since BIT() cannot meaningfully represent this, and the symbol has no users in the kernel tree, remove it. Signed-off-by: Cristian Ciocaltea --- include/drm/display/drm_scdc.h | 85 +++++++++++++++++----------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/include/drm/display/drm_scdc.h b/include/drm/display/drm_scdc.h index 3d58f37e8ed8ed..7ed40017e66068 100644 --- a/include/drm/display/drm_scdc.h +++ b/include/drm/display/drm_scdc.h @@ -24,65 +24,66 @@ #ifndef DRM_SCDC_H #define DRM_SCDC_H -#define SCDC_SINK_VERSION 0x01 +#include -#define SCDC_SOURCE_VERSION 0x02 +#define SCDC_SINK_VERSION 0x01 -#define SCDC_UPDATE_0 0x10 -#define SCDC_READ_REQUEST_TEST (1 << 2) -#define SCDC_CED_UPDATE (1 << 1) -#define SCDC_STATUS_UPDATE (1 << 0) +#define SCDC_SOURCE_VERSION 0x02 -#define SCDC_UPDATE_1 0x11 +#define SCDC_UPDATE_0 0x10 +#define SCDC_READ_REQUEST_TEST BIT(2) +#define SCDC_CED_UPDATE BIT(1) +#define SCDC_STATUS_UPDATE BIT(0) -#define SCDC_TMDS_CONFIG 0x20 -#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 (1 << 1) -#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_10 (0 << 1) -#define SCDC_SCRAMBLING_ENABLE (1 << 0) +#define SCDC_UPDATE_1 0x11 -#define SCDC_SCRAMBLER_STATUS 0x21 -#define SCDC_SCRAMBLING_STATUS (1 << 0) +#define SCDC_TMDS_CONFIG 0x20 +#define SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 BIT(1) +#define SCDC_SCRAMBLING_ENABLE BIT(0) -#define SCDC_CONFIG_0 0x30 -#define SCDC_READ_REQUEST_ENABLE (1 << 0) +#define SCDC_SCRAMBLER_STATUS 0x21 +#define SCDC_SCRAMBLING_STATUS BIT(0) -#define SCDC_STATUS_FLAGS_0 0x40 -#define SCDC_CH2_LOCK (1 << 3) -#define SCDC_CH1_LOCK (1 << 2) -#define SCDC_CH0_LOCK (1 << 1) -#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK) -#define SCDC_CLOCK_DETECT (1 << 0) +#define SCDC_CONFIG_0 0x30 +#define SCDC_READ_REQUEST_ENABLE BIT(0) -#define SCDC_STATUS_FLAGS_1 0x41 +#define SCDC_STATUS_FLAGS_0 0x40 +#define SCDC_CH2_LOCK BIT(3) +#define SCDC_CH1_LOCK BIT(2) +#define SCDC_CH0_LOCK BIT(1) +#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK) +#define SCDC_CLOCK_DETECT BIT(0) -#define SCDC_ERR_DET_0_L 0x50 -#define SCDC_ERR_DET_0_H 0x51 -#define SCDC_ERR_DET_1_L 0x52 -#define SCDC_ERR_DET_1_H 0x53 -#define SCDC_ERR_DET_2_L 0x54 -#define SCDC_ERR_DET_2_H 0x55 -#define SCDC_CHANNEL_VALID (1 << 7) +#define SCDC_STATUS_FLAGS_1 0x41 -#define SCDC_ERR_DET_CHECKSUM 0x56 +#define SCDC_ERR_DET_0_L 0x50 +#define SCDC_ERR_DET_0_H 0x51 +#define SCDC_ERR_DET_1_L 0x52 +#define SCDC_ERR_DET_1_H 0x53 +#define SCDC_ERR_DET_2_L 0x54 +#define SCDC_ERR_DET_2_H 0x55 +#define SCDC_CHANNEL_VALID BIT(7) -#define SCDC_TEST_CONFIG_0 0xc0 -#define SCDC_TEST_READ_REQUEST (1 << 7) -#define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f) +#define SCDC_ERR_DET_CHECKSUM 0x56 -#define SCDC_MANUFACTURER_IEEE_OUI 0xd0 -#define SCDC_MANUFACTURER_IEEE_OUI_SIZE 3 +#define SCDC_TEST_CONFIG_0 0xc0 +#define SCDC_TEST_READ_REQUEST BIT(7) +#define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f) -#define SCDC_DEVICE_ID 0xd3 -#define SCDC_DEVICE_ID_SIZE 8 +#define SCDC_MANUFACTURER_IEEE_OUI 0xd0 +#define SCDC_MANUFACTURER_IEEE_OUI_SIZE 3 -#define SCDC_DEVICE_HARDWARE_REVISION 0xdb +#define SCDC_DEVICE_ID 0xd3 +#define SCDC_DEVICE_ID_SIZE 8 + +#define SCDC_DEVICE_HARDWARE_REVISION 0xdb #define SCDC_GET_DEVICE_HARDWARE_REVISION_MAJOR(x) (((x) >> 4) & 0xf) #define SCDC_GET_DEVICE_HARDWARE_REVISION_MINOR(x) (((x) >> 0) & 0xf) -#define SCDC_DEVICE_SOFTWARE_MAJOR_REVISION 0xdc -#define SCDC_DEVICE_SOFTWARE_MINOR_REVISION 0xdd +#define SCDC_DEVICE_SOFTWARE_MAJOR_REVISION 0xdc +#define SCDC_DEVICE_SOFTWARE_MINOR_REVISION 0xdd -#define SCDC_MANUFACTURER_SPECIFIC 0xde -#define SCDC_MANUFACTURER_SPECIFIC_SIZE 34 +#define SCDC_MANUFACTURER_SPECIFIC 0xde +#define SCDC_MANUFACTURER_SPECIFIC_SIZE 34 #endif From afc2c67323af1c9e9efdec15bd445fa9caff8bbf Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 12 Mar 2026 17:55:32 +0200 Subject: [PATCH 135/208] drm/display: scdc: Add HDMI 2.1 FRL register definitions HDMI 2.1 introduced Fixed Rate Link (FRL) as a new physical layer transport to replace TMDS for high-bandwidth modes requiring more than ~18 Gbps. Establishing a FRL link requires a training procedure known as Fixed Rate Link Training (FLT), which is coordinated between source and sink via the Status and Control Data Channel (SCDC). Add the register definitions and bit-field macros needed to implement this link training handshake. Signed-off-by: Cristian Ciocaltea --- include/drm/display/drm_scdc.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/drm/display/drm_scdc.h b/include/drm/display/drm_scdc.h index 7ed40017e66068..cd842940d634f0 100644 --- a/include/drm/display/drm_scdc.h +++ b/include/drm/display/drm_scdc.h @@ -31,6 +31,9 @@ #define SCDC_SOURCE_VERSION 0x02 #define SCDC_UPDATE_0 0x10 +#define SCDC_FLT_UPDATE BIT(5) +#define SCDC_FRL_START BIT(4) +#define SCDC_SOURCE_TEST_UPDATE BIT(3) #define SCDC_READ_REQUEST_TEST BIT(2) #define SCDC_CED_UPDATE BIT(1) #define SCDC_STATUS_UPDATE BIT(0) @@ -45,9 +48,25 @@ #define SCDC_SCRAMBLING_STATUS BIT(0) #define SCDC_CONFIG_0 0x30 +#define SCDC_FLT_NO_RETRAIN BIT(1) #define SCDC_READ_REQUEST_ENABLE BIT(0) +#define SCDC_CONFIG_1 0x31 +#define SCDC_FFE_LEVELS_MASK GENMASK(7, 4) +#define SCDC_FRL_RATE_MASK GENMASK(3, 0) +#define SCDC_FRL_RATE_DISABLE 0 +#define SCDC_FRL_RATE_3GBPS_3LANE 1 +#define SCDC_FRL_RATE_6GBPS_3LANE 2 +#define SCDC_FRL_RATE_6GBPS_4LANE 3 +#define SCDC_FRL_RATE_8GBPS_4LANE 4 +#define SCDC_FRL_RATE_10GBPS_4LANE 5 +#define SCDC_FRL_RATE_12GBPS_4LANE 6 + +#define SCDC_SOURCE_TEST_CONFIG 0x35 +#define SCDC_FLT_NO_TIMEOUT BIT(5) + #define SCDC_STATUS_FLAGS_0 0x40 +#define SCDC_FLT_READY BIT(6) #define SCDC_CH2_LOCK BIT(3) #define SCDC_CH1_LOCK BIT(2) #define SCDC_CH0_LOCK BIT(1) @@ -55,6 +74,12 @@ #define SCDC_CLOCK_DETECT BIT(0) #define SCDC_STATUS_FLAGS_1 0x41 +#define SCDC_FRL_LN1_LTP_REQ_MASK GENMASK(7, 4) +#define SCDC_FRL_LN0_LTP_REQ_MASK GENMASK(3, 0) + +#define SCDC_STATUS_FLAGS_2 0x42 +#define SCDC_FRL_LN3_LTP_REQ_MASK GENMASK(7, 4) +#define SCDC_FRL_LN2_LTP_REQ_MASK GENMASK(3, 0) #define SCDC_ERR_DET_0_L 0x50 #define SCDC_ERR_DET_0_H 0x51 From 1fbebe91de20151272611ecd477a1623b06a9de9 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sat, 28 Mar 2026 22:08:28 +0200 Subject: [PATCH 136/208] drm/display: scdc-helper: Add FRL link training control functions Provide a few SCDC-side primitives to support implementing the Fixed Rate Link (FRL) link training handshake as mandated by the HDMI 2.1 specification: - drm_scdc_set_frl(): configures the FRL rate and FFE (Feed-Forward Equalization) level on the sink via the SCDC CONFIG_1 register, so the source can negotiate and apply a specific FRL operating point during link training - drm_scdc_calc_lower_frl(): computes the next-lower FRL bandwidth configuration given a current one, to support implementing the FRL link training fallback mechanism, where the source must step down to a lower rate when the sink signals training failure - drm_scdc_get_frl_ltp_request(): reads the Link Training Pattern (LTP) requests from the sink for all four FRL lanes via SCDC STATUS_FLAGS registers, to determine what pattern each lane expects during FRL link training so the source can respond appropriately Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_scdc_helper.c | 186 ++++++++++++++++++++++ include/drm/display/drm_scdc_helper.h | 6 + 2 files changed, 192 insertions(+) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index df878aad4a36b2..1f10f0fb7cabb7 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -21,6 +21,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -276,3 +277,188 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, return true; } EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio); + +static int drm_scdc_frl_config_to_rate(u8 config, u8 *rate_per_lane, u8 *lanes) +{ + switch (config) { + case SCDC_FRL_RATE_12GBPS_4LANE: + *rate_per_lane = 12; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_10GBPS_4LANE: + *rate_per_lane = 10; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_8GBPS_4LANE: + *rate_per_lane = 8; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_6GBPS_4LANE: + *rate_per_lane = 6; + *lanes = 4; + return 0; + case SCDC_FRL_RATE_6GBPS_3LANE: + *rate_per_lane = 6; + *lanes = 3; + return 0; + case SCDC_FRL_RATE_3GBPS_3LANE: + *rate_per_lane = 3; + *lanes = 3; + return 0; + case SCDC_FRL_RATE_DISABLE: + *rate_per_lane = 0; + *lanes = 0; + return 0; + default: + return -EINVAL; + } +} + +static int drm_scdc_frl_rate_to_config(u8 rate_per_lane, u8 lanes) +{ + if (lanes != 0 && lanes != 3 && lanes != 4) + return -EINVAL; + + switch (rate_per_lane * lanes) { + case 48: + return SCDC_FRL_RATE_12GBPS_4LANE; + case 40: + return SCDC_FRL_RATE_10GBPS_4LANE; + case 32: + return SCDC_FRL_RATE_8GBPS_4LANE; + case 24: + return SCDC_FRL_RATE_6GBPS_4LANE; + case 18: + return SCDC_FRL_RATE_6GBPS_3LANE; + case 9: + return SCDC_FRL_RATE_3GBPS_3LANE; + case 0: + return SCDC_FRL_RATE_DISABLE; + default: + return -EINVAL; + } +} + +/** + * drm_scdc_set_frl - set FRL rate and FFE + * @connector: connector + * @rate_per_lane: FRL rate for a single lane (Gbps) + * @lanes: FRL lane count (3 or 4) + * @max_ffe_level: max TxFFE level for indicated FRL Rate (0..3) + * + * Writes over SCDC the FRL config register over SCDC channel, and sets + * FRL_Rate according to rate_per_lane x lanes, as well as FFE_levels + * according to max_ffe_level. + * + * Returns: + * True if write is successful, false otherwise. + */ +bool drm_scdc_set_frl(struct drm_connector *connector, + u8 rate_per_lane, u8 lanes, u8 max_ffe_level) +{ + u8 config; + int ret; + + ret = drm_scdc_frl_rate_to_config(rate_per_lane, lanes); + if (ret < 0 || max_ffe_level > 3) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Invalid FRL config: rate=%ux%u ffe=%u\n", + connector->base.id, connector->name, rate_per_lane, + lanes, max_ffe_level); + return false; + } + + config = FIELD_PREP(SCDC_FRL_RATE_MASK, ret) | + FIELD_PREP(SCDC_FFE_LEVELS_MASK, max_ffe_level); + + ret = drm_scdc_writeb(connector->ddc, SCDC_CONFIG_1, config); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to set FRL: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + return true; +} +EXPORT_SYMBOL(drm_scdc_set_frl); + +/** + * drm_scdc_calc_lower_frl - compute a reduced bandwidth FRL rate + * @in_rate_per_lane: input FRL rate for a single lane (Gbps) + * @in_lanes: input FRL lane count (3 or 4) + * @out_rate_per_lane: output FRL rate for a single lane (Gbps) + * @out_lanes: output FRL lane count (3 or 4) + * + * Determinates the FRL rate configuration with the highest bandwidth that is + * still lower than the bandwidth corresponding to the given input configuration. + * The resulting configuration is stored in out_rate_per_lane and out_lanes. + * + * Returns: + * True if computation was successful, false otherwise. + */ +bool drm_scdc_calc_lower_frl(u8 in_rate_per_lane, u8 in_lanes, + u8 *out_rate_per_lane, u8 *out_lanes) +{ + int ret; + + ret = drm_scdc_frl_rate_to_config(in_rate_per_lane, in_lanes); + if (ret < 0) + return false; + + ret--; + + if (ret <= SCDC_FRL_RATE_DISABLE) + return false; + + ret = drm_scdc_frl_config_to_rate(ret, out_rate_per_lane, out_lanes); + + return ret == 0; +} +EXPORT_SYMBOL(drm_scdc_calc_lower_frl); + +/** + * drm_scdc_get_frl_ltp_request - read LTP requested by the Sink for FRL lanes + * @connector: connector + * @ln0: output LTP request for FRL lane 0 + * @ln1: output LTP request for FRL lane 1 + * @ln2: output LTP request for FRL lane 2 + * @ln3: output LTP request for FRL lane 3 + * + * Reads over SCDC the Link Training Pattern (LTP) requested by the Sink for + * each of the four FRL lanes and stores the values in ln0..3. + * + * Returns: + * True on success, false otherwise. + */ +bool drm_scdc_get_frl_ltp_request(struct drm_connector *connector, + u8 *ln0, u8 *ln1, u8 *ln2, u8 *ln3) +{ + u8 status; + int ret; + + ret = drm_scdc_readb(connector->ddc, SCDC_STATUS_FLAGS_1, &status); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to read LTP{0,1}: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + *ln0 = FIELD_GET(SCDC_FRL_LN0_LTP_REQ_MASK, status); + *ln1 = FIELD_GET(SCDC_FRL_LN1_LTP_REQ_MASK, status); + + ret = drm_scdc_readb(connector->ddc, SCDC_STATUS_FLAGS_2, &status); + if (ret < 0) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Failed to read LTP{2,3}: %d\n", + connector->base.id, connector->name, ret); + return false; + } + + *ln2 = FIELD_GET(SCDC_FRL_LN2_LTP_REQ_MASK, status); + *ln3 = FIELD_GET(SCDC_FRL_LN3_LTP_REQ_MASK, status); + + return true; +} +EXPORT_SYMBOL(drm_scdc_get_frl_ltp_request); diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index 34600476a1b9c9..4bd1caee0d2817 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -77,4 +77,10 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector); bool drm_scdc_set_scrambling(struct drm_connector *connector, bool enable); bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, bool set); +bool drm_scdc_set_frl(struct drm_connector *connector, + u8 rate_per_lane, u8 lanes, u8 max_ffe_level); +bool drm_scdc_calc_lower_frl(u8 in_rate_per_lane, u8 in_lanes, + u8 *out_rate_per_lane, u8 *out_lanes); +bool drm_scdc_get_frl_ltp_request(struct drm_connector *connector, + u8 *ln0, u8 *ln1, u8 *ln2, u8 *ln3); #endif From da34a9afdcbe034b80277cdb3afdd59ec3381f9e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 25 Mar 2026 01:34:26 +0200 Subject: [PATCH 137/208] drm/display: hdmi: Provide hook for HDMI 2.1 FRL mode validation HDMI 2.1 sinks that support Fixed Rate Link (FRL) can accept pixel clocks that exceed their maximum TMDS character rate. The existing hdmi_clock_valid() helper unconditionally rejects any mode whose clock surpasses max_tmds_clock, making it impossible to validate modes that would be transported over FRL. Add the .frl_rate_valid() hook to struct drm_connector_hdmi_funcs, mirroring the existing .tmds_char_rate_valid() pattern. The callback receives both the required data rate and the sink's maximum link rate (both in bps) and returns a drm_mode_status value. Implementing the hook is mandatory for drivers that wish to expose FRL-capable modes. Extend hdmi_clock_valid() to fall through to an FRL validation path when the TMDS clock limit is exceeded: 1. Verify the sink advertises FRL capability via max_frl_rate_per_lane and max_lanes fields parsed from the HF-VSDB in the sink EDID. 2. Require the driver to implement the new .frl_rate_valid() callback; otherwise reject the mode, since FRL link management is driver-specific. 3. Compute the required FRL bandwidth using 16b/18b encoding and compare it against the sink's maximum FRL link capacity. Reject the mode if the required bandwidth exceeds what the sink can handle. 4. Delegate the final validation to the driver's .frl_rate_valid() callback, which can apply hardware-specific constraints on the achievable FRL lane/rate configuration. Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_hdmi_state_helper.c | 26 +++++++++++++++++-- include/drm/drm_connector.h | 22 ++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 9f3b696aceeb4b..208180ccffdad0 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -555,8 +555,30 @@ hdmi_clock_valid(const struct drm_connector *connector, const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; const struct drm_display_info *info = &connector->display_info; - if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) - return MODE_CLOCK_HIGH; + if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) { + unsigned long long req_frl_bw, max_frl_bw; + + /* Check if FRL is supported by the sink */ + if (!info->hdmi.max_frl_rate_per_lane || !info->hdmi.max_lanes) + return MODE_CLOCK_HIGH; + + /* Mandate further (i.e. driver) FRL checks */ + if (!funcs || !funcs->frl_rate_valid) + return MODE_CLOCK_HIGH; + + /* + * Check required FRL bandwidth assuming 16b/18b encoding: + * bw (bps) = clock (Hz) * 3 (channels) * 8 (bit) * 18 / 16 + */ + req_frl_bw = clock * 27; + max_frl_bw = info->hdmi.max_frl_rate_per_lane * + info->hdmi.max_lanes * 1000000000ULL; + + if (req_frl_bw > max_frl_bw) + return MODE_CLOCK_HIGH; + + return funcs->frl_rate_valid(connector, mode, req_frl_bw, max_frl_bw); + } if (funcs && funcs->tmds_char_rate_valid) { enum drm_mode_status status; diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f83f28cae20757..9605896287fcb6 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1343,6 +1343,28 @@ struct drm_connector_hdmi_funcs { const struct drm_display_mode *mode, unsigned long long tmds_rate); + /** + * @frl_rate_valid: + * + * This callback is invoked at atomic_check time to figure out + * whether a particular FRL data rate (given in bps) is supported + * by the driver by using a FRL link rate that doesn't exceed the + * maximum advertised by the sink (also expressed in bps). + * + * The @frl_rate_valid callback is only mandatory when drivers + * need to advertise FRL support. + * + * Returns: + * + * Either &drm_mode_status.MODE_OK or one of the failure reasons + * in &enum drm_mode_status. + */ + enum drm_mode_status + (*frl_rate_valid)(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate); + /** * @read_edid: * From 7dd6ee40a83a5f896467e7f1ac9fad02e3910204 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 25 Mar 2026 01:27:05 +0200 Subject: [PATCH 138/208] drm/display: bridge-connector: Wire up rate validation for HDMI 2.1 bridges The bridge connector infrastructure acts as a generic glue layer between drm_bridge implementations and the DRM connector framework, delegating TMDS character rate validation to the underlying bridge via .hdmi_tmds_char_rate_valid(). FRL-capable bridges need the same treatment for the new .frl_rate_valid() connector hook introduced to support HDMI 2.1 mode validation. Add the corresponding .hdmi_frl_rate_valid() hook to struct drm_bridge_funcs, documented as optional and gated on DRM_BRIDGE_OP_HDMI, to be consistent with all other HDMI bridge callbacks. Provide drm_bridge_connector_frl_rate_valid(), which implements the .frl_rate_valid() callback in drm_connector_hdmi_funcs by forwarding the call to the HDMI bridge .hdmi_frl_rate_valid() hook. The delegation follows the same pattern as the TMDS equivalent, with one exception: if the bridge does not implement the callback, reject the FRL modes rather than silently permitting them. Unlike the TMDS path, FRL support must be explicit. Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 22 ++++++++++++++++++ include/drm/drm_bridge.h | 23 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index d3aaa81838320e..d9e5f02ccb7f50 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -416,6 +416,27 @@ drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector, return MODE_OK; } +static enum drm_mode_status +drm_bridge_connector_frl_rate_valid(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *bridge; + + bridge = bridge_connector->bridge_hdmi; + if (!bridge) + return MODE_ERROR; + + if (bridge->funcs->hdmi_frl_rate_valid) + return bridge->funcs->hdmi_frl_rate_valid(bridge, mode, + frl_data_rate, + frl_max_link_rate); + return MODE_CLOCK_HIGH; +} + static int drm_bridge_connector_clear_avi_infoframe(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = @@ -567,6 +588,7 @@ drm_bridge_connector_read_edid(struct drm_connector *connector) static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = { .tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid, + .frl_rate_valid = drm_bridge_connector_frl_rate_valid, .read_edid = drm_bridge_connector_read_edid, .avi = { .clear_infoframe = drm_bridge_connector_clear_avi_infoframe, diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 6626c1afbd5940..7cf37cc9d52536 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -709,6 +709,29 @@ struct drm_bridge_funcs { const struct drm_display_mode *mode, unsigned long long tmds_rate); + /** + * @hdmi_frl_rate_valid: + * + * Check whether a particular FRL data rate (given in bps) is + * supported by the driver by using a FRL link rate that doesn't + * exceed the maximum advertised by the sink (also given in bps). + * + * This callback is optional and should only be implemented by the + * bridges that take part in the HDMI connector implementation and + * need to advertise FRL support. Bridges that implement it shall + * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. + * + * Returns: + * + * Either &drm_mode_status.MODE_OK or one of the failure reasons + * in &enum drm_mode_status. + */ + enum drm_mode_status + (*hdmi_frl_rate_valid)(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate); + /** * @hdmi_clear_avi_infoframe: * From af622c9aaf4024023d34790a21af352ace3bef77 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 8 Apr 2026 00:23:35 +0300 Subject: [PATCH 139/208] drm/rockchip: vop2: Scale ACLK rate up for RK3588 FRL display modes At the default 500 MHz clock rate, the VOP2 AXI bus on RK3588 cannot sustain the memory bandwidth required by high-resolution FRL modes (e.g. 4K@120Hz), leading to rendering artifacts and POST_BUF_EMPTY interrupt errors. Increase the ACLK_VOP rate from 500 MHz to 750 MHz when any video port enters HDMI 2.1 FRL mode, and restore it to 500 MHz when the last FRL video port is disabled. In order for the FRL link state to be propagated from the HDMI encoder into rockchip_crtc_state, introduce a new frl_enabled flag, and use a reference counter (frl_vp_count) in struct vop2 to track how many video ports are driving FRL outputs, so the rate boost is applied for the first FRL VP and removed only after the last one goes idle. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 20 ++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 1 + 3 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 2e86ad00979c41..4ea8878e10a86d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -53,6 +53,7 @@ struct rockchip_crtc_state { u32 bus_format; u32 bus_flags; int color_space; + bool frl_enabled; }; #define to_rockchip_crtc_state(s) \ container_of(s, struct rockchip_crtc_state, base) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 9f30d2c7a2b400..9c802946c25022 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -104,6 +104,8 @@ enum vop2_afbc_format { }; #define VOP2_MAX_DCLK_RATE 600000000UL +#define VOP2_ACLK_RATE_DEFAULT 500000000UL +#define VOP2_ACLK_RATE_FRL 750000000UL /* * bus-format types. @@ -965,6 +967,17 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, clk_disable_unprepare(vp->dclk); + if (vop2->version == VOP_VERSION_RK3588) { + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(old_crtc_state); + + if (vcstate->frl_enabled) { + vop2->frl_vp_count--; + + if (!vop2->frl_vp_count) + clk_set_rate(vop2->aclk, VOP2_ACLK_RATE_DEFAULT); + } + } + vop2->enable_count--; if (!vop2->enable_count) @@ -1694,6 +1707,13 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2->enable_count++; + if (vop2->version == VOP_VERSION_RK3588 && vcstate->frl_enabled) { + if (!vop2->frl_vp_count) + clk_set_rate(vop2->aclk, VOP2_ACLK_RATE_FRL); + + vop2->frl_vp_count++; + } + vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format); vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 0296e35579a921..310e6547f3bc6e 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -333,6 +333,7 @@ struct vop2 { * we need a ref counter here. */ unsigned int enable_count; + unsigned int frl_vp_count; struct clk *hclk; struct clk *aclk; struct clk *pclk; From d6ded8359082fbde62148ab5ff76dae6430296d2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 31 Mar 2026 17:52:43 +0300 Subject: [PATCH 140/208] drm/rockchip: dw_hdmi_qp: Consolidate link config into dedicated struct Abstract link configuration state into a dedicated dw_hdmi_qp_link_cfg struct by moving tmds_char_rate out of the main driver data struct into link_cfg, while also storing the configured PHY bit depth there. Eventually expose it to the bridge layer via a new .get_link_cfg() PHY operation. This refactoring is needed to give the bridge core a clean, extensible interface to query the active link parameters, a prerequisite for adding HDMI 2.1 FRL fields (rate, lane count, etc.) to the same structure without further scattering state across the driver. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 18 +++++++++++++++--- include/drm/bridge/dw_hdmi_qp.h | 6 ++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index d6338195a5b751..b0a95ea711708d 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -96,11 +96,11 @@ struct rockchip_hdmi_qp { struct drm_connector *connector; struct dw_hdmi_qp *hdmi; struct phy *phy; + struct dw_hdmi_qp_link_cfg link_cfg; struct gpio_desc *frl_enable_gpio; struct delayed_work hpd_work; int port_id; const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops; - unsigned long long tmds_char_rate; }; struct rockchip_hdmi_qp_ctrl_ops { @@ -139,10 +139,11 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder, { struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; union phy_configure_opts phy_cfg = {}; int ret; - if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate && + if (lcfg->tmds_char_rate == conn_state->hdmi.tmds_char_rate && s->output_bpc == conn_state->hdmi.output_bpc) return 0; @@ -151,7 +152,8 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder, ret = phy_configure(hdmi->phy, &phy_cfg); if (!ret) { - hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + hdmi->link_cfg.tmds_char_rate = conn_state->hdmi.tmds_char_rate; + hdmi->link_cfg.bpc = phy_cfg.hdmi.bpc; s->output_mode = ROCKCHIP_OUT_MODE_AAAA; s->output_type = DRM_MODE_CONNECTOR_HDMIA; s->output_bpc = conn_state->hdmi.output_bpc; @@ -210,11 +212,20 @@ static void dw_hdmi_qp_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data) regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val); } +static const struct dw_hdmi_qp_link_cfg * +dw_hdmi_qp_rk3588_get_link_cfg(struct dw_hdmi_qp *dw_hdmi, void *data) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + + return &hdmi->link_cfg; +} + static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { .init = dw_hdmi_qp_rk3588_phy_init, .disable = dw_hdmi_qp_rk3588_phy_disable, .read_hpd = dw_hdmi_qp_rk3588_read_hpd, .setup_hpd = dw_hdmi_qp_rk3588_setup_hpd, + .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, }; static enum drm_connector_status @@ -246,6 +257,7 @@ static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = { .disable = dw_hdmi_qp_rk3588_phy_disable, .read_hpd = dw_hdmi_qp_rk3576_read_hpd, .setup_hpd = dw_hdmi_qp_rk3576_setup_hpd, + .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, }; static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index 6ea9c561cfef6d..55532b7d045873 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -12,11 +12,17 @@ struct drm_encoder; struct dw_hdmi_qp; struct platform_device; +struct dw_hdmi_qp_link_cfg { + unsigned long long tmds_char_rate; + unsigned int bpc; +}; + struct dw_hdmi_qp_phy_ops { int (*init)(struct dw_hdmi_qp *hdmi, void *data); void (*disable)(struct dw_hdmi_qp *hdmi, void *data); enum drm_connector_status (*read_hpd)(struct dw_hdmi_qp *hdmi, void *data); void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data); + const struct dw_hdmi_qp_link_cfg *(*get_link_cfg)(struct dw_hdmi_qp *hdmi, void *data); }; struct dw_hdmi_qp_plat_data { From a6e733af4deb67faf1a511b4385a18fd82d6a6e3 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 4 Feb 2026 01:11:56 +0200 Subject: [PATCH 141/208] drm/bridge: dw-hdmi-qp: Log resolution and refresh rate in atomic_enable() The debug entry in the HDMI branch of dw_hdmi_qp_bridge_atomic_enable() previously printed the literal string 'HDMI' as the mode field, giving no information about the actual display timing being configured. Extend it to include the active resolution and refresh rate by retrieving the CRTC mode from the incoming atomic state: dw_hdmi_qp_bridge_atomic_enable mode=1920x1080@50Hz fmt=RGB rate=185625000 bpc=10 This makes the log line self-contained and directly useful when debugging mode-setting issues, format negotiation, or TMDS rate mismatches without having to cross-reference a separate mode dump. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 36fbd3831bc4d3..8de0da238a020d 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -857,6 +857,8 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; struct drm_connector_state *conn_state; + const struct drm_display_mode *mode; + struct drm_crtc_state *crtc_state; unsigned int op_mode; hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, @@ -869,9 +871,13 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, return; if (hdmi->curr_conn->display_info.is_hdmi) { - dev_dbg(hdmi->dev, "%s mode=HDMI %s rate=%llu bpc=%u\n", __func__, + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + mode = &crtc_state->mode; + dev_dbg(hdmi->dev, "%s mode=%ux%u@%uHz fmt=%s rate=%llu bpc=%u\n", __func__, + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format), conn_state->hdmi.tmds_char_rate, conn_state->hdmi.output_bpc); + op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; From 923622d290c7d93e2cac1571bdcaeb404033d946 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 17 Mar 2026 22:42:11 +0200 Subject: [PATCH 142/208] drm/bridge: dw-hdmi-qp: Replace sentinel entries with ARRAY_SIZE() iteration The two static lookup tables for audio TMDS N and CTS values use all-zero sentinel entries as end-of-table markers, required to terminate the iteration loops. This is a rather fragile pattern, hence switch to ARRAY_SIZE()-bounded for loops and remove the now redundant 'End of table' comments along with the related rows. No functional change intended. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 8de0da238a020d..e770195b104fee 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -103,9 +103,6 @@ static const struct dw_hdmi_audio_tmds_n { /* For 297 MHz+ HDMI spec have some other rule for setting N */ { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240,}, - - /* End of table */ - { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, }; /* @@ -124,9 +121,6 @@ static const struct dw_hdmi_audio_tmds_cts { { .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, }, { .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, }, { .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, }, - - /* End of table */ - { .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, }, }; struct dw_hdmi_qp_i2c { @@ -232,7 +226,7 @@ static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; int i; - for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { + for (i = 0; i < ARRAY_SIZE(common_tmds_n_table); i++) { if (pixel_clk == common_tmds_n_table[i].tmds) { tmds_n = &common_tmds_n_table[i]; break; @@ -323,7 +317,7 @@ static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long p const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL; int i; - for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) { + for (i = 0; i < ARRAY_SIZE(common_tmds_cts_table); i++) { if (pixel_clk == common_tmds_cts_table[i].tmds) { tmds_cts = &common_tmds_cts_table[i]; break; From 0b173f5d5cfedb8d0423f597cbfcaa6da3f94420 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 7 Apr 2026 00:39:51 +0300 Subject: [PATCH 143/208] drm/bridge: dw-hdmi-qp: Add HDMI 2.1 FRL support Enable the DesignWare HDMI QP bridge to operate in HDMI 2.1 Fixed Rate Link (FRL) mode, in addition to the existing TMDS mode: - Expand dw_hdmi_qp_link_cfg to carry FRL rate/lane configuration (current, max, and min), allowing the PHY layer to communicate FRL operating parameters to the bridge - Implement the full FRL Link Training (FLT) state machine as a kernel work item, driving the SCDC-based handshake with the sink across all training states - Add an optional .set_frl_rate() PHY callback so the bridge can instruct the PHY to switch to a lower FRL rate during training fallback - Implement the .hdmi_frl_rate_valid() bridge callback so the DRM framework can correctly filter modes based on the hardware's FRL bandwidth capabilities Co-developed-by: Algea Cao Signed-off-by: Algea Cao Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 634 ++++++++++++++++++- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 3 + include/drm/bridge/dw_hdmi_qp.h | 12 +- 3 files changed, 627 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index e770195b104fee..9e4cf46c214060 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -47,6 +47,22 @@ #define SCDC_MAX_SOURCE_VERSION 0x1 #define SCRAMB_POLL_DELAY_MS 3000 +/* + * Recommended N and Expected CTS Values in FRL Mode. + */ +static const struct dw_hdmi_qp_audio_frl_n { + unsigned int r_bit; + unsigned int n_32k; + unsigned int n_44k1; + unsigned int n_48k; +} common_frl_n_table[] = { + { .r_bit = 3, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 5760, }, + { .r_bit = 6, .n_32k = 4032, .n_44k1 = 5292, .n_48k = 6048, }, + { .r_bit = 8, .n_32k = 4032, .n_44k1 = 3969, .n_48k = 6048, }, + { .r_bit = 10, .n_32k = 3456, .n_44k1 = 3969, .n_48k = 5184, }, + { .r_bit = 12, .n_32k = 3072, .n_44k1 = 3969, .n_48k = 4752, }, +}; + /* * Unless otherwise noted, entries in this table are 100% optimization. * Values can be obtained from dw_hdmi_qp_compute_n() but that function is @@ -168,10 +184,13 @@ struct dw_hdmi_qp { struct delayed_work scramb_work; bool scramb_enabled; + struct work_struct flt_work; + bool flt_no_timeout; + struct regmap *regm; int main_irq; - unsigned long tmds_char_rate; + unsigned long long tmds_char_rate; bool no_hpd; }; @@ -219,6 +238,49 @@ static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, AUDPKT_ACR_CONTROL1); } +static int dw_hdmi_qp_match_frl_n_table(struct dw_hdmi_qp *hdmi, + unsigned long r_bit, + unsigned long freq) +{ + const struct dw_hdmi_qp_audio_frl_n *frl_n = NULL; + int i, n; + + for (i = 0; i < ARRAY_SIZE(common_frl_n_table); i++) { + if (r_bit == common_frl_n_table[i].r_bit) { + frl_n = &common_frl_n_table[i]; + break; + } + } + + if (!frl_n) { + dev_err(hdmi->dev, "Unexpected FRL Rbit: %lu Gbps\n", r_bit); + return 0; + } + + switch (freq) { + case 32000: + case 64000: + case 128000: + n = (freq / 32000) * frl_n->n_32k; + break; + case 44100: + case 88200: + case 176400: + n = (freq / 44100) * frl_n->n_44k1; + break; + case 48000: + case 96000: + case 192000: + n = (freq / 48000) * frl_n->n_48k; + break; + default: + dev_err(hdmi->dev, "Unexpected FRL freq: %lu Hz\n", freq); + n = 0; + } + + return n; +} + static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) @@ -300,10 +362,18 @@ static unsigned int dw_hdmi_qp_compute_n(struct dw_hdmi_qp *hdmi, static unsigned int dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long sample_rate) { - int n = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); + const struct dw_hdmi_qp_link_cfg *link_cfg; + int ret; + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + if (link_cfg->frl_enabled) + return dw_hdmi_qp_match_frl_n_table(hdmi, + link_cfg->frl_rate_per_lane, + sample_rate); - if (n > 0) - return n; + ret = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); + if (ret > 0) + return ret; dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", pixel_clk); @@ -753,6 +823,451 @@ static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi) return adap; } +enum dw_hdmi_qp_frl_lts { + LTS1, /* Read EDID */ + LTS2, /* Prepare for FRL */ + LTS3, /* Training in progress */ + LTS4, /* Update FRL rate */ + LTSP, /* Training passed */ + LTSL, /* Legacy TMDS */ + LTSU, /* Undefined */ +}; + +/* + * Check sink version and FLT no-timeout mode. + */ +static int dw_hdmi_qp_frl_lts1(struct dw_hdmi_qp *hdmi) +{ + int ret; + u8 val; + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "lts1: hdmi disabled\n"); + return LTSL; + } + + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + /* Reset AVP data path */ + dw_hdmi_qp_write(hdmi, AVP_DATAPATH_SWINIT_P, GLOBAL_SWRESET_REQUEST); + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &val); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC read failed\n"); + return LTSL; + } + + if (!val) { + dev_warn(hdmi->dev, "lts1: SCDC sink version is zero\n"); + return LTSL; + } + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, 1); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC write failed\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SOURCE_TEST_CONFIG, &val); + if (ret) { + dev_err(hdmi->dev, "lts1: SCDC read failed\n"); + return LTSL; + } + + hdmi->flt_no_timeout = !!(val & SCDC_FLT_NO_TIMEOUT); + dev_dbg(hdmi->dev, "lts1: flt_no_timeout=%d\n", hdmi->flt_no_timeout); + + return LTS2; +} + +/* + * Check if sink is ready to training. Set FRL rate & max FFE level. + */ +static int dw_hdmi_qp_frl_lts2(struct dw_hdmi_qp *hdmi) +{ + const struct dw_hdmi_qp_link_cfg *link_cfg; + int ret, i; + u8 val; + + for (i = 0; i < 20; i++) { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_STATUS_FLAGS_0, &val); + if (ret) { + dev_err(hdmi->dev, "lts2: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_FLT_READY) { + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + dev_dbg(hdmi->dev, "lts2: set rate=%ux%u\n", + link_cfg->frl_rate_per_lane, link_cfg->frl_lanes); + + ret = drm_scdc_set_frl(hdmi->curr_conn, link_cfg->frl_rate_per_lane, + link_cfg->frl_lanes, 0); + if (ret) + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_CONFIG_0, 0); + else + ret = -EIO; + + if (ret) { + dev_err(hdmi->dev, "lts2: SCDC write failed\n"); + return LTSL; + } + + return LTS3; + } + + msleep(20); + } + + dev_err(hdmi->dev, "lts2: sink flt not ready\n"); + return LTSL; +} + +/* + * Conduct link training for the specified FRL rate. + */ +static int dw_hdmi_qp_frl_lts3(struct dw_hdmi_qp *hdmi) +{ + u8 val; + int i, ret; + + /* Set 2s timeout */ + i = 4000; + + while (i-- > 0 || hdmi->flt_no_timeout) { + /* Poll FLT_update flag every 2 ms or less */ + usleep_range(400, 500); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "lts3: hdmi disabled\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_SOURCE_TEST_UPDATE) { + u8 test_cfg; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SOURCE_TEST_CONFIG, + &test_cfg); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + if (hdmi->flt_no_timeout && !(test_cfg & SCDC_FLT_NO_TIMEOUT)) { + dev_dbg(hdmi->dev, "lts3: exit test mode\n"); + hdmi->flt_no_timeout = false; + } else if (!hdmi->flt_no_timeout && (test_cfg & SCDC_FLT_NO_TIMEOUT)) { + dev_dbg(hdmi->dev, "lts3: enter test mode\n"); + hdmi->flt_no_timeout = true; + } + + /* Clear SCDC_SOURCE_TEST_UPDATE flag */ + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_SOURCE_TEST_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC write failed\n"); + return LTSL; + } + } + + if (val & SCDC_FLT_UPDATE) { + u8 ln0, ln1, ln2, ln3; + u32 flt_cfg; + + ret = drm_scdc_get_frl_ltp_request(hdmi->curr_conn, + &ln0, &ln1, &ln2, &ln3); + if (!ret) { + dev_err(hdmi->dev, "lts3: SCDC read failed\n"); + return LTSL; + } + + dev_dbg(hdmi->dev, "lts3: ln0=0x%x ln1=0x%x ln2=0x%x ln3=0x%x\n", + ln0, ln1, ln2, ln3); + + if (!ln0 && !ln1 && !ln2 && !ln3) { + dw_hdmi_qp_write(hdmi, 0, FLT_CONFIG1); + return LTSP; + } + + if (ln0 == 0xf && ln1 == 0xf && ln2 == 0xf && ln3 == 0xf) { + dev_dbg(hdmi->dev, "lts3: rate change request\n"); + return LTS4; + } + + if (ln0 == 0xe || ln1 == 0xe || ln2 == 0xe || ln3 == 0xe) { + dev_err(hdmi->dev, "lts3: ffe level update not expected\n"); + return LTSL; + } else { + flt_cfg = (ln3 << 16) | (ln2 << 12) | (ln1 << 8) | + (ln0 << 4) | 0xf; + + /* Support HFR1-10; send old LTP if ln0..3 == 0x3 */ + if (!hdmi->flt_no_timeout && flt_cfg == 0x3333f) + flt_cfg = dw_hdmi_qp_read(hdmi, FLT_CONFIG1); + + dw_hdmi_qp_write(hdmi, flt_cfg, FLT_CONFIG1); + } + + /* Clear FLT_update flag */ + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts3: SCDC write failed\n"); + return LTSL; + } + } + } + + dev_err(hdmi->dev, "lts3: timed out\n"); + return LTSL; +} + +/* + * Handle FRL rate change request from sink. + */ +static int dw_hdmi_qp_frl_lts4(struct dw_hdmi_qp *hdmi) +{ + const struct dw_hdmi_qp_link_cfg *link_cfg; + u8 try_rate_per_lane, try_lanes; + int ret; + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + /* Choose a reduced bandwidth FRL rate */ + ret = drm_scdc_calc_lower_frl(link_cfg->frl_rate_per_lane, link_cfg->frl_lanes, + &try_rate_per_lane, &try_lanes); + if (!ret) { + dev_err(hdmi->dev, "lts4: failed to compute lower frl\n"); + return LTSL; + } + + if (try_rate_per_lane < link_cfg->min_frl_rate_per_lane || + try_lanes < link_cfg->min_frl_lanes) { + dev_err(hdmi->dev, "lts4: unsupported %ux%u rate\n", + try_rate_per_lane, try_lanes); + return LTSL; + } + + dev_dbg(hdmi->dev, "lts4: switching to %ux%u\n", try_rate_per_lane, try_lanes); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "lts4: SCDC write failed\n"); + return LTSL; + } + + /* Disable phy */ + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + + ret = hdmi->phy.ops->set_frl_rate(hdmi, hdmi->phy.data, + try_rate_per_lane, try_lanes); + if (ret) { + dev_err(hdmi->dev, "lts4: failed to set phy link rate\n"); + return LTSL; + } + + /* Enable phy */ + hdmi->phy.ops->init(hdmi, hdmi->phy.data); + + /* Set new rate */ + ret = drm_scdc_set_frl(hdmi->curr_conn, try_rate_per_lane, try_lanes, 0); + if (ret) + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + else + ret = -EIO; + + if (ret) { + dev_err(hdmi->dev, "lts4: SCDC write failed\n"); + return LTSL; + } + + return LTS3; +} + +/* + * Training passed, wait for further changes. + */ +static int dw_hdmi_qp_frl_ltsp(struct dw_hdmi_qp *hdmi) +{ + int i, ret; + u8 val; + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + /* Set 2s timeout */ + i = 4000; + + while (i--) { + /* Poll Update Flags every 2 ms or less */ + usleep_range(400, 500); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "ltsp: hdmi disabled\n"); + return LTSL; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC read failed\n"); + return LTSL; + } + + if (val & SCDC_FRL_START) { + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_VIDEO_SWDISABLE, + GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FRL_START); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_CLEAR_AVMUTE, + PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); + + dev_dbg(hdmi->dev, "ltsp: flt success\n"); + break; + } + + if (val & SCDC_FLT_UPDATE) { + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + return LTSL; + } + + return LTS3; + } + } + + if (i < 0) { + dev_err(hdmi->dev, "ltsp: timed out\n"); + return LTSL; + } + + /* Ensure FLT_update flag is polled at least once every 250 ms. */ + i = 5; + + while (true) { + msleep(20); + + if (!hdmi->tmds_char_rate) { + dev_dbg(hdmi->dev, "ltsp: hdmi disabled\n"); + break; + } + + if (i) { + i--; + continue; + } + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_UPDATE_0, &val); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC read failed\n"); + break; + } + + if (val & SCDC_FLT_UPDATE) { + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_SET_AVMUTE, + PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); + + msleep(50); + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE, + AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, + SCDC_FLT_UPDATE); + if (ret) { + dev_err(hdmi->dev, "ltsp: SCDC write failed\n"); + break; + } + + return LTS2; + } + + i = 5; + } + + return LTSL; +} + +/* + * Exit frl mode, i.e. for training failures or hdmi disabled. + */ +static int dw_hdmi_qp_frl_ltsl(struct dw_hdmi_qp *hdmi) +{ + enum drm_connector_status status; + + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + dev_dbg(hdmi->dev, "ltsl: conn_stat=%d\n", status); + + if (status != connector_status_disconnected) { + drm_scdc_set_frl(hdmi->curr_conn, 0, 0, 0); + drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); + } + + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE); + + return LTSU; +} + +static void dw_hdmi_qp_flt_work(struct work_struct *work) +{ + struct dw_hdmi_qp *hdmi = container_of(work, struct dw_hdmi_qp, flt_work); + enum dw_hdmi_qp_frl_lts state = LTS1; + + while (true) { + switch (state) { + case LTS1: + state = dw_hdmi_qp_frl_lts1(hdmi); + break; + case LTS2: + state = dw_hdmi_qp_frl_lts2(hdmi); + break; + case LTS3: + state = dw_hdmi_qp_frl_lts3(hdmi); + break; + case LTS4: + state = dw_hdmi_qp_frl_lts4(hdmi); + break; + case LTSP: + state = dw_hdmi_qp_frl_ltsp(hdmi); + break; + case LTSL: + state = dw_hdmi_qp_frl_ltsl(hdmi); + break; + case LTSU: + return; + default: + dev_err(hdmi->dev, "unexpected flt state: %d\n", state); + return; + } + } +} + static bool dw_hdmi_qp_supports_scrambling(struct drm_display_info *display) { if (!display->is_hdmi) @@ -850,7 +1365,8 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - struct drm_connector_state *conn_state; + const struct drm_connector_state *conn_state; + const struct dw_hdmi_qp_link_cfg *link_cfg; const struct drm_display_mode *mode; struct drm_crtc_state *crtc_state; unsigned int op_mode; @@ -864,6 +1380,8 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, if (WARN_ON(!conn_state)) return; + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + if (hdmi->curr_conn->display_info.is_hdmi) { crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); mode = &crtc_state->mode; @@ -875,7 +1393,8 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; - if (conn_state->hdmi.tmds_char_rate > HDMI14_MAX_TMDSCLK) + if (!link_cfg->frl_enabled && + conn_state->hdmi.tmds_char_rate > HDMI14_MAX_TMDSCLK) dw_hdmi_qp_enable_scramb(hdmi); } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); @@ -887,7 +1406,21 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0); + dw_hdmi_qp_mod(hdmi, + link_cfg->frl_enabled && link_cfg->frl_lanes == 4 ? OPMODE_FRL_4LANES : 0, + OPMODE_FRL_4LANES, LINK_CONFIG0); + dw_hdmi_qp_mod(hdmi, link_cfg->frl_enabled, OPMODE_FRL, LINK_CONFIG0); + drm_atomic_helper_connector_hdmi_update_infoframes(hdmi->curr_conn, state); + + if (link_cfg->frl_enabled) { + /* Ensure phy output is stable before starting FLT */ + msleep(50); + schedule_work(&hdmi->flt_work); + } else { + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_CLEAR_AVMUTE, PKTSCHED_PKT_CONTROL0); + dw_hdmi_qp_mod(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); + } } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, @@ -897,7 +1430,11 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, hdmi->tmds_char_rate = 0; + dw_hdmi_qp_write(hdmi, PKTSCHED_GCP_SET_AVMUTE, PKTSCHED_PKT_CONTROL0); + msleep(50); + dw_hdmi_qp_disable_scramb(hdmi); + cancel_work_sync(&hdmi->flt_work); hdmi->curr_conn = NULL; hdmi->phy.ops->disable(hdmi, hdmi->phy.data); @@ -910,14 +1447,25 @@ static int dw_hdmi_qp_reset_crtc(struct dw_hdmi_qp *hdmi, u8 config; int ret; - ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_TMDS_CONFIG, &config); - if (ret < 0) { - dev_err(hdmi->dev, "Failed to read TMDS config: %d\n", ret); - return ret; - } + if (hdmi->scramb_enabled) { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_TMDS_CONFIG, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read TMDS config: %d\n", ret); + return ret; + } - if (!!(config & SCDC_SCRAMBLING_ENABLE) == hdmi->scramb_enabled) - return 0; + if (config & SCDC_SCRAMBLING_ENABLE) + return 0; + } else { + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_CONFIG_1, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read FRL config: %d\n", ret); + return ret; + } + + if (config & SCDC_FRL_RATE_MASK && work_busy(&hdmi->flt_work)) + return 0; + } drm_atomic_helper_connector_hdmi_hotplug(connector, connector_status_connected); @@ -937,8 +1485,10 @@ static int dw_hdmi_qp_bridge_detect_ctx(struct drm_bridge *bridge, struct drm_modeset_acquire_ctx *ctx) { struct dw_hdmi_qp *hdmi = bridge->driver_private; + const struct dw_hdmi_qp_link_cfg *link_cfg; enum drm_connector_status status; const struct drm_edid *drm_edid; + bool frl_active; int ret; if (hdmi->no_hpd) { @@ -951,10 +1501,14 @@ static int dw_hdmi_qp_bridge_detect_ctx(struct drm_bridge *bridge, status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); - dev_dbg(hdmi->dev, "%s status=%d scramb=%d\n", __func__, - status, hdmi->scramb_enabled); + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + frl_active = link_cfg->frl_enabled && hdmi->tmds_char_rate; - if (status == connector_status_connected && hdmi->scramb_enabled) { + dev_dbg(hdmi->dev, "%s status=%d frl=%u scramb=%u\n", __func__, + status, frl_active, hdmi->scramb_enabled); + + if (status == connector_status_connected && + (frl_active || hdmi->scramb_enabled)) { ret = dw_hdmi_qp_reset_crtc(hdmi, connector, ctx); if (ret == -EDEADLK) return ret; @@ -999,12 +1553,47 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, return MODE_OK; } +static enum drm_mode_status +dw_hdmi_qp_bridge_frl_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long frl_data_rate, + unsigned long long frl_max_link_rate) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + const struct dw_hdmi_qp_link_cfg *link_cfg; + unsigned long long frl_bw; + + if (!hdmi->phy.ops->set_frl_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL rate: %lld\n", frl_data_rate); + return MODE_CLOCK_HIGH; + } + + link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + + frl_bw = link_cfg->max_frl_rate_per_lane * + link_cfg->max_frl_lanes * 1000000000ULL; + if (frl_bw < frl_data_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL data rate: req=%llu max=%llu\n", + frl_data_rate, frl_bw); + return MODE_CLOCK_HIGH; + } + + frl_bw = link_cfg->min_frl_rate_per_lane * + link_cfg->min_frl_lanes * 1000000000ULL; + if (frl_bw > frl_max_link_rate) { + dev_dbg(hdmi->dev, "Unsupported FRL link rate: req=%llu min=%llu\n", + frl_max_link_rate, frl_bw); + return MODE_CLOCK_LOW; + } + + return MODE_OK; +} + static int dw_hdmi_qp_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, - PKTSCHED_PKT_EN); + dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN, PKTSCHED_PKT_EN); return 0; } @@ -1085,8 +1674,8 @@ static int dw_hdmi_qp_bridge_write_avi_infoframe(struct drm_bridge *bridge, dw_hdmi_qp_write_infoframe(hdmi, buffer, len, PKT_AVI_CONTENTS0); dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1); - dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, - PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); + dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN, PKTSCHED_AVI_TX_EN, + PKTSCHED_PKT_EN); return 0; } @@ -1354,6 +1943,7 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .detect_ctx = dw_hdmi_qp_bridge_detect_ctx, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, + .hdmi_frl_rate_valid = dw_hdmi_qp_bridge_frl_rate_valid, .hdmi_clear_avi_infoframe = dw_hdmi_qp_bridge_clear_avi_infoframe, .hdmi_write_avi_infoframe = dw_hdmi_qp_bridge_write_avi_infoframe, .hdmi_clear_hdmi_infoframe = dw_hdmi_qp_bridge_clear_hdmi_infoframe, @@ -1431,7 +2021,8 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, int ret; if (!plat_data->phy_ops || !plat_data->phy_ops->init || - !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd) { + !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd || + !plat_data->phy_ops->get_link_cfg) { dev_err(dev, "Missing platform PHY ops\n"); return ERR_PTR(-ENODEV); } @@ -1442,6 +2033,7 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, return ERR_CAST(hdmi); INIT_DELAYED_WORK(&hdmi->scramb_work, dw_hdmi_qp_scramb_work); + INIT_WORK(&hdmi->flt_work, dw_hdmi_qp_flt_work); hdmi->dev = dev; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index c07847e8d7dd09..dfb8cb6614ad7d 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -23,6 +23,7 @@ #define GLOBAL_SWRESET_REQUEST 0x40 #define EARCRX_CMDC_SWINIT_P BIT(27) #define AVP_DATAPATH_PACKET_AUDIO_SWINIT_P BIT(10) +#define AVP_DATAPATH_SWINIT_P BIT(6) #define GLOBAL_SWDISABLE 0x44 #define CEC_SWDISABLE BIT(17) #define AVP_DATAPATH_PACKET_AUDIO_SWDISABLE BIT(10) @@ -216,6 +217,8 @@ #define PKTSCHED_ACR_TX_EN BIT(1) #define PKTSCHED_NULL_TX_EN BIT(0) #define PKTSCHED_PKT_CONTROL0 0xaac +#define PKTSCHED_GCP_CLEAR_AVMUTE BIT(1) +#define PKTSCHED_GCP_SET_AVMUTE BIT(0) #define PKTSCHED_PKT_SEND 0xab0 #define PKTSCHED_PKT_STATUS0 0xab4 #define PKTSCHED_PKT_STATUS1 0xab8 diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index 55532b7d045873..ea3c6c9fdd5cd1 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. */ #ifndef __DW_HDMI_QP__ #define __DW_HDMI_QP__ +#include + struct device; struct drm_encoder; struct dw_hdmi_qp; @@ -14,6 +16,13 @@ struct platform_device; struct dw_hdmi_qp_link_cfg { unsigned long long tmds_char_rate; + bool frl_enabled; + u8 frl_rate_per_lane; + u8 frl_lanes; + u8 max_frl_rate_per_lane; + u8 max_frl_lanes; + u8 min_frl_rate_per_lane; + u8 min_frl_lanes; unsigned int bpc; }; @@ -23,6 +32,7 @@ struct dw_hdmi_qp_phy_ops { enum drm_connector_status (*read_hpd)(struct dw_hdmi_qp *hdmi, void *data); void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data); const struct dw_hdmi_qp_link_cfg *(*get_link_cfg)(struct dw_hdmi_qp *hdmi, void *data); + int (*set_frl_rate)(struct dw_hdmi_qp *hdmi, void *data, u8 rate_per_lane, u8 lanes); }; struct dw_hdmi_qp_plat_data { From 924e850b0f12595082b73d22c86389264f4f7feb Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 7 Apr 2026 13:28:59 +0300 Subject: [PATCH 144/208] drm/bridge: dw-hdmi-qp: Add TxFFE level control support Extend the FRL link training state machine to handle sink-requested Transmitter Feed-Forward Equalization (TxFFE) level increases. During LTS3, a sink can signal that it needs a higher FFE level on the transmitter to improve signal integrity at a given FRL rate, instead of immediately requesting a rate downgrade. Handle this by incrementally stepping up the FFE level (up to the hardware maximum) via a new optional .set_ffe_level() PHY callback. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 34 +++++++++++++++----- include/drm/bridge/dw_hdmi_qp.h | 2 ++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 9e4cf46c214060..5582b661a10ebe 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -899,12 +899,13 @@ static int dw_hdmi_qp_frl_lts2(struct dw_hdmi_qp *hdmi) if (val & SCDC_FLT_READY) { link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); + val = hdmi->phy.ops->set_ffe_level ? link_cfg->max_ffe_level : 0; - dev_dbg(hdmi->dev, "lts2: set rate=%ux%u\n", - link_cfg->frl_rate_per_lane, link_cfg->frl_lanes); + dev_dbg(hdmi->dev, "lts2: set rate=%ux%u maxffe=%u\n", + link_cfg->frl_rate_per_lane, link_cfg->frl_lanes, val); ret = drm_scdc_set_frl(hdmi->curr_conn, link_cfg->frl_rate_per_lane, - link_cfg->frl_lanes, 0); + link_cfg->frl_lanes, val); if (ret) ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_CONFIG_0, 0); else @@ -930,7 +931,7 @@ static int dw_hdmi_qp_frl_lts2(struct dw_hdmi_qp *hdmi) */ static int dw_hdmi_qp_frl_lts3(struct dw_hdmi_qp *hdmi) { - u8 val; + u8 val, ffe_lv = 0; int i, ret; /* Set 2s timeout */ @@ -1003,8 +1004,22 @@ static int dw_hdmi_qp_frl_lts3(struct dw_hdmi_qp *hdmi) } if (ln0 == 0xe || ln1 == 0xe || ln2 == 0xe || ln3 == 0xe) { - dev_err(hdmi->dev, "lts3: ffe level update not expected\n"); - return LTSL; + if (!hdmi->phy.ops->set_ffe_level) { + dev_err(hdmi->dev, "lts3: ffe level update not expected\n"); + return LTSL; + } + + if (ffe_lv >= 3) { + dev_err(hdmi->dev, "lts3: ffe level limit reached\n"); + return LTSL; + } + + ffe_lv++; + dev_dbg(hdmi->dev, "lts3: ffe level up %d\n", ffe_lv); + + ret = hdmi->phy.ops->set_ffe_level(hdmi, hdmi->phy.data, ffe_lv); + if (ret) + return LTSL; } else { flt_cfg = (ln3 << 16) | (ln2 << 12) | (ln1 << 8) | (ln0 << 4) | 0xf; @@ -1036,7 +1051,7 @@ static int dw_hdmi_qp_frl_lts3(struct dw_hdmi_qp *hdmi) static int dw_hdmi_qp_frl_lts4(struct dw_hdmi_qp *hdmi) { const struct dw_hdmi_qp_link_cfg *link_cfg; - u8 try_rate_per_lane, try_lanes; + u8 try_rate_per_lane, try_lanes, max_ffe_level; int ret; link_cfg = hdmi->phy.ops->get_link_cfg(hdmi, hdmi->phy.data); @@ -1077,8 +1092,11 @@ static int dw_hdmi_qp_frl_lts4(struct dw_hdmi_qp *hdmi) /* Enable phy */ hdmi->phy.ops->init(hdmi, hdmi->phy.data); + max_ffe_level = hdmi->phy.ops->set_ffe_level ? link_cfg->max_ffe_level : 0; + /* Set new rate */ - ret = drm_scdc_set_frl(hdmi->curr_conn, try_rate_per_lane, try_lanes, 0); + ret = drm_scdc_set_frl(hdmi->curr_conn, try_rate_per_lane, try_lanes, + max_ffe_level); if (ret) ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_UPDATE_0, SCDC_FLT_UPDATE); diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index ea3c6c9fdd5cd1..0d5f09ccb957c5 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -23,6 +23,7 @@ struct dw_hdmi_qp_link_cfg { u8 max_frl_lanes; u8 min_frl_rate_per_lane; u8 min_frl_lanes; + u8 max_ffe_level; unsigned int bpc; }; @@ -33,6 +34,7 @@ struct dw_hdmi_qp_phy_ops { void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data); const struct dw_hdmi_qp_link_cfg *(*get_link_cfg)(struct dw_hdmi_qp *hdmi, void *data); int (*set_frl_rate)(struct dw_hdmi_qp *hdmi, void *data, u8 rate_per_lane, u8 lanes); + int (*set_ffe_level)(struct dw_hdmi_qp *hdmi, void *data, u8 ffe_level); }; struct dw_hdmi_qp_plat_data { From 1acbe0f79901f50b13ba4d8e83c7d44090e7d08d Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 7 Apr 2026 00:31:24 +0300 Subject: [PATCH 145/208] drm/rockchip: dw_hdmi_qp: Wire up FRL operating mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable HDMI 2.1 FRL mode in the Rockchip platform glue driver for the DesignWare HDMI QP controller: - Switch PHY mode selection to use phy_set_mode_ext() with PHY_HDMI_MODE_FRL or PHY_HDMI_MODE_TMDS depending on whether the required data rate exceeds HDMI 2.0's TMDS ceiling or the sink's own max TMDS clock - Negotiate the FRL rate/lane count by capping the sink's advertised capabilities against the hardware limits, and pass the result to the PHY via phy_configure() - Drive the frl_enable GPIO and set the HDMI 2.1 mode bit in the relevant GRF SoC control registers to reflect the active link mode - Implement .set_frl_rate() to allow the bridge's FLT state machine to reconfigure the PHY to a lower FRL rate during training fallback - Populate and validate the FRL rate/lane bounds in link_cfg at bind time, with per-SoC overrides in the config table (RK3588 is capped at 10 Gbps/lane × 4 lanes due to a known flicker issue at higher rates) Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 150 ++++++++++++++++-- 1 file changed, 136 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index b0a95ea711708d..a5f135ceee866e 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. - * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2024-2026 Collabora Ltd. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -63,13 +63,18 @@ #define RK3588_HDMI0_HPD_INT_CLR BIT(12) #define RK3588_HDMI1_HPD_INT_MSK BIT(15) #define RK3588_HDMI1_HPD_INT_CLR BIT(14) + #define RK3588_GRF_SOC_CON7 0x031c #define RK3588_HPD_HDMI0_IO_EN_MASK BIT(12) #define RK3588_HPD_HDMI1_IO_EN_MASK BIT(13) #define RK3588_GRF_SOC_STATUS1 0x0384 #define RK3588_HDMI0_LEVEL_INT BIT(16) #define RK3588_HDMI1_LEVEL_INT BIT(24) + #define RK3588_GRF_VO1_CON3 0x000c +#define RK3588_GRF_VO1_CON4 0x0010 +#define RK3588_HDMI21_MASK BIT(0) + #define RK3588_GRF_VO1_CON6 0x0018 #define RK3588_COLOR_DEPTH_MASK GENMASK(7, 4) #define RK3588_8BPC 0x0 @@ -81,12 +86,19 @@ #define RK3588_SDAIN_MASK BIT(10) #define RK3588_MODE_MASK BIT(11) #define RK3588_I2S_SEL_MASK BIT(13) + +#define RK3588_GRF_VO1_CON7 0x001c #define RK3588_GRF_VO1_CON9 0x0024 #define RK3588_HDMI0_GRANT_SEL BIT(10) #define RK3588_HDMI1_GRANT_SEL BIT(12) #define HOTPLUG_DEBOUNCE_MS 150 #define MAX_HDMI_PORT_NUM 2 +#define HDMI20_MAX_TMDS_RATE 600000000 +#define HDMI21_MAX_FRL_LANE_RATE 12 +#define HDMI21_MAX_FRL_LANE_NUM 4 +#define HDMI21_MIN_FRL_LANE_RATE 3 +#define HDMI21_MIN_FRL_LANE_NUM 3 struct rockchip_hdmi_qp { struct device *dev; @@ -120,16 +132,24 @@ static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder) static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); + const struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; struct drm_crtc *crtc = encoder->crtc; + struct rockchip_crtc_state *rks; - /* Unconditionally switch to TMDS as FRL is not yet supported */ - gpiod_set_value_cansleep(hdmi->frl_enable_gpio, 0); + gpiod_set_value_cansleep(hdmi->frl_enable_gpio, lcfg->frl_enabled); if (!crtc || !crtc->state) return; + rks = to_rockchip_crtc_state(crtc->state); + if (hdmi->ctrl_ops->enc_init) - hdmi->ctrl_ops->enc_init(hdmi, to_rockchip_crtc_state(crtc->state)); + hdmi->ctrl_ops->enc_init(hdmi, rks); + + dev_dbg(hdmi->dev, "%s port=%d tmds=%llu frl=%ux%u bpc=%u/%u\n", + __func__, hdmi->port_id, lcfg->tmds_char_rate, + lcfg->frl_rate_per_lane, lcfg->frl_lanes, + rks->output_bpc, lcfg->bpc); } static int @@ -137,31 +157,72 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); + const struct drm_display_info *info = &conn_state->connector->display_info; struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; union phy_configure_opts phy_cfg = {}; + enum phy_hdmi_mode mode; int ret; if (lcfg->tmds_char_rate == conn_state->hdmi.tmds_char_rate && s->output_bpc == conn_state->hdmi.output_bpc) return 0; - phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; phy_cfg.hdmi.bpc = conn_state->hdmi.output_bpc; - ret = phy_configure(hdmi->phy, &phy_cfg); - if (!ret) { - hdmi->link_cfg.tmds_char_rate = conn_state->hdmi.tmds_char_rate; - hdmi->link_cfg.bpc = phy_cfg.hdmi.bpc; - s->output_mode = ROCKCHIP_OUT_MODE_AAAA; - s->output_type = DRM_MODE_CONNECTOR_HDMIA; - s->output_bpc = conn_state->hdmi.output_bpc; + if (conn_state->hdmi.tmds_char_rate > HDMI20_MAX_TMDS_RATE || + (info->max_tmds_clock && + conn_state->hdmi.tmds_char_rate > info->max_tmds_clock * 1000)) { + mode = PHY_HDMI_MODE_FRL; + + if (info->hdmi.max_frl_rate_per_lane > lcfg->max_frl_rate_per_lane) + phy_cfg.hdmi.frl.rate_per_lane = lcfg->max_frl_rate_per_lane; + else + phy_cfg.hdmi.frl.rate_per_lane = info->hdmi.max_frl_rate_per_lane; + + if (info->hdmi.max_lanes > lcfg->max_frl_lanes) + phy_cfg.hdmi.frl.lanes = lcfg->max_frl_lanes; + else + phy_cfg.hdmi.frl.lanes = info->hdmi.max_lanes; } else { + mode = PHY_HDMI_MODE_TMDS; + + phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; + } + + ret = phy_set_mode_ext(hdmi->phy, PHY_MODE_HDMI, mode); + if (ret) { + dev_err(hdmi->dev, "Failed to switch phy mode: %d\n", ret); + return ret; + } + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) { dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret); + return ret; } - return ret; + lcfg->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + if (mode == PHY_HDMI_MODE_FRL) { + lcfg->frl_enabled = true; + lcfg->frl_rate_per_lane = phy_cfg.hdmi.frl.rate_per_lane; + lcfg->frl_lanes = phy_cfg.hdmi.frl.lanes; + } else { + lcfg->frl_enabled = false; + lcfg->frl_rate_per_lane = 0; + lcfg->frl_lanes = 0; + } + + lcfg->bpc = phy_cfg.hdmi.bpc; + + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + s->output_bpc = conn_state->hdmi.output_bpc; + s->frl_enabled = lcfg->frl_enabled; + + return 0; } static const struct @@ -220,12 +281,38 @@ dw_hdmi_qp_rk3588_get_link_cfg(struct dw_hdmi_qp *dw_hdmi, void *data) return &hdmi->link_cfg; } +static int dw_hdmi_qp_rk3588_set_frl_rate(struct dw_hdmi_qp *dw_hdmi, void *data, + u8 rate_per_lane, u8 lanes) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + union phy_configure_opts phy_cfg = {}; + int ret; + + if (!hdmi->link_cfg.frl_enabled || !rate_per_lane || !lanes) + return -EINVAL; + + phy_cfg.hdmi.frl.rate_per_lane = rate_per_lane; + phy_cfg.hdmi.frl.lanes = lanes; + phy_cfg.hdmi.bpc = hdmi->link_cfg.bpc; + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) { + dev_err(hdmi->dev, "Failed to set PHY FRL rate: %d\n", ret); + } else { + hdmi->link_cfg.frl_rate_per_lane = rate_per_lane; + hdmi->link_cfg.frl_lanes = lanes; + } + + return ret; +} + static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { .init = dw_hdmi_qp_rk3588_phy_init, .disable = dw_hdmi_qp_rk3588_phy_disable, .read_hpd = dw_hdmi_qp_rk3588_read_hpd, .setup_hpd = dw_hdmi_qp_rk3588_setup_hpd, .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, + .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, }; static enum drm_connector_status @@ -258,6 +345,7 @@ static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = { .read_hpd = dw_hdmi_qp_rk3576_read_hpd, .setup_hpd = dw_hdmi_qp_rk3576_setup_hpd, .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, + .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, }; static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) @@ -397,6 +485,9 @@ static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi, { u32 val; + val = FIELD_PREP_WM16(RK3576_HDMI_FRL_MOD, hdmi->link_cfg.frl_enabled); + regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON1, val); + if (state->output_bpc == 10) val = FIELD_PREP_WM16(RK3576_COLOR_DEPTH_MASK, RK3576_10BPC); else @@ -410,6 +501,11 @@ static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi, { u32 val; + val = FIELD_PREP_WM16(RK3588_HDMI21_MASK, hdmi->link_cfg.frl_enabled); + regmap_write(hdmi->vo_regmap, + hdmi->port_id ? RK3588_GRF_VO1_CON7 : RK3588_GRF_VO1_CON4, + val); + if (state->output_bpc == 10) val = FIELD_PREP_WM16(RK3588_COLOR_DEPTH_MASK, RK3588_10BPC); else @@ -439,6 +535,10 @@ struct rockchip_hdmi_qp_cfg { unsigned int port_ids[MAX_HDMI_PORT_NUM]; const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops; const struct dw_hdmi_qp_phy_ops *phy_ops; + u8 max_frl_rate_per_lane; + u8 max_frl_lanes; + u8 min_frl_rate_per_lane; + u8 min_frl_lanes; }; static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = { @@ -458,6 +558,9 @@ static const struct rockchip_hdmi_qp_cfg rk3588_hdmi_cfg = { }, .ctrl_ops = &rk3588_hdmi_ctrl_ops, .phy_ops = &rk3588_hdmi_phy_ops, + /* FIXME: Intermittent screen flicker if rate exceeds 40 Gbps */ + .max_frl_rate_per_lane = 10, + .max_frl_lanes = 4, }; static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = { @@ -478,6 +581,7 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, struct platform_device *pdev = to_platform_device(dev); struct dw_hdmi_qp_plat_data plat_data = {}; const struct rockchip_hdmi_qp_cfg *cfg; + struct dw_hdmi_qp_link_cfg *lcfg; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi_qp *hdmi; @@ -572,6 +676,24 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, if (IS_ERR(hdmi->phy)) return dev_err_probe(dev, PTR_ERR(hdmi->phy), "Failed to get phy\n"); + lcfg = &hdmi->link_cfg; + lcfg->max_frl_rate_per_lane = cfg->max_frl_rate_per_lane ?: HDMI21_MAX_FRL_LANE_RATE; + lcfg->max_frl_lanes = cfg->max_frl_lanes ?: HDMI21_MAX_FRL_LANE_NUM; + lcfg->min_frl_rate_per_lane = cfg->min_frl_rate_per_lane ?: HDMI21_MIN_FRL_LANE_RATE; + lcfg->min_frl_lanes = cfg->min_frl_lanes ?: HDMI21_MIN_FRL_LANE_NUM; + + if (lcfg->max_frl_rate_per_lane < lcfg->min_frl_rate_per_lane || + lcfg->max_frl_lanes < lcfg->min_frl_lanes || + lcfg->max_frl_rate_per_lane > HDMI21_MAX_FRL_LANE_RATE || + lcfg->max_frl_rate_per_lane < HDMI21_MIN_FRL_LANE_RATE || + lcfg->max_frl_lanes > HDMI21_MAX_FRL_LANE_NUM || + lcfg->max_frl_lanes < HDMI21_MIN_FRL_LANE_NUM || + lcfg->min_frl_rate_per_lane > HDMI21_MAX_FRL_LANE_RATE || + lcfg->min_frl_rate_per_lane < HDMI21_MIN_FRL_LANE_RATE || + lcfg->min_frl_lanes > HDMI21_MAX_FRL_LANE_NUM || + lcfg->min_frl_lanes < HDMI21_MIN_FRL_LANE_NUM) + return dev_err_probe(hdmi->dev, -EINVAL, "Invalid FRL config\n"); + cfg->ctrl_ops->io_init(hdmi); INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work); From 8133d7cab5a440d1de43c2e5b535c0f43a1ed0ac Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 7 Apr 2026 00:33:45 +0300 Subject: [PATCH 146/208] drm/rockchip: dw_hdmi_qp: Wire up TxFFE level adjustment Provide the .set_ffe_level() PHY callback for the Rockchip platform glue driver, enabling the bridge's FLT state machine to request FFE level adjustments from the PHY during FRL link training. The callback delegates to phy_configure() with the requested FFE level, using a dedicated flag (set_ffe_level) to distinguish it from a regular rate reconfiguration. A max_ffe_level field is added to the per-SoC config table (defaulting to level 3 in auto mode) and propagated into link_cfg at bind time, so the bridge knows the upper bound to advertise to the sink. Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index a5f135ceee866e..863e9238f2373b 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -306,6 +306,26 @@ static int dw_hdmi_qp_rk3588_set_frl_rate(struct dw_hdmi_qp *dw_hdmi, void *data return ret; } +static int dw_hdmi_qp_rk3588_set_ffe_level(struct dw_hdmi_qp *dw_hdmi, void *data, + u8 ffe_level) +{ + struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data; + union phy_configure_opts phy_cfg = {}; + int ret; + + if (!hdmi->link_cfg.frl_enabled) + return -EINVAL; + + phy_cfg.hdmi.frl.ffe_level = ffe_level; + phy_cfg.hdmi.frl.set_ffe_level = true; + + ret = phy_configure(hdmi->phy, &phy_cfg); + if (ret) + dev_err(hdmi->dev, "Failed to set PHY FFE level: %d\n", ret); + + return ret; +} + static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { .init = dw_hdmi_qp_rk3588_phy_init, .disable = dw_hdmi_qp_rk3588_phy_disable, @@ -313,6 +333,7 @@ static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { .setup_hpd = dw_hdmi_qp_rk3588_setup_hpd, .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, + .set_ffe_level = dw_hdmi_qp_rk3588_set_ffe_level, }; static enum drm_connector_status @@ -346,6 +367,7 @@ static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = { .setup_hpd = dw_hdmi_qp_rk3576_setup_hpd, .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg, .set_frl_rate = dw_hdmi_qp_rk3588_set_frl_rate, + .set_ffe_level = dw_hdmi_qp_rk3588_set_ffe_level, }; static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work) @@ -530,6 +552,14 @@ static const struct rockchip_hdmi_qp_ctrl_ops rk3588_hdmi_ctrl_ops = { .hardirq_callback = dw_hdmi_qp_rk3588_hardirq, }; +enum rockchip_hdmi_qp_ffe_cfg { + FFE_LEVEL_AUTO = 0, + FFE_LEVEL_0, + FFE_LEVEL_1, + FFE_LEVEL_2, + FFE_LEVEL_3, +}; + struct rockchip_hdmi_qp_cfg { unsigned int num_ports; unsigned int port_ids[MAX_HDMI_PORT_NUM]; @@ -539,6 +569,7 @@ struct rockchip_hdmi_qp_cfg { u8 max_frl_lanes; u8 min_frl_rate_per_lane; u8 min_frl_lanes; + enum rockchip_hdmi_qp_ffe_cfg max_ffe; }; static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = { @@ -575,6 +606,14 @@ static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, dw_hdmi_qp_rockchip_dt_ids); +static u8 dw_hdmi_qp_rockchip_ffe_cfg_to_level(enum rockchip_hdmi_qp_ffe_cfg ffe) +{ + if (ffe == FFE_LEVEL_AUTO) + ffe = FFE_LEVEL_3; + + return ffe - 1; +} + static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, void *data) { @@ -694,6 +733,8 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, lcfg->min_frl_lanes < HDMI21_MIN_FRL_LANE_NUM) return dev_err_probe(hdmi->dev, -EINVAL, "Invalid FRL config\n"); + lcfg->max_ffe_level = dw_hdmi_qp_rockchip_ffe_cfg_to_level(cfg->max_ffe); + cfg->ctrl_ops->io_init(hdmi); INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work); From d9bcaebd8fd85fe8f23463b8ca4611286c0ae180 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 1 Jun 2026 15:42:59 +0200 Subject: [PATCH 147/208] drm/rockchip: Select SND_SOC_HDMI_CODEC for ROCKCHIP_DW_HDMI_QP The HDMI QP bridge driver supports HDMI audio, but this requires SND_SOC_HDMI_CODEC, which needs to be selected. Make sure the select is done instead of relying on another config option enabling it for us. Reported-by: Michal Tomek Fixes: fd0141d1a8a2 ("drm/bridge: synopsys: Add audio support for dw-hdmi-qp") Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 1479b8c4ed40ad..d12dce371cf0b3 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -20,7 +20,7 @@ config DRM_ROCKCHIP select DRM_INNO_HDMI if ROCKCHIP_INNO_HDMI select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI - select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC + select SND_SOC_HDMI_CODEC if SND_SOC && (ROCKCHIP_CDN_DP || ROCKCHIP_DW_HDMI_QP) help Choose this option if you have a Rockchip soc chipset. This driver provides kernel mode setting and buffer From 73d8107dd35a8f5dd86f3b8007faf41545c6053d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 1 Jun 2026 15:57:35 +0200 Subject: [PATCH 148/208] drm/rockchip: Select SND_SOC_HDMI_CODEC for ROCKCHIP_DW_DP The DW DP bridge driver supports audio, but this requires SND_SOC_HDMI_CODEC, which needs to be selected. Make sure the select is done instead of relying on another config option enabling it for us. Reported-by: Michal Tomek Fixes: b6798038bbe3 ("drm/bridge: synopsys: dw-dp: Add audio support") Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/rockchip/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index d12dce371cf0b3..68e222350aeaec 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -20,7 +20,7 @@ config DRM_ROCKCHIP select DRM_INNO_HDMI if ROCKCHIP_INNO_HDMI select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI - select SND_SOC_HDMI_CODEC if SND_SOC && (ROCKCHIP_CDN_DP || ROCKCHIP_DW_HDMI_QP) + select SND_SOC_HDMI_CODEC if SND_SOC && (ROCKCHIP_CDN_DP || ROCKCHIP_DW_HDMI_QP || ROCKCHIP_DW_DP) help Choose this option if you have a Rockchip soc chipset. This driver provides kernel mode setting and buffer From 7aeb7ce7fb429ecd344354801e799b47948b8036 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 16 Apr 2026 01:42:34 +0300 Subject: [PATCH 149/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3576 boards The following RK3576 boards expose a GPIO pin to control the voltage bias on the HDMI data lines: - rk3576-100ask-dshanpi-a1 - rk3576-armsom-sige5 - rk3576-evb1-v10 - rk3576-evb2-v10 - rk3576-nanopi-m5 - rk3576-roc-pc - rk3576-rock-4d The pin must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. Signed-off-by: Cristian Ciocaltea --- .../arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts | 9 +++++++++ arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts | 9 +++++++++ 7 files changed, 63 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts b/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts index b19f9b6be6bf8a..a9e3beb3ee5ee6 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-100ask-dshanpi-a1.dts @@ -278,6 +278,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -738,6 +741,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + pcie { pcie_reset: pcie-reset { rockchip,pins = <1 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index b76b9c9fe0110b..f0afbd1d5449e4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -324,6 +324,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -841,6 +844,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts index 7081c7cdd12641..f3ede9465d14a5 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts @@ -370,6 +370,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -897,6 +900,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts index 98d5d00d63b577..dfc756d5ec5e18 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb2-v10.dts @@ -375,6 +375,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -832,6 +835,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts index 7406a4adf8105a..227fe6f3ec7c7d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts @@ -327,6 +327,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -813,6 +816,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts b/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts index d0ab1d1e0e1195..54de3a50571f0a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-roc-pc.dts @@ -283,6 +283,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -720,6 +723,12 @@ }; &pinctrl { + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int_l: rtc-int-l { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts index 43a8b2153a8b6a..6e5c891a85f9f0 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts @@ -356,6 +356,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -843,6 +846,12 @@ }; }; + hdmi { + hdmi_tx_on_h: hdmi-tx-on-h { + rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; From e58a7ff22308542a882d0340f0a8ef34b9fb4971 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 16 Apr 2026 01:34:12 +0300 Subject: [PATCH 150/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3576-luckfox-core3576 The board exposes the GPIO4_C6 pin to control the voltage bias on the HDMI data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. Additionally, remove the now unnecessary workaround of using vcc_5v0_hdmi as hdmi-pwr-supply solely to drive the GPIO into its default state. Also rename the hdmi_con_en pinctrl to hdmi_tx_on_h to match the schematic naming. Signed-off-by: Cristian Ciocaltea --- .../dts/rockchip/rk3576-luckfox-core3576.dtsi | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi index 749f0a54b478e4..4fc8496828f802 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi @@ -26,7 +26,6 @@ hdmi-con { compatible = "hdmi-connector"; - hdmi-pwr-supply = <&vcc_5v0_hdmi>; type = "a"; port { @@ -138,22 +137,6 @@ }; }; - vcc_5v0_hdmi: regulator-vcc-5v0-hdmi { - compatible = "regulator-fixed"; - enable-active-high; - gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>; - pinctrl-names = "default"; - pinctrl-0 = <&hdmi_con_en>; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-name = "vcc_5v0_hdmi"; - vin-supply = <&vcc_5v0_sys>; - - regulator-state-mem { - regulator-off-in-suspend; - }; - }; - vcc_5v0_host: regulator-vcc-5v0-host { compatible = "regulator-fixed"; enable-active-high; @@ -231,6 +214,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -655,7 +641,7 @@ &pinctrl { hdmi { - hdmi_con_en: hdmi-con-en { + hdmi_tx_on_h: hdmi-tx-on-h { rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; }; }; From b57d78818ff75cc1b3d5ec7920ddd30b6eeb81b4 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 16 Apr 2026 01:02:49 +0300 Subject: [PATCH 151/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3576-nanopi-r76s The board exposes the GPIO4_C6 pin to control the voltage bias on the HDMI data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. Additionally, drop the now unnecessary workaround of using vcc5v_hdmi_tx as hdmi-pwr-supply solely to drive the GPIO into its default state. Signed-off-by: Cristian Ciocaltea --- .../boot/dts/rockchip/rk3576-nanopi-r76s.dts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts index 7ec27b05ff10e6..0a5cd5f6fd3349 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts @@ -70,7 +70,6 @@ hdmi-con { compatible = "hdmi-connector"; - hdmi-pwr-supply = <&vcc5v_hdmi_tx>; type = "a"; port { @@ -109,18 +108,6 @@ regulator-name = "vcc5v_dcin"; }; - vcc5v_hdmi_tx: regulator-vcc5v-hdmi-tx { - compatible = "regulator-fixed"; - enable-active-high; - gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>; - pinctrl-names = "default"; - pinctrl-0 = <&hdmi_tx_on_h>; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-name = "vcc5v_hdmi_tx"; - vin-supply = <&vcc5v0_sys_s5>; - }; - vcc5v0_device_s0: regulator-vcc5v0-device-s0 { compatible = "regulator-fixed"; regulator-always-on; @@ -252,6 +239,9 @@ }; &hdmi { + frl-enable-gpios = <&gpio4 RK_PC6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmi_txm0_pins &hdmi_tx_scl &hdmi_tx_sda &hdmi_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; From c5e3e58b5121159e98412aa8bfcbe7b791a471b2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 15 Apr 2026 02:45:06 +0300 Subject: [PATCH 152/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588 boards The following RK3588 boards expose one or two GPIO pins to control the voltage bias on the HDMI0 and/or HDMI1 data lines: - rk3588-armsom-sige7 - rk3588-armsom-w3 - rk3588-coolpi-cm5-evb - rk3588-coolpi-cm5-genbook - rk3588-evb1-v10 - rk3588-evb2-v10 - rk3588-firefly-itx-3588j - rk3588-friendlyelec-cm3588-nas - rk3588-h96-max-v58 - rk3588-jaguar - rk3588-mnt-reform2 - rk3588-nanopc-t6 - rk3588-orangepi-5-max - rk3588-orangepi-5-plus - rk3588-orangepi-5-ultra - rk3588-roc-rt - rk3588-rock-5-itx - rk3588-rock-5b-5bp-5t - rk3588-tiger The pins must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 and/or hdmi1 nodes to their dedicated GPIO pin(s) via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. While at it, also ensure that pinctrl-names is present and ordered alphabetically within the hdmi nodes. Signed-off-by: Cristian Ciocaltea --- .../boot/dts/rockchip/rk3588-armsom-sige7.dts | 10 ++++++++++ .../boot/dts/rockchip/rk3588-armsom-w3.dts | 18 ++++++++++++++++++ .../dts/rockchip/rk3588-coolpi-cm5-evb.dts | 18 +++++++++++++++++- .../dts/rockchip/rk3588-coolpi-cm5-genbook.dts | 11 ++++++++++- .../boot/dts/rockchip/rk3588-evb1-v10.dts | 18 ++++++++++++++++++ .../boot/dts/rockchip/rk3588-evb2-v10.dts | 10 ++++++++++ .../dts/rockchip/rk3588-firefly-itx-3588j.dts | 10 ++++++++++ .../rk3588-friendlyelec-cm3588-nas.dts | 18 ++++++++++++++++++ .../boot/dts/rockchip/rk3588-h96-max-v58.dts | 10 ++++++++++ arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts | 10 +++++++++- .../boot/dts/rockchip/rk3588-mnt-reform2.dts | 10 ++++++++++ .../boot/dts/rockchip/rk3588-nanopc-t6.dtsi | 18 ++++++++++++++++++ .../dts/rockchip/rk3588-orangepi-5-max.dts | 18 ++++++++++++++++-- .../dts/rockchip/rk3588-orangepi-5-plus.dts | 18 ++++++++++++++++++ .../dts/rockchip/rk3588-orangepi-5-ultra.dts | 11 +++++++++-- arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts | 18 ++++++++++++++++++ .../boot/dts/rockchip/rk3588-rock-5-itx.dts | 10 +++++++++- .../dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 18 +++++++++++++++++- .../boot/dts/rockchip/rk3588-tiger-haikou.dts | 3 ++- arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi | 11 +++++++++-- 20 files changed, 256 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts b/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts index 39197ee198370b..c4854c0f22e26b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dts @@ -177,6 +177,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -347,6 +351,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts b/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts index 6ad2759ddccafe..3bbafdd89ede81 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts @@ -162,6 +162,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -182,6 +186,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -330,6 +338,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PA1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts index 3d5c8b753208ab..66e3c20d7b4f13 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-evb.dts @@ -125,6 +125,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -145,8 +149,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd &hdmim1_tx1_scl &hdmim1_tx1_sda + &hdmi1_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd &hdmim1_tx1_scl &hdmim1_tx1_sda>; status = "okay"; }; @@ -211,6 +217,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcdpwr_en: lcdpwr-en { rockchip,pins = <1 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts index 738637ecaf557f..e05e6b2d513673 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts @@ -184,7 +184,10 @@ /* HDMI CEC is not used */ &hdmi0 { - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda>; + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -277,6 +280,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcdpwr_en: lcdpwr-en { rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts index 85adfadc6e4a9a..415bbe4df2f254 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts @@ -415,6 +415,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -431,6 +435,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -693,6 +701,16 @@ }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <2 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts index 60ba6ac55b2399..b7d2cb4561484b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb2-v10.dts @@ -143,6 +143,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -183,6 +187,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts b/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts index e086114c763487..e43afb0c53fbdb 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-firefly-itx-3588j.dts @@ -322,6 +322,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -487,6 +491,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts b/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts index 10a7d3691a26f8..0dce96ca8c288e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-friendlyelec-cm3588-nas.dts @@ -331,6 +331,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -347,6 +351,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -520,6 +528,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <3 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts b/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts index 73d8ce4fde2b81..7c2a1e6bcff9b1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-h96-max-v58.dts @@ -210,6 +210,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -339,6 +343,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts index 5f5d89a33a4ae7..05b524f682cf6d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts @@ -278,8 +278,10 @@ &hdmi0 { /* No CEC on Jaguar */ + frl-enable-gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda + &hdmi0_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda>; status = "okay"; }; @@ -571,6 +573,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { led1_pin: led1-pin { rockchip,pins = <1 RK_PD4 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts b/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts index 78a4e896f665b5..36a5977d079520 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-mnt-reform2.dts @@ -148,6 +148,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -205,6 +209,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + pcie2 { pcie2_0_rst: pcie2-0-rst { rockchip,pins = <3 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi index 84b6b53f016ab1..3b0903d0e3e2a6 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi @@ -361,6 +361,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -381,6 +385,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -682,6 +690,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hdmirx { hdmirx_hpd: hdmirx-5v-detection { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts index 8b1d35760c3bc9..762f9fd966a42b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts @@ -35,6 +35,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -55,9 +59,10 @@ }; &hdmi1 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -102,6 +107,15 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; usb { usb_otg_pwren: usb-otg-pwren { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts index 9950d1147e129d..7142938d297146 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts @@ -121,6 +121,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -141,6 +145,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -278,6 +286,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts index f8c6c080e418db..2b693dfb434c4e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-ultra.dts @@ -25,9 +25,10 @@ }; &hdmi1 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -64,6 +65,12 @@ }; &pinctrl { + hdmi { + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + usb { usb_otg_pwren: usb-otg-pwren { rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts b/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts index 2d6fed2a84a38d..c50217a7b5596b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-roc-rt.dts @@ -325,6 +325,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -345,6 +349,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim2_tx1_cec &hdmim0_tx1_hpd + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -583,6 +591,16 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts index f7dd01d6fa0ab3..d5c0b01987fc2e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts @@ -346,8 +346,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -696,6 +698,12 @@ }; &pinctrl { + hdmi { + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index 611a23b5fb87cb..9c069737c76579 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -199,6 +199,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -219,8 +223,10 @@ }; &hdmi1 { + frl-enable-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd - &hdmim1_tx1_scl &hdmim1_tx1_sda>; + &hdmim1_tx1_scl &hdmim1_tx1_sda &hdmi1_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -527,6 +533,16 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + hdmi1_tx_on_h: hdmi1-tx-on-h { + rockchip,pins = <4 RK_PA1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts index caa43d1abf1793..08b7d477d8dbc4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-tiger-haikou.dts @@ -172,8 +172,9 @@ * While HDMI-CEC is present on the Q7 connector, it is not * connected on Haikou itself. */ + pinctrl-0 = <&hdmim0_tx0_hpd &hdmim1_tx0_scl &hdmim1_tx0_sda + &hdmi0_tx_on_h>; pinctrl-names = "default"; - pinctrl-0 = <&hdmim0_tx0_hpd &hdmim1_tx0_scl &hdmim1_tx0_sda>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi index a0e97481afb7d7..ea5c264d153719 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-tiger.dtsi @@ -148,9 +148,10 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim1_tx0_cec &hdmim0_tx0_hpd &hdmim1_tx0_scl - &hdmim1_tx0_sda>; + &hdmim1_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; }; &i2c1 { @@ -349,6 +350,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { module_led_pin: module-led-pin { rockchip,pins = <1 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; From 1b9ab570fec9c1c063c52798ceab1589f5d7dfc8 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sat, 11 Apr 2026 01:06:42 +0000 Subject: [PATCH 153/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588s boards The following RK3588s boards expose a GPIO pin to control the voltage bias on the HDMI0 data lines: - rk3588s-coolpi-4b - rk3588s-indiedroid-nova - rk3588s-nanopi-r6 - rk3588s-odroid-m2 - rk3588s-orangepi-5 - rk3588s-radxa-cm5-io - rk3588s-rock-5a - rk3588s-rock-5c The pin must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. While at it, also ensure that pinctrl-names is present and ordered alphabetically within the hdmi nodes. Signed-off-by: Cristian Ciocaltea --- arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts | 10 ++++++++++ .../boot/dts/rockchip/rk3588s-indiedroid-nova.dts | 10 +++++++++- arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi | 10 ++++++++++ arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts | 10 ++++++++++ arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi | 10 ++++++++++ .../arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts | 10 ++++++++++ arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts | 12 ++++++++++-- arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts | 12 ++++++++++-- 8 files changed, 79 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts index 189444d2077979..16f19109fca615 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts @@ -251,6 +251,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -396,6 +400,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts b/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts index 174d299cc6bb94..f9e3c0134d5fbb 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-indiedroid-nova.dts @@ -277,8 +277,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_scl>, <&hdmim0_tx0_sda>, - <&hdmim0_tx0_hpd>, <&hdmim0_tx0_cec>; + <&hdmim0_tx0_hpd>, <&hdmim0_tx0_cec>, + <&hdmi0_tx_on_h>; pinctrl-names = "default"; status = "okay"; }; @@ -517,6 +519,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi index 1b6a59f7cabce6..b9c0a1a050a969 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-nanopi-r6.dtsi @@ -236,6 +236,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -404,6 +408,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { rtc_int: rtc-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts b/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts index a72063c5514010..4e98fa33492c6a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-odroid-m2.dts @@ -249,6 +249,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -438,6 +442,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + lcd { lcd_pwren: lcd-pwren { rockchip,pins = <4 RK_PA3 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi index dafad29f98544a..cd80cb15a405b5 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi @@ -181,6 +181,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -381,6 +385,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + hym8563 { hym8563_int: hym8563-int { rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts b/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts index f80d5a00a4bdd3..af4a9bc015e16d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-radxa-cm5-io.dts @@ -120,6 +120,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -217,6 +221,12 @@ }; }; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + usb { vcc5v0_host_en: vcc5v0-host-en { rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts index 0991f6a2119005..ffd26b43ae8c96 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts @@ -323,11 +323,13 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim1_tx0_hpd &hdmim0_tx0_scl - &hdmim0_tx0_sda>; + &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -373,6 +375,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { io_led: io-led { rockchip,pins = <3 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts index 7fe42f4ff82798..9d3d0791554d95 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5c.dts @@ -258,11 +258,13 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim1_tx0_hpd &hdmim0_tx0_scl - &hdmim0_tx0_sda>; + &hdmim0_tx0_sda + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -461,6 +463,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + leds { led_pins: led-pins { rockchip,pins = <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>, From b493a657adcdd9acb43bf88cf396399d8c9b4f93 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 16 Apr 2026 22:32:06 +0300 Subject: [PATCH 154/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588s-gameforce-ace The board exposes the GPIO4_B3 pin to control the voltage bias on the HDMI0 data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. Additionally, drop the now unnecessary ddc-en-gpios property and the associated pinctrl-* entries from hdmi0-con, and rename the hdmi0_en pinmux to hdmi0_tx_on_h, in line with the naming commonly used in RK3588s-based board schematics. Signed-off-by: Cristian Ciocaltea --- arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts b/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts index 89618394c0bfb4..b657d54c2c5903 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-gameforce-ace.dts @@ -302,9 +302,6 @@ hdmi0-con { compatible = "hdmi-connector"; - ddc-en-gpios = <&gpio4 RK_PB3 GPIO_ACTIVE_HIGH>; - pinctrl-0 = <&hdmi0_en>; - pinctrl-names = "default"; type = "d"; port { @@ -514,8 +511,9 @@ &hdmi0 { no-hpd; + frl-enable-gpios = <&gpio4 RK_PB3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec>, <&hdmim0_tx0_scl>, - <&hdmim0_tx0_sda>; + <&hdmim0_tx0_sda>, <&hdmi0_tx_on_h>; pinctrl-names = "default"; status = "okay"; }; @@ -893,7 +891,7 @@ }; hdmi { - hdmi0_en: hdmi0-en { + hdmi0_tx_on_h: hdmi0-tx-on-h { rockchip,pins = <4 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>; }; From 75019a201df1a306b99e6d77b390297a7b32a343 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 16 Apr 2026 02:27:22 +0300 Subject: [PATCH 155/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588s-khadas-edge2 The board exposes the GPIO4_B1 pin to control the voltage bias on the HDMI0 data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. While at it, remove the duplicated &hdmi0_sound node. Signed-off-by: Cristian Ciocaltea --- .../boot/dts/rockchip/rk3588s-khadas-edge2.dts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts b/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts index 2c22abaf40a82a..5afbc593341ba4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts @@ -194,6 +194,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -217,10 +221,6 @@ status = "okay"; }; -&hdmi0_sound { - status = "okay"; -}; - &i2c0 { pinctrl-names = "default"; pinctrl-0 = <&i2c0m2_xfer>; @@ -282,6 +282,12 @@ }; &pinctrl { + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + vdd_sd { vdd_sd_en: vdd-sd-en { rockchip,pins = <1 RK_PB6 RK_FUNC_GPIO &pcfg_pull_up>; From d7c61bf6a47561000da803b16bc6279bbdbb9142 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sat, 11 Apr 2026 00:37:23 +0000 Subject: [PATCH 156/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588s-orangepi-cm5-base The board exposes the GPIO4_B5 pin to control the voltage bias on the HDMI0 data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. While at it, rename the hdmi_frl_pin pinmux to hdmi0_tx_on_h, in line with the naming commonly used in RK3588s-bassed board schematics. Signed-off-by: Cristian Ciocaltea --- .../boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts index 06120b2db690a9..20da0c2b3d92ea 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-cm5-base.dts @@ -143,10 +143,11 @@ }; &hdmi0 { - pinctrl-names = "default"; + frl-enable-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_LOW>; pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd &hdmim0_tx0_scl &hdmim0_tx0_sda - &hdmi_frl_pin>; + &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -245,8 +246,8 @@ }; hdmi { - hdmi_frl_pin: hdmi-frl-pin { - rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>; + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; }; }; From 6225b7602bc3c460ca5c8ac8488c03457eb721d9 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 15 Apr 2026 19:52:34 +0300 Subject: [PATCH 157/208] arm64: dts: rockchip: Add frl-enable-gpios to rk3588s-roc-pc The board exposes the GPIO4_B2 line to control the voltage bias on the HDMI0 data lines. It must be asserted when operating in HDMI 2.1 FRL mode and deasserted for HDMI 1.4/2.0 TMDS mode. Wire up the hdmi0 node to its dedicated GPIO via frl-enable-gpios to allow adjusting the bias when transitioning between TMDS and FRL modes. While at it, move hym8563 down to fix the ordering of &pinctrl entries. Signed-off-by: Cristian Ciocaltea --- arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts b/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts index 7e179862da6e56..aa02cf510d6db5 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-roc-pc.dts @@ -224,6 +224,10 @@ }; &hdmi0 { + frl-enable-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd + &hdmim0_tx0_scl &hdmim0_tx0_sda &hdmi0_tx_on_h>; + pinctrl-names = "default"; status = "okay"; }; @@ -367,9 +371,9 @@ }; &pinctrl { - hym8563 { - hym8563_int: hym8563-int { - rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; + hdmi { + hdmi0_tx_on_h: hdmi0-tx-on-h { + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; }; }; @@ -379,6 +383,12 @@ }; }; + hym8563 { + hym8563_int: hym8563-int { + rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + leds { led_pins: led-pins { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>, From 3acacb2658b1642cf8daa11a6707f5ac65e5994e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 27 Apr 2026 15:36:05 +0300 Subject: [PATCH 158/208] arm64: dts: rockchip: Drop unnecessary #{address,size}-cells from rk3588-jaguar Remove the unnecessary #address-cells and #size-cells properties from the usb_host0_xhci and usb_host1_xhci port nodes, as they each contain a single endpoint child with no reg property. This fixes the following dtc warnings: rk3588-jaguar.dts: Warning (avoid_unnecessary_addr_size): /usb@fc000000/port: unnecessary #address-cells/#size-cells [...] /usb@fc400000/port: unnecessary #address-cells/#size-cells [...] Signed-off-by: Cristian Ciocaltea --- arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts index 05b524f682cf6d..033db78aff676e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-jaguar.dts @@ -1142,9 +1142,6 @@ status = "okay"; port { - #address-cells = <1>; - #size-cells = <0>; - usb_host0_xhci_drd_sw: endpoint { remote-endpoint = <&usbc0_hs>; }; @@ -1157,9 +1154,6 @@ status = "okay"; port { - #address-cells = <1>; - #size-cells = <0>; - usb_host1_xhci_drd_sw: endpoint { remote-endpoint = <&usbc1_hs>; }; From b5614046b3c18728a91f9c80a299b4113b15ef2a Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Tue, 19 May 2026 14:00:11 -0400 Subject: [PATCH 159/208] drm/bridge: dw-hdmi-qp: Return -EOPNOTSUPP in HDMI audio functions -EOPNOTSUPP is not logged as an error by the ASoC subsystem, but -ENODEV is. It also better represents the situation: The operation is currently not supported (because clocks are not enabled and tmds_char_rate is unavailable), but the hardware is present. Using -EOPNOTSUPP in the audio_prepare callback removes possible repeated warning log lines when HDMI is not connected. Returning -EOPNOTSUPP in the audio_enable callback is also needed as it avoids logging 0-valued ELD errors. When tmds_char_rate is available, the clocks are enabled and the functions will keep returning 0 as before. Signed-off-by: Detlev Casanova Fixes: fd0141d1a8a2a ("drm/bridge: synopsys: Add audio support for dw-hdmi-qp") --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 5582b661a10ebe..c81de8285e13f9 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -543,8 +543,10 @@ static int dw_hdmi_qp_audio_enable(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); - if (hdmi->tmds_char_rate) - dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); + if (!hdmi->tmds_char_rate) + return -EOPNOTSUPP; + + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); return 0; } @@ -558,7 +560,7 @@ static int dw_hdmi_qp_audio_prepare(struct drm_bridge *bridge, bool ref2stream = false; if (!hdmi->tmds_char_rate) - return -ENODEV; + return -EOPNOTSUPP; if (fmt->bit_clk_provider | fmt->frame_clk_provider) { dev_err(hdmi->dev, "unsupported clock settings\n"); From e0416457ce53fb3ca4f03ff3aa06364bacaf803e Mon Sep 17 00:00:00 2001 From: Frank Zhang Date: Tue, 12 May 2026 18:31:53 +0800 Subject: [PATCH 160/208] drm/bridge: dw-hdmi-qp: Guard clear_audio_infoframe when PHY is down The following panic was observed during system reboot: Kernel panic - not syncing: Asynchronous SError Interrupt CPU: 6 UID: 1000 PID: 2348 Comm: pipewire ... 7.0.5+ #4 PREEMPT(full) Call trace: ... regmap_update_bits_base+0x70/0xa8 dw_hdmi_qp_bridge_clear_audio_infoframe+0x3c/0x58 [dw_hdmi_qp] drm_bridge_connector_clear_audio_infoframe+0x2c/0x48 [drm_display_helper] ... dw_hdmi_qp_audio_disable+0x28/0xa8 [dw_hdmi_qp] drm_bridge_connector_audio_shutdown+0x38/0x68 [drm_display_helper] drm_connector_hdmi_audio_shutdown+0x28/0x40 [drm_display_helper] hdmi_codec_shutdown+0x60/0x90 [snd_soc_hdmi_codec] ... snd_pcm_release_substream+0xcc/0x120 [snd_pcm] snd_pcm_release+0x4c/0xc0 [snd_pcm] ... The root cause is pipewire tries to close the HDMI audio device after atomic_disable(), which sets tmds_char_rate to 0 and disables the PHY. In this case, dw_hdmi_qp_audio_disable() will call dw_hdmi_qp_bridge_clear_audio_infoframe(), accessing register without checking tmds_char_rate. Add a tmds_char_rate guard in dw_hdmi_qp_bridge_clear_audio_infoframe(). Decouple write_audio_infoframe from clear_audio_infoframe to avoid the redundant check in the write path. Add PKTSCHED_AMD_TX_EN to the clear mask to keep the enable/disable balance. Fixes: fd0141d1a8a2 ("drm/bridge: synopsys: Add audio support for dw-hdmi-qp") Cc: stable@vger.kernel.org Signed-off-by: Frank Zhang --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index c81de8285e13f9..47de45d6225a46 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -1649,11 +1649,11 @@ static int dw_hdmi_qp_bridge_clear_audio_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_mod(hdmi, 0, - PKTSCHED_ACR_TX_EN | - PKTSCHED_AUDS_TX_EN | - PKTSCHED_AUDI_TX_EN, - PKTSCHED_PKT_EN); + if (hdmi->tmds_char_rate) + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AMD_TX_EN | + PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); return 0; } @@ -1752,7 +1752,10 @@ static int dw_hdmi_qp_bridge_write_audio_infoframe(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_bridge_clear_audio_infoframe(bridge); + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AMD_TX_EN | + PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); /* * AUDI_CONTENTS0: { RSV, HB2, HB1, RSV } From 0f4af6c739f90bf737800275481903fdb6e69c53 Mon Sep 17 00:00:00 2001 From: Anton Burticica Date: Tue, 23 Dec 2025 11:34:32 +0400 Subject: [PATCH 161/208] LIN-14: Use wiphy_dbg instead of bphy_err In environment with too many APs, the buffer of 64k is not enough to keep all BSS entries. Let's suppress the message (can be enabled via debugfs). --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0b55d445895f20..c24e6e17163ddc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -3703,7 +3703,7 @@ brcmf_cfg80211_escan_handler(struct brcmf_if *ifp, list = (struct brcmf_scan_results *) cfg->escan_info.escan_buf; if (bi_length > BRCMF_ESCAN_BUF_SIZE - list->buflen) { - bphy_err(drvr, "Buffer is too small: ignoring\n"); + wiphy_dbg((drvr)->wiphy, "Buffer is too small: ignoring\n"); goto exit; } From 701634b8bc41515025c97068bb658941e23d4372 Mon Sep 17 00:00:00 2001 From: Pavel Zhovner Date: Mon, 29 Dec 2025 17:11:42 +0000 Subject: [PATCH 162/208] Hardcode always show single logo regardless of CPU count With our custom logo it becomes weird to duplicate it according to the number of CPU cores, so force only one copy by default Signed-off-by: Alexey Charkov --- drivers/video/fbdev/core/fb_logo.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/core/fb_logo.c b/drivers/video/fbdev/core/fb_logo.c index 0bab8352b684a1..5568e23f8b0978 100644 --- a/drivers/video/fbdev/core/fb_logo.c +++ b/drivers/video/fbdev/core/fb_logo.c @@ -6,7 +6,15 @@ #include "fb_internal.h" bool fb_center_logo __read_mostly; -int fb_logo_count __read_mostly = -1; +/* + * fb_logo_count: number of boot logos to display + * -1 = show one logo per online CPU (default upstream behavior) + * 0 = disable logo + * >0 = show exactly this many logos + * + * Flipper: hardcode to 1 to always show single logo regardless of CPU count + */ +int fb_logo_count __read_mostly = 1; static inline unsigned int safe_shift(unsigned int d, int n) { From 33e7f8fe4f5f6f338db13e83ec4349b6e963d759 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 14 Jan 2026 19:03:10 +0400 Subject: [PATCH 163/208] arm64: dts: rockchip: Fix Type-C SBU lines bias on ArmSoM Sige5 Remove extra pull from the GPIOs controlling the voltage bias of SBU lines on Sige5, as it makes DP AUX communication unreliable. The lines are supposed to be pulled up or down with a 10-105kOhm resistor, and the board schematic already includes a discrete 100kOhm resistor on each line, making the resulting pull-up too weak vs. spec. Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index f0afbd1d5449e4..fdf2a7d333aae4 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -885,10 +885,10 @@ rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; }; usbc0_sbu1: usbc0-sbu1 { - rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_down>; + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; }; usbc0_sbu2: usbc0-sbu2 { - rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_down>; + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>; }; }; From d952544e2a5cb459c82bed194cc1c80f8f261ba0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 14 Jan 2026 12:30:40 +0400 Subject: [PATCH 164/208] WIP: Link up HUSB311 driver and DP on EVB1 Signed-off-by: Alexey Charkov --- .../boot/dts/rockchip/rk3576-evb1-v10.dts | 151 +++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts index f3ede9465d14a5..d1e243e2d277e8 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts @@ -11,6 +11,7 @@ #include #include #include +#include #include "rk3576.dtsi" / { @@ -334,6 +335,22 @@ status = "okay"; }; +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + &gmac0 { clock_in_out = "output"; phy-mode = "rgmii-rxid"; @@ -772,6 +789,58 @@ &i2c2 { status = "okay"; + usbc0: typec-portc@4e { + compatible = "hynetek,husb311", "richtek,rt1711h"; + reg = <0x4e>; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_int>; + vbus-supply = <&vbus5v0_typec>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + power-role = "source"; + source-pdos = ; + + altmodes { + displayport { + svid = /bits/ 16 <0xff01>; + vdo = <0xffffffff>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_drd0_hs_ep>; + }; + }; + + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; + }; + }; + + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy_dp_out>; + }; + }; + }; + }; + }; + hym8563: rtc@51 { compatible = "haoyu,hym8563"; reg = <0x51>; @@ -946,6 +1015,14 @@ usbc0_int: usbc0-int { rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>; }; + + usbc0_sbu1: usbc0-sbu1 { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + usbc0_sbu2: usbc0-sbu2 { + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>; + }; }; wifi { @@ -1054,13 +1131,76 @@ }; &usbdp_phy { - rockchip,dp-lane-mux = <2 3>; + mode-switch; + orientation-switch; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>; + sbu1-dc-gpios = <&gpio2 RK_PA6 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>; status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; + }; + }; }; &usb_drd0_dwc3 { - dr_mode = "host"; + usb-role-switch; + dr_mode = "otg"; status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs_ep: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + usb_drd0_ss_ep: endpoint { + remote-endpoint = <&usbdp_phy_ss_in>; + }; + }; + }; }; &usb_drd1_dwc3 { @@ -1082,3 +1222,10 @@ remote-endpoint = <&hdmi_in_vp0>; }; }; + +&vp1 { + vp1_out_dp: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; From 6444421734bacf33323843e1a1bf0674882efc54 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 26 Jan 2026 18:40:51 +0400 Subject: [PATCH 165/208] arm64: dts: rockchip: Add DP audio on RK3576 EVB1 Enable audio nodes for sound over Type-C DP AltMode on RK3576 EVB1 Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts index d1e243e2d277e8..b1fc17e72d28d9 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10.dts @@ -351,6 +351,10 @@ }; }; +&dp0_sound { + status = "okay"; +}; + &gmac0 { clock_in_out = "output"; phy-mode = "rgmii-rxid"; @@ -1083,6 +1087,10 @@ status = "okay"; }; +&spdif_tx3 { + status = "okay"; +}; + &u2phy0 { status = "okay"; }; From fe7c39d5ae609814f0fd0981edd3a826d2b89a6b Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 2 Feb 2026 13:36:56 +0400 Subject: [PATCH 166/208] arm64: dts: rockchip: Add Ethernet support for Luckfox Omni3576 Include device tree nodes to enable Ethernet support on the Luckfox Omni3576 board. This board has its GMAC0+PHY0 on the pluggable SoM, with only magnetics and the RJ45 connector provided by the carrier board. For GMAC1 though the accompanying PHY1 is located on the carrier board, connected via RGMII pins exposed by the SoM. Link: https://wiki.luckfox.com/assets/files/Omni3576-3be654963438c9b39fd22672f449f807.pdf Signed-off-by: Alexey Charkov --- .../dts/rockchip/rk3576-luckfox-core3576.dtsi | 34 +++++++++++++++++++ .../dts/rockchip/rk3576-luckfox-omni3576.dts | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi index 4fc8496828f802..6ab15cf8716b31 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi @@ -208,6 +208,21 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&gmac0 { + clock_in_out = "output"; + phy-mode = "rgmii-rxid"; + phy-handle = <&rgmii_phy0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + &gpu { mali-supply = <&vdd_gpu_s0>; status = "okay"; @@ -631,6 +646,19 @@ }; }; +&mdio0 { + rgmii_phy0: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB3 GPIO_ACTIVE_LOW>; + }; +}; + &pcie0 { pinctrl-names = "default"; pinctrl-0 = <&pcie_reset>; @@ -640,6 +668,12 @@ }; &pinctrl { + gmac { + gmac0_rst: ethphy0-rst { + rockchip,pins = <2 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + hdmi { hdmi_tx_on_h: hdmi-tx-on-h { rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts index 6c75959adfe199..5ab0c7f3b54105 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-omni3576.dts @@ -30,7 +30,41 @@ }; }; +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-rxid"; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; + }; +}; + &pinctrl { + gmac { + gmac1_rst: ethphy1-rst { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + leds { led_green_pin: led-green-pin { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; From b353265f304dbf0b6da8810306a6cc348b435ed4 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 2 Feb 2026 15:42:13 +0400 Subject: [PATCH 167/208] arm64: dts: rockchip: Drop incorrect eMMC regulators on Luckfox Core3576 Remove the supply regulators from the eMMC controller node on Luckfox Core3576, as they cause the eMMC to endlessly re-tune phase at startup, and are most likely wrong: 1. The vendor DTS doesn't define those regulators 2. The VCCQ regulator referenced here is actually used for the SD card, and it is unlikely that both can share the same regulator with an 1.8-3.3V range, whereas eMMC only expects 1.8V 3. Rockchip reference schematic, which this board broadly follows, drives the VCCQ supply of eMMC flash from VCC_1V8_S0 rather than VCCIO_SD_S0, where VCC_1V8_S0 is a fixed 1.8V load switch with VIN tied to VCC_1V8_S3 and EN tied to VCCA_1V8_S0 (a.k.a. PMIC PLDO1, not PLDO5) There is no published schematic for the Core3576 SoM unfortunately. Cc: stable@vger.kernel.org Fixes: d7ad90d22abe ("arm64: dts: rockchip: Add Luckfox Omni3576 Board support") Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi index 6ab15cf8716b31..861b8556831361 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576-luckfox-core3576.dtsi @@ -732,8 +732,6 @@ no-sd; no-sdio; non-removable; - vmmc-supply = <&vcc_3v3_s3>; - vqmmc-supply = <&vccio_sd_s0>; status = "okay"; }; From 7c89e74274be57e4b9e6231bc1f04c59f5378f7e Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 27 Jan 2026 11:31:15 +0400 Subject: [PATCH 168/208] dt-bindings: vendor-prefixes: Add Flipper FZCO Add a vendor prefix for Flipper FZCO, the company behind Flipper Zero, creating open and customizable multi-tool devices for tech enthusiasts. Link: https://flipper.net/ Signed-off-by: Alexey Charkov --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 28784d66ae7ba5..021032422a782b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -598,6 +598,8 @@ patternProperties: description: Fitipower Integrated Technology Inc. "^flipkart,.*": description: Flipkart Inc. + "^flipper,.*": + description: Flipper FZCO "^focaltech,.*": description: FocalTech Systems Co.,Ltd "^forlinx,.*": From 793becfe5ab15bf9f036f96478d40b5a5b010881 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 27 Jan 2026 11:38:30 +0400 Subject: [PATCH 169/208] dt-bindings: display: Add Flipper One display Document the display part used in the upcoming Flipper One handheld network multi-tool, connected over an SPI bus. Signed-off-by: Alexey Charkov --- .../bindings/display/flipper,one-display.yaml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/flipper,one-display.yaml diff --git a/Documentation/devicetree/bindings/display/flipper,one-display.yaml b/Documentation/devicetree/bindings/display/flipper,one-display.yaml new file mode 100644 index 00000000000000..0c5f7c59975f37 --- /dev/null +++ b/Documentation/devicetree/bindings/display/flipper,one-display.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/flipper,one-display.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Flipper One display + +maintainers: + - Alexey Charkov + +description: + Flipper One is a handheld network multi-tool, which includes a custom + grayscale LCD panel driven by an MCU accepting image contents over a SPI bus. + The image data must consist of a full frame in each transmission, signalled + by the "active" GPIO line. One byte per pixel is used, with 0 representing + black and 255 white, lower two bits are discarded, and the expected frame + size is 258x144 pixels of which the leftmost 256x144 are visible. + + The MCU expects SPI mode 3 (CPOL=1, CPHA=1), up to 24MHz frequency. + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - flipper,one-display + + reg: + maxItems: 1 + + spi-cpha: true + spi-cpol: true + + spi-max-frequency: + maximum: 24000000 + + active-gpios: + maxItems: 1 + description: Frame data is being transmitted when this line is asserted. + Deassert to signal the end of frame. + +required: + - compatible + - reg + - active-gpios + - spi-max-frequency + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + display@0 { + compatible = "flipper,one-display"; + reg = <0>; + active-gpios = <&gpio4 6 GPIO_ACTIVE_LOW>; + spi-max-frequency = <20000000>; + spi-cpha; + spi-cpol; + spi-rx-bus-width = <0>; + }; + }; +... From ae18ca9d1e1d14a826338426b2200134538638af Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 27 Jan 2026 12:30:56 +0400 Subject: [PATCH 170/208] drm/tiny: Add driver for the Flipper One display Flipper One has an SPI-attached custom grayscale display. Add a driver for it. Signed-off-by: Alexey Charkov --- MAINTAINERS | 4 + drivers/gpu/drm/tiny/Kconfig | 14 + drivers/gpu/drm/tiny/Makefile | 31 +- drivers/gpu/drm/tiny/flipper-one-display.c | 366 +++++++++++++++++++++ 4 files changed, 400 insertions(+), 15 deletions(-) create mode 100644 drivers/gpu/drm/tiny/flipper-one-display.c diff --git a/MAINTAINERS b/MAINTAINERS index 207ef803dbe7e6..1100ed2dccd5e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7982,6 +7982,10 @@ F: include/linux/aperture.h F: include/linux/sysfb.h F: include/video/nomodeset.h +DRM DRIVER FOR THE FLIPPER ONE DISPLAY +M: Alexey Charkov +F: drivers/gpu/drm/tiny/flipper-one-display.c + DRM DRIVER FOR GENERIC EDP PANELS R: Douglas Anderson F: Documentation/devicetree/bindings/display/panel/panel-edp.yaml diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index f0e72d4b6a4709..4835e06c0724d2 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -98,6 +98,20 @@ config DRM_PIXPAPER If M is selected, the module will be built as pixpaper.ko. +config TINYDRM_FLIPPER_ONE_DISPLAY + tristate "DRM support for Flipper One display" + depends on DRM && SPI + select DRM_CLIENT_SELECTION + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + default y + help + DRM Driver for the Flipper One display, which uses an onboard MCU + to drive a 256x144 grayscale LCD based on a framebuffer written + over SPI (full frame at a time, 6 bpp padded to 8 bpp at 258x144). + + If M is selected the module will be called flipper_one_display. + config TINYDRM_HX8357D tristate "DRM support for HX8357D display panels" depends on DRM && SPI diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile index 48d30bf6152f97..5df261a871f09d 100644 --- a/drivers/gpu/drm/tiny/Makefile +++ b/drivers/gpu/drm/tiny/Makefile @@ -1,17 +1,18 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_DRM_APPLETBDRM) += appletbdrm.o -obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o -obj-$(CONFIG_DRM_BOCHS) += bochs.o -obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o -obj-$(CONFIG_DRM_GM12U320) += gm12u320.o -obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o -obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o -obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o -obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o -obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o -obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o -obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o -obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o -obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o -obj-$(CONFIG_TINYDRM_SHARP_MEMORY) += sharp-memory.o +obj-$(CONFIG_DRM_APPLETBDRM) += appletbdrm.o +obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o +obj-$(CONFIG_DRM_BOCHS) += bochs.o +obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o +obj-$(CONFIG_DRM_GM12U320) += gm12u320.o +obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o +obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o +obj-$(CONFIG_TINYDRM_FLIPPER_ONE_DISPLAY) += flipper-one-display.o +obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o +obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o +obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o +obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o +obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o +obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o +obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o +obj-$(CONFIG_TINYDRM_SHARP_MEMORY) += sharp-memory.o diff --git a/drivers/gpu/drm/tiny/flipper-one-display.c b/drivers/gpu/drm/tiny/flipper-one-display.c new file mode 100644 index 00000000000000..adcd9fa8f9138e --- /dev/null +++ b/drivers/gpu/drm/tiny/flipper-one-display.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct fo_device { + struct drm_device drm; + struct spi_device *spi; + + const struct drm_display_mode *mode; + + struct drm_crtc crtc; + struct drm_plane plane; + struct drm_encoder encoder; + struct drm_connector connector; + + struct gpio_desc *active_gpio; + + u32 pitch; + u32 tx_buffer_size; + u8 *tx_buffer; +}; + +static inline struct fo_device *drm_to_fo_device(struct drm_device *drm) +{ + return container_of(drm, struct fo_device, drm); +} + +DEFINE_DRM_GEM_DMA_FOPS(fo_fops); + +static const struct drm_driver fo_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &fo_fops, + DRM_GEM_DMA_DRIVER_OPS_VMAP, + DRM_FBDEV_DMA_DRIVER_OPS, + .name = "flipper_one_display", + .desc = "Flipper One LCD screen", + .major = 1, + .minor = 0, +}; + +static void fo_set_tx_buffer_data(struct fo_device *fo, + struct drm_plane_state *plane_state) +{ + struct drm_shadow_plane_state *s_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_rect clip; + struct iosys_map *src = s_plane_state->data; + struct iosys_map dst; + + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) + return; + + clip.x1 = 0; + clip.x2 = fb->width; + clip.y1 = 0; + clip.y2 = fb->height; + + iosys_map_set_vaddr(&dst, fo->tx_buffer); + drm_fb_xrgb8888_to_gray8(&dst, &fo->pitch, src, fb, &clip, &s_plane_state->fmtcnv_state); + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} + +static int fo_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct fo_device *fo; + struct drm_crtc_state *crtc_state; + + fo = container_of(plane, struct fo_device, plane); + crtc_state = drm_atomic_get_new_crtc_state(state, &fo->crtc); + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static void fo_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct fo_device *fo = container_of(plane, struct fo_device, plane); + struct drm_plane_state *plane_state = plane->state; + + if (!fo->crtc.state->active) + return; + + /* Populate the transmit buffer with frame data */ + fo_set_tx_buffer_data(fo, plane_state); + + spi_write(fo->spi, fo->tx_buffer, fo->tx_buffer_size); +} + +static const struct drm_plane_helper_funcs fo_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = fo_plane_atomic_check, + .atomic_update = fo_plane_atomic_update, + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, +}; + +static bool fo_format_mod_supported(struct drm_plane *plane, u32 format, u64 modifier) +{ + return modifier == DRM_FORMAT_MOD_LINEAR; +} + +static const struct drm_plane_funcs fo_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, + .format_mod_supported = fo_format_mod_supported, +}; + +static enum drm_mode_status fo_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + return drm_crtc_helper_mode_valid_fixed(crtc, mode, fo->mode); +} + +static int fo_crtc_check(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + int ret; + + if (!crtc_state->enable) + goto out; + + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + +out: + return drm_atomic_add_affected_planes(state, crtc); +} + +static void fo_begin_frame(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + if (fo->active_gpio) + gpiod_set_value(fo->active_gpio, 1); +} + +static void fo_end_frame(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct fo_device *fo = drm_to_fo_device(crtc->dev); + + if (fo->active_gpio) + gpiod_set_value(fo->active_gpio, 0); +} + +static const struct drm_crtc_helper_funcs fo_crtc_helper_funcs = { + .mode_valid = fo_crtc_mode_valid, + .atomic_check = fo_crtc_check, + .atomic_begin = fo_begin_frame, + .atomic_flush = fo_end_frame, +}; + +static const struct drm_crtc_funcs fo_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_encoder_funcs fo_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int fo_connector_get_modes(struct drm_connector *connector) +{ + struct fo_device *fo = drm_to_fo_device(connector->dev); + + return drm_connector_helper_get_modes_fixed(connector, fo->mode); +} + +static const struct drm_connector_helper_funcs fo_connector_hfuncs = { + .get_modes = fo_connector_get_modes, +}; + +static const struct drm_connector_funcs fo_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs fo_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_display_mode fo_display_mode = { + DRM_MODE_INIT(60, 256, 144, 53, 30), +}; + +static const struct spi_device_id fo_ids[] = { + {"flipper_one_display", (kernel_ulong_t)&fo_display_mode}, + {}, +}; +MODULE_DEVICE_TABLE(spi, fo_ids); + +static const struct of_device_id fo_of_match[] = { + {.compatible = "flipper,one-display", &fo_display_mode}, + {}, +}; +MODULE_DEVICE_TABLE(of, fo_of_match); + +static const u32 fo_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +static int fo_pipe_init(struct drm_device *dev, struct fo_device *fo, + const u32 *formats, unsigned int format_count, + const u64 *format_modifiers) +{ + int ret; + struct drm_encoder *encoder = &fo->encoder; + struct drm_plane *plane = &fo->plane; + struct drm_crtc *crtc = &fo->crtc; + struct drm_connector *connector = &fo->connector; + + drm_plane_helper_add(plane, &fo_plane_helper_funcs); + ret = drm_universal_plane_init(dev, plane, 0, &fo_plane_funcs, + formats, format_count, + format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(crtc, &fo_crtc_helper_funcs); + ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL, + &fo_crtc_funcs, NULL); + if (ret) + return ret; + + encoder->possible_crtcs = drm_crtc_mask(crtc); + ret = drm_encoder_init(dev, encoder, &fo_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + + ret = drm_connector_init(&fo->drm, &fo->connector, + &fo_connector_funcs, + DRM_MODE_CONNECTOR_SPI); + if (ret) + return ret; + + drm_connector_helper_add(&fo->connector, &fo_connector_hfuncs); + + return drm_connector_attach_encoder(connector, encoder); +} + +static int fo_probe(struct spi_device *spi) +{ + int ret; + struct device *dev; + struct fo_device *fo; + struct drm_device *drm; + + dev = &spi->dev; + spi->mode |= SPI_MODE_3; + + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to setup spi device\n"); + + if (!dev->coherent_dma_mask) { + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set dma mask\n"); + } + + fo = devm_drm_dev_alloc(dev, &fo_drm_driver, struct fo_device, drm); + if (!fo) + return -ENOMEM; + + spi_set_drvdata(spi, fo); + + fo->spi = spi; + drm = &fo->drm; + ret = drmm_mode_config_init(drm); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize drm config\n"); + + fo->active_gpio = devm_gpiod_get_optional(dev, "active", GPIOD_OUT_LOW); + if (!fo->active_gpio) + dev_warn(dev, "Active gpio not defined\n"); + + drm->mode_config.funcs = &fo_mode_config_funcs; + fo->mode = spi_get_device_match_data(spi); + /* The controller expects 3-byte aligned input lines */ + fo->pitch = roundup(fo->mode->hdisplay, 3); + fo->tx_buffer_size = (fo->pitch) * (fo->mode->vdisplay); + + fo->tx_buffer = devm_kzalloc(dev, fo->tx_buffer_size, GFP_KERNEL); + if (!fo->tx_buffer) + return -ENOMEM; + + drm->mode_config.min_width = fo->mode->hdisplay; + drm->mode_config.max_width = fo->mode->hdisplay; + drm->mode_config.min_height = fo->mode->vdisplay; + drm->mode_config.max_height = fo->mode->vdisplay; + + ret = fo_pipe_init(drm, fo, fo_formats, ARRAY_SIZE(fo_formats), NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize display pipeline.\n"); + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to register drm device.\n"); + + drm_client_setup(drm, NULL); + + return 0; +} + +static void fo_remove(struct spi_device *spi) +{ + struct fo_device *fo = spi_get_drvdata(spi); + + drm_dev_unplug(&fo->drm); + drm_atomic_helper_shutdown(&fo->drm); +} + +static struct spi_driver fo_spi_driver = { + .driver = { + .name = "flipper_one_display", + .of_match_table = fo_of_match, + }, + .probe = fo_probe, + .remove = fo_remove, + .id_table = fo_ids, +}; +module_spi_driver(fo_spi_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("SPI Protocol driver for the Flipper One display"); +MODULE_LICENSE("GPL"); From 183c5a569dbd2f26bd94dc2c26397a2d9118cbd3 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 27 Jan 2026 11:40:17 +0400 Subject: [PATCH 171/208] dt-bindings: arm: rockchip: Add Flipper One Add the compatibles for the Flipper One, which is an upcoming network multi-tool device built around the Rockchip RK3576 SoC. This is revision F0B0C1, which is a pre-production engineering version. Signed-off-by: Alexey Charkov --- Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml index 1a9dde18626d00..671b4d04350461 100644 --- a/Documentation/devicetree/bindings/arm/rockchip.yaml +++ b/Documentation/devicetree/bindings/arm/rockchip.yaml @@ -299,6 +299,11 @@ properties: - const: firefly,rk3568-roc-pc - const: rockchip,rk3568 + - description: Flipper One rev. F0B0C1 + items: + - const: flipper,one-rev-f0b0c1 + - const: rockchip,rk3576 + - description: Forlinx FET3588-C SoM items: - enum: From 73b30f990550095bb29f01374d64419057183e0e Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 27 Jan 2026 11:41:58 +0400 Subject: [PATCH 172/208] arm64: dts: rockchip: Add Flipper One rev. F0B0C1 Add a device tree for Flipper One, which is an upcoming handheld network multi-tool device for hardware enthusiasts. It is based on the Rockchip RK3576 SoC paired with an onboard MCU for low power tasks, has battery power with powerbank functionality, built-in custom grayscale display, controls and a wide array of connectivity options. This is revision F0B0C1, which is a pre-production engineering version. Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/Makefile | 11 + .../boot/dts/rockchip/rk3576-armsom-sige5.dts | 1 + .../rk3576-flipper-one-i2c2-free.dtso | 51 + .../rk3576-flipper-one-rev-f0b0c1.dts | 1797 +++++++++++++++++ .../dts/rockchip/rk3576-flipper-one-sata.dtso | 16 + 5 files changed, 1876 insertions(+) create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index 761d82b4f4f2ac..dd4899035f8acb 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -170,6 +170,9 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb2-v10.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-rev-f0b0c1.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-i2c2-free.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-sata.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-khadas-edge-2l.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-luckfox-omni3576.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-nanopi-m5.dtb @@ -299,6 +302,14 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtb rk3576-evb1-v10-pcie1-dtbs := rk3576-evb1-v10.dtb \ rk3576-evb1-v10-pcie1.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-i2c2-free.dtb +rk3576-flipper-one-i2c2-free-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-flipper-one-i2c2-free.dtbo + +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-sata.dtb +rk3576-flipper-one-sata-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-flipper-one-sata.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-edgeble-neu6a-wifi.dtb rk3588-edgeble-neu6a-wifi-dtbs := rk3588-edgeble-neu6a-io.dtb \ rk3588-edgeble-neu6a-wifi.dtbo diff --git a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts index fdf2a7d333aae4..769842159b9bef 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-armsom-sige5.dts @@ -708,6 +708,7 @@ }; &i2c2 { + clock-frequency = <400000>; status = "okay"; usbc0: typec-portc@22 { diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso new file mode 100644 index 00000000000000..16f691abc26424 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-i2c2-free.dtso @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to disable CPU side access to all I2C2 peripherals + */ + +/dts-v1/; +/plugin/; + +&gpio_expander { + status = "disabled"; +}; + +&usbc0 { + status = "disabled"; +}; + +&ina4_1 { + status = "disabled"; +}; + +&ina4_2 { + status = "disabled"; +}; + +&ina4_3 { + status = "disabled"; +}; + +&ina4_4 { + status = "disabled"; +}; + +&ina_sys { + status = "disabled"; +}; + +&ina4_5 { + status = "disabled"; +}; + +&usbmux { + status = "disabled"; +}; + +&gauge { + status = "disabled"; +}; + +&bq25792 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts new file mode 100644 index 00000000000000..ad1d08f7fd2a6b --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts @@ -0,0 +1,1797 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2026 Flipper FZCO + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include "rk3576.dtsi" + +/ { + model = "Flipper One rev. F0B0C1"; + compatible = "flipper,one-rev-f0b0c1", "rockchip,rk3576"; + + aliases { + ethernet0 = &gmac0; + ethernet1 = &gmac1; + }; + + battery: battery { + compatible = "simple-battery"; + charge-full-design-microamp-hours = <3100000>; + constant-charge-current-max-microamp = <3100000>; + constant-charge-voltage-max-microvolt = <8650000>; + device-chemistry = "lithium-ion-polymer"; + operating-range-celsius = <0 60>; + voltage-min-design-microvolt = <6000000>; + }; + + chosen: chosen { + stdout-path = "serial0:1500000n8"; + }; + + hdmi-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&led_cd2>, <&led_cd3>; + pinctrl-names = "default"; + + led-cd2 { + color = ; + function = LED_FUNCTION_HEARTBEAT; + gpios = <&gpio0 RK_PD2 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led-cd3 { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "default-on"; + }; + }; + + vcc8v4_sys: regulator-vcc8v4-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc8v4_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <8400000>; + regulator-max-microvolt = <8400000>; + /* Powered by the charger's SYS output */ + }; + + vcc3v3_control: regulator-vcc3v3-control { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_control"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc3v3: regulator-vcc3v3-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc3v3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpios = <&gpio_expander 0xe GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc3v3_control>; + }; + + vcc3v3_m2: regulator-vcc3v3-m2 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_m2"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&m2_pwr_en>; + pinctrl-names = "default"; + startup-delay-us = <5000>; + vin-supply = <&vcc8v4_sys>; + }; + + vcc3v3_rtc_s5: regulator-vcc3v3-rtc-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_rtc_s5"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* Powered by the system battery */ + }; + + vcc5v0_device_s0: regulator-vcc5v0-device-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_device"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P13, but turning it off + * makes the expander I2C bus stuck and unrecoverable due to + * the USB MUX misbehavior on the same bus + */ + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc5v0: regulator-vcc5v0-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc5v0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xd GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vusb_typea_up: regulator-vcc5v0-vusb-typea-up { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vusb_typea_up"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xa GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vcc5v0_sys_s5: regulator-vcc5v0-sys-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys_s5"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P14, but we + * can't let this pin to ever change state, as it makes + * the system lose power + */ + vin-supply = <&vcc8v4_sys>; + }; + + vcc_2v0_pldo_s3: regulator-vcc-2v0-pldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_2v0_pldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v1_nldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd2l_0v9_ddr_s3: regulator-vdd2l-0v9-ddr-s3 { + compatible = "regulator-fixed"; + regulator-name = "vdd2l_0v9_ddr_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + /* VCC_3V3_S3 */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd1v1_hub: regulator-vdd-1v1-hub { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vdd1v1_hub"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + gpios = <&gpio_expander 0x9 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_3v3_s0: regulator-vcc-3v3-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_3v3_s3>; + }; + + vcc_1v8_s0: regulator-vcc-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vccio_1v8_s0: regulator-vccio-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vccio_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vcc1v8_ufs_vccq2_s0: regulator-vcc1v8-ufs-vccq2-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc1v8_ufs_vccq2_s0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc_1v8_s3>; + }; + + rfkill-onboard-wifi { + compatible = "rfkill-gpio"; + default-blocked; + radio-type = "wlan"; + label = "Onboard WiFi+BT"; + pinctrl-0 = <&wifi_pmu_en>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable1 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN"; + pinctrl-0 = <&m2b_w_disable1>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable2 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN (WDISABLE2)"; + pinctrl-0 = <&m2b_w_disable2>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_HIGH>; + }; + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "On-board Analog NAU8822"; + simple-audio-card,bitclock-master = <&masterdai>; + simple-audio-card,format = "i2s"; + simple-audio-card,frame-master = <&masterdai>; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,pin-switches = "Headphones", "Speaker", + "Headset Microphone", "Internal Microphone"; + simple-audio-card,routing = + "Headphones", "LHP", + "Headphones", "RHP", + "Speaker", "LSPK", + "Speaker", "RSPK", + "Line Out", "AUXOUT1", + "Line Out", "AUXOUT2", + "LMICP", "Internal Microphone", + "LMICN", "Internal Microphone", + "RMICP", "Headset Microphone"; + simple-audio-card,widgets = + "Headphones", "Headphones", + "Line Out", "Line Out", + "Speaker", "Speaker", + "Microphone", "Internal Microphone", + "Microphone", "Headset Microphone"; + + simple-audio-card,cpu { + sound-dai = <&sai2>; + }; + + masterdai: simple-audio-card,codec { + sound-dai = <&nau8822>; + system-clock-frequency = <12288000>; + }; + }; + + typea_up_con: usb-a-connector { + compatible = "usb-a-connector"; + data-role = "host"; + label = "USB-A (up)"; + power-role = "source"; + vbus-supply = <&vusb_typea_up>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typea_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds2>; + }; + }; + + port@1 { + reg = <1>; + typea_up_con_ss: endpoint { + remote-endpoint = <&hub_3_0_ds2>; + }; + }; + }; + }; + + typec_up_con: usb-c-connector { + compatible = "usb-c-connector"; + data-role = "host"; + label = "USB-C (up)"; + power-role = "source"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds3>; + }; + }; + + port@1 { + reg = <1>; + typec_up_con_ss: endpoint { + remote-endpoint = <&usb_mux_in>; + }; + }; + }; + }; +}; + +&cpu_l0 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l1 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l2 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l3 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_b0 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b1 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b2 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b3 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&combphy0_ps { + status = "okay"; +}; + +&combphy1_psu { + status = "okay"; +}; + +&dp { + status = "okay"; +}; + +&dp0_in { + dp0_in_vp1: endpoint { + remote-endpoint = <&vp1_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&usbdp_phy_dp_in>; + }; +}; + +&dp0_sound { + status = "okay"; +}; + +&gmac0 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy0>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + status = "okay"; +}; + +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + status = "okay"; +}; + +&gpio2 { + m2-poweroff-hog { + gpios = ; + output-low; + line-name = "M.2 Full Card Power Off Disable"; + gpio-hog; + }; +}; + +&gpio3 { + m2-reset-hog { + gpios = ; + output-low; + line-name = "M.2 Reset Disable"; + gpio-hog; + }; +}; + +&gpu { + mali-supply = <&vdd_gpu_s0>; + status = "okay"; +}; + +&hdmi { + frl-enable-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&hdmi_in { + hdmi_in_vp0: endpoint { + remote-endpoint = <&vp0_out_hdmi>; + }; +}; + +&hdmi_out { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + +&hdmi_sound { + status = "okay"; +}; + +&hdptxphy { + status = "okay"; +}; + +&i2c0 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c0m1_xfer &i2c0_sda_pullup>; + pinctrl-names = "default"; + status = "okay"; + + embedded-controller@69 { + compatible = "flipper,one-mcu"; + reg = <0x69>; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&audio_headset_int &cpu_int>; + wakeup-source; + }; +}; + +&i2c1 { + status = "okay"; + + rk806: pmic@23 { + compatible = "rockchip,rk806"; + reg = <0x23>; + interrupt-parent = <&gpio0>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>, + <&rk806_dvs2_null>, <&rk806_dvs3_null>; + system-power-controller; + + vcc1-supply = <&vcc5v0_sys_s5>; + vcc2-supply = <&vcc5v0_sys_s5>; + vcc3-supply = <&vcc5v0_sys_s5>; + vcc4-supply = <&vcc5v0_sys_s5>; + vcc5-supply = <&vcc5v0_sys_s5>; + vcc6-supply = <&vcc5v0_sys_s5>; + vcc7-supply = <&vcc5v0_sys_s5>; + vcc8-supply = <&vcc5v0_sys_s5>; + vcc9-supply = <&vcc5v0_sys_s5>; + vcc10-supply = <&vcc5v0_sys_s5>; + vcc11-supply = <&vcc_2v0_pldo_s3>; + vcc12-supply = <&vcc5v0_sys_s5>; + vcc13-supply = <&vcc_1v1_nldo_s3>; + vcc14-supply = <&vcc_1v1_nldo_s3>; + vcca-supply = <&vcc5v0_sys_s5>; + + rk806_dvs1_null: dvs1-null-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun0"; + }; + + rk806_dvs2_null: dvs2-null-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun0"; + }; + + rk806_dvs3_null: dvs3-null-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun0"; + }; + + rk806_dvs1_slp: dvs1-slp-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun1"; + }; + + rk806_dvs1_pwrdn: dvs1-pwrdn-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun2"; + }; + + rk806_dvs1_rst: dvs1-rst-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun3"; + }; + + rk806_dvs2_slp: dvs2-slp-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun1"; + }; + + rk806_dvs2_pwrdn: dvs2-pwrdn-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun2"; + }; + + rk806_dvs2_rst: dvs2-rst-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun3"; + }; + + rk806_dvs2_dvs: dvs2-dvs-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun4"; + }; + + rk806_dvs2_gpio: dvs2-gpio-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun5"; + }; + + rk806_dvs3_slp: dvs3-slp-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun1"; + }; + + rk806_dvs3_pwrdn: dvs3-pwrdn-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun2"; + }; + + rk806_dvs3_rst: dvs3-rst-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun3"; + }; + + rk806_dvs3_dvs: dvs3-dvs-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun4"; + }; + + rk806_dvs3_gpio: dvs3-gpio-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun5"; + }; + + regulators { + vdd_cpu_big_s0: dcdc-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_big_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_npu_s0: dcdc-reg2 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_npu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_lit_s0: dcdc-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_lit_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vcc_3v3_s3: dcdc-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc_3v3_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vdd_gpu_s0: dcdc-reg5 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_gpu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + vddq_ddr_s0: dcdc-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vddq_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_logic_s0: dcdc-reg7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <800000>; + regulator-name = "vdd_logic_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s3: dcdc-reg8 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd2_ddr_s3: dcdc-reg9 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vdd2_ddr_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vdd_ddr_s0: dcdc-reg10 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdd_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_1v8_s0: pldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca_1v8_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo2_s0: pldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_1v2_s0: pldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdda_1v2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_3v3_s0: pldo-reg4 { + regulator-enable-ramp-delay = <500>; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <12500>; + regulator-name = "vcca_3v3_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vccio_3v3_sd_s0: pldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_3v3_sd_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo6_s3: pldo-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo6_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd_0v75_s3: nldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_0v75_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdda_ddr_pll_s0: nldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_ddr_pll_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda0v75_hdmi_s0: nldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <837500>; + regulator-max-microvolt = <837500>; + regulator-name = "vdda0v75_hdmi_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v85_s0: nldo-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_0v85_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v75_s0: nldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdda_0v75_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + }; +}; + +&i2c2 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c2m0_xfer_dr5>; + pinctrl-names = "default"; + status = "okay"; + + gpio_expander: gpio@20 { + compatible = "ti,tca6416"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-0 = <&expander_int>; + pinctrl-names = "default"; + vcc-supply = <&vcc3v3_control>; + + usb2-switch-hog { + gpios = <8 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main Type-C USB 2.0 pins switch to CPU"; + gpio-hog; + }; + + vcc5v0-device-s0-hog { + gpios = <0xb GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Peripheral power supply"; + gpio-hog; + }; + + vcc5v0-sys-s5-hog { + gpios = <0xc GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main power supply to the system PMIC"; + gpio-hog; + }; + }; + + usbc0: typec-portc@22 { + compatible = "fcs,fusb302"; + reg = <0x22>; + interrupt-parent = <&gpio_expander>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + vbus-supply = <&vusb_typec>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + op-sink-microwatt = <10000000>; + /* fusb302 supports PD Rev 2.0 Ver 1.2 */ + pd-revision = /bits/ 8 <0x2 0x0 0x1 0x2>; + power-role = "dual"; + self-powered; + sink-pdos = ; + source-pdos = ; + try-power-role = "sink"; + + altmodes { + displayport { + svid = /bits/ 16 <0xff01>; + vdo = <0xffffffff>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_drd0_hs_ep>; + }; + }; + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; + }; + }; + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_dp_out>; + }; + }; + }; + }; + }; + + ina4_1: power-sensor@40 { + compatible = "ti,ina4230"; + reg = <0x40>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd_0v75_s3"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vcc_3v3_control"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <4000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v85_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vcc_3v3_s3"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_2: power-sensor@41 { + compatible = "ti,ina4230"; + reg = <0x41>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vddq0v51_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@1 { + reg = <1>; + label = "vdd0v75_npu_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v75_gpu_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_logic_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + ina4_3: power-sensor@43 { + compatible = "ti,ina4230"; + reg = <0x43>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd0v75_cpu_big_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <6500000>; + }; + + input@1 { + reg = <1>; + label = "vdda_1v2_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vcca_1v8_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_cpu_lit_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_4: power-sensor@44 { + compatible = "ti,ina4230"; + reg = <0x44>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdda_0v75_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vdda_0v85_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@2 { + reg = <2>; + label = "vdda0v75_hdmi_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdda0v85_ddr_pll_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + }; + + ina_sys: power-sensor@45 { + compatible = "ti,ina219"; + reg = <0x45>; + #io-channel-cells = <1>; + label = "vcc8v4_sys"; + shunt-resistor = <4000>; + vs-supply = <&vcc3v3_control>; + }; + + ina4_5: power-sensor@46 { + compatible = "ti,ina4230"; + reg = <0x46>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vcca_3v3_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@1 { + reg = <1>; + label = "vccio_3v3_sd_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vdd2_1v05_ddr_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <2500000>; + }; + + input@3 { + reg = <3>; + label = "vcc_1v8_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + usbmux: usb-mux@47 { + compatible = "ti,hd3ss3220"; + reg = <0x47>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&usb_mux_int>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_mux_in: endpoint { + remote-endpoint = <&typec_up_con_ss>; + }; + }; + + port@1 { + reg = <1>; + usb_mux_out: endpoint { + remote-endpoint = <&hub_3_0_ds3>; + }; + }; + }; + }; + + gauge: fuel-gauge@55 { + compatible = "ti,bq28z610"; /* really bq28z620: tbc if we need a new compatible */ + reg = <0x55>; + monitored-battery = <&battery>; + }; + + bq25792: charger@6b { + compatible = "ti,bq25792"; + reg = <0x6b>; + input-current-limit-microamp = <3300000>; + interrupt-parent = <&gpio_expander>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + monitored-battery = <&battery>; + power-supplies = <&usbc0>; + + regulators { + vusb_typec: vbus { + regulator-max-microamp = <3320000>; + regulator-max-microvolt = <22000000>; + regulator-min-microamp = <0>; + regulator-min-microvolt = <2800000>; + regulator-name = "vusb_typec"; + }; + }; + }; +}; + +&i2c5 { + pinctrl-0 = <&i2c5m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MIPI CSI camera + * PDN GPIO: GPIO3 RK_PC6 <&cam_pdn> + * CLK: CAM_CLK2_OUT_M0 <&cam_clk2m0_clk2> + */ +}; + +&i2c6 { + pinctrl-0 = <&i2c6m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + nau8822: audio-codec@1a { + compatible = "nuvoton,nau8822"; + reg = <0x1a>; + + /* all supplies: VCCA_3V3_S0 */ + assigned-clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + assigned-clock-rates = <12288000>; + clock-names = "mclk"; + clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + pinctrl-names = "default"; + pinctrl-0 = <&sai2m0_mclk>; + vdda-supply = <&vcca_3v3_s0>; + vddb-supply = <&vcca_3v3_s0>; + vddc-supply = <&vcca_3v3_s0>; + vddspk-supply = <&vcca_3v3_s0>; + #sound-dai-cells = <0>; + nuvoton,spk-btl; + }; +}; + +&i2c8 { + pinctrl-0 = <&i2c8m2_xfer>; + pinctrl-names = "default"; + status = "okay"; + + hym8563: rtc@51 { + compatible = "haoyu,hym8563"; + reg = <0x51>; + clock-output-names = "hym8563"; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rtc_int &clk_32k_pins>; + wakeup-source; + #clock-cells = <0>; + }; +}; + +&mdio0 { + rgmii_phy0: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy0_int &rgmii_phy0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy1_int &rgmii_phy1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&pcie0 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0m1_pins &pcie0_rst>; + reset-gpios = <&gpio1 RK_PC3 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_m2>; + status = "okay"; +}; + +&pinctrl { + camera { + cam_pdn: cam-pdn { + rockchip,pins = <3 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + display { + spi0m0_wo_miso: spi0m0-wo-miso { + rockchip,pins = + /* spi0m0_csn0 */ + <0 RK_PC6 11 &pcfg_pull_none>, + /* spi0_clk_m0 */ + <0 RK_PC7 11 &pcfg_pull_none>, + /* spi0_mosi_m0 */ + <0 RK_PD0 11 &pcfg_pull_none>; + }; + + spi0m0_gpiomiso: spi0m0-gpiomiso { + rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + gpio-expander { + expander_int: expander-int { + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + hdmi { + hdmi_enable_frl: hdmi-enable-frl { + rockchip,pins = <0 RK_PC3 RK_FUNC_GPIO &pcfg_pull_down>; + }; + }; + + hym8563 { + rtc_int: rtc-int { + rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c0 { + i2c0_sda_pullup: i2c0-sda-pullup { + rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c2 { + i2c2m0_xfer_dr5: i2c2m0-xfer-dr5 { + rockchip,pins = + /* i2c2_scl_m0 */ + <0 RK_PB7 9 &pcfg_pull_none_drv_level_5_smt>, + /* i2c2_sda_m0 */ + <0 RK_PC0 9 &pcfg_pull_none_drv_level_5_smt>; + }; + }; + + leds { + led_cd2: led-cd2 { + rockchip,pins = <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + led_cd3: led-cd3 { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + m2 { + m2_pwr_en: m2-pwren { + rockchip,pins = <0 RK_PB1 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + m2b_w_disable1: m2-rfkill-wdisable1 { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + m2b_w_disable2: m2-rfkill-wdisable2 { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + mcu { + audio_headset_int: audio-headset-int { + rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cpu_int: cpu-int { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + network { + rgmii_phy0_int: rgmii-phy0-int { + rockchip,pins = <1 RK_PC1 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy0_rst: rgmii-phy0-rst { + rockchip,pins = <1 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + rgmii_phy1_int: rgmii-phy1-int { + rockchip,pins = <1 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy1_rst: rgmii-phy1-rst { + rockchip,pins = <1 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + pcie0 { + pcie0_rst: pcie0-rst { + rockchip,pins = <1 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + usb { + hub_reset: usb-hub-reset { + rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usb_mux_int: usb-mux-int { + rockchip,pins = <1 RK_PC0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usbc0_sbu1: usbc0-sbu1 { + rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + usbc0_sbu2: usbc0-sbu2 { + rockchip,pins = <4 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + wifibt { + wifi_pmu_en: wifi-pmu-en { + rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + wifi_wgpio0: bt-wake-host { + rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + wifi_wgpio1: wifi-wake-host { + rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&sai0 { + /* Serial audio for M.2 WWAN */ + pinctrl-names = "default"; + pinctrl-0 = <&sai0m2_lrck + &sai0m2_sclk + &sai0m2_sdi0 + &sai0m2_sdo0>; + status = "okay"; +}; + +&sai2 { + status = "okay"; +}; + +&sai6 { + status = "okay"; +}; + +&sdmmc { + bus-width = <4>; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_LOW>; + disable-wp; + no-sdio; + no-mmc; + pinctrl-names = "default"; + pinctrl-0 = <&sdmmc0_clk &sdmmc0_cmd &sdmmc0_det &sdmmc0_bus4>; + sd-uhs-sdr104; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vccio_3v3_sd_s0>; + status = "okay"; +}; + +&saradc { + vref-supply = <&vcca_1v8_s0>; + status = "okay"; +}; + +&spdif_tx3 { + status = "okay"; +}; + +&spi0 { + pinctrl-0 = <&spi0m0_wo_miso &spi0m0_gpiomiso>; + status = "okay"; + + display@0 { + compatible = "flipper,one-display"; + reg = <0>; + active-gpios = <&gpio0 RK_PD1 GPIO_ACTIVE_LOW>; + spi-cpha; + spi-cpol; + spi-max-frequency = <20000000>; + spi-rx-bus-width = <0>; + }; +}; + +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + +&u2phy1 { + status = "okay"; +}; + +&u2phy1_otg { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart4 { + pinctrl-0 = <&uart4m1_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MCU interconnect */ +}; + +&ufshc { + vcc-supply = <&vcc_3v3_s3>; + vccq2-supply = <&vcc1v8_ufs_vccq2_s0>; + status = "okay"; +}; + +&usbdp_phy { + mode-switch; + orientation-switch; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>; + sbu1-dc-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_HIGH>; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; + }; + }; +}; + +&usb_drd0_dwc3 { + usb-role-switch; + dr_mode = "otg"; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs_ep: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + usb_drd0_ss_ep: endpoint { + remote-endpoint = <&usbdp_phy_ss_in>; + }; + }; + }; +}; + +&usb_drd1_dwc3 { + #address-cells = <1>; + #size-cells = <0>; + dr_mode = "host"; + pinctrl-0 = <&hub_reset>; + pinctrl-names = "default"; + status = "okay"; + + /* 2.0 hub */ + hub_2_0: hub@1 { + compatible = "usb3431,6241"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_LOW>; + vdd1v1-supply = <&vdd1v1_hub>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + /* ext USB port 2.54mm */ + }; + + port@2 { + reg = <2>; + hub_2_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_hs>; + }; + }; + + port@3 { + reg = <3>; + hub_2_0_ds3: endpoint { + remote-endpoint = <&typec_up_con_hs>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; + + /* 3.0 hub */ + hub_3_0: hub@2 { + compatible = "usb3431,6341"; + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + peer-hub = <&hub_2_0>; + vdd1v1-supply = <&vdd1v1_hub>; + + device@1 { + /* WiFi-BT module connection */ + compatible = "usbe8d,7961"; + reg = <1>; + #address-cells = <2>; + #size-cells = <0>; + + interface@0 { + /* BT part */ + compatible = "usbife8d,7961.config1.0"; + reg = <0 1>; + interrupt-parent = <&gpio1>; + interrupts = ; + interrupt-names = "wakeup"; + pinctrl-0 = <&wifi_wgpio0>; + pinctrl-names = "default"; + }; + + interface@0,3 { + /* WiFi part */ + compatible = "usbife8d,7961.config1.3"; + reg = <0 3>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&wifi_wgpio1>; + pinctrl-names = "default"; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + hub_3_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_ss>; + }; + }; + + port@3 { + reg = <3>; + hub_3_0_ds3: endpoint { + remote-endpoint = <&usb_mux_out>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; +}; + +&vop { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vp0 { + vp0_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi_in_vp0>; + }; +}; + +&vp1 { + vp1_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp1>; + }; +}; diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso new file mode 100644 index 00000000000000..559d64ff50fa5f --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-sata.dtso @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to switch the M.2 slot from PCIe mode to SATA (which shares the + * same pins). + */ + +/dts-v1/; +/plugin/; + +&sata0 { + status = "okay"; +}; + +&pcie0 { + status = "disabled"; +}; From c23e56a3a925afad50f3bbab6aef8e74225bf6f4 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Feb 2026 17:38:32 +0400 Subject: [PATCH 173/208] dt-bindings: mfd: ti,bq25703a: Expand to include BQ25792 TI BQ25792 is similar in operation to BQ25703A, but has a different register layout and different current/voltage capabilities. Expand the existing BQ25703A binding to include BQ25792, and move the voltage and current limits into per-variant conditional statements. Reviewed-by: Krzysztof Kozlowski Tested-by: Chris Morgan Signed-off-by: Alexey Charkov --- .../devicetree/bindings/mfd/ti,bq25703a.yaml | 73 ++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml b/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml index ba14663c9266a5..cdce83f0580415 100644 --- a/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml +++ b/Documentation/devicetree/bindings/mfd/ti,bq25703a.yaml @@ -4,17 +4,16 @@ $id: http://devicetree.org/schemas/mfd/ti,bq25703a.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: BQ25703A Charger Manager/Buck/Boost Converter +title: BQ257xx Charger Manager/Buck/Boost Converter maintainers: - Chris Morgan -allOf: - - $ref: /schemas/power/supply/power-supply.yaml# - properties: compatible: - const: ti,bq25703a + enum: + - ti,bq25703a + - ti,bq25792 reg: const: 0x6b @@ -25,7 +24,6 @@ properties: powering the device. minimum: 50000 maximum: 6400000 - default: 3250000 interrupts: maxItems: 1 @@ -57,11 +55,11 @@ properties: minimum: 0 maximum: 6350000 regulator-min-microvolt: - minimum: 4480000 - maximum: 20800000 + minimum: 2800000 + maximum: 22000000 regulator-max-microvolt: - minimum: 4480000 - maximum: 20800000 + minimum: 2800000 + maximum: 22000000 enable-gpios: description: The BQ25703 may require both a register write and a GPIO @@ -74,6 +72,61 @@ properties: - regulator-min-microvolt - regulator-max-microvolt +allOf: + - $ref: /schemas/power/supply/power-supply.yaml# + - if: + properties: + compatible: + const: ti,bq25703a + then: + properties: + input-current-limit-microamp: + minimum: 50000 + maximum: 6400000 + default: 3250000 + regulators: + properties: + vbus: + properties: + regulator-min-microamp: + minimum: 0 + maximum: 6350000 + regulator-max-microamp: + minimum: 0 + maximum: 6350000 + regulator-min-microvolt: + minimum: 4480000 + maximum: 20800000 + regulator-max-microvolt: + minimum: 4480000 + maximum: 20800000 + - if: + properties: + compatible: + const: ti,bq25792 + then: + properties: + input-current-limit-microamp: + minimum: 100000 + maximum: 3300000 + default: 3000000 + regulators: + properties: + vbus: + properties: + regulator-min-microamp: + minimum: 0 + maximum: 3320000 + regulator-max-microamp: + minimum: 0 + maximum: 3320000 + regulator-min-microvolt: + minimum: 2800000 + maximum: 22000000 + regulator-max-microvolt: + minimum: 2800000 + maximum: 22000000 + unevaluatedProperties: false required: From d3b46b1bf19270e325580756c952897214a73201 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Feb 2026 17:50:37 +0400 Subject: [PATCH 174/208] regulator: bq257xx: Drop the regulator_dev from the driver data The field was not used anywhere in the driver, so just drop it. This helps further slim down the platform data structure. Acked-by: Mark Brown Tested-by: Chris Morgan Signed-off-by: Alexey Charkov --- drivers/regulator/bq257xx-regulator.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/regulator/bq257xx-regulator.c b/drivers/regulator/bq257xx-regulator.c index 09c466052c0487..913ca9186bf1e1 100644 --- a/drivers/regulator/bq257xx-regulator.c +++ b/drivers/regulator/bq257xx-regulator.c @@ -15,7 +15,6 @@ #include struct bq257xx_reg_data { - struct regulator_dev *bq257xx_reg; struct gpio_desc *otg_en_gpio; struct regulator_desc desc; }; @@ -145,6 +144,7 @@ static int bq257xx_regulator_probe(struct platform_device *pdev) struct bq257xx_reg_data *pdata; struct device_node *np = dev->of_node; struct regulator_config cfg = {}; + struct regulator_dev *rdev; device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); @@ -164,9 +164,9 @@ static int bq257xx_regulator_probe(struct platform_device *pdev) if (!cfg.regmap) return -ENODEV; - pdata->bq257xx_reg = devm_regulator_register(dev, &pdata->desc, &cfg); - if (IS_ERR(pdata->bq257xx_reg)) { - return dev_err_probe(&pdev->dev, PTR_ERR(pdata->bq257xx_reg), + rdev = devm_regulator_register(dev, &pdata->desc, &cfg); + if (IS_ERR(rdev)) { + return dev_err_probe(&pdev->dev, PTR_ERR(rdev), "error registering bq257xx regulator"); } From c99e5872518006e48ee4ea68bd88b483556f34ad Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 14:56:35 +0400 Subject: [PATCH 175/208] power: supply: bq257xx: Fix VSYSMIN clamping logic The minimal system voltage (VSYSMIN) is meant to protect the battery from dangerous over-discharge. When the device tree provides a value for the minimum design voltage of the battery, the user should not be allowed to set a lower VSYSMIN, as that would defeat the purpose of this protection. Flip the clamping logic when setting VSYSMIN to ensure that battery design voltage is respected. Cc: stable@vger.kernel.org Fixes: 1cc017b7f9c7 ("power: supply: bq257xx: Add support for BQ257XX charger") Tested-by: Chris Morgan Reviewed-by: Sebastian Reichel Signed-off-by: Alexey Charkov --- drivers/power/supply/bq257xx_charger.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 02c7d8b61e82b6..7ca4ae6109027a 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -128,9 +128,8 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) * @vsys: voltage value to set in uV. * * This function takes a requested minimum system voltage value, clamps - * it between the minimum supported value by the charger and a user - * defined minimum system value, and then writes the value to the - * appropriate register. + * it between the user defined minimum system value and the maximum supported + * value by the charger, and then writes the value to the appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ @@ -139,7 +138,7 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) unsigned int reg; int vsys_min = pdata->vsys_min; - vsys = clamp(vsys, BQ25703_MINVSYS_MIN_UV, vsys_min); + vsys = clamp(vsys, vsys_min, BQ25703_MINVSYS_MAX_UV); reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV); reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg); From 3f3d1a13b616656509045ecda19206b60d1f6243 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 15:24:02 +0400 Subject: [PATCH 176/208] power: supply: bq257xx: Make the default current limit a per-chip attribute Add a field for the default current limit to the bq257xx_info structure and use it instead of the hardcoded value in the probe function. This prepares the driver for allowing different electrical constraints for different chip variants. Tested-by: Chris Morgan Reviewed-by: Sebastian Reichel Signed-off-by: Alexey Charkov --- drivers/power/supply/bq257xx_charger.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 7ca4ae6109027a..39718223c3f9ce 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -18,6 +18,7 @@ struct bq257xx_chg; /** * struct bq257xx_chip_info - chip specific routines + * @default_iindpm_uA: default input current limit in microamps * @bq257xx_hw_init: init function for hw * @bq257xx_hw_shutdown: shutdown function for hw * @bq257xx_get_state: get and update state of hardware @@ -26,6 +27,7 @@ struct bq257xx_chg; * @bq257xx_set_iindpm: set maximum input current (in uA) */ struct bq257xx_chip_info { + int default_iindpm_uA; int (*bq257xx_hw_init)(struct bq257xx_chg *pdata); void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata); int (*bq257xx_get_state)(struct bq257xx_chg *pdata); @@ -627,6 +629,7 @@ static const struct power_supply_desc bq257xx_power_supply_desc = { }; static const struct bq257xx_chip_info bq25703_chip_info = { + .default_iindpm_uA = BQ25703_IINDPM_DEFAULT_UA, .bq257xx_hw_init = &bq25703_hw_init, .bq257xx_hw_shutdown = &bq25703_hw_shutdown, .bq257xx_get_state = &bq25703_get_state, @@ -675,7 +678,7 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, "input-current-limit-microamp", &pdata->iindpm_max); if (ret) - pdata->iindpm_max = BQ25703_IINDPM_DEFAULT_UA; + pdata->iindpm_max = pdata->chip->default_iindpm_uA; return 0; } From b842dacdc7de25e63d43ef8bd09bf91f1a173aa9 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 15:30:25 +0400 Subject: [PATCH 177/208] power: supply: bq257xx: Consistently use indirect get/set helpers Move the remaining get/set helper functions to indirect calls via the per-chip bq257xx_chip_info struct. This improves the consistency of the code and prepares the driver to support multiple chip variants with different register layouts and bit definitions. Tested-by: Chris Morgan Reviewed-by: Sebastian Reichel Signed-off-by: Alexey Charkov --- drivers/power/supply/bq257xx_charger.c | 30 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 39718223c3f9ce..0765673728e436 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -22,18 +22,30 @@ struct bq257xx_chg; * @bq257xx_hw_init: init function for hw * @bq257xx_hw_shutdown: shutdown function for hw * @bq257xx_get_state: get and update state of hardware + * @bq257xx_get_ichg: get maximum charge current (in uA) * @bq257xx_set_ichg: set maximum charge current (in uA) + * @bq257xx_get_vbatreg: get maximum charge voltage (in uV) * @bq257xx_set_vbatreg: set maximum charge voltage (in uV) + * @bq257xx_get_iindpm: get maximum input current (in uA) * @bq257xx_set_iindpm: set maximum input current (in uA) + * @bq257xx_get_cur: get battery current from ADC (in uA) + * @bq257xx_get_vbat: get battery voltage from ADC (in uV) + * @bq257xx_get_min_vsys: get minimum system voltage (in uV) */ struct bq257xx_chip_info { int default_iindpm_uA; int (*bq257xx_hw_init)(struct bq257xx_chg *pdata); void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata); int (*bq257xx_get_state)(struct bq257xx_chg *pdata); + int (*bq257xx_get_ichg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg); + int (*bq257xx_get_vbatreg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg); + int (*bq257xx_get_iindpm)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm); + int (*bq257xx_get_cur)(struct bq257xx_chg *pdata, int *intval); + int (*bq257xx_get_vbat)(struct bq257xx_chg *pdata, int *intval); + int (*bq257xx_get_min_vsys)(struct bq257xx_chg *pdata, int *intval); }; /** @@ -490,22 +502,22 @@ static int bq257xx_get_charger_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return bq25703_get_iindpm(pdata, &val->intval); + return pdata->chip->bq257xx_get_iindpm(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - return bq25703_get_chrg_volt(pdata, &val->intval); + return pdata->chip->bq257xx_get_vbatreg(pdata, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: - return bq25703_get_cur(pdata, &val->intval); + return pdata->chip->bq257xx_get_cur(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return bq25703_get_vbat(pdata, &val->intval); + return pdata->chip->bq257xx_get_vbat(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - return bq25703_get_ichg_cur(pdata, &val->intval); + return pdata->chip->bq257xx_get_ichg(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN: - return bq25703_get_min_vsys(pdata, &val->intval); + return pdata->chip->bq257xx_get_min_vsys(pdata, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: val->intval = pdata->usb_type; @@ -633,9 +645,15 @@ static const struct bq257xx_chip_info bq25703_chip_info = { .bq257xx_hw_init = &bq25703_hw_init, .bq257xx_hw_shutdown = &bq25703_hw_shutdown, .bq257xx_get_state = &bq25703_get_state, + .bq257xx_get_ichg = &bq25703_get_ichg_cur, .bq257xx_set_ichg = &bq25703_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25703_get_chrg_volt, .bq257xx_set_vbatreg = &bq25703_set_chrg_volt, + .bq257xx_get_iindpm = &bq25703_get_iindpm, .bq257xx_set_iindpm = &bq25703_set_iindpm, + .bq257xx_get_cur = &bq25703_get_cur, + .bq257xx_get_vbat = &bq25703_get_vbat, + .bq257xx_get_min_vsys = &bq25703_get_min_vsys, }; /** From b51a28918d1b05e94d7b7168e10f2eb0a307341e Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 15:37:06 +0400 Subject: [PATCH 178/208] power: supply: bq257xx: Add fields for 'charging' and 'overvoltage' states The driver currently reports the 'charging' and 'overvoltage' states based on a logical expression in the get_charger_property() wrapper function. This doesn't scale well to other chip variants, which may have a different number and type of hardware reported conditions which fall into these broad power supply states. Move the logic for determining 'charging' and 'overvoltage' states into chip-specific accessors, which can be overridden by each variant as needed. This helps keep the get_charger_property() wrapper function chip-agnostic while allowing for new chip variants to be added bringing their own logic. Tested-by: Chris Morgan Reviewed-by: Sebastian Reichel Signed-off-by: Alexey Charkov --- drivers/power/supply/bq257xx_charger.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 0765673728e436..9c082865e745bb 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -54,8 +54,10 @@ struct bq257xx_chip_info { * @bq: parent MFD device * @charger: power supply device * @online: charger input is present + * @charging: charger is actively charging the battery * @fast_charge: charger is in fast charge mode * @pre_charge: charger is in pre-charge mode + * @overvoltage: overvoltage fault detected * @ov_fault: charger reports over voltage fault * @batoc_fault: charger reports battery over current fault * @oc_fault: charger reports over current fault @@ -71,8 +73,10 @@ struct bq257xx_chg { struct bq257xx_device *bq; struct power_supply *charger; bool online; + bool charging; bool fast_charge; bool pre_charge; + bool overvoltage; bool ov_fault; bool batoc_fault; bool oc_fault; @@ -106,8 +110,10 @@ static int bq25703_get_state(struct bq257xx_chg *pdata) pdata->online = reg & BQ25703_STS_AC_STAT; pdata->fast_charge = reg & BQ25703_STS_IN_FCHRG; pdata->pre_charge = reg & BQ25703_STS_IN_PCHRG; + pdata->charging = pdata->fast_charge || pdata->pre_charge; pdata->ov_fault = reg & BQ25703_STS_FAULT_ACOV; pdata->batoc_fault = reg & BQ25703_STS_FAULT_BATOC; + pdata->overvoltage = pdata->ov_fault || pdata->batoc_fault; pdata->oc_fault = reg & BQ25703_STS_FAULT_ACOC; return 0; @@ -478,14 +484,14 @@ static int bq257xx_get_charger_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: if (!pdata->online) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else if (pdata->fast_charge || pdata->pre_charge) + else if (pdata->charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_HEALTH: - if (pdata->ov_fault || pdata->batoc_fault) + if (pdata->overvoltage) val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else if (pdata->oc_fault) val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; From 110da779eecf45a21776bb5925bb86025b42b65a Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Feb 2026 23:56:44 +0400 Subject: [PATCH 179/208] mfd: bq257xx: Add BQ25792 support Add register definitions and a new 'type' enum to be passed via MFD private data to support the BQ25792, which is a newer variant of the BQ257xx family. BQ25792 shares similar logic of operation with the already supported BQ25703A but has a completely different register map and different electrical constraints. Tested-by: Chris Morgan Signed-off-by: Alexey Charkov --- drivers/mfd/bq257xx.c | 54 ++++- include/linux/mfd/bq257xx.h | 414 ++++++++++++++++++++++++++++++++++++ 2 files changed, 465 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/bq257xx.c b/drivers/mfd/bq257xx.c index e9d49dac0a1670..054342c60b7318 100644 --- a/drivers/mfd/bq257xx.c +++ b/drivers/mfd/bq257xx.c @@ -39,6 +39,39 @@ static const struct regmap_config bq25703_regmap_config = { .val_format_endian = REGMAP_ENDIAN_LITTLE, }; +static const struct regmap_range bq25792_writeable_reg_ranges[] = { + regmap_reg_range(BQ25792_REG00_MIN_SYS_VOLTAGE, + BQ25792_REG18_NTC_CONTROL_1), + regmap_reg_range(BQ25792_REG28_CHARGER_MASK_0, + BQ25792_REG30_ADC_FUNCTION_DISABLE_1), +}; + +static const struct regmap_access_table bq25792_writeable_regs = { + .yes_ranges = bq25792_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25792_writeable_reg_ranges), +}; + +static const struct regmap_range bq25792_volatile_reg_ranges[] = { + regmap_reg_range(BQ25792_REG19_ICO_CURRENT_LIMIT, + BQ25792_REG27_FAULT_FLAG_1), + regmap_reg_range(BQ25792_REG31_IBUS_ADC, + BQ25792_REG47_DPDM_DRIVER), +}; + +static const struct regmap_access_table bq25792_volatile_regs = { + .yes_ranges = bq25792_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25792_volatile_reg_ranges), +}; + +static const struct regmap_config bq25792_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BQ25792_REG48_PART_INFORMATION, + .cache_type = REGCACHE_MAPLE, + .wr_table = &bq25792_writeable_regs, + .volatile_table = &bq25792_volatile_regs, +}; + static const struct mfd_cell cells[] = { MFD_CELL_NAME("bq257xx-regulator"), MFD_CELL_NAME("bq257xx-charger"), @@ -46,6 +79,7 @@ static const struct mfd_cell cells[] = { static int bq257xx_probe(struct i2c_client *client) { + const struct regmap_config *rcfg; struct bq257xx_device *ddata; int ret; @@ -53,9 +87,21 @@ static int bq257xx_probe(struct i2c_client *client) if (!ddata) return -ENOMEM; + ddata->type = (uintptr_t)i2c_get_match_data(client); ddata->client = client; - ddata->regmap = devm_regmap_init_i2c(client, &bq25703_regmap_config); + switch (ddata->type) { + case BQ25703A: + rcfg = &bq25703_regmap_config; + break; + case BQ25792: + rcfg = &bq25792_regmap_config; + break; + default: + return dev_err_probe(&client->dev, -ENODEV, "Unsupported device type\n"); + } + + ddata->regmap = devm_regmap_init_i2c(client, rcfg); if (IS_ERR(ddata->regmap)) { return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap), "Failed to allocate register map\n"); @@ -73,13 +119,15 @@ static int bq257xx_probe(struct i2c_client *client) } static const struct i2c_device_id bq257xx_i2c_ids[] = { - { "bq25703a" }, + { "bq25703a", BQ25703A }, + { "bq25792", BQ25792 }, {} }; MODULE_DEVICE_TABLE(i2c, bq257xx_i2c_ids); static const struct of_device_id bq257xx_of_match[] = { - { .compatible = "ti,bq25703a" }, + { .compatible = "ti,bq25703a", .data = (void *)BQ25703A }, + { .compatible = "ti,bq25792", .data = (void *)BQ25792 }, {} }; MODULE_DEVICE_TABLE(of, bq257xx_of_match); diff --git a/include/linux/mfd/bq257xx.h b/include/linux/mfd/bq257xx.h index 1d6ddc7fb09fcb..9b1752b0a18b13 100644 --- a/include/linux/mfd/bq257xx.h +++ b/include/linux/mfd/bq257xx.h @@ -98,7 +98,421 @@ #define BQ25703_EN_OTG_MASK BIT(12) +#define BQ25792_REG00_MIN_SYS_VOLTAGE 0x00 +#define BQ25792_REG01_CHARGE_VOLTAGE_LIMIT 0x01 +#define BQ25792_REG03_CHARGE_CURRENT_LIMIT 0x03 +#define BQ25792_REG05_INPUT_VOLTAGE_LIMIT 0x05 +#define BQ25792_REG06_INPUT_CURRENT_LIMIT 0x06 +#define BQ25792_REG08_PRECHARGE_CONTROL 0x08 +#define BQ25792_REG09_TERMINATION_CONTROL 0x09 +#define BQ25792_REG0A_RECHARGE_CONTROL 0x0a +#define BQ25792_REG0B_VOTG_REGULATION 0x0b +#define BQ25792_REG0D_IOTG_REGULATION 0x0d +#define BQ25792_REG0E_TIMER_CONTROL 0x0e +#define BQ25792_REG0F_CHARGER_CONTROL_0 0x0f +#define BQ25792_REG10_CHARGER_CONTROL_1 0x10 +#define BQ25792_REG11_CHARGER_CONTROL_2 0x11 +#define BQ25792_REG12_CHARGER_CONTROL_3 0x12 +#define BQ25792_REG13_CHARGER_CONTROL_4 0x13 +#define BQ25792_REG14_CHARGER_CONTROL_5 0x14 +/* REG15 reserved */ +#define BQ25792_REG16_TEMPERATURE_CONTROL 0x16 +#define BQ25792_REG17_NTC_CONTROL_0 0x17 +#define BQ25792_REG18_NTC_CONTROL_1 0x18 +#define BQ25792_REG19_ICO_CURRENT_LIMIT 0x19 +#define BQ25792_REG1B_CHARGER_STATUS_0 0x1b +#define BQ25792_REG1C_CHARGER_STATUS_1 0x1c +#define BQ25792_REG1D_CHARGER_STATUS_2 0x1d +#define BQ25792_REG1E_CHARGER_STATUS_3 0x1e +#define BQ25792_REG1F_CHARGER_STATUS_4 0x1f +#define BQ25792_REG20_FAULT_STATUS_0 0x20 +#define BQ25792_REG21_FAULT_STATUS_1 0x21 +#define BQ25792_REG22_CHARGER_FLAG_0 0x22 +#define BQ25792_REG23_CHARGER_FLAG_1 0x23 +#define BQ25792_REG24_CHARGER_FLAG_2 0x24 +#define BQ25792_REG25_CHARGER_FLAG_3 0x25 +#define BQ25792_REG26_FAULT_FLAG_0 0x26 +#define BQ25792_REG27_FAULT_FLAG_1 0x27 +#define BQ25792_REG28_CHARGER_MASK_0 0x28 +#define BQ25792_REG29_CHARGER_MASK_1 0x29 +#define BQ25792_REG2A_CHARGER_MASK_2 0x2a +#define BQ25792_REG2B_CHARGER_MASK_3 0x2b +#define BQ25792_REG2C_FAULT_MASK_0 0x2c +#define BQ25792_REG2D_FAULT_MASK_1 0x2d +#define BQ25792_REG2E_ADC_CONTROL 0x2e +#define BQ25792_REG2F_ADC_FUNCTION_DISABLE_0 0x2f +#define BQ25792_REG30_ADC_FUNCTION_DISABLE_1 0x30 +#define BQ25792_REG31_IBUS_ADC 0x31 +#define BQ25792_REG33_IBAT_ADC 0x33 +#define BQ25792_REG35_VBUS_ADC 0x35 +#define BQ25792_REG37_VAC1_ADC 0x37 +#define BQ25792_REG39_VAC2_ADC 0x39 +#define BQ25792_REG3B_VBAT_ADC 0x3b +#define BQ25792_REG3D_VSYS_ADC 0x3d +#define BQ25792_REG3F_TS_ADC 0x3f +#define BQ25792_REG41_TDIE_ADC 0x41 +#define BQ25792_REG43_DP_ADC 0x43 +#define BQ25792_REG45_DM_ADC 0x45 +#define BQ25792_REG47_DPDM_DRIVER 0x47 +#define BQ25792_REG48_PART_INFORMATION 0x48 + +/* Minimal System Voltage */ +#define BQ25792_REG00_VSYSMIN_MASK GENMASK(5, 0) + +#define BQ25792_MINVSYS_MIN_UV 2500000 +#define BQ25792_MINVSYS_STEP_UV 250000 +#define BQ25792_MINVSYS_MAX_UV 16000000 + +/* Charge Voltage Limit */ +#define BQ25792_REG01_VREG_MASK GENMASK(10, 0) + +#define BQ25792_VBATREG_MIN_UV 3000000 +#define BQ25792_VBATREG_STEP_UV 10000 +#define BQ25792_VBATREG_MAX_UV 18800000 + +/* Charge Current Limit */ +#define BQ25792_REG03_ICHG_MASK GENMASK(8, 0) + +#define BQ25792_ICHG_MIN_UA 50000 +#define BQ25792_ICHG_STEP_UA 10000 +#define BQ25792_ICHG_MAX_UA 5000000 + +/* Input Voltage Limit */ +#define BQ25792_REG05_VINDPM_MASK GENMASK(7, 0) + +/* Input Current Limit */ +#define BQ25792_REG06_IINDPM_MASK GENMASK(8, 0) +#define BQ25792_IINDPM_DEFAULT_UA 3000000 +#define BQ25792_IINDPM_STEP_UA 10000 +#define BQ25792_IINDPM_MIN_UA 100000 +#define BQ25792_IINDPM_MAX_UA 3300000 + +/* Precharge Control */ +#define BQ25792_REG08_VBAT_LOWV_MASK GENMASK(7, 6) +#define BQ25792_REG08_IPRECHG_MASK GENMASK(5, 0) + +/* Termination Control */ +#define BQ25792_REG09_REG_RST BIT(6) +#define BQ25792_REG09_ITERM_MASK GENMASK(4, 0) + +/* Re-charge Control */ +#define BQ25792_REG0A_CELL_MASK GENMASK(7, 6) +#define BQ25792_REG0A_TRECHG_MASK GENMASK(5, 4) +#define BQ25792_REG0A_VRECHG_MASK GENMASK(3, 0) + +/* VOTG regulation */ +#define BQ25792_REG0B_VOTG_MASK GENMASK(10, 0) + +#define BQ25792_OTG_VOLT_MIN_UV 2800000 +#define BQ25792_OTG_VOLT_STEP_UV 10000 +#define BQ25792_OTG_VOLT_MAX_UV 22000000 +#define BQ25792_OTG_VOLT_NUM_VOLT ((BQ25792_OTG_VOLT_MAX_UV \ + - BQ25792_OTG_VOLT_MIN_UV) \ + / BQ25792_OTG_VOLT_STEP_UV + 1) + +/* IOTG regulation */ +#define BQ25792_REG0D_PRECHG_TMR BIT(7) +#define BQ25792_REG0D_IOTG_MASK GENMASK(6, 0) + +#define BQ25792_OTG_CUR_MIN_UA 120000 +#define BQ25792_OTG_CUR_STEP_UA 40000 +#define BQ25792_OTG_CUR_MAX_UA 3320000 + +/* Timer Control */ +#define BQ25792_REG0E_TOPOFF_TMR_MASK GENMASK(7, 6) +#define BQ25792_REG0E_EN_TRICHG_TMR BIT(5) +#define BQ25792_REG0E_EN_PRECHG_TMR BIT(4) +#define BQ25792_REG0E_EN_CHG_TMR BIT(3) +#define BQ25792_REG0E_CHG_TMR_MASK GENMASK(2, 1) +#define BQ25792_REG0E_TMR2X_EN BIT(0) + +/* Charger Control 0 */ +#define BQ25792_REG0F_EN_AUTO_IBATDIS BIT(7) +#define BQ25792_REG0F_FORCE_IBATDIS BIT(6) +#define BQ25792_REG0F_EN_CHG BIT(5) +#define BQ25792_REG0F_EN_ICO BIT(4) +#define BQ25792_REG0F_FORCE_ICO BIT(3) +#define BQ25792_REG0F_EN_HIZ BIT(2) +#define BQ25792_REG0F_EN_TERM BIT(1) +/* bit0 reserved */ + +/* Charger Control 1 */ +#define BQ25792_REG10_VAC_OVP_MASK GENMASK(5, 4) +#define BQ25792_REG10_WD_RST BIT(3) +#define BQ25792_REG10_WATCHDOG_MASK GENMASK(2, 0) + +/* Charger Control 2 */ +#define BQ25792_REG11_FORCE_INDET BIT(7) +#define BQ25792_REG11_AUTO_INDET_EN BIT(6) +#define BQ25792_REG11_EN_12V BIT(5) +#define BQ25792_REG11_EN_9V BIT(4) +#define BQ25792_REG11_HVDCP_EN BIT(3) +#define BQ25792_REG11_SDRV_CTRL_MASK GENMASK(2, 1) +#define BQ25792_REG11_SDRV_DLY BIT(0) + +/* Charger Control 3 */ +#define BQ25792_REG12_DIS_ACDRV BIT(7) +#define BQ25792_REG12_EN_OTG BIT(6) +#define BQ25792_REG12_PFM_OTG_DIS BIT(5) +#define BQ25792_REG12_PFM_FWD_DIS BIT(4) +#define BQ25792_REG12_WKUP_DLY BIT(3) +#define BQ25792_REG12_DIS_LDO BIT(2) +#define BQ25792_REG12_DIS_OTG_OOA BIT(1) +#define BQ25792_REG12_DIS_FWD_OOA BIT(0) + +/* Charger Control 4 */ +#define BQ25792_REG13_EN_ACDRV2 BIT(7) +#define BQ25792_REG13_EN_ACDRV1 BIT(6) +#define BQ25792_REG13_PWM_FREQ BIT(5) +#define BQ25792_REG13_DIS_STAT BIT(4) +#define BQ25792_REG13_DIS_VSYS_SHORT BIT(3) +#define BQ25792_REG13_DIS_VOTG_UVP BIT(2) +#define BQ25792_REG13_FORCE_VINDPM_DET BIT(1) +#define BQ25792_REG13_EN_IBUS_OCP BIT(0) + +/* Charger Control 5 */ +#define BQ25792_REG14_SFET_PRESENT BIT(7) +/* bit6 reserved */ +#define BQ25792_REG14_EN_IBAT BIT(5) +#define BQ25792_REG14_IBAT_REG_MASK GENMASK(4, 3) +#define BQ25792_REG14_EN_IINDPM BIT(2) +#define BQ25792_REG14_EN_EXTILIM BIT(1) +#define BQ25792_REG14_EN_BATOC BIT(0) + +#define BQ25792_IBAT_3A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 0) +#define BQ25792_IBAT_4A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 1) +#define BQ25792_IBAT_5A FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 2) +#define BQ25792_IBAT_UNLIM FIELD_PREP(BQ25792_REG14_IBAT_REG_MASK, 3) + +/* Temperature Control */ +#define BQ25792_REG16_TREG_MASK GENMASK(7, 6) +#define BQ25792_REG16_TSHUT_MASK GENMASK(5, 4) +#define BQ25792_REG16_VBUS_PD_EN BIT(3) +#define BQ25792_REG16_VAC1_PD_EN BIT(2) +#define BQ25792_REG16_VAC2_PD_EN BIT(1) + +/* NTC Control 0 */ +#define BQ25792_REG17_JEITA_VSET_MASK GENMASK(7, 5) +#define BQ25792_REG17_JEITA_ISETH_MASK GENMASK(4, 3) +#define BQ25792_REG17_JEITA_ISETC_MASK GENMASK(2, 1) + +/* NTC Control 1 */ +#define BQ25792_REG18_TS_COOL_MASK GENMASK(7, 6) +#define BQ25792_REG18_TS_WARM_MASK GENMASK(5, 4) +#define BQ25792_REG18_BHOT_MASK GENMASK(3, 2) +#define BQ25792_REG18_BCOLD BIT(1) +#define BQ25792_REG18_TS_IGNORE BIT(0) + +/* ICO Current Limit */ +#define BQ25792_REG19_ICO_ILIM_MASK GENMASK(8, 0) + +/* Charger Status 0 */ +#define BQ25792_REG1B_IINDPM_STAT BIT(7) +#define BQ25792_REG1B_VINDPM_STAT BIT(6) +#define BQ25792_REG1B_WD_STAT BIT(5) +#define BQ25792_REG1B_POORSRC_STAT BIT(4) +#define BQ25792_REG1B_PG_STAT BIT(3) +#define BQ25792_REG1B_AC2_PRESENT_STAT BIT(2) +#define BQ25792_REG1B_AC1_PRESENT_STAT BIT(1) +#define BQ25792_REG1B_VBUS_PRESENT_STAT BIT(0) + +/* Charger Status 1 */ +#define BQ25792_REG1C_CHG_STAT_MASK GENMASK(7, 5) +#define BQ25792_REG1C_VBUS_STAT_MASK GENMASK(4, 1) +#define BQ25792_REG1C_BC12_DONE_STAT BIT(0) + +/* Charger Status 2 */ +#define BQ25792_REG1D_ICO_STAT_MASK GENMASK(7, 6) +#define BQ25792_REG1D_TREG_STAT BIT(2) +#define BQ25792_REG1D_DPDM_STAT BIT(1) +#define BQ25792_REG1D_VBAT_PRESENT_STAT BIT(0) + +/* Charger Status 3 */ +#define BQ25792_REG1E_ACRB2_STAT BIT(7) +#define BQ25792_REG1E_ACRB1_STAT BIT(6) +#define BQ25792_REG1E_ADC_DONE_STAT BIT(5) +#define BQ25792_REG1E_VSYS_STAT BIT(4) +#define BQ25792_REG1E_CHG_TMR_STAT BIT(3) +#define BQ25792_REG1E_TRICHG_TMR_STAT BIT(2) +#define BQ25792_REG1E_PRECHG_TMR_STAT BIT(1) + +/* Charger Status 4 */ +#define BQ25792_REG1F_VBATOTG_LOW_STAT BIT(4) +#define BQ25792_REG1F_TS_COLD_STAT BIT(3) +#define BQ25792_REG1F_TS_COOL_STAT BIT(2) +#define BQ25792_REG1F_TS_WARM_STAT BIT(1) +#define BQ25792_REG1F_TS_HOT_STAT BIT(0) + +/* FAULT Status 0 */ +#define BQ25792_REG20_IBAT_REG_STAT BIT(7) +#define BQ25792_REG20_VBUS_OVP_STAT BIT(6) +#define BQ25792_REG20_VBAT_OVP_STAT BIT(5) +#define BQ25792_REG20_IBUS_OCP_STAT BIT(4) +#define BQ25792_REG20_IBAT_OCP_STAT BIT(3) +#define BQ25792_REG20_CONV_OCP_STAT BIT(2) +#define BQ25792_REG20_VAC2_OVP_STAT BIT(1) +#define BQ25792_REG20_VAC1_OVP_STAT BIT(0) + +#define BQ25792_REG20_OVERVOLTAGE_MASK (BQ25792_REG20_VBUS_OVP_STAT | \ + BQ25792_REG20_VBAT_OVP_STAT | \ + BQ25792_REG20_VAC2_OVP_STAT | \ + BQ25792_REG20_VAC1_OVP_STAT) +#define BQ25792_REG20_OVERCURRENT_MASK (BQ25792_REG20_IBUS_OCP_STAT | \ + BQ25792_REG20_IBAT_OCP_STAT | \ + BQ25792_REG20_CONV_OCP_STAT) + +/* FAULT Status 1 */ +#define BQ25792_REG21_VSYS_SHORT_STAT BIT(7) +#define BQ25792_REG21_VSYS_OVP_STAT BIT(6) +#define BQ25792_REG21_OTG_OVP_STAT BIT(5) +#define BQ25792_REG21_OTG_UVP_STAT BIT(4) +#define BQ25792_REG21_TSHUT_STAT BIT(2) + + +/* Charger Flag 0 */ +#define BQ25792_REG22_IINDPM_FLAG BIT(7) +#define BQ25792_REG22_VINDPM_FLAG BIT(6) +#define BQ25792_REG22_WD_FLAG BIT(5) +#define BQ25792_REG22_POORSRC_FLAG BIT(4) +#define BQ25792_REG22_PG_FLAG BIT(3) +#define BQ25792_REG22_AC2_PRESENT_FLAG BIT(2) +#define BQ25792_REG22_AC1_PRESENT_FLAG BIT(1) +#define BQ25792_REG22_VBUS_PRESENT_FLAG BIT(0) + +/* Charger Flag 1 */ +#define BQ25792_REG23_CHG_FLAG BIT(7) +#define BQ25792_REG23_ICO_FLAG BIT(6) +#define BQ25792_REG23_VBUS_FLAG BIT(4) +#define BQ25792_REG23_TREG_FLAG BIT(2) +#define BQ25792_REG23_VBAT_PRESENT_FLAG BIT(1) +#define BQ25792_REG23_BC12_DONE_FLAG BIT(0) + +/* Charger Flag 2 */ +#define BQ25792_REG24_DPDM_DONE_FLAG BIT(6) +#define BQ25792_REG24_ADC_DONE_FLAG BIT(5) +#define BQ25792_REG24_VSYS_FLAG BIT(4) +#define BQ25792_REG24_CHG_TMR_FLAG BIT(3) +#define BQ25792_REG24_TRICHG_TMR_FLAG BIT(2) +#define BQ25792_REG24_PRECHG_TMR_FLAG BIT(1) +#define BQ25792_REG24_TOPOFF_TMR_FLAG BIT(0) + +/* Charger Flag 3 */ +#define BQ25792_REG25_VBATOTG_LOW_FLAG BIT(4) +#define BQ25792_REG25_TS_COLD_FLAG BIT(3) +#define BQ25792_REG25_TS_COOL_FLAG BIT(2) +#define BQ25792_REG25_TS_WARM_FLAG BIT(1) +#define BQ25792_REG25_TS_HOT_FLAG BIT(0) + +/* FAULT Flag 0 */ +#define BQ25792_REG26_IBAT_REG_FLAG BIT(7) +#define BQ25792_REG26_VBUS_OVP_FLAG BIT(6) +#define BQ25792_REG26_VBAT_OVP_FLAG BIT(5) +#define BQ25792_REG26_IBUS_OCP_FLAG BIT(4) +#define BQ25792_REG26_IBAT_OCP_FLAG BIT(3) +#define BQ25792_REG26_CONV_OCP_FLAG BIT(2) +#define BQ25792_REG26_VAC2_OVP_FLAG BIT(1) +#define BQ25792_REG26_VAC1_OVP_FLAG BIT(0) + +/* FAULT Flag 1 */ +#define BQ25792_REG27_VSYS_SHORT_FLAG BIT(7) +#define BQ25792_REG27_VSYS_OVP_FLAG BIT(6) +#define BQ25792_REG27_OTG_OVP_FLAG BIT(5) +#define BQ25792_REG27_OTG_UVP_FLAG BIT(4) +#define BQ25792_REG27_TSHUT_FLAG BIT(2) + +/* Charger Mask 0 */ +#define BQ25792_REG28_IINDPM_MASK BIT(7) +#define BQ25792_REG28_VINDPM_MASK BIT(6) +#define BQ25792_REG28_WD_MASK BIT(5) +#define BQ25792_REG28_POORSRC_MASK BIT(4) +#define BQ25792_REG28_PG_MASK BIT(3) +#define BQ25792_REG28_AC2_PRESENT_MASK BIT(2) +#define BQ25792_REG28_AC1_PRESENT_MASK BIT(1) +#define BQ25792_REG28_VBUS_PRESENT_MASK BIT(0) + +/* Charger Mask 1 */ +#define BQ25792_REG29_CHG_MASK BIT(7) +#define BQ25792_REG29_ICO_MASK BIT(6) +#define BQ25792_REG29_VBUS_MASK BIT(4) +#define BQ25792_REG29_TREG_MASK BIT(2) +#define BQ25792_REG29_VBAT_PRESENT_MASK BIT(1) +#define BQ25792_REG29_BC12_DONE_MASK BIT(0) + +/* Charger Mask 2 */ +#define BQ25792_REG2A_DPDM_DONE_MASK BIT(6) +#define BQ25792_REG2A_ADC_DONE_MASK BIT(5) +#define BQ25792_REG2A_VSYS_MASK BIT(4) +#define BQ25792_REG2A_CHG_TMR_MASK BIT(3) +#define BQ25792_REG2A_TRICHG_TMR_MASK BIT(2) +#define BQ25792_REG2A_PRECHG_TMR_MASK BIT(1) +#define BQ25792_REG2A_TOPOFF_TMR_MASK BIT(0) + +/* Charger Mask 3 */ +#define BQ25792_REG2B_VBATOTG_LOW_MASK BIT(4) +#define BQ25792_REG2B_TS_COLD_MASK BIT(3) +#define BQ25792_REG2B_TS_COOL_MASK BIT(2) +#define BQ25792_REG2B_TS_WARM_MASK BIT(1) +#define BQ25792_REG2B_TS_HOT_MASK BIT(0) + +/* FAULT Mask 0 */ +#define BQ25792_REG2C_IBAT_REG_MASK BIT(7) +#define BQ25792_REG2C_VBUS_OVP_MASK BIT(6) +#define BQ25792_REG2C_VBAT_OVP_MASK BIT(5) +#define BQ25792_REG2C_IBUS_OCP_MASK BIT(4) +#define BQ25792_REG2C_IBAT_OCP_MASK BIT(3) +#define BQ25792_REG2C_CONV_OCP_MASK BIT(2) +#define BQ25792_REG2C_VAC2_OVP_MASK BIT(1) +#define BQ25792_REG2C_VAC1_OVP_MASK BIT(0) + +/* FAULT Mask 1 */ +#define BQ25792_REG2D_VSYS_SHORT_MASK BIT(7) +#define BQ25792_REG2D_VSYS_OVP_MASK BIT(6) +#define BQ25792_REG2D_OTG_OVP_MASK BIT(5) +#define BQ25792_REG2D_OTG_UVP_MASK BIT(4) +#define BQ25792_REG2D_TSHUT_MASK BIT(2) + +/* ADC Control */ +#define BQ25792_REG2E_ADC_EN BIT(7) +#define BQ25792_REG2E_ADC_RATE BIT(6) +#define BQ25792_REG2E_ADC_SAMPLE_MASK GENMASK(5, 4) +#define BQ25792_REG2E_ADC_AVG BIT(3) +#define BQ25792_REG2E_ADC_AVG_INIT BIT(2) + +/* ADC Function Disable 0 */ +#define BQ25792_REG2F_IBUS_ADC_DIS BIT(7) +#define BQ25792_REG2F_IBAT_ADC_DIS BIT(6) +#define BQ25792_REG2F_VBUS_ADC_DIS BIT(5) +#define BQ25792_REG2F_VBAT_ADC_DIS BIT(4) +#define BQ25792_REG2F_VSYS_ADC_DIS BIT(3) +#define BQ25792_REG2F_TS_ADC_DIS BIT(2) +#define BQ25792_REG2F_TDIE_ADC_DIS BIT(1) + +/* ADC Function Disable 1 */ +#define BQ25792_REG30_DP_ADC_DIS BIT(7) +#define BQ25792_REG30_DM_ADC_DIS BIT(6) +#define BQ25792_REG30_VAC2_ADC_DIS BIT(5) +#define BQ25792_REG30_VAC1_ADC_DIS BIT(4) + +/* 0x31-0x45: ADC result registers (16-bit, RO): single full-width field */ + +#define BQ25792_ADCVSYSVBAT_STEP_UV 1000 +#define BQ25792_ADCIBAT_STEP_UA 1000 + +/* DPDM Driver */ +#define BQ25792_REG47_DPLUS_DAC_MASK GENMASK(7, 5) +#define BQ25792_REG47_DMINUS_DAC_MASK GENMASK(4, 2) + +/* Part Information */ +#define BQ25792_REG48_PN_MASK GENMASK(5, 3) +#define BQ25792_REG48_DEV_REV_MASK GENMASK(2, 0) + +enum bq257xx_type { + BQ25703A = 1, + BQ25792, +}; + struct bq257xx_device { struct i2c_client *client; struct regmap *regmap; + enum bq257xx_type type; }; From c3e24877921ca7590345f0a68422fa1dbdb53e86 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 15:15:16 +0400 Subject: [PATCH 180/208] regulator: bq257xx: Add support for BQ25792 Add support for TI BQ25792, an integrated battery charger and buck/boost regulator. This enables VBUS output from the charger's boost converter for use in USB OTG applications, supporting 2.8-22V output at up to 3.32A with 10mV and 40mA resolution. Acked-by: Mark Brown Tested-by: Chris Morgan Signed-off-by: Alexey Charkov --- drivers/regulator/bq257xx-regulator.c | 98 ++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/bq257xx-regulator.c b/drivers/regulator/bq257xx-regulator.c index 913ca9186bf1e1..fec75b23cd40ff 100644 --- a/drivers/regulator/bq257xx-regulator.c +++ b/drivers/regulator/bq257xx-regulator.c @@ -31,6 +31,32 @@ static int bq25703_vbus_get_cur_limit(struct regulator_dev *rdev) return FIELD_GET(BQ25703_OTG_CUR_MASK, reg) * BQ25703_OTG_CUR_STEP_UA; } +static int bq25792_vbus_get_cur_limit(struct regulator_dev *rdev) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + int ret; + unsigned int reg; + + ret = regmap_read(regmap, BQ25792_REG0D_IOTG_REGULATION, ®); + if (ret) + return ret; + return FIELD_GET(BQ25792_REG0D_IOTG_MASK, reg) * BQ25792_OTG_CUR_STEP_UA; +} + +static int bq25792_vbus_get_voltage_sel(struct regulator_dev *rdev) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + __be16 reg; + int ret; + + ret = regmap_raw_read(regmap, BQ25792_REG0B_VOTG_REGULATION, + ®, sizeof(reg)); + if (ret) + return ret; + + return FIELD_GET(BQ25792_REG0B_VOTG_MASK, be16_to_cpu(reg)); +} + /* * Check if the minimum current and maximum current requested are * sane values, then set the register accordingly. @@ -54,6 +80,37 @@ static int bq25703_vbus_set_cur_limit(struct regulator_dev *rdev, FIELD_PREP(BQ25703_OTG_CUR_MASK, reg)); } +static int bq25792_vbus_set_cur_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int reg; + + if ((min_uA > BQ25792_OTG_CUR_MAX_UA) || + (max_uA < BQ25792_OTG_CUR_MIN_UA)) + return -EINVAL; + + reg = (max_uA / BQ25792_OTG_CUR_STEP_UA); + + /* Catch rounding errors since our step is 40000uA. */ + if ((reg * BQ25792_OTG_CUR_STEP_UA) < min_uA) + return -EINVAL; + + return regmap_write(regmap, BQ25792_REG0D_IOTG_REGULATION, + FIELD_PREP(BQ25792_REG0D_IOTG_MASK, reg)); +} + +static int bq25792_vbus_set_voltage_sel(struct regulator_dev *rdev, + unsigned int sel) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + __be16 reg; + + reg = cpu_to_be16(FIELD_PREP(BQ25792_REG0B_VOTG_MASK, sel)); + return regmap_raw_write(regmap, BQ25792_REG0B_VOTG_REGULATION, + ®, sizeof(reg)); +} + static int bq25703_vbus_enable(struct regulator_dev *rdev) { struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev); @@ -101,6 +158,34 @@ static const struct regulator_desc bq25703_vbus_desc = { .vsel_mask = BQ25703_OTG_VOLT_MASK, }; +static const struct regulator_ops bq25792_vbus_ops = { + /* No GPIO for enabling the OTG regulator */ + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = bq25792_vbus_get_voltage_sel, + .set_voltage_sel = bq25792_vbus_set_voltage_sel, + .get_current_limit = bq25792_vbus_get_cur_limit, + .set_current_limit = bq25792_vbus_set_cur_limit, +}; + +static const struct regulator_desc bq25792_vbus_desc = { + .name = "vbus", + .of_match = of_match_ptr("vbus"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &bq25792_vbus_ops, + .min_uV = BQ25792_OTG_VOLT_MIN_UV, + .uV_step = BQ25792_OTG_VOLT_STEP_UV, + .n_voltages = BQ25792_OTG_VOLT_NUM_VOLT, + .enable_mask = BQ25792_REG12_EN_OTG, + .enable_reg = BQ25792_REG12_CHARGER_CONTROL_3, + .enable_val = BQ25792_REG12_EN_OTG, + .disable_val = 0, +}; + /* Get optional GPIO for OTG regulator enable. */ static void bq257xx_reg_dt_parse_gpio(struct platform_device *pdev) { @@ -141,6 +226,7 @@ static void bq257xx_reg_dt_parse_gpio(struct platform_device *pdev) static int bq257xx_regulator_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent); struct bq257xx_reg_data *pdata; struct device_node *np = dev->of_node; struct regulator_config cfg = {}; @@ -152,7 +238,17 @@ static int bq257xx_regulator_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; - pdata->desc = bq25703_vbus_desc; + switch (bq->type) { + case BQ25703A: + pdata->desc = bq25703_vbus_desc; + break; + case BQ25792: + pdata->desc = bq25792_vbus_desc; + break; + default: + return dev_err_probe(&pdev->dev, -EINVAL, + "Unsupported device type\n"); + } platform_set_drvdata(pdev, pdata); bq257xx_reg_dt_parse_gpio(pdev); From b0128dac38289624f5883757cb4edb78164724e4 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Feb 2026 15:46:02 +0400 Subject: [PATCH 181/208] power: supply: bq257xx: Add support for BQ25792 Add support for TI BQ25792 integrated battery charger and buck-boost converter. It shares high-level logic of operation with the already supported BQ25703A, but has a different register map, bit definitions and some of the lower-level hardware states. Tested-by: Chris Morgan Signed-off-by: Alexey Charkov --- drivers/power/supply/bq257xx_charger.c | 528 ++++++++++++++++++++++++- include/linux/mfd/bq257xx.h | 20 +- 2 files changed, 543 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 9c082865e745bb..7d02169248b17f 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -88,6 +89,53 @@ struct bq257xx_chg { u32 vsys_min; }; +/** + * bq25792_read16() - Read a 16-bit value from device register + * @pdata: driver platform data + * @reg: register address to read from + * @val: pointer to store the register value + * + * Read a 16-bit big-endian value from the BQ25792 device via regmap + * and convert to CPU byte order. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val) +{ + __be16 regval; + int ret; + + ret = regmap_raw_read(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be16_to_cpu(regval); + return 0; +} + +/** + * bq25792_write16() - Write a 16-bit value to device register + * @pdata: driver platform data + * @reg: register address to write to + * @val: 16-bit value to write in CPU byte order + * + * Convert the value to big-endian and write a 16-bit value to the + * BQ25792 device via regmap. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val) +{ + __be16 regval = cpu_to_be16(val); + int ret; + + ret = regmap_raw_write(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + return 0; +} + /** * bq25703_get_state() - Get the current state of the device * @pdata: driver platform data @@ -119,6 +167,43 @@ static int bq25703_get_state(struct bq257xx_chg *pdata) return 0; } +/** + * bq25792_get_state() - Get the current state of the device + * @pdata: driver platform data + * + * Get the current state of the BQ25792 charger by reading status + * registers. Updates the online, charging, overvoltage, and fault + * status fields in the driver data structure. + * + * Return: Returns 0 on success or error on failure to read device. + */ +static int bq25792_get_state(struct bq257xx_chg *pdata) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, ®); + if (ret) + return ret; + + pdata->online = reg & BQ25792_REG1B_PG_STAT; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, ®); + if (ret) + return ret; + + pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, ®); + if (ret) + return ret; + + pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK; + pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK; + + return 0; +} + /** * bq25703_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data @@ -142,6 +227,31 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_min_vsys() - Get the minimum system voltage + * @pdata: driver platform data + * @intval: pointer to store the minimum voltage value + * + * Read the current minimum system voltage setting from the device + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg); + *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_MINVSYS_MIN_UV; + + return ret; +} + /** * bq25703_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data @@ -166,6 +276,29 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) reg); } +/** + * bq25792_set_min_vsys() - Set the minimum system voltage + * @pdata: driver platform data + * @vsys: voltage value to set in uV + * + * Set the minimum system voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys) +{ + unsigned int reg; + int vsys_min = pdata->vsys_min; + + vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV); + reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg); + + return regmap_write(pdata->bq->regmap, + BQ25792_REG00_MIN_SYS_VOLTAGE, reg); +} + /** * bq25703_get_cur() - Get the reported current from the battery * @pdata: driver platform data @@ -195,6 +328,30 @@ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_cur() - Get the reported current from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery current value + * + * Read the current ADC value from the device representing the battery + * charge or discharge current and return it in microamps. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, ®); + if (ret < 0) + return ret; + + *intval = (s16)reg * BQ25792_ADCIBAT_STEP_UA; + + return ret; +} + /** * bq25703_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data @@ -218,6 +375,30 @@ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_ichg_cur() - Get the maximum reported charge current + * @pdata: driver platform data + * @intval: pointer to store the maximum charge current value + * + * Read the programmed maximum charge current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) * + BQ25792_ICHG_STEP_UA; + + return ret; +} + /** * bq25703_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data @@ -242,6 +423,28 @@ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) reg); } +/** + * bq25792_set_ichg_cur() - Set the maximum charge current + * @pdata: driver platform data + * @ichg: current value to set in uA + * + * Set the maximum charge current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) +{ + int ichg_max = pdata->ichg_max; + u16 reg; + + ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max); + reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK, + (ichg / BQ25792_ICHG_STEP_UA)); + + return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg); +} + /** * bq25703_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data @@ -265,6 +468,30 @@ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_chrg_volt() - Get the maximum set charge voltage + * @pdata: driver platform data + * @intval: pointer to store the maximum charge voltage value + * + * Read the current charge voltage limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) * + BQ25792_VBATREG_STEP_UV; + + return ret; +} + /** * bq25703_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data @@ -291,6 +518,29 @@ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) reg); } +/** + * bq25792_set_chrg_volt() - Set the maximum charge voltage + * @pdata: driver platform data + * @vbat: voltage value to set in uV + * + * Set the maximum charge voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) +{ + int vbat_max = pdata->vbat_max; + u16 reg; + + vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max); + + reg = FIELD_PREP(BQ25792_REG01_VREG_MASK, + (vbat / BQ25792_VBATREG_STEP_UV)); + + return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg); +} + /** * bq25703_get_iindpm() - Get the maximum set input current * @pdata: driver platform data @@ -319,6 +569,30 @@ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_iindpm() - Get the maximum set input current + * @pdata: driver platform data + * @intval: pointer to store the maximum input current value + * + * Read the current input current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg); + *intval = reg * BQ25792_IINDPM_STEP_UA; + + return ret; +} + /** * bq25703_set_iindpm() - Set the maximum input current * @pdata: driver platform data @@ -344,6 +618,29 @@ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) FIELD_PREP(BQ25703_IINDPM_MASK, reg)); } +/** + * bq25792_set_iindpm() - Set the maximum input current + * @pdata: driver platform data + * @iindpm: current value in uA + * + * Set the maximum input current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm) +{ + u16 reg; + int iindpm_max = pdata->iindpm_max; + + iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max); + + reg = iindpm / BQ25792_IINDPM_STEP_UA; + + return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, + FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg)); +} + /** * bq25703_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data @@ -368,6 +665,30 @@ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_vbat() - Get the reported voltage from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery voltage value + * + * Read the current ADC value representing the battery voltage + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, ®); + if (ret) + return ret; + + *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV; + + return ret; +} + /** * bq25703_hw_init() - Set all the required registers to init the charger * @pdata: driver platform data @@ -434,6 +755,108 @@ static int bq25703_hw_init(struct bq257xx_chg *pdata) return ret; } +/** + * bq25792_hw_init() - Initialize BQ25792 hardware + * @pdata: driver platform data + * + * Initialize the BQ25792 by disabling the watchdog, enabling discharge + * current sensing with 5A limit, and configuring input current regulation. + * Set the charge current, charge voltage, minimum system voltage, and + * input current limit from platform data. Enable and configure the ADC + * to measure all available channels. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq25792_hw_init(struct bq257xx_chg *pdata) +{ + struct regmap *regmap = pdata->bq->regmap; + int ret = 0; + u8 reg; + + /* Disable watchdog (TODO: make it work instead) */ + ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0); + if (ret) + return ret; + + /* + * Enable battery discharge current sensing, 5A discharge current + * limit, input current regulation and ship FET functions + */ + ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5, + BQ25792_REG14_SFET_PRESENT | + BQ25792_REG14_EN_IBAT | + BQ25792_IBAT_5A | + BQ25792_REG14_EN_IINDPM); + if (ret) + return ret; + + if (pdata->vbat_max < 5000000) { + /* 1S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S); + } else if (pdata->vbat_max < 10000000) { + /* 2S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S); + } else if (pdata->vbat_max < 14000000) { + /* 3S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S); + } else { + /* 4S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S); + } + + /* Recharge voltage detection deglitch time (default 1024ms) */ + reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS); + + /* Recharge voltage offset: 5% of the set charge voltage */ + reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK, + (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV); + + ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + if (ret) + return ret; + + ret = bq25792_set_min_vsys(pdata, pdata->vsys_min); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); + if (ret) + return ret; + + /* Enable the Input Current Optimizer (the rest is at POR value) */ + ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0, + BQ25792_REG0F_EN_AUTO_IBATDIS | + BQ25792_REG0F_EN_CHG | + BQ25792_REG0F_EN_ICO | + BQ25792_REG0F_EN_TERM); + if (ret) + return ret; + + /* Enable the ADC. */ + ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN); + if (ret) + return ret; + + /* Clear per-channel ADC disable bits - enable all channels */ + ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0); + if (ret) + return ret; + + ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0); + if (ret) + return ret; + + return ret; +} + /** * bq25703_hw_shutdown() - Set registers for shutdown * @pdata: driver platform data @@ -446,6 +869,30 @@ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); } +/** + * bq25792_hw_shutdown() - Shutdown BQ25792 hardware + * @pdata: driver platform data + * + * Perform hardware shutdown for the BQ25792. Currently a no-op + * as the device does not require special shutdown configuration. + */ +static void bq25792_hw_shutdown(struct bq257xx_chg *pdata) +{ + /* Nothing to do here */ +} + +/** + * bq257xx_set_charger_property() - Set a power supply property + * @psy: power supply device + * @prop: power supply property to set + * @val: value to set for the property + * + * Handle requests to set power supply properties such as input current + * limit, constant charge voltage, and constant charge current. Routes + * the request to the chip-specific implementation. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_set_charger_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) @@ -469,6 +916,19 @@ static int bq257xx_set_charger_property(struct power_supply *psy, return -EINVAL; } +/** + * bq257xx_get_charger_property() - Get a power supply property + * @psy: power supply device + * @psp: power supply property to get + * @val: pointer to store the property value + * + * Handle requests to get power supply properties, including status, + * health, manufacturer, online state, and various voltage/current + * measurements. Reads current device state and routes chip-specific + * property requests to appropriate handlers. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_get_charger_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -550,6 +1010,17 @@ static enum power_supply_property bq257xx_power_supply_props[] = { POWER_SUPPLY_PROP_USB_TYPE, }; +/** + * bq257xx_property_is_writeable() - Check if a property is writeable + * @psy: power supply device + * @prop: power supply property to check + * + * Determines which power supply properties can be written to. Only + * charge current limit, charge voltage limit, and input current + * limit are writeable. + * + * Return: Returns 1 if property is writeable, 0 otherwise. + */ static int bq257xx_property_is_writeable(struct power_supply *psy, enum power_supply_property prop) { @@ -622,6 +1093,17 @@ static void bq257xx_external_power_changed(struct power_supply *psy) power_supply_changed(psy); } +/** + * bq257xx_irq_handler_thread() - Handle charger interrupt + * @irq: interrupt number + * @private: pointer to driver private data + * + * Thread handler for charger interrupts. Triggers re-evaluation of + * external power status and updates power supply state in response + * to charger events. + * + * Return: Returns IRQ_HANDLED if interrupt was processed. + */ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) { struct bq257xx_chg *pdata = private; @@ -662,6 +1144,22 @@ static const struct bq257xx_chip_info bq25703_chip_info = { .bq257xx_get_min_vsys = &bq25703_get_min_vsys, }; +static const struct bq257xx_chip_info bq25792_chip_info = { + .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA, + .bq257xx_hw_init = &bq25792_hw_init, + .bq257xx_hw_shutdown = &bq25792_hw_shutdown, + .bq257xx_get_state = &bq25792_get_state, + .bq257xx_get_ichg = &bq25792_get_ichg_cur, + .bq257xx_set_ichg = &bq25792_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25792_get_chrg_volt, + .bq257xx_set_vbatreg = &bq25792_set_chrg_volt, + .bq257xx_get_iindpm = &bq25792_get_iindpm, + .bq257xx_set_iindpm = &bq25792_set_iindpm, + .bq257xx_get_cur = &bq25792_get_cur, + .bq257xx_get_vbat = &bq25792_get_vbat, + .bq257xx_get_min_vsys = &bq25792_get_min_vsys, +}; + /** * bq257xx_parse_dt() - Parse the device tree for required properties * @pdata: driver platform data @@ -707,6 +1205,17 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, return 0; } +/** + * bq257xx_charger_probe() - Probe routine for charger platform device + * @pdev: platform device + * + * Probe the charger device, allocate driver data structure, select the + * appropriate chip-specific function pointers, register the power supply, + * parse device tree properties for battery limits, initialize hardware, + * and set up the interrupt handler if available. + * + * Return: Returns 0 on success or error code on failure. + */ static int bq257xx_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -722,7 +1231,17 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return -ENOMEM; pdata->bq = bq; - pdata->chip = &bq25703_chip_info; + + switch (bq->type) { + case BQ25703A: + pdata->chip = &bq25703_chip_info; + break; + case BQ25792: + pdata->chip = &bq25792_chip_info; + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown chip type\n"); + } platform_set_drvdata(pdev, pdata); @@ -760,6 +1279,13 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return ret; } +/** + * bq257xx_charger_shutdown() - Shutdown routine for charger platform device + * @pdev: platform device + * + * Called during system shutdown to perform charger cleanup, including + * disabling watchdog timers or other chip-specific shutdown procedures. + */ static void bq257xx_charger_shutdown(struct platform_device *pdev) { struct bq257xx_chg *pdata = platform_get_drvdata(pdev); diff --git a/include/linux/mfd/bq257xx.h b/include/linux/mfd/bq257xx.h index 9b1752b0a18b13..379ef4ee8291b5 100644 --- a/include/linux/mfd/bq257xx.h +++ b/include/linux/mfd/bq257xx.h @@ -200,6 +200,20 @@ #define BQ25792_REG0A_TRECHG_MASK GENMASK(5, 4) #define BQ25792_REG0A_VRECHG_MASK GENMASK(3, 0) +#define BQ25792_CELL_1S 0 +#define BQ25792_CELL_2S 1 +#define BQ25792_CELL_3S 2 +#define BQ25792_CELL_4S 3 + +#define BQ25792_TRECHG_64MS 0 +#define BQ25792_TRECHG_256MS 1 +#define BQ25792_TRECHG_1024MS 2 +#define BQ25792_TRECHG_2048MS 3 + +#define BQ25792_VRECHG_MIN_UV 50000 +#define BQ25792_VRECHG_STEP_UV 50000 +#define BQ25792_VRECHG_MAX_UV 800000 + /* VOTG regulation */ #define BQ25792_REG0B_VOTG_MASK GENMASK(10, 0) @@ -353,12 +367,10 @@ #define BQ25792_REG20_VAC2_OVP_STAT BIT(1) #define BQ25792_REG20_VAC1_OVP_STAT BIT(0) -#define BQ25792_REG20_OVERVOLTAGE_MASK (BQ25792_REG20_VBUS_OVP_STAT | \ - BQ25792_REG20_VBAT_OVP_STAT | \ +#define BQ25792_REG20_OVERVOLTAGE_MASK (BQ25792_REG20_VBAT_OVP_STAT | \ BQ25792_REG20_VAC2_OVP_STAT | \ BQ25792_REG20_VAC1_OVP_STAT) -#define BQ25792_REG20_OVERCURRENT_MASK (BQ25792_REG20_IBUS_OCP_STAT | \ - BQ25792_REG20_IBAT_OCP_STAT | \ +#define BQ25792_REG20_OVERCURRENT_MASK (BQ25792_REG20_IBAT_OCP_STAT | \ BQ25792_REG20_CONV_OCP_STAT) /* FAULT Status 1 */ From 58f2064f77f2cd5301b0acdd385bb792873cf473 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 19 Feb 2026 17:29:09 +0400 Subject: [PATCH 182/208] dt-bindings: hwmon: Add TI INA4230 4-channel I2C power monitor Add TI INA4230, which is a 48V 4-channel 16-bit I2C-based current/voltage/power/energy monitor with alert function. Link: https://www.ti.com/product/INA4230 Reviewed-by: Krzysztof Kozlowski Signed-off-by: Alexey Charkov --- .../devicetree/bindings/hwmon/ti,ina4230.yaml | 134 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 140 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml new file mode 100644 index 00000000000000..f33e52a12657fe --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/ti,ina4230.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments INA4230 quad-channel power monitors + +maintainers: + - Alexey Charkov + +description: | + The INA4230 is a 48V quad-channel 16-bit current, voltage, power and energy + monitor with an I2C interface. + + Datasheet: + https://www.ti.com/product/INA4230 + +properties: + compatible: + enum: + - ti,ina4230 + + reg: + maxItems: 1 + + "#address-cells": + description: Required only if a child node is present. + const: 1 + + "#size-cells": + description: Required only if a child node is present. + const: 0 + + vs-supply: + description: phandle to the regulator that provides the VS supply typically + in range from 1.7 V to 5.5 V. + + ti,alert-polarity-active-high: + description: Alert pin is asserted based on the value of Alert polarity Bit + of the CONFIG2 register. Default value is 0, for which the alert pin + toggles from high to low during faults. When this property is set, the + corresponding register bit is set to 1, and the alert pin toggles from + low to high during faults. + $ref: /schemas/types.yaml#/definitions/flag + +patternProperties: + "^input@[0-3]$": + description: The node contains optional child nodes for four channels. + Each child node describes the information of input source. Input channels + default to enabled in the chip. Unless channels are explicitly disabled + in device-tree, input channels will be enabled. + type: object + additionalProperties: false + properties: + reg: + description: Must be 0, 1, 2 or 3, corresponding to the IN1, IN2, IN3 + or IN4 ports of the INA4230, respectively. + enum: [ 0, 1, 2, 3 ] + + label: + description: name of the input source + + shunt-resistor-micro-ohms: + description: shunt resistor value in micro-Ohm + + ti,maximum-expected-current-microamp: + description: | + This value indicates the maximum current in microamps that you can + expect to measure with ina4230 in your circuit. + + This value will be used to calculate the Current_LSB to maximize the + available precision while ensuring your expected maximum current fits + within the chip's ADC range. It will also enable built-in shunt gain + to increase ADC granularity by a factor of 4 if the provided maximum + current / shunt resistance combination does not produce more than + 20.48 mV drop at the shunt. + minimum: 32768 + maximum: 4294967295 + default: 32768000 + + required: + - reg + +required: + - compatible + - reg + +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + power-sensor@44 { + compatible = "ti,ina4230"; + reg = <0x44>; + vs-supply = <&vdd_3v0>; + ti,alert-polarity-active-high; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0x0>; + /* + * Input channels are enabled by default in the device and so + * to disable, must be explicitly disabled in device-tree. + */ + status = "disabled"; + }; + + input@1 { + reg = <0x1>; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <0x2>; + label = "VDD_5V"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + + input@3 { + reg = <0x3>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1100ed2dccd5e1..8fa7375fc2d38b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12587,6 +12587,12 @@ S: Maintained F: Documentation/hwmon/ina233.rst F: drivers/hwmon/pmbus/ina233.c +INA4230 HWMON DRIVER +M: Alexey Charkov +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml + INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao S: Maintained From 5a3a976754d6ca84985a6d60a5bf7dbe9c083c29 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 20 Feb 2026 16:48:29 +0400 Subject: [PATCH 183/208] hwmon: Add support for TI INA4230 power monitor Add a driver for the TI INA4230, a 4-channel power monitor with I2C interface. The driver supports voltage, current, power and energy measurements, but skips the alert functionality in this initial implementation. Signed-off-by: Alexey Charkov --- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina4230.c | 1032 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 1045 insertions(+) create mode 100644 drivers/hwmon/ina4230.c diff --git a/MAINTAINERS b/MAINTAINERS index 8fa7375fc2d38b..37364291dea1d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12592,6 +12592,7 @@ M: Alexey Charkov L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml +F: drivers/hwmon/ina4230.c INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 14e4cea48acc47..79b4bd232e9c34 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2307,6 +2307,17 @@ config SENSORS_INA3221 This driver can also be built as a module. If so, the module will be called ina3221. +config SENSORS_INA4230 + tristate "Texas Instruments INA4230 Quad Current/Voltage Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA4230 Quad + Current/Voltage Monitor. + + This driver can also be built as a module. If so, the module + will be called ina4230. + config SENSORS_SPD5118 tristate "SPD5118 Compliant Temperature Sensors" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 982ee2c6f9deb6..673ecf989fe09b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_INA238) += ina238.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o +obj-$(CONFIG_SENSORS_INA4230) += ina4230.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_ISL28022) += isl28022.o obj-$(CONFIG_SENSORS_IT87) += it87.o diff --git a/drivers/hwmon/ina4230.c b/drivers/hwmon/ina4230.c new file mode 100644 index 00000000000000..b5233c004089aa --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * INA4230 Quad Current/Voltage Monitor + * + * Based on INA3221 driver by Texas Instruments Incorporated - https://www.ti.com/ + * Adapted for INA4230 by Alexey Charkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INA4230_DRIVER_NAME "ina4230" + +#define INA4230_SHUNT_VOLTAGE_CH1 0x00 +#define INA4230_BUS_VOLTAGE_CH1 0x01 +#define INA4230_CURRENT_CH1 0x02 +#define INA4230_POWER_CH1 0x03 +#define INA4230_ENERGY_CH1 0x04 +#define INA4230_CALIBRATION_CH1 0x05 +#define INA4230_ALERT_LIMIT1 0x06 +#define INA4230_ALERT_CONFIG1 0x07 +#define INA4230_SHUNT_VOLTAGE_CH2 0x08 +#define INA4230_BUS_VOLTAGE_CH2 0x09 +#define INA4230_CURRENT_CH2 0x0A +#define INA4230_POWER_CH2 0x0B +#define INA4230_ENERGY_CH2 0x0C +#define INA4230_CALIBRATION_CH2 0x0D +#define INA4230_ALERT_LIMIT2 0x0E +#define INA4230_ALERT_CONFIG2 0x0F +#define INA4230_SHUNT_VOLTAGE_CH3 0x10 +#define INA4230_BUS_VOLTAGE_CH3 0x11 +#define INA4230_CURRENT_CH3 0x12 +#define INA4230_POWER_CH3 0x13 +#define INA4230_ENERGY_CH3 0x14 +#define INA4230_CALIBRATION_CH3 0x15 +#define INA4230_ALERT_LIMIT3 0x16 +#define INA4230_ALERT_CONFIG3 0x17 +#define INA4230_SHUNT_VOLTAGE_CH4 0x18 +#define INA4230_BUS_VOLTAGE_CH4 0x19 +#define INA4230_CURRENT_CH4 0x1A +#define INA4230_POWER_CH4 0x1B +#define INA4230_ENERGY_CH4 0x1C +#define INA4230_CALIBRATION_CH4 0x1D +#define INA4230_ALERT_LIMIT4 0x1E +#define INA4230_ALERT_CONFIG4 0x1F +#define INA4230_CONFIG1 0x20 +#define INA4230_CONFIG2 0x21 +#define INA4230_FLAGS 0x22 +#define INA4230_MANUFACTURER_ID 0x7E + +#define INA4230_CALIBRATION_MASK GENMASK(14, 0) + +#define INA4230_ALERT_CHANNEL_MASK GENMASK(4, 3) +#define INA4230_ALERT_MASK GENMASK(2, 0) +/* Shunt voltage over limit */ +#define INA4230_ALERT_MASK_SOL 0x1 +/* Shunt voltage under limit */ +#define INA4230_ALERT_MASK_SUL 0x2 +/* Bus voltage over limit */ +#define INA4230_ALERT_MASK_BOL 0x3 +/* Bus voltage under limit */ +#define INA4230_ALERT_MASK_BUL 0x4 +/* Power over limit */ +#define INA4230_ALERT_MASK_POL 0x5 + +#define INA4230_CONFIG1_ACTIVE_CHANNEL_MASK GENMASK(15, 12) +#define INA4230_CONFIG1_AVG_MASK GENMASK(11, 9) +#define INA4230_CONFIG1_VBUSCT_MASK GENMASK(8, 6) +#define INA4230_CONFIG1_VSHCT_MASK GENMASK(5, 3) +#define INA4230_CONFIG1_MODE_MASK GENMASK(2, 0) +#define INA4230_MODE_POWERDOWN 0 +#define INA4230_MODE_SHUNT_SINGLE 1 +#define INA4230_MODE_BUS_SINGLE 2 +#define INA4230_MODE_BUS_SHUNT_SINGLE 3 +#define INA4230_MODE_POWERDOWN1 4 +#define INA4230_MODE_SHUNT_CONTINUOUS 5 +#define INA4230_MODE_BUS_CONTINUOUS 6 +#define INA4230_MODE_BUS_SHUNT_CONTINUOUS 7 + +#define INA4230_CONFIG2_RST BIT(15) +#define INA4230_CONFIG2_ACC_RST_MASK GENMASK(11, 8) +#define INA4230_CONFIG2_CNVR_MASK BIT(7) +#define INA4230_CONFIG2_ENOF_MASK BIT(6) +#define INA4230_CONFIG2_ALERT_LATCH BIT(5) +#define INA4230_CONFIG2_ALERT_POL BIT(4) +#define INA4230_CONFIG2_RANGE_MASK GENMASK(3, 0) +#define INA4230_CONFIG2_RANGE_CH(x) \ + FIELD_PREP(INA4230_CONFIG2_RANGE_MASK, BIT((x))) + +#define INA4230_FLAGS_LIMIT4_ALERT BIT(15) +#define INA4230_FLAGS_LIMIT3_ALERT BIT(14) +#define INA4230_FLAGS_LIMIT2_ALERT BIT(13) +#define INA4230_FLAGS_LIMIT1_ALERT BIT(12) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH4 BIT(11) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH3 BIT(10) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH2 BIT(9) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH1 BIT(8) +#define INA4230_FLAGS_CVRF BIT(7) +#define INA4230_FLAGS_MATH_OVERFLOW BIT(6) + +#define INA4230_RSHUNT_DEFAULT 10000 +#define INA4230_CONFIG_DEFAULT \ + (FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, 0xF) | \ + FIELD_PREP(INA4230_CONFIG1_AVG_MASK, 0x1) | \ + FIELD_PREP(INA4230_CONFIG1_VBUSCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_VSHCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_MODE_MASK, 0x7)) +#define INA4230_CONFIG_CHx_EN(x) \ + FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, BIT((x))) + +enum ina4230_fields { + /* Alert configuration settings: channel masks */ + F_ALERT1_CH, F_ALERT2_CH, F_ALERT3_CH, F_ALERT4_CH, + /* Alert configuration settings: alert masks */ + F_ALERT1_TYPE, F_ALERT2_TYPE, F_ALERT3_TYPE, F_ALERT4_TYPE, + /* Configuration registers */ + F_CH_EN, F_AVG, F_VBUSCT, F_VSHCT, F_MODE, + F_RST, F_ACC_RST, F_CNV_ALERT, F_ENOF, F_ALERT_LATCH, F_ALERT_POL, F_RANGE, + /* Status flags */ + F_LIMIT1_ALERT, F_LIMIT2_ALERT, F_LIMIT3_ALERT, F_LIMIT4_ALERT, + F_ENERGY_OVERFLOW_CH1, F_ENERGY_OVERFLOW_CH2, F_ENERGY_OVERFLOW_CH3, F_ENERGY_OVERFLOW_CH4, + F_CVRF, F_MATH_OVERFLOW, + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina4230_reg_fields[] = { + [F_ALERT1_CH] = REG_FIELD(INA4230_ALERT_CONFIG1, 3, 4), + [F_ALERT2_CH] = REG_FIELD(INA4230_ALERT_CONFIG2, 3, 4), + [F_ALERT3_CH] = REG_FIELD(INA4230_ALERT_CONFIG3, 3, 4), + [F_ALERT4_CH] = REG_FIELD(INA4230_ALERT_CONFIG4, 3, 4), + + [F_ALERT1_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG1, 0, 2), + [F_ALERT2_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG2, 0, 2), + [F_ALERT3_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG3, 0, 2), + [F_ALERT4_TYPE] = REG_FIELD(INA4230_ALERT_CONFIG4, 0, 2), + + [F_CH_EN] = REG_FIELD(INA4230_CONFIG1, 12, 15), + [F_AVG] = REG_FIELD(INA4230_CONFIG1, 9, 11), + [F_VBUSCT] = REG_FIELD(INA4230_CONFIG1, 6, 8), + [F_VSHCT] = REG_FIELD(INA4230_CONFIG1, 3, 5), + [F_MODE] = REG_FIELD(INA4230_CONFIG1, 0, 2), + [F_RST] = REG_FIELD(INA4230_CONFIG2, 15, 15), + [F_ACC_RST] = REG_FIELD(INA4230_CONFIG2, 8, 11), + [F_CNV_ALERT] = REG_FIELD(INA4230_CONFIG2, 7, 7), + [F_ENOF] = REG_FIELD(INA4230_CONFIG2, 6, 6), + [F_ALERT_LATCH] = REG_FIELD(INA4230_CONFIG2, 5, 5), + [F_ALERT_POL] = REG_FIELD(INA4230_CONFIG2, 4, 4), + [F_RANGE] = REG_FIELD(INA4230_CONFIG2, 0, 3), + + [F_LIMIT1_ALERT] = REG_FIELD(INA4230_FLAGS, 12, 12), + [F_LIMIT2_ALERT] = REG_FIELD(INA4230_FLAGS, 13, 13), + [F_LIMIT3_ALERT] = REG_FIELD(INA4230_FLAGS, 14, 14), + [F_LIMIT4_ALERT] = REG_FIELD(INA4230_FLAGS, 15, 15), + [F_ENERGY_OVERFLOW_CH1] = REG_FIELD(INA4230_FLAGS, 8, 8), + [F_ENERGY_OVERFLOW_CH2] = REG_FIELD(INA4230_FLAGS, 9, 9), + [F_ENERGY_OVERFLOW_CH3] = REG_FIELD(INA4230_FLAGS, 10, 10), + [F_ENERGY_OVERFLOW_CH4] = REG_FIELD(INA4230_FLAGS, 11, 11), + [F_CVRF] = REG_FIELD(INA4230_FLAGS, 7, 7), + [F_MATH_OVERFLOW] = REG_FIELD(INA4230_FLAGS, 6, 6), +}; + +enum ina4230_channels { + INA4230_CHANNEL1, + INA4230_CHANNEL2, + INA4230_CHANNEL3, + INA4230_CHANNEL4, + INA4230_NUM_CHANNELS +}; + +/** + * struct ina4230_input - channel input source specific information + * @label: label of channel input source + * @shunt_resistor: shunt resistor value of channel input source + * @shunt_gain: gain of shunt voltage for current calculation + * @max_expected_current: maximum expected current in micro-Ampere for ADC + * calibration + * @current_lsb_uA: current LSB in micro-Amperes + * @disconnected: connection status of channel input source + */ +struct ina4230_input { + const char *label; + int shunt_resistor; + int shunt_gain; + int max_expected_current; + int current_lsb_uA; + bool disconnected; +}; + +/** + * struct ina4230_data - device specific information + * @pm_dev: Device pointer for pm runtime + * @regmap: Register map of the device + * @fields: Register fields of the device + * @inputs: Array of channel input source specific structures + * @reg_config1: cached value of CONFIG1 register + * @reg_config2: cached value of CONFIG2 register + * @alert_active_high: flag indicating alert polarity is active high + */ +struct ina4230_data { + struct device *pm_dev; + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct ina4230_input inputs[INA4230_NUM_CHANNELS]; + unsigned int reg_config1; + unsigned int reg_config2; + bool alert_active_high; +}; + +static inline bool ina4230_is_enabled(struct ina4230_data *ina, int channel) +{ + return pm_runtime_active(ina->pm_dev) && + !ina->inputs[channel].disconnected && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(channel); +} + +/* Lookup table for Bus and Shunt conversion times in usec */ +static const u16 ina4230_conv_time[] = { + 140, 204, 332, 588, 1100, 2116, 4156, 8244, +}; + +/* Lookup table for number of samples used in averaging mode */ +static const int ina4230_avg_samples[] = { + 1, 4, 16, 64, 128, 256, 512, 1024, +}; + +/* Converting update_interval in msec to conversion time in usec */ +static inline u32 ina4230_interval_ms_to_conv_time(u16 config, int interval) +{ + u32 channels = hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 samples_idx = FIELD_GET(INA4230_CONFIG1_AVG_MASK, config); + u32 samples = ina4230_avg_samples[samples_idx]; + + /* Bisect the result to Bus and Shunt conversion times */ + return DIV_ROUND_CLOSEST(interval * 1000 / 2, channels * samples); +} + +/* Converting CONFIG register value to update_interval in usec */ +static inline u32 ina4230_reg_to_interval_us(u16 config) +{ + u32 channels = hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 vbus_ct_idx = FIELD_GET(INA4230_CONFIG1_VBUSCT_MASK, config); + u32 vsh_ct_idx = FIELD_GET(INA4230_CONFIG1_VSHCT_MASK, config); + u32 vbus_ct = ina4230_conv_time[vbus_ct_idx]; + u32 vsh_ct = ina4230_conv_time[vsh_ct_idx]; + + /* Calculate total conversion time */ + return channels * (vbus_ct + vsh_ct); +} + +static const u8 ina4230_calibration_reg[] = { + INA4230_CALIBRATION_CH1, + INA4230_CALIBRATION_CH2, + INA4230_CALIBRATION_CH3, + INA4230_CALIBRATION_CH4, +}; + +static int ina4230_set_calibration(struct ina4230_data *ina, int channel) +{ + struct ina4230_input *input = &ina->inputs[channel]; + u8 reg = ina4230_calibration_reg[channel]; + int shunt_range_uV, ret; + u32 calibration; + u64 n, d; + + shunt_range_uV = mult_frac(input->max_expected_current, + input->shunt_resistor, + 1000000); + input->shunt_gain = shunt_range_uV > 20480 ? 1 : 4; + ina->reg_config2 &= ~INA4230_CONFIG2_RANGE_CH(channel); + if (input->shunt_gain == 4) + ina->reg_config2 |= INA4230_CONFIG2_RANGE_CH(channel); + + ret = regmap_write(ina->regmap, INA4230_CONFIG2, ina->reg_config2); + if (ret) + return ret; + + input->current_lsb_uA = DIV_ROUND_UP(input->max_expected_current, 32768); + n = 5120000000ULL; + d = (u64)input->current_lsb_uA * input->shunt_resistor * input->shunt_gain; + /* Ensure rounding to the closest integer */ + n += d / 2; + n = div64_u64(n, d); + if (n > INA4230_CALIBRATION_MASK) { + dev_err(ina->pm_dev, + "Shunt %duOhm too low for expected current %duA, cannot calibrate channel %d\n", + input->shunt_resistor, input->max_expected_current, channel + 1); + return -ERANGE; + } + + calibration = n & INA4230_CALIBRATION_MASK; + + return regmap_write(ina->regmap, reg, calibration); +} + +static const u8 ina4230_in_reg[] = { + INA4230_BUS_VOLTAGE_CH1, + INA4230_BUS_VOLTAGE_CH2, + INA4230_BUS_VOLTAGE_CH3, + INA4230_BUS_VOLTAGE_CH4, + INA4230_SHUNT_VOLTAGE_CH1, + INA4230_SHUNT_VOLTAGE_CH2, + INA4230_SHUNT_VOLTAGE_CH3, + INA4230_SHUNT_VOLTAGE_CH4, +}; + +static const u8 ina4230_curr_reg[][INA4230_NUM_CHANNELS] = { + [hwmon_curr_input] = { INA4230_CURRENT_CH1, INA4230_CURRENT_CH2, + INA4230_CURRENT_CH3, INA4230_CURRENT_CH4 }, +}; + +static const u8 ina4230_power_reg[] = { + INA4230_POWER_CH1, INA4230_POWER_CH2, INA4230_POWER_CH3, INA4230_POWER_CH4 +}; + +static const u8 ina4230_energy_reg[] = { + INA4230_ENERGY_CH1, INA4230_ENERGY_CH2, + INA4230_ENERGY_CH3, INA4230_ENERGY_CH4 +}; + +static int ina4230_read_chip(struct device *dev, u32 attr, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int regval; + + switch (attr) { + case hwmon_chip_samples: + regval = FIELD_GET(INA4230_CONFIG1_AVG_MASK, ina->reg_config1); + *val = ina4230_avg_samples[regval]; + return 0; + case hwmon_chip_update_interval: + /* Return in msec */ + *val = ina4230_reg_to_interval_us(ina->reg_config1); + *val = DIV_ROUND_CLOSEST(*val, 1000); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + const bool is_shunt = channel > INA4230_CHANNEL4; + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_in_reg[channel]; + int regval, ret; + + /* + * Translate shunt channel index to sensor channel index + */ + channel %= INA4230_NUM_CHANNELS; + + switch (attr) { + case hwmon_in_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + /* + * Scale of shunt voltage (uV): LSB is 2.5uV or 625nV + * depending on gain setting + * Scale of bus voltage (mV): LSB is 1.6mV + */ + if (is_shunt) + *val = mult_frac((long)(int16_t)regval, + 2500 / ina->inputs[channel].shunt_gain, + 1000000); + else + *val = mult_frac((long)(int16_t)regval, + 1600, + 1000); + return 0; + case hwmon_in_enable: + *val = ina4230_is_enabled(ina, channel); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_power(struct device *dev, u32 attr, int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_power_reg[channel]; + int regval, ret; + + switch (attr) { + case hwmon_power_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_energy(struct device *dev, u32 attr, int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_energy_reg[channel]; + int ret; + __be32 regval; + + switch (attr) { + case hwmon_energy_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_noinc_read(ina->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be32_to_cpu(regval) * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_curr(struct device *dev, u32 attr, + int channel, long *val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u8 reg = ina4230_curr_reg[attr][channel]; + int regval, ret; + + switch (attr) { + case hwmon_curr_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA / 1000; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_chip(struct device *dev, u32 attr, long val) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int idx; + u32 tmp; + + switch (attr) { + case hwmon_chip_samples: + idx = find_closest(val, ina4230_avg_samples, + ARRAY_SIZE(ina4230_avg_samples)); + + FIELD_MODIFY(INA4230_CONFIG1_AVG_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + case hwmon_chip_update_interval: + tmp = ina4230_interval_ms_to_conv_time(ina->reg_config1, val); + idx = find_closest(tmp, ina4230_conv_time, + ARRAY_SIZE(ina4230_conv_time)); + + FIELD_MODIFY(INA4230_CONFIG1_VBUSCT_MASK, &ina->reg_config1, idx); + FIELD_MODIFY(INA4230_CONFIG1_VSHCT_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_enable(struct device *dev, int channel, bool enable) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + u16 config, mask = INA4230_CONFIG_CHx_EN(channel); + u16 config_old = ina->reg_config1 & mask; + u32 tmp; + int ret; + + config = enable ? mask : 0; + + /* Bypass if enable status is not being changed */ + if (config_old == config) + return 0; + + /* For enabling routine, increase refcount and resume() at first */ + if (enable) { + ret = pm_runtime_resume_and_get(ina->pm_dev); + if (ret < 0) { + dev_err(dev, "Failed to get PM runtime\n"); + return ret; + } + } + + /* Enable or disable the channel */ + tmp = (ina->reg_config1 & ~mask) | (config & mask); + ret = regmap_write(ina->regmap, INA4230_CONFIG1, tmp); + if (ret) + goto fail; + + /* Cache the latest config register value */ + ina->reg_config1 = tmp; + + /* For disabling routine, decrease refcount or suspend() at last */ + if (!enable) + pm_runtime_put_sync(ina->pm_dev); + + return 0; + +fail: + if (enable) { + dev_err(dev, "Failed to enable channel %d: error %d\n", + channel, ret); + pm_runtime_put_sync(ina->pm_dev); + } + + return ret; +} + +static int ina4230_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret = ina4230_read_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret = ina4230_read_in(dev, attr, channel - 1, val); + break; + case hwmon_curr: + ret = ina4230_read_curr(dev, attr, channel, val); + break; + case hwmon_power: + ret = ina4230_read_power(dev, attr, channel, val); + break; + case hwmon_energy: + ret = ina4230_read_energy(dev, attr, channel, val); + break; + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret = ina4230_write_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret = ina4230_write_enable(dev, channel - 1, val); + break; + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int index = channel - 1; + + *str = ina->inputs[index].label; + + return 0; +} + +static umode_t ina4230_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ina4230_data *ina = drvdata; + const struct ina4230_input *input = NULL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + case hwmon_in: + /* Ignore in0_ */ + if (channel == 0) + return 0; + + switch (attr) { + case hwmon_in_label: + if (channel - 1 <= INA4230_CHANNEL4) + input = &ina->inputs[channel - 1]; + /* Hide label node if label is not provided */ + return (input && input->label) ? 0444 : 0; + case hwmon_in_input: + return 0444; + case hwmon_in_enable: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + default: + return 0; + } + case hwmon_energy: + switch (attr) { + case hwmon_energy_input: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_channel_info * const ina4230_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_SAMPLES, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + /* 0: dummy, skipped in is_visible */ + HWMON_I_INPUT, + /* 1-4: input voltage Channels */ + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + /* 5-8: shunt voltage Channels */ + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT), + HWMON_CHANNEL_INFO(curr, + /* 1-4: current channels*/ + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 1-4: power channels*/ + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT), + HWMON_CHANNEL_INFO(energy, + /* 1-4: energy channels*/ + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT), + NULL +}; + +static const struct hwmon_ops ina4230_hwmon_ops = { + .is_visible = ina4230_is_visible, + .read_string = ina4230_read_string, + .read = ina4230_read, + .write = ina4230_write, +}; + +static const struct hwmon_chip_info ina4230_chip_info = { + .ops = &ina4230_hwmon_ops, + .info = ina4230_info, +}; + +/* Extra attribute groups */ +static ssize_t ina4230_shunt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina4230_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + struct ina4230_input *input = &ina->inputs[channel]; + + return sysfs_emit(buf, "%d\n", input->shunt_resistor); +} + +static ssize_t ina4230_shunt_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina4230_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + struct ina4230_input *input = &ina->inputs[channel]; + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + val = clamp_val(val, 1, INT_MAX); + + input->shunt_resistor = val; + ret = ina4230_set_calibration(ina, channel); + if (ret) + return ret; + + return count; +} + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR_RW(shunt1_resistor, ina4230_shunt, INA4230_CHANNEL1); +static SENSOR_DEVICE_ATTR_RW(shunt2_resistor, ina4230_shunt, INA4230_CHANNEL2); +static SENSOR_DEVICE_ATTR_RW(shunt3_resistor, ina4230_shunt, INA4230_CHANNEL3); +static SENSOR_DEVICE_ATTR_RW(shunt4_resistor, ina4230_shunt, INA4230_CHANNEL4); + +static struct attribute *ina4230_attrs[] = { + &sensor_dev_attr_shunt1_resistor.dev_attr.attr, + &sensor_dev_attr_shunt2_resistor.dev_attr.attr, + &sensor_dev_attr_shunt3_resistor.dev_attr.attr, + &sensor_dev_attr_shunt4_resistor.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ina4230); + +static const struct regmap_range ina4230_vol_ranges[] = { + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH1, INA4230_ENERGY_CH1), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH2, INA4230_ENERGY_CH2), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH3, INA4230_ENERGY_CH3), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH4, INA4230_ENERGY_CH4), + regmap_reg_range(INA4230_FLAGS, INA4230_FLAGS), +}; + +static const struct regmap_access_table ina4230_volatile_table = { + .yes_ranges = ina4230_vol_ranges, + .n_yes_ranges = ARRAY_SIZE(ina4230_vol_ranges), +}; + +static const struct regmap_config ina4230_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .cache_type = REGCACHE_MAPLE, + .volatile_table = &ina4230_volatile_table, +}; + +static int ina4230_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct ina4230_data *ina) +{ + struct ina4230_input *input; + u32 val; + int ret; + + ret = of_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, + "missing reg property of %pOFn\n", child); + else if (val > INA4230_CHANNEL4) + return dev_err_probe(dev, -EINVAL, + "invalid reg %d of %pOFn\n", val, child); + + input = &ina->inputs[val]; + + /* Log the disconnected channel input */ + if (!of_device_is_available(child)) { + input->disconnected = true; + return 0; + } + + /* Save the connected input label if available */ + of_property_read_string(child, "label", &input->label); + + /* Overwrite default shunt resistor value optionally */ + if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val)) { + if (val < 1 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid shunt resistor value %u of %pOFn\n", + val, child); + + input->shunt_resistor = val; + } + + /* Save the expected maxcurrent */ + if (!of_property_read_u32(child, "ti,maximum-expected-current-microamp", &val)) { + if (val < 32768 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid max current value %u of %pOFn\n", + val, child); + + input->max_expected_current = val; + } + + return 0; +} + +static int ina4230_probe_from_dt(struct device *dev, struct ina4230_data *ina) +{ + const struct device_node *np = dev->of_node; + int ret; + + /* Compatible with non-DT platforms */ + if (!np) + return 0; + + ina->alert_active_high = of_property_read_bool(np, "ti,alert-polarity-active-high"); + + for_each_child_of_node_scoped(np, child) { + ret = ina4230_probe_child_from_dt(dev, child, ina); + if (ret) + return ret; + } + + ret = devm_regulator_get_enable_optional(dev, "vs"); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + return 0; +} + +static int ina4230_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ina4230_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap = devm_regmap_init_i2c(client, &ina4230_regmap_config); + if (IS_ERR(ina->regmap)) + return PTR_ERR(ina->regmap); + + ret = devm_regmap_field_bulk_alloc(dev, ina->regmap, ina->fields, + ina4230_reg_fields, + ARRAY_SIZE(ina4230_reg_fields)); + if (ret) + return ret; + + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + ina->inputs[i].shunt_resistor = INA4230_RSHUNT_DEFAULT; + /* Default for 1mA LSB current measurements */ + ina->inputs[i].max_expected_current = 32768000; + } + + ret = ina4230_probe_from_dt(dev, ina); + if (ret) + return dev_err_probe(dev, ret, + "Unable to probe from device tree\n"); + + /* The driver will be reset, so use reset value */ + ina->reg_config1 = INA4230_CONFIG_DEFAULT; + ina->reg_config2 = 0; + + if (ina->alert_active_high) + FIELD_MODIFY(INA4230_CONFIG2_ALERT_POL, &ina->reg_config2, 1); + + /* Disable channels if their inputs are disconnected */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + ina->reg_config1 &= ~INA4230_CONFIG_CHx_EN(i); + } + + ina->pm_dev = dev; + dev_set_drvdata(dev, ina); + + /* Enable PM runtime -- status is suspended by default */ + pm_runtime_enable(ina->pm_dev); + + /* Initialize (resume) the device */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + continue; + + /* Match the refcount with number of enabled channels */ + ret = pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) + goto fail; + } + + /* Set calibration values after device resume/reset */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) { + ret = ina4230_set_calibration(ina, i); + if (ret) + goto fail; + } + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, ina, + &ina4230_chip_info, + ina4230_groups); + if (IS_ERR(hwmon_dev)) { + ret = dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device\n"); + goto fail; + } + + return 0; + +fail: + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + pm_runtime_put_noidle(ina->pm_dev); + } + + return ret; +} + +static void ina4230_remove(struct i2c_client *client) +{ + struct ina4230_data *ina = dev_get_drvdata(&client->dev); + int i; + + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i = 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + pm_runtime_put_noidle(ina->pm_dev); + } +} + +static int ina4230_suspend(struct device *dev) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int ret; + + /* Save config register value and enable cache-only */ + ret = regmap_read(ina->regmap, INA4230_CONFIG1, &ina->reg_config1); + if (ret) + return ret; + + regcache_cache_only(ina->regmap, true); + regcache_mark_dirty(ina->regmap); + + return 0; +} + +static int ina4230_resume(struct device *dev) +{ + struct ina4230_data *ina = dev_get_drvdata(dev); + int ret; + + regcache_cache_only(ina->regmap, false); + + /* Software reset the chip */ + ret = regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + /* Restore cached register values to hardware */ + ret = regcache_sync(ina->regmap); + if (ret) + return ret; + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ina4230_pm, ina4230_suspend, ina4230_resume, + NULL); + +static const struct of_device_id ina4230_of_match_table[] = { + { .compatible = "ti,ina4230", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina4230_of_match_table); + +static const struct i2c_device_id ina4230_ids[] = { + { "ina4230" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina4230_ids); + +static struct i2c_driver ina4230_i2c_driver = { + .probe = ina4230_probe, + .remove = ina4230_remove, + .driver = { + .name = INA4230_DRIVER_NAME, + .of_match_table = ina4230_of_match_table, + .pm = pm_ptr(&ina4230_pm), + }, + .id_table = ina4230_ids, +}; +module_i2c_driver(ina4230_i2c_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("Texas Instruments INA4230 HWMon Driver"); +MODULE_LICENSE("GPL"); From 766b9dae7df50f3fc976aa2b1ec857548c3e02ae Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 13 Mar 2026 13:02:31 +0400 Subject: [PATCH 184/208] usb: typec: tcpci: add DRM DP HPD bridge support Add support to use TCPCI based USB-C connectors with the DP AltMode helper code on devicetree based platforms. Signed-off-by: Alexey Charkov --- drivers/usb/typec/tcpm/Kconfig | 2 ++ drivers/usb/typec/tcpm/tcpci.c | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index 00baa7503d4527..53abde8ebef93a 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -13,7 +13,9 @@ if TYPEC_TCPM config TYPEC_TCPCI tristate "Type-C Port Controller Interface driver" + depends on DRM || DRM=n depends on I2C + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF select REGMAP_I2C help Type-C Port Controller driver for TCPCI-compliant controller. diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 0148b8f50412b0..e6cccbd377f7db 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -5,6 +5,7 @@ * USB Type-C Port Controller Interface. */ +#include #include #include #include @@ -837,6 +838,7 @@ static int tcpci_parse_config(struct tcpci *tcpci) struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) { + struct auxiliary_device *bridge_dev; struct tcpci *tcpci; int err; @@ -889,12 +891,23 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) if (err < 0) return ERR_PTR(err); + bridge_dev = devm_drm_dp_hpd_bridge_alloc(tcpci->dev, to_of_node(tcpci->tcpc.fwnode)); + if (IS_ERR(bridge_dev)) + return ERR_CAST(bridge_dev); + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); if (IS_ERR(tcpci->port)) { fwnode_handle_put(tcpci->tcpc.fwnode); return ERR_CAST(tcpci->port); } + err = devm_drm_dp_hpd_bridge_add(tcpci->dev, bridge_dev); + if (err < 0) { + tcpm_unregister_port(tcpci->port); + fwnode_handle_put(tcpci->tcpc.fwnode); + return ERR_PTR(err); + } + return tcpci; } EXPORT_SYMBOL_GPL(tcpci_register_port); From 75bb60c9eb2d34635f99d496313090b177df19c1 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 25 Mar 2026 11:49:48 +0400 Subject: [PATCH 185/208] WIP: mfd: Add support for Flipper One's MCU Signed-off-by: Alexey Charkov --- drivers/mfd/Kconfig | 15 +++ drivers/mfd/Makefile | 1 + drivers/mfd/flipper-one-mcu.c | 166 ++++++++++++++++++++++++++++ include/linux/mfd/flipper-one-mcu.h | 54 +++++++++ 4 files changed, 236 insertions(+) create mode 100644 drivers/mfd/flipper-one-mcu.c create mode 100644 include/linux/mfd/flipper-one-mcu.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 80b4e82c4937fd..83d6969faf4a55 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -539,6 +539,21 @@ config MFD_EXYNOS_LPASS SoCs (e.g. Exynos5433). Choose Y here only if you build for such Samsung SoC. +config MFD_FLIPPER_ONE + tristate "Flipper One MCU support" + depends on I2C && OF + depends on ARCH_ROCKCHIP || COMPILE_TEST + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + This adds support for the MCU handling human-machine interface, + power controls and USB Type-C functions on the Flipper One + networking multitool. + This driver provides common support for accessing the device. + Additional drivers must be enabled in order to use the + functionality of the device. + config MFD_GATEWORKS_GSC tristate "Gateworks System Controller" depends on I2C && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index ebadbaea9e4a09..3ff94e56e16583 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o +obj-$(CONFIG_MFD_FLIPPER_ONE) += flipper-one-mcu.o obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o obj-$(CONFIG_MFD_MACSMC) += macsmc.o diff --git a/drivers/mfd/flipper-one-mcu.c b/drivers/mfd/flipper-one-mcu.c new file mode 100644 index 00000000000000..f109343e81333b --- /dev/null +++ b/drivers/mfd/flipper-one-mcu.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flipper One MCU interconnect driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct regmap_range fomcu_writeable_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_INTMSK_INPUT, + FOMCU_REG_INPUT_BTNS - 1), + regmap_reg_range(FOMCU_REG_LEDS_BR_LINK, + FOMCU_REG_LEDS_COLOR_LINK4), +}; + +static const struct regmap_access_table fomcu_writeable_regs = { + .yes_ranges = fomcu_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(fomcu_writeable_reg_ranges), +}; + +static const struct regmap_range fomcu_nonvolatile_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_VERSION, FOMCU_REG_VERSION + 1), + regmap_reg_range(FOMCU_REG_INTMSK_INPUT, FOMCU_REG_INPUT_BTNS - 1), +}; + +static const struct regmap_access_table fomcu_volatile_regs = { + .no_ranges = fomcu_nonvolatile_reg_ranges, + .n_no_ranges = ARRAY_SIZE(fomcu_nonvolatile_reg_ranges), +}; + +static const struct regmap_range fomcu_precious_reg_ranges[] = { + regmap_reg_range(FOMCU_REG_INTSTS_INPUT, + FOMCU_REG_INTMSK_INPUT - 1), +}; + +static const struct regmap_access_table fomcu_precious_regs = { + .yes_ranges = fomcu_precious_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(fomcu_precious_reg_ranges), +}; + +static const struct regmap_config fomcu_regmap_config = { + .name = "flipper-one-mcu", + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .max_register = FOMCU_REG_MAX, + .wr_table = &fomcu_writeable_regs, + .volatile_table = &fomcu_volatile_regs, + .precious_table = &fomcu_precious_regs, +}; + +#define CAT(a, b) CAT_I(a, b) +#define CAT_I(a, b) a##b +#define FOMCU_IRQ_REG(subsys, bit) \ + REGMAP_IRQ_REG(CAT(FOMCU_INT_, CAT(subsys, CAT(_, bit))), \ + CAT(FOMCU_INTOFF_, subsys), \ + CAT(FOMCU_INTSTS_, CAT(subsys, CAT(_, bit)))) + +static const struct regmap_irq fomcu_irqs[] = { + FOMCU_IRQ_REG(INPUT, BTN), + FOMCU_IRQ_REG(INPUT, TOUCH), + FOMCU_IRQ_REG(INPUT, HEADSET), +}; + +static unsigned int irq_input_offsets[] = { FOMCU_INTOFF_INPUT }; + +static const struct regmap_irq_sub_irq_map fomcu_sub_irqs[] = { + REGMAP_IRQ_MAIN_REG_OFFSET(irq_input_offsets), +}; + +static const struct regmap_irq_chip fomcu_irq_chip = { + .name = "fomcu-irq", + .irqs = fomcu_irqs, + .num_irqs = ARRAY_SIZE(fomcu_irqs), + .main_status = FOMCU_REG_INTSTS, + .status_base = FOMCU_REG_INTSTS_INPUT, + .mask_base = FOMCU_REG_INTMSK_INPUT, + .sub_reg_offsets = &fomcu_sub_irqs[0], + .num_main_regs = 1, + .num_regs = ARRAY_SIZE(fomcu_sub_irqs), +}; + +static const struct resource fo_input_irqs[] = { + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_BTN, "flipper-one-input-btn"), + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_TOUCH, "flipper-one-input-touch"), + DEFINE_RES_IRQ_NAMED(FOMCU_INT_INPUT_HEADSET, "flipper-one-input-headset"), +}; + +static const struct mfd_cell cells[] = { + MFD_CELL_RES("flipper-one-input", fo_input_irqs), + MFD_CELL_NAME("flipper-one-leds"), + MFD_CELL_NAME("flipper-one-power"), + MFD_CELL_NAME("flipper-one-regulators"), + MFD_CELL_NAME("flipper-one-thermal"), + MFD_CELL_NAME("flipper-one-typec"), +}; + +static int fomcu_probe(struct i2c_client *client) +{ + struct regmap_irq_chip_data *irq_data; + struct fomcu_device *ddata; + int ret; + + ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->client = client; + + ddata->regmap = devm_regmap_init_i2c(client, &fomcu_regmap_config); + if (IS_ERR(ddata->regmap)) { + return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap), + "Failed to allocate register map\n"); + } + + i2c_set_clientdata(client, ddata); + + ret = devm_regmap_add_irq_chip(&client->dev, ddata->regmap, + client->irq, IRQF_ONESHOT, 0, + &fomcu_irq_chip, &irq_data); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to add IRQ chip\n"); + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + cells, ARRAY_SIZE(cells), NULL, 0, + regmap_irq_get_domain(irq_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register child devices\n"); + + return ret; +} + +static const struct i2c_device_id fomcu_i2c_ids[] = { + { "flipper-one-mcu" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, fomcu_i2c_ids); + +static const struct of_device_id fomcu_of_match[] = { + { .compatible = "flipper,one-mcu" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fomcu_of_match); + +static struct i2c_driver fomcu_driver = { + .driver = { + .name = "flipper-one-mcu", + .of_match_table = fomcu_of_match, + }, + .probe = fomcu_probe, + .id_table = fomcu_i2c_ids, +}; +module_i2c_driver(fomcu_driver); + +MODULE_DESCRIPTION("Flipper One MCU driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/flipper-one-mcu.h b/include/linux/mfd/flipper-one-mcu.h new file mode 100644 index 00000000000000..7fcd547840dfa7 --- /dev/null +++ b/include/linux/mfd/flipper-one-mcu.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Register definitions for the Flipper One MCU interconnect + * Copyright (C) 2026 Flipper FZCO + */ + +#ifndef __LINUX_MFD_FLIPPER_ONE_MCU_H +#define __LINUX_MFD_FLIPPER_ONE_MCU_H + +#include +#include + +enum fomcu_interrupts { + FOMCU_INT_INPUT_BTN, + FOMCU_INT_INPUT_TOUCH, + FOMCU_INT_INPUT_HEADSET, +}; + +#define FOMCU_REG_INTSTS 0x0000 + +#define FOMCU_REG_VERSION 0x0080 + +#define FOMCU_REG_INTSTS_INPUT 0x0100 +#define FOMCU_INTOFF_INPUT 0x0 +#define FOMCU_INTSTS_INPUT_BTN BIT(0) +#define FOMCU_INTSTS_INPUT_TOUCH BIT(1) +#define FOMCU_INTSTS_INPUT_HEADSET BIT(2) + +#define FOMCU_REG_INTMSK_INPUT 0x0180 + +#define FOMCU_REG_INPUT_BTNS 0x0200 +#define FOMCU_REG_INPUT_TOUCH_X 0x0202 +#define FOMCU_REG_INPUT_TOUCH_Y 0x0204 +#define FOMCU_REG_INPUT_TOUCH_Z 0x0206 +#define FOMCU_REG_INPUT_HEADSET 0x0208 + +#define FOMCU_REG_LEDS_BR_LINK 0x0300 +#define FOMCU_REG_LEDS_BR_POWER 0x0302 +#define FOMCU_REG_LEDS_BR_WATT 0x0304 + +/* RGB565 values per each LED */ +#define FOMCU_REG_LEDS_COLOR_LINK1 0x0310 +#define FOMCU_REG_LEDS_COLOR_LINK2 0x0312 +#define FOMCU_REG_LEDS_COLOR_LINK3 0x0314 +#define FOMCU_REG_LEDS_COLOR_LINK4 0x0316 + +#define FOMCU_REG_MAX (FOMCU_REG_LEDS_COLOR_LINK4 + 1) + +struct fomcu_device { + struct i2c_client *client; + struct regmap *regmap; +}; + +#endif /* __LINUX_MFD_FLIPPER_ONE_MCU_H */ From 444751a85f54c17c2c8aa153217767cce7f4cbb0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 25 Mar 2026 11:50:54 +0400 Subject: [PATCH 186/208] WIP: Input: add support for Flipper One buttons and touchpad Signed-off-by: Alexey Charkov --- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/flipper-one-input.c | 268 +++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 drivers/input/misc/flipper-one-input.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 94a753fcb64fa5..b6817b25b4c7eb 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -178,6 +178,17 @@ config INPUT_E3X0_BUTTON To compile this driver as a module, choose M here: the module will be called e3x0_button. +config INPUT_FLIPPER_ONE + tristate "Flipper One input support" + depends on MFD_FLIPPER_ONE + help + Say Y here to enable support for human-machine interfacing + functions on the Flipper One networking multitool, including its + builtin buttons, touchpad, haptic feedback and headset inputs. + + To compile this driver as a module, choose M here: the + module will be called flipper-one-input. + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 415fc4e2918be8..704b6cc9b41c1c 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o +obj-$(CONFIG_INPUT_FLIPPER_ONE) += flipper-one-input.o obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o obj-$(CONFIG_INPUT_DRV2665_HAPTICS) += drv2665.o obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o diff --git a/drivers/input/misc/flipper-one-input.c b/drivers/input/misc/flipper-one-input.c new file mode 100644 index 00000000000000..eb0cbeb9c529f0 --- /dev/null +++ b/drivers/input/misc/flipper-one-input.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Flipper One builtin buttons and touchpad driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FO_BTN_VIEW BIT(0) +#define FO_BTN_ESCAPE BIT(1) +#define FO_BTN_POWER BIT(2) +#define FO_BTN_EDIT BIT(3) +#define FO_BTN_RUN BIT(4) +#define FO_BTN_APPSELECT BIT(5) +#define FO_BTN_BACK BIT(6) +#define FO_BTN_DOWN BIT(7) +#define FO_BTN_RIGHT BIT(8) +#define FO_BTN_CENTER BIT(9) +#define FO_BTN_LEFT BIT(10) +#define FO_BTN_UP BIT(11) +#define FO_BTN_PTT BIT(12) + +#define FO_HS_HPPRESENT BIT(0) +#define FO_HS_MICPRESENT BIT(1) +#define FO_HS_BTN_A BIT(2) +#define FO_HS_BTN_B BIT(3) +#define FO_HS_BTN_C BIT(4) +#define FO_HS_BTN_D BIT(5) + +struct fo_input { + struct input_dev *idev_btn; + struct input_dev *idev_touch; + struct input_dev *idev_headset; + struct fomcu_device *fomcu; +}; + +struct fo_irq { + const char *name; + irqreturn_t (*handler)(int, void *); +}; + +static irqreturn_t fo_input_btn_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_btn; + struct device *parent = idev->dev.parent; + unsigned int reg; + int err; + + err = regmap_read(regmap, FOMCU_REG_INPUT_BTNS, ®); + if (err) { + dev_err(parent, "Failed to read button states: %d\n", err); + return IRQ_NONE; + } + + input_report_key(idev, KEY_ENTER, FO_BTN_CENTER & reg); + input_report_key(idev, KEY_UP, FO_BTN_UP & reg); + input_report_key(idev, KEY_DOWN, FO_BTN_DOWN & reg); + input_report_key(idev, KEY_LEFT, FO_BTN_LEFT & reg); + input_report_key(idev, KEY_RIGHT, FO_BTN_RIGHT & reg); + input_report_key(idev, KEY_TAB, FO_BTN_APPSELECT & reg); + input_report_key(idev, KEY_BACKSPACE, FO_BTN_BACK & reg); + input_report_key(idev, KEY_A, FO_BTN_PTT & reg); + input_report_key(idev, KEY_Z, FO_BTN_ESCAPE & reg); + input_report_key(idev, KEY_X, FO_BTN_VIEW & reg); + input_report_key(idev, KEY_C, FO_BTN_POWER & reg); + input_report_key(idev, KEY_V, FO_BTN_EDIT & reg); + input_report_key(idev, KEY_B, FO_BTN_RUN & reg); + input_sync(idev); + + return IRQ_HANDLED; +} + +static irqreturn_t fo_input_touch_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_touch; + struct device *parent = idev->dev.parent; + uint16_t buf[3]; + int err; + + err = regmap_bulk_read(regmap, FOMCU_REG_INPUT_TOUCH_X, &buf, ARRAY_SIZE(buf)); + if (err) { + dev_err(parent, "Failed to read touch inputs: %d\n", err); + return IRQ_NONE; + } + + input_report_key(idev, BTN_TOUCH, !!buf[2]); + input_report_key(idev, BTN_TOOL_FINGER, !!buf[2]); + input_report_abs(idev, ABS_X, buf[0]); + input_report_abs(idev, ABS_Y, buf[1]); + input_report_abs(idev, ABS_PRESSURE, buf[2]); + input_sync(idev); + + return IRQ_HANDLED; +} + +static irqreturn_t fo_input_headset_handler(int irq, void *data) +{ + struct fo_input *input = data; + struct regmap *regmap = input->fomcu->regmap; + struct input_dev *idev = input->idev_headset; + struct device *parent = idev->dev.parent; + unsigned int reg; + int err; + + err = regmap_read(regmap, FOMCU_REG_INPUT_HEADSET, ®); + if (err) { + dev_err(parent, "Failed to read headset states: %d\n", err); + return IRQ_NONE; + } + + input_report_switch(idev, SW_HEADPHONE_INSERT, reg & FO_HS_HPPRESENT); + input_report_switch(idev, SW_MICROPHONE_INSERT, reg & FO_HS_MICPRESENT); + input_report_key(idev, KEY_PLAYPAUSE, reg & FO_HS_BTN_A); + input_report_key(idev, KEY_VOLUMEUP, reg & FO_HS_BTN_B); + input_report_key(idev, KEY_VOLUMEDOWN, reg & FO_HS_BTN_C); + input_report_key(idev, KEY_VOICECOMMAND, reg & FO_HS_BTN_D); + input_sync(idev); + + return IRQ_HANDLED; +} + +static const struct fo_irq fo_irqs[] = { + { .name = "flipper-one-input-btn", .handler = fo_input_btn_handler }, + { .name = "flipper-one-input-touch", .handler = fo_input_touch_handler }, + { .name = "flipper-one-input-headset", .handler = fo_input_headset_handler }, +}; + +static int fo_input_probe(struct platform_device *pdev) +{ + struct fomcu_device *fomcu = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct fo_input *input; + struct input_dev *idev_btn, *idev_touch, *idev_headset; + int irq, err, i; + + input = devm_kzalloc(dev, sizeof(*input), GFP_KERNEL); + if (!input) + return -ENOMEM; + + input->fomcu = fomcu; + + idev_btn = devm_input_allocate_device(dev); + if (!idev_btn) { + dev_err(dev, "Failed to allocate buttons input device\n"); + return -ENOMEM; + } + input->idev_btn = idev_btn; + + idev_btn->name = "Flipper One Buttons"; + idev_btn->phys = "flipper-one-input/input0"; + idev_btn->id.bustype = BUS_I2C; + + idev_touch = devm_input_allocate_device(dev); + if (!idev_touch) { + dev_err(dev, "Failed to allocate touch input device\n"); + return -ENOMEM; + } + input->idev_touch = idev_touch; + + idev_touch->name = "Flipper One Touchpad"; + idev_touch->phys = "flipper-one-input/input1"; + idev_touch->id.bustype = BUS_I2C; + + idev_headset = devm_input_allocate_device(dev); + if (!idev_headset) { + dev_err(dev, "Failed to allocate headset input device\n"); + return -ENOMEM; + } + input->idev_headset = idev_headset; + + idev_headset->name = "Flipper One Headset"; + idev_headset->phys = "flipper-one-input/input2"; + idev_headset->id.bustype = BUS_I2C; + + /* Buttons */ + input_set_capability(idev_btn, EV_KEY, KEY_ENTER); /* D-pad center */ + input_set_capability(idev_btn, EV_KEY, KEY_UP); /* D-pad up */ + input_set_capability(idev_btn, EV_KEY, KEY_DOWN); /* D-pad down */ + input_set_capability(idev_btn, EV_KEY, KEY_LEFT); /* D-pad left */ + input_set_capability(idev_btn, EV_KEY, KEY_RIGHT); /* D-pad right */ + input_set_capability(idev_btn, EV_KEY, KEY_TAB); /* App switcher */ + input_set_capability(idev_btn, EV_KEY, KEY_BACKSPACE); /* Back */ + input_set_capability(idev_btn, EV_KEY, KEY_A); /* PTT */ + input_set_capability(idev_btn, EV_KEY, KEY_Z); /* Escape */ + input_set_capability(idev_btn, EV_KEY, KEY_X); /* View */ + input_set_capability(idev_btn, EV_KEY, KEY_C); /* Power */ + input_set_capability(idev_btn, EV_KEY, KEY_V); /* Edit */ + input_set_capability(idev_btn, EV_KEY, KEY_B); /* Run */ + + /* Touchpad */ + input_set_capability(idev_touch, EV_KEY, BTN_TOUCH); + input_set_capability(idev_touch, EV_KEY, BTN_TOOL_FINGER); + input_set_abs_params(idev_touch, ABS_X, 0, 1024, 0, 0); + input_set_abs_params(idev_touch, ABS_Y, 0, 800, 0, 0); + input_set_abs_params(idev_touch, ABS_PRESSURE, 0, 12288, 0, 0); + __set_bit(INPUT_PROP_POINTER, idev_touch->propbit); + + /* Headset */ + input_set_capability(idev_headset, EV_SW, SW_HEADPHONE_INSERT); + input_set_capability(idev_headset, EV_SW, SW_MICROPHONE_INSERT); + input_set_capability(idev_headset, EV_KEY, KEY_PLAYPAUSE); + input_set_capability(idev_headset, EV_KEY, KEY_VOLUMEUP); + input_set_capability(idev_headset, EV_KEY, KEY_VOLUMEDOWN); + input_set_capability(idev_headset, EV_KEY, KEY_VOICECOMMAND); + + device_set_wakeup_capable(dev, true); + device_wakeup_enable(dev); + + for (i = 0; i < ARRAY_SIZE(fo_irqs); i++) { + irq = platform_get_irq_byname(pdev, fo_irqs[i].name); + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get IRQ %s\n", + fo_irqs[i].name); + + err = devm_request_threaded_irq(dev, irq, NULL, fo_irqs[i].handler, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + fo_irqs[i].name, input); + if (err) + return dev_err_probe(dev, err, "Failed to request IRQ %s\n", + fo_irqs[i].name); + } + + err = input_register_device(idev_btn); + if (err) + return dev_err_probe(dev, err, "Failed to register buttons input device\n"); + + err = input_register_device(idev_touch); + if (err) + return dev_err_probe(dev, err, "Failed to register touch input device\n"); + + err = input_register_device(idev_headset); + if (err) + return dev_err_probe(dev, err, "Failed to register headset input device\n"); + + return 0; +} + +static const struct platform_device_id fo_input_id_table[] = { + { "flipper-one-input", }, + { } +}; +MODULE_DEVICE_TABLE(platform, fo_input_id_table); + +static struct platform_driver fo_input_driver = { + .driver = { + .name = "flipper-one-input", + }, + .probe = fo_input_probe, + .id_table = fo_input_id_table, +}; +module_platform_driver(fo_input_driver); + +MODULE_DESCRIPTION("Flipper One buttons and touchpad driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); From b3e65cc4350d510ec6028bf1bed8943ba71c42a8 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 23 Apr 2026 14:15:26 +0400 Subject: [PATCH 187/208] WIP: leds: rgb: Add support for Flipper One LEDs The Flipper One networking multitool has a number of RGB LEDs on its front panel, four of which (the "link" LEDs) can be controlled by the user. They are driven by the Flipper One MCU, which exposes them as registers over the I2C interconnect bus (single RGB565 value per LED). Add a driver to expose them to the user. Signed-off-by: Alexey Charkov --- drivers/leds/rgb/Kconfig | 11 +++ drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-flipper-one.c | 133 ++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 drivers/leds/rgb/leds-flipper-one.c diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 28ef4c487367cf..3e83dea254a72e 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. +config LEDS_FLIPPER_ONE + tristate "LED support for Flipper One" + depends on MFD_FLIPPER_ONE + help + This option enables support for the four RGB link LEDs on the + Flipper One networking multitool. Each LED is driven via a packed + RGB565 value written to the Flipper One MCU over I2C. + + To compile this driver as a module, choose M here: the module + will be called leds-flipper-one. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index be45991f63f50d..234682f96ba2fb 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o +obj-$(CONFIG_LEDS_FLIPPER_ONE) += leds-flipper-one.o obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o diff --git a/drivers/leds/rgb/leds-flipper-one.c b/drivers/leds/rgb/leds-flipper-one.c new file mode 100644 index 00000000000000..837513486c61bd --- /dev/null +++ b/drivers/leds/rgb/leds-flipper-one.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flipper One LED driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include + +#define FOLED_NUM_LEDS 4 +#define FOLED_NUM_COLORS 3 + +/* RGB565 component shifts and max values */ +#define FOLED_R_SHIFT 11 +#define FOLED_G_SHIFT 5 +#define FOLED_B_SHIFT 0 +#define FOLED_R_MAX 31 +#define FOLED_G_MAX 63 +#define FOLED_B_MAX 31 + +struct foled_device; + +struct foled_led { + struct foled_device *foled; + struct led_classdev_mc mc_cdev; + struct mc_subled subleds[FOLED_NUM_COLORS]; + unsigned int reg; +}; + +struct foled_device { + struct regmap *regmap; + struct foled_led leds[FOLED_NUM_LEDS]; +}; + +static const unsigned int foled_regs[FOLED_NUM_LEDS] = { + FOMCU_REG_LEDS_COLOR_LINK1, + FOMCU_REG_LEDS_COLOR_LINK2, + FOMCU_REG_LEDS_COLOR_LINK3, + FOMCU_REG_LEDS_COLOR_LINK4, +}; + +static const char * const foled_led_names[FOLED_NUM_LEDS] = { + "flipper-one:rgb:link", + "flipper-one:rgb:wifi", + "flipper-one:rgb:eth1", + "flipper-one:rgb:eth0", +}; + +static int foled_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct foled_led *led = container_of(mc_cdev, struct foled_led, mc_cdev); + u16 r, g, b, rgb565; + + led_mc_calc_color_components(mc_cdev, brightness); + + r = (u16)mc_cdev->subled_info[0].brightness * FOLED_R_MAX / LED_FULL; + g = (u16)mc_cdev->subled_info[1].brightness * FOLED_G_MAX / LED_FULL; + b = (u16)mc_cdev->subled_info[2].brightness * FOLED_B_MAX / LED_FULL; + + rgb565 = (r << FOLED_R_SHIFT) | (g << FOLED_G_SHIFT) | (b << FOLED_B_SHIFT); + + return regmap_write(led->foled->regmap, led->reg, rgb565); +} + +static int foled_probe(struct platform_device *pdev) +{ + struct foled_device *foled; + struct foled_led *led; + struct led_classdev *cdev; + int i, ret; + + foled = devm_kzalloc(&pdev->dev, sizeof(*foled), GFP_KERNEL); + if (!foled) + return -ENOMEM; + + foled->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!foled->regmap) + return dev_err_probe(&pdev->dev, -ENODEV, + "Failed to get parent regmap\n"); + + for (i = 0; i < FOLED_NUM_LEDS; i++) { + led = &foled->leds[i]; + led->foled = foled; + led->reg = foled_regs[i]; + + led->subleds[0].color_index = LED_COLOR_ID_RED; + led->subleds[1].color_index = LED_COLOR_ID_GREEN; + led->subleds[2].color_index = LED_COLOR_ID_BLUE; + + led->mc_cdev.subled_info = led->subleds; + led->mc_cdev.num_colors = FOLED_NUM_COLORS; + + cdev = &led->mc_cdev.led_cdev; + cdev->name = foled_led_names[i]; + cdev->max_brightness = LED_FULL; + cdev->brightness_set_blocking = foled_brightness_set; + cdev->flags = LED_CORE_SUSPENDRESUME; + + ret = devm_led_classdev_multicolor_register(&pdev->dev, + &led->mc_cdev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register LED %d\n", i + 1); + } + + platform_set_drvdata(pdev, foled); + return 0; +} + +static const struct platform_device_id foled_id_table[] = { + { "flipper-one-leds" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, foled_id_table); + +static struct platform_driver foled_driver = { + .probe = foled_probe, + .driver = { + .name = "flipper-one-leds", + }, + .id_table = foled_id_table, +}; +module_platform_driver(foled_driver); + +MODULE_DESCRIPTION("Flipper One LED driver"); +MODULE_AUTHOR("Alexey Charkov "); +MODULE_LICENSE("GPL"); From 3e6e1d6135a274e78e06ca87dd1394b27c15a136 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 2 Apr 2026 12:59:52 +0400 Subject: [PATCH 188/208] ASoC: dt-bindings: nau8822: Add supply regulators NAU8822 has 4 power supply pins: VDDA, VDDB, VDDC and VDDSPK, which need to be online and stable before communication with the device is attempted. List them (as optional) so that device tree users can ensure correct power sequencing. Signed-off-by: Alexey Charkov --- .../devicetree/bindings/sound/nuvoton,nau8822.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml index cb8182bbc491fc..0a8e40a140c3a9 100644 --- a/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml +++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8822.yaml @@ -30,6 +30,20 @@ properties: clock-names: const: mclk + vdda-supply: + description: Analog power supply + + vddb-supply: + description: Digital buffer (input/output) supply + + vddc-supply: + description: Digital core supply + + vddspk-supply: + description: + Speaker supply (power supply pin for RSPKOUT, LSPKOUT, AUXOUT2 and + AUXTOUT1 output drivers) + nuvoton,spk-btl: description: If set, configure the two loudspeaker outputs as a Bridge Tied Load output From 8b5867fbf7d68a68f8a7a605832c03f0d11e0a88 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 2 Apr 2026 13:03:59 +0400 Subject: [PATCH 189/208] ASoC: codecs: nau8822: add support for supply regulators NAU8822 has four power supply pins: VDDA, VDDB, VDDC, and VDDSPK, which need to be online and stable before communication with the device is attempted. Request and enable these regulators at init time, if provided. Also wait for 100 us after powering up the supply regulators before attempting to access the device registers, as recommended by the datasheet. This helps avoid -ENXIO errors when the codec is probed before the regulators are ready. Signed-off-by: Alexey Charkov --- sound/soc/codecs/nau8822.c | 46 +++++++++++++++++++++++++++++++++++--- sound/soc/codecs/nau8822.h | 3 +++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index a11759f85eaca1..cd4a7de47939b6 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,10 @@ static const struct reg_default nau8822_reg_defaults[] = { { NAU8822_REG_OUTPUT_TIEOFF, 0x0000 }, }; +static const char * const nau8822_supply_names[NAU8822_NUM_SUPPLIES] = { + "vdda", "vddb", "vddc", "vddspk", +}; + static bool nau8822_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -1056,6 +1061,7 @@ static int nau8822_suspend(struct snd_soc_component *component) struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); regcache_mark_dirty(nau8822->regmap); @@ -1066,6 +1072,15 @@ static int nau8822_resume(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + + if (ret) { + dev_err(component->dev, + "Failed to enable regulators: %d\n", ret); + return ret; + } + + fsleep(100); regcache_sync(nau8822->regmap); @@ -1153,7 +1168,7 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct nau8822 *nau8822 = dev_get_platdata(dev); - int ret; + int ret, i; if (!nau8822) { nau8822 = devm_kzalloc(dev, sizeof(*nau8822), GFP_KERNEL); @@ -1167,6 +1182,13 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) return dev_err_probe(&i2c->dev, PTR_ERR(nau8822->mclk), "Error getting mclk\n"); + for (i = 0; i < NAU8822_NUM_SUPPLIES; i++) + nau8822->supplies[i].supply = nau8822_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + nau8822->regmap = devm_regmap_init_i2c(i2c, &nau8822_regmap_config); if (IS_ERR(nau8822->regmap)) { ret = PTR_ERR(nau8822->regmap); @@ -1175,21 +1197,38 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) } nau8822->dev = dev; + ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + fsleep(100); + /* Reset the codec */ ret = regmap_write(nau8822->regmap, NAU8822_REG_RESET, 0x00); if (ret != 0) { dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); - return ret; + goto err_reg; } ret = devm_snd_soc_register_component(dev, &soc_component_dev_nau8822, &nau8822_dai, 1); if (ret != 0) { dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); - return ret; + goto err_reg; } return 0; + +err_reg: + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + return ret; +} + +static void nau8822_i2c_remove(struct i2c_client *i2c) +{ + struct nau8822 *nau8822 = i2c_get_clientdata(i2c); + + regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); } static const struct i2c_device_id nau8822_i2c_id[] = { @@ -1212,6 +1251,7 @@ static struct i2c_driver nau8822_i2c_driver = { .of_match_table = of_match_ptr(nau8822_of_match), }, .probe = nau8822_i2c_probe, + .remove = nau8822_i2c_remove, .id_table = nau8822_i2c_id, }; module_i2c_driver(nau8822_i2c_driver); diff --git a/sound/soc/codecs/nau8822.h b/sound/soc/codecs/nau8822.h index 13fe0a091e9ed4..24799c7b5931b8 100644 --- a/sound/soc/codecs/nau8822.h +++ b/sound/soc/codecs/nau8822.h @@ -211,6 +211,8 @@ struct nau8822_pll { int freq_out; }; +#define NAU8822_NUM_SUPPLIES 4 + /* Codec Private Data */ struct nau8822 { struct device *dev; @@ -219,6 +221,7 @@ struct nau8822 { struct nau8822_pll pll; int sysclk; int div_id; + struct regulator_bulk_data supplies[NAU8822_NUM_SUPPLIES]; }; #endif /* __NAU8822_H__ */ From 5229014717f2fe1160381ef360a6211b60a0bcd3 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 15 Apr 2026 18:50:45 +0400 Subject: [PATCH 190/208] arm64: dts: rockchip: rk3576: assign dclk_vp{0,1}_src to VPLL Reparent dclk_vp{0,1}_src from GPLL to VPLL on Rockchip RK3576. VPLL is a programmable PLL with no other consumers, allowing the CCF to synthesize accurate pixel clocks for the two display outputs with arbitrary modes, as long as only one of them is active at a time (or using the HDMI PHY as the clock source, which works for HDMI modes up to 4K@60Hz). This gives much greater flexibility for display modes as compared to the boot-time default of using GPLL, which is effectively fixed at 1188 MHz due to a large number of system components depending on it and making runtime rate changes unrealistic. Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index f98d7803dc3236..239196b137aef0 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -1379,6 +1379,8 @@ "dclk_vp1", "dclk_vp2", "pll_hdmiphy0"; + assigned-clocks = <&cru DCLK_VP0_SRC>, <&cru DCLK_VP1_SRC>; + assigned-clock-parents = <&cru PLL_VPLL>, <&cru PLL_VPLL>; iommus = <&vop_mmu>; power-domains = <&power RK3576_PD_VOP>; rockchip,grf = <&sys_grf>; From 2e6b77557b2b122084920e55905bdf599de85e5a Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 21 Apr 2026 17:04:11 +0400 Subject: [PATCH 191/208] clk: rockchip: Fix fractional PLL calculation for RK3588 RK3588/RK3576 have several fractional PLLs using a delta-sigma adjustment factor (k) for the fractional component of the PLL output frequency. According to the TRM, the k is a two's complement 16-bit value, not an unsiged integer (see for example the TRM description of CRU_GPLL_CON2, and also a related U-boot fix at [1]). Adjust the code to properly handle negative k values. While at it, also fix the denominator to use the correct value of 65536, not 65535 for the full range of PLL ratios. Cc: stable@vger.kernel.org Fixes: 8f6594494b1c ("clk: rockchip: add pll type for RK3588") Link: https://lore.kernel.org/all/20231012101828.27195-1-zhangqing@rock-chips.com/ [1] Signed-off-by: Alexey Charkov --- drivers/clk/rockchip/clk-pll.c | 10 +++++----- drivers/clk/rockchip/clk.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c index 6b853800cb6bc6..23a1d4ee9ccb97 100644 --- a/drivers/clk/rockchip/clk-pll.c +++ b/drivers/clk/rockchip/clk-pll.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include "clk.h" @@ -897,7 +898,7 @@ static void rockchip_rk3588_pll_get_params(struct rockchip_clk_pll *pll, rate->s = ((pllcon >> RK3588_PLLCON1_S_SHIFT) & RK3588_PLLCON1_S_MASK); pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(2)); - rate->k = ((pllcon >> RK3588_PLLCON2_K_SHIFT) & RK3588_PLLCON2_K_MASK); + rate->k = (s16)((pllcon >> RK3588_PLLCON2_K_SHIFT) & RK3588_PLLCON2_K_MASK); } static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) @@ -913,11 +914,10 @@ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned if (cur.k) { /* fractional mode */ - u64 frac_rate64 = prate * cur.k; + s64 frac_rate64 = prate * cur.k; - postdiv = cur.p * 65535; - do_div(frac_rate64, postdiv); - rate64 += frac_rate64; + postdiv = cur.p * 65536ULL; + rate64 += div_s64(frac_rate64, postdiv); } rate64 = rate64 >> cur.s; diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index cf0f5f11c34b5a..5136bf4b5f69c8 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -638,7 +638,7 @@ struct rockchip_pll_rate_table { unsigned int m; unsigned int p; unsigned int s; - unsigned int k; + int k; }; }; }; From 41791a2015e5371e29f37c14b773dd3c50a0b292 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 21 Apr 2026 13:51:32 +0400 Subject: [PATCH 192/208] clk: rockchip: rk3576: add PLL rates for weird display clocks Add precomputed PLL parameters for the display clocks found out in the wild which can't be cleanly derived from the existing PLL rates. These have been computed in a semi-bruteforce way to solve for low overall error while preferring smaller integer dividers and multipliers, and avoiding the fractional delta-sigma K component where possible. Signed-off-by: Alexey Charkov --- drivers/clk/rockchip/clk-rk3576.c | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c index 2557358e0b9d87..48272314613ae2 100644 --- a/drivers/clk/rockchip/clk-rk3576.c +++ b/drivers/clk/rockchip/clk-rk3576.c @@ -68,13 +68,16 @@ static struct rockchip_pll_rate_table rk3576_pll_rates[] = { RK3588_PLL_RATE(1536000000, 2, 256, 1, 0), RK3588_PLL_RATE(1512000000, 2, 252, 1, 0), RK3588_PLL_RATE(1488000000, 2, 248, 1, 0), + RK3588_PLL_RATE(1478740000, 1, 123, 1, 14964), RK3588_PLL_RATE(1464000000, 2, 244, 1, 0), RK3588_PLL_RATE(1440000000, 2, 240, 1, 0), RK3588_PLL_RATE(1416000000, 2, 236, 1, 0), RK3588_PLL_RATE(1392000000, 2, 232, 1, 0), RK3588_PLL_RATE(1320000000, 2, 220, 1, 0), + RK3588_PLL_RATE(1275830000, 1, 851, 4, -29273), RK3588_PLL_RATE(1200000000, 2, 200, 1, 0), RK3588_PLL_RATE(1188000000, 2, 198, 1, 0), + RK3588_PLL_RATE(1186813000, 1, 791, 4, 13675), RK3588_PLL_RATE(1100000000, 3, 550, 2, 0), RK3588_PLL_RATE(1008000000, 2, 336, 2, 0), RK3588_PLL_RATE(1000000000, 3, 500, 2, 0), @@ -87,11 +90,43 @@ static struct rockchip_pll_rate_table rk3576_pll_rates[] = { RK3588_PLL_RATE(786000000, 1, 131, 2, 0), RK3588_PLL_RATE(785560000, 3, 392, 2, 51117), RK3588_PLL_RATE(722534400, 8, 963, 2, 24850), + RK3588_PLL_RATE(645000000, 1, 215, 3, 0), RK3588_PLL_RATE(600000000, 2, 200, 2, 0), RK3588_PLL_RATE(594000000, 2, 198, 2, 0), + RK3588_PLL_RATE(593410000, 1, 791, 5, 13981), + RK3588_PLL_RATE(593407000, 7, 173, 0, 5049), + RK3588_PLL_RATE(533250000, 4, 355, 2, 32767), + RK3588_PLL_RATE(497750000, 3, 124, 1, 28672), + RK3588_PLL_RATE(488400000, 5, 407, 2, 0), RK3588_PLL_RATE(408000000, 2, 272, 3, 0), + RK3588_PLL_RATE(368881000, 5, 307, 2, 26269), RK3588_PLL_RATE(312000000, 2, 208, 3, 0), + RK3588_PLL_RATE(304250000, 4, 811, 4, 10923), + RK3588_PLL_RATE(296703000, 5, 989, 4, 655), + RK3588_PLL_RATE(296700000, 5, 989, 4, 0), + RK3588_PLL_RATE(285500000, 3, 571, 4, 0), + RK3588_PLL_RATE(277250000, 3, 69, 1, 20480), + RK3588_PLL_RATE(262750000, 4, 701, 4, 10923), + RK3588_PLL_RATE(248880000, 1, 166, 4, -5243), + RK3588_PLL_RATE(245500000, 3, 491, 4, 0), + RK3588_PLL_RATE(241700000, 1, 81, 3, -28399), + RK3588_PLL_RATE(241500000, 1, 161, 4, 0), + RK3588_PLL_RATE(237600000, 5, 99, 1, 0), RK3588_PLL_RATE(216000000, 2, 288, 4, 0), + RK3588_PLL_RATE(193250000, 3, 773, 5, 0), + RK3588_PLL_RATE(177500000, 3, 355, 4, 0), + RK3588_PLL_RATE(174250000, 3, 87, 2, 8192), + RK3588_PLL_RATE(167000000, 3, 167, 3, 0), + RK3588_PLL_RATE(163240000, 5, 272, 3, 4369), + RK3588_PLL_RATE(148360000, 5, 989, 5, 4369), + RK3588_PLL_RATE(148352000, 1, 396, 6, -25865), + RK3588_PLL_RATE(147180000, 5, 981, 5, 13107), + RK3588_PLL_RATE(146250000, 1, 195, 5, 0), + RK3588_PLL_RATE(119000000, 3, 119, 3, 0), + RK3588_PLL_RATE(116460000, 1, 156, 5, -23593), + RK3588_PLL_RATE(108108000, 3, 865, 6, -8913), + RK3588_PLL_RATE(100700000, 2, 537, 6, 4369), + RK3588_PLL_RATE(100680000, 5, 671, 5, 13107), RK3588_PLL_RATE(96000000, 2, 256, 5, 0), { /* sentinel */ }, }; From 2498daff18ed938dec582b30d88d8b0797bfdb4b Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 15 Apr 2026 18:46:19 +0400 Subject: [PATCH 193/208] clk: rockchip: rk3576: allow dclk_vp{0,1}_src to propagate rate to parent PLL dclk_vp{0,1}_src muxes feed the display clock for Video Ports 0 and 1. With CLK_SET_RATE_NO_REPARENT the mux is locked to its current parent, but without CLK_SET_RATE_PARENT rate requests stop at the integer divider and never reach the parent PLL, making it impossible to achieve certain pixel clock frequencies. Add CLK_SET_RATE_PARENT so that when dclk_vp{0,1}_src is reparented to a programmable PLL (e.g. VPLL via assigned-clock-parents), the CCF divider can ask the PLL to retune, utilizing its fractional capabilities to obtain the exact pixel clock. This flag relies on reparenting the VP0-1 source clock to VPLL at DT level to ensure no consumer calls clk_set_rate on dclk_vp{0,1} while its parent is set to the boot-time default of GPLL, which may skew clocks for other consumers. Signed-off-by: Alexey Charkov --- drivers/clk/rockchip/clk-rk3576.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c index 48272314613ae2..39b8dbd0f2af9a 100644 --- a/drivers/clk/rockchip/clk-rk3576.c +++ b/drivers/clk/rockchip/clk-rk3576.c @@ -1137,10 +1137,10 @@ static struct rockchip_clk_branch rk3576_clk_branches[] __initdata = { RK3576_CLKGATE_CON(61), 8, GFLAGS), GATE(ACLK_VOP, "aclk_vop", "aclk_vop_root", 0, RK3576_CLKGATE_CON(61), 9, GFLAGS), - COMPOSITE(DCLK_VP0_SRC, "dclk_vp0_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VP0_SRC, "dclk_vp0_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, RK3576_CLKSEL_CON(145), 8, 3, MFLAGS, 0, 8, DFLAGS, RK3576_CLKGATE_CON(61), 10, GFLAGS), - COMPOSITE(DCLK_VP1_SRC, "dclk_vp1_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VP1_SRC, "dclk_vp1_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, RK3576_CLKSEL_CON(146), 8, 3, MFLAGS, 0, 8, DFLAGS, RK3576_CLKGATE_CON(61), 11, GFLAGS), COMPOSITE(DCLK_VP2_SRC, "dclk_vp2_src", gpll_cpll_vpll_bpll_lpll_p, CLK_SET_RATE_NO_REPARENT, From cd8a1cb660121fe8ae38cd39481d92beb615ecc1 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 20 Apr 2026 21:08:31 +0400 Subject: [PATCH 194/208] arm64: dts: rockchip: Add overlay for 4K DP and 2.5K HDMI on RK3576 Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/Makefile | 13 +++ .../dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso | 83 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index dd4899035f8acb..a7ee53efc38fba 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -167,6 +167,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-wolfvision-pf5-io-expander.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-100ask-dshanpi-a1.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-dp-4k-hdmi-2.5k.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb2-v10.dtb @@ -294,14 +295,26 @@ rk3568-wolfvision-pf5-vz-2-uhd-dtbs := rk3568-wolfvision-pf5.dtb \ rk3568-wolfvision-pf5-display-vz.dtbo \ rk3568-wolfvision-pf5-io-expander.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-dp-4k-hdmi-2.5k.dtb +rk3576-armsom-sige5-dp-4k-hdmi-2.5k-dtbs := rk3576-armsom-sige5.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtb rk3576-armsom-sige5-v1.2-wifibt-dtbs := rk3576-armsom-sige5.dtb \ rk3576-armsom-sige5-v1.2-wifibt.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-dp-4k-hdmi-2.5k.dtb +rk3576-evb1-v10-dp-4k-hdmi-2.5k-dtbs := rk3576-evb1-v10.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10-pcie1.dtb rk3576-evb1-v10-pcie1-dtbs := rk3576-evb1-v10.dtb \ rk3576-evb1-v10-pcie1.dtbo +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-dp-4k-hdmi-2.5k.dtb +rk3576-flipper-one-dp-4k-hdmi-2.5k-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ + rk3576-dp-4k-hdmi-2.5k.dtbo + dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-flipper-one-i2c2-free.dtb rk3576-flipper-one-i2c2-free-dtbs := rk3576-flipper-one-rev-f0b0c1.dtb \ rk3576-flipper-one-i2c2-free.dtbo diff --git a/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso b/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso new file mode 100644 index 00000000000000..b172c97004ce15 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3576-dp-4k-hdmi-2.5k.dtso @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * DT-overlay to switch from the default HDMI 4k and DP 2.5k mode to DP 4K and + * HDMI 2.5k mode. Should be applicable for any boards having both HDMI and DP + */ + +#include + +/dts-v1/; +/plugin/; + +&dp { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dp0_in: port@0 { + reg = <0>; + }; + }; +}; + +&dp0_in { + dp0_in_vp0: endpoint { + remote-endpoint = <&vp0_out_dp0>; + }; +}; + +&hdmi { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi_in: port@0 { + reg = <0>; + }; + }; +}; + +&hdmi_in { + hdmi_in_vp1: endpoint { + remote-endpoint = <&vp1_out_hdmi>; + }; +}; + +&vop { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vp0: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + + vp1: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + }; + }; +}; + +&vp0 { + vp0_out_dp0: endpoint@a { + reg = ; + remote-endpoint = <&dp0_in_vp0>; + }; +}; + +&vp1 { + vp1_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi_in_vp1>; + }; +}; From f3646ff71710fe4fb87d50c9e4472c0c0e7dcbed Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 19 May 2026 17:29:49 +0400 Subject: [PATCH 195/208] Bluetooth: btusb: Enable Mediatek modules to work on USB 3.0 busses MT7921U and possibly other related chips expose a different configuration on USB 3.0 vs. USB 2.0. Current driver logic only works with the USB 2.0 configuration, while connecting the module to a USB 3.0-only bus results in enumeration and scanning succeeding but connections silently failing. Add a special case for Mediatek modules on SuperSpeed busses to select the right bulk OUT endpoint (second not first) to make them work. Signed-off-by: Alexey Charkov --- drivers/bluetooth/btusb.c | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 830fefb342c6b1..437e4bec454a1c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -4090,6 +4090,49 @@ static int btusb_probe(struct usb_interface *intf, return -ENODEV; } + /* + * MediaTek MT7921U (and likely related MTK combo chips), when + * attached over the SuperSpeed lanes, present a HCI interface with + * *two* bulk OUT endpoints: the lower-numbered one is a vendor / + * diagnostic pipe that silently consumes data without ever raising + * a transfer-complete event, and the higher-numbered one is the + * actual ACL OUT. usb_find_common_endpoints() returns the first + * match, which sinks all outbound HCI traffic and leaves the device + * able to scan but unable to complete any connection. + * + * Most MT7921U deployments wire the Bluetooth function to the USB + * 2.0 differential pair and enumerate at High-Speed, where the + * descriptor only exposes a single bulk OUT and this code path is + * a no-op. + */ + if ((id->driver_info & BTUSB_MEDIATEK) && + interface_to_usbdev(intf)->speed >= USB_SPEED_SUPER) { + struct usb_host_interface *alt = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep, *second_bulk_out = NULL; + int i, bulk_out_count = 0; + + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i].desc; + if (!usb_endpoint_is_bulk_out(ep)) + continue; + if (++bulk_out_count == 2) + second_bulk_out = ep; + } + + if (bulk_out_count == 2 && second_bulk_out && + second_bulk_out != data->bulk_tx_ep) { + dev_info(&intf->dev, + "btusb: MTK: SuperSpeed: using bEP 0x%02x as ACL OUT (overrides diag bEP 0x%02x)\n", + second_bulk_out->bEndpointAddress, + data->bulk_tx_ep->bEndpointAddress); + data->bulk_tx_ep = second_bulk_out; + } else if (bulk_out_count > 2) { + dev_warn(&intf->dev, + "btusb: MTK: SuperSpeed: unexpected bulk OUT count %d, leaving default selection\n", + bulk_out_count); + } + } + if (id->driver_info & BTUSB_AMP) { data->cmdreq_type = USB_TYPE_CLASS | 0x01; data->cmdreq = 0x2b; From dc20b66c197fd57b7994b75a6217d4bd078b02c6 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 1 Jun 2026 15:22:06 +0400 Subject: [PATCH 196/208] drm/rockchip: vop2: honor TV margins from CRTC state for overscan compensation Replace the hard-coded percent values with pixel margins carried in struct rockchip_crtc_state, sourced from the standard DRM "left/right/top/bottom margin" connector properties (struct drm_connector_tv_margins) to pave way for HDMI overscan compensation support. Signed-off-by: Alexey Charkov --- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 2 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 4ea8878e10a86d..926608da21a7bc 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -10,6 +10,7 @@ #define _ROCKCHIP_DRM_DRV_H #include +#include #include #include @@ -54,6 +55,7 @@ struct rockchip_crtc_state { u32 bus_flags; int color_space; bool frl_enabled; + struct drm_connector_tv_margins tv_margins; }; #define to_rockchip_crtc_state(s) \ container_of(s, struct rockchip_crtc_state, base) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 9c802946c25022..62c0b940ef3112 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1577,30 +1577,35 @@ static void vop2_post_config(struct drm_crtc *crtc) { struct vop2_video_port *vp = to_vop2_video_port(crtc); struct vop2 *vop2 = vp->vop2; + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); struct drm_display_mode *mode = &crtc->state->adjusted_mode; + const struct drm_connector_tv_margins *m = &vcstate->tv_margins; u64 bgcolor = crtc->state->background_color; u16 vtotal = mode->crtc_vtotal; u16 hdisplay = mode->crtc_hdisplay; u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start; u16 vdisplay = mode->crtc_vdisplay; u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start; - u32 left_margin = 100, right_margin = 100; - u32 top_margin = 100, bottom_margin = 100; - u16 hsize = hdisplay * (left_margin + right_margin) / 200; - u16 vsize = vdisplay * (top_margin + bottom_margin) / 200; + u16 hsize = hdisplay; + u16 vsize = vdisplay; u16 hact_end, vact_end; u32 val; vop2->ops->setup_bg_dly(vp); + if (m->left + m->right < hdisplay) + hsize = hdisplay - m->left - m->right; + if (m->top + m->bottom < vdisplay) + vsize = vdisplay - m->top - m->bottom; + vsize = rounddown(vsize, 2); hsize = rounddown(hsize, 2); - hact_st += hdisplay * (100 - left_margin) / 200; + hact_st += m->left; hact_end = hact_st + hsize; val = hact_st << 16; val |= hact_end; vop2_vp_write(vp, RK3568_VP_POST_DSP_HACT_INFO, val); - vact_st += vdisplay * (100 - top_margin) / 200; + vact_st += m->top; vact_end = vact_st + vsize; val = vact_st << 16; val |= vact_end; From 1309c21701fe029f4a35ae78d0fbfc974626d3ba Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 2 Jun 2026 16:58:06 +0400 Subject: [PATCH 197/208] drm/rockchip: dw_hdmi_qp: expose "overscan" property Expose the "overscan" connector property as recognized by KWin and the likes to compensate for TV overscan cropping. The CRTC will use the margin values derived from this overscan percentage in its post-composition scaler to add appropriate blank margins on all sides of the output image so that the TV doesn't eat up visible content. Signed-off-by: Alexey Charkov --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 863e9238f2373b..4947b368bea040 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -158,13 +158,21 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_connector_state *conn_state) { const struct drm_display_info *info = &conn_state->connector->display_info; + const struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder); struct dw_hdmi_qp_link_cfg *lcfg = &hdmi->link_cfg; union phy_configure_opts phy_cfg = {}; + unsigned int overscan; enum phy_hdmi_mode mode; int ret; + overscan = min(conn_state->tv.overscan, 100u); + s->tv_margins.left = adj_mode->hdisplay * overscan / 200; + s->tv_margins.right = s->tv_margins.left; + s->tv_margins.top = adj_mode->vdisplay * overscan / 200; + s->tv_margins.bottom = s->tv_margins.top; + if (lcfg->tmds_char_rate == conn_state->hdmi.tmds_char_rate && s->output_bpc == conn_state->hdmi.output_bpc) return 0; @@ -768,6 +776,14 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, PTR_ERR(hdmi->connector), "Failed to init bridge connector\n"); + ret = drm_mode_create_tv_properties_legacy(drm, 0, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Failed to create TV connector properties\n"); + + drm_object_attach_property(&hdmi->connector->base, + drm->mode_config.tv_overscan_property, 0); + ret = drm_connector_attach_encoder(hdmi->connector, encoder); if (ret) return dev_err_probe(dev, ret, "Failed to attach connector\n"); From c32805cd378fa91ffdef9a35580643a4709bc96a Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 10 Apr 2026 00:53:11 +0400 Subject: [PATCH 198/208] ASoC: codecs: nau8822: add support for speaker gain boost setting The speaker amplifier can be supplied by a higher voltage than the rest of the codec, namely up to 5V. When the speaker supply voltage is above 3.6V, the speaker gain boost setting should be enabled to prevent distortion on speaker and AUX pins. Enable gain boost setting based on the reading of the VDDSPK supply regulator's voltage. Signed-off-by: Alexey Charkov --- sound/soc/codecs/nau8822.c | 19 ++++++++++++++++++- sound/soc/codecs/nau8822.h | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index cd4a7de47939b6..535e43f0c26a11 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -10,6 +10,7 @@ // // Based on WM8974.c +#include "linux/regulator/consumer.h" #include #include #include @@ -1168,7 +1169,7 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct nau8822 *nau8822 = dev_get_platdata(dev); - int ret, i; + int ret, i, vddspk; if (!nau8822) { nau8822 = devm_kzalloc(dev, sizeof(*nau8822), GFP_KERNEL); @@ -1189,6 +1190,11 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(dev, ret, "Failed to get regulators\n"); + vddspk = regulator_get_voltage(nau8822->supplies[SUPPLY_VDDSPK].consumer); + if (vddspk < 0 && vddspk != -ENODEV) + return dev_err_probe(dev, vddspk, + "Failed to get VDDSPK voltage\n"); + nau8822->regmap = devm_regmap_init_i2c(i2c, &nau8822_regmap_config); if (IS_ERR(nau8822->regmap)) { ret = PTR_ERR(nau8822->regmap); @@ -1210,6 +1216,17 @@ static int nau8822_i2c_probe(struct i2c_client *i2c) goto err_reg; } + if (vddspk > 3600000) { + ret = regmap_update_bits(nau8822->regmap, + NAU8822_REG_OUTPUT_CONTROL, + NAU8822_SPKBST | + NAU8822_AUX2BST | + NAU8822_AUX1BST, 0x7); + if (ret != 0) + return dev_err_probe(dev, ret, + "Failed to update gain boost control\n"); + } + ret = devm_snd_soc_register_component(dev, &soc_component_dev_nau8822, &nau8822_dai, 1); if (ret != 0) { diff --git a/sound/soc/codecs/nau8822.h b/sound/soc/codecs/nau8822.h index 24799c7b5931b8..cefc2c3feca28d 100644 --- a/sound/soc/codecs/nau8822.h +++ b/sound/soc/codecs/nau8822.h @@ -196,6 +196,15 @@ #define NAU8822_RAUXSMUT 0x01 +/* NAU8822_REG_OUTPUT_CONTROL (0x31) */ +#define NAU8822_AOUTIMP (1 << 0) +#define NAU8822_TSEN (1 << 1) +#define NAU8822_SPKBST (1 << 2) +#define NAU8822_AUX2BST (1 << 3) +#define NAU8822_AUX1BST (1 << 4) +#define NAU8822_RDACLMX (1 << 5) +#define NAU8822_LDACLMX (1 << 6) + /* System Clock Source */ enum { NAU8822_CLK_MCLK, @@ -211,7 +220,13 @@ struct nau8822_pll { int freq_out; }; -#define NAU8822_NUM_SUPPLIES 4 +enum { + SUPPLY_VDDA = 0, + SUPPLY_VDDB, + SUPPLY_VDDC, + SUPPLY_VDDSPK, + NAU8822_NUM_SUPPLIES +}; /* Codec Private Data */ struct nau8822 { From 9e19452179f01b82416e01d84a84b5d2d85935a7 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 10 Jun 2026 14:13:06 +0400 Subject: [PATCH 199/208] arm64: dts: rockchip: Revert SD PWREN to SoC default function on Flipper One Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts index ad1d08f7fd2a6b..333647e2ab09ef 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts @@ -1525,8 +1525,6 @@ disable-wp; no-sdio; no-mmc; - pinctrl-names = "default"; - pinctrl-0 = <&sdmmc0_clk &sdmmc0_cmd &sdmmc0_det &sdmmc0_bus4>; sd-uhs-sdr104; vmmc-supply = <&vcc_3v3_s3>; vqmmc-supply = <&vccio_3v3_sd_s0>; From 0a148c9c1d620d2aa886354af60edf540e92088e Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 10 Jun 2026 16:23:12 +0400 Subject: [PATCH 200/208] arm64: dts: rockchip: Switch Flipper One PD revision to 3.1 FUSB302 only provides the data layer, and the PD protocol layer is handled by Linux TCPM code. As the data layer didn't change from PD 2.0 to PD 3.1, switch to PD 3.1 to match the actual capability of the device. Signed-off-by: Alexey Charkov --- arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts index 333647e2ab09ef..5ba517a700ea93 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-flipper-one-rev-f0b0c1.dts @@ -961,8 +961,7 @@ label = "USB-C"; data-role = "dual"; op-sink-microwatt = <10000000>; - /* fusb302 supports PD Rev 2.0 Ver 1.2 */ - pd-revision = /bits/ 8 <0x2 0x0 0x1 0x2>; + pd-revision = /bits/ 8 <0x3 0x1 0x1 0x8>; power-role = "dual"; self-powered; sink-pdos = Date: Fri, 22 May 2026 09:11:57 -0700 Subject: [PATCH 201/208] phy: rockchip: inno-csidphy: add support for RK3576 The RK3576 has two Innosilicon MIPI CSI D-PHY instances that use the same register layout as the RK3588 variant (THS-settle at offset 0x160, calibration at 0x168, GRF at offset 0x0). Add the compatible string and driver data for these PHY instances. Signed-off-by: Jason Devers --- drivers/phy/rockchip/phy-rockchip-inno-csidphy.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c index c79fb53d8ee5c5..c88ca713d76dc6 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c @@ -403,6 +403,17 @@ static const struct dphy_drv_data rk3568_mipidphy_drv_data = { .resets_num = ARRAY_SIZE(rk3368_reset_names), }; +static const struct dphy_drv_data rk3576_mipidphy_drv_data = { + .pwrctl_offset = -1, + .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, + .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN, + .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges, + .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges), + .grf_regs = rk3588_grf_dphy_regs, + .resets = rk3588_reset_names, + .resets_num = ARRAY_SIZE(rk3588_reset_names), +}; + static const struct dphy_drv_data rk3588_mipidphy_drv_data = { .pwrctl_offset = -1, .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE, @@ -435,6 +446,10 @@ static const struct of_device_id rockchip_inno_csidphy_match_id[] = { .compatible = "rockchip,rk3568-csi-dphy", .data = &rk3568_mipidphy_drv_data, }, + { + .compatible = "rockchip,rk3576-csi-dphy", + .data = &rk3576_mipidphy_drv_data, + }, { .compatible = "rockchip,rk3588-csi-dphy", .data = &rk3588_mipidphy_drv_data, From a9de343cf0ef634509125fdd3fa6bd8e141fd458 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Fri, 22 May 2026 09:12:02 -0700 Subject: [PATCH 202/208] media: synopsys: dw-mipi-csi2rx: add support for RK3576 The RK3576 uses the same Synopsys DesignWare MIPI CSI-2 Host controller IP as the RK3568. Add the compatible string using the existing RK3568 driver data. Signed-off-by: Jason Devers --- drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c index 02eb4a6cafadea..57dabb88ca16c7 100644 --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c @@ -839,6 +839,10 @@ static const struct of_device_id dw_mipi_csi2rx_of_match[] = { .compatible = "fsl,imx93-mipi-csi2", .data = &imx93_drvdata, }, + { + .compatible = "rockchip,rk3576-mipi-csi2", + .data = &rk3568_drvdata, + }, { .compatible = "rockchip,rk3568-mipi-csi2", .data = &rk3568_drvdata, From f84ad1b005ba42b874d4dc55343e21e8fadf7b52 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Fri, 22 May 2026 09:12:08 -0700 Subject: [PATCH 203/208] media: rockchip: rkcif: add support for RK3576 VICAP The RK3576 Video Capture (VICAP) unit features five MIPI CSI-2 capture interfaces (compared to six on RK3588). The register layout is identical to the RK3588 variant. Add the compatible string and match data with mipi_num set to 5. Signed-off-by: Jason Devers --- .../rockchip/rkcif/rkcif-capture-mipi.c | 81 +++++++++++++++++++ .../rockchip/rkcif/rkcif-capture-mipi.h | 1 + .../media/platform/rockchip/rkcif/rkcif-dev.c | 18 +++++ 3 files changed, 100 insertions(+) diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c index aa70d3e9db047f..b255a744312bd7 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c @@ -531,6 +531,87 @@ rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream, return ctrl0; } +const struct rkcif_mipi_match_data rkcif_rk3576_vicap_mipi_match_data = { + .mipi_num = 5, + .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, + .regs = { + [RKCIF_MIPI_CTRL] = 0x20, + [RKCIF_MIPI_INTEN] = 0x74, + [RKCIF_MIPI_INTSTAT] = 0x78, + }, + .regs_id = { + [RKCIF_ID0] = { + [RKCIF_MIPI_CTRL0] = 0x00, + [RKCIF_MIPI_CTRL1] = 0x04, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x8c, + }, + [RKCIF_ID1] = { + [RKCIF_MIPI_CTRL0] = 0x08, + [RKCIF_MIPI_CTRL1] = 0x0c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x38, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x40, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x48, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x3c, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x44, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x90, + }, + [RKCIF_ID2] = { + [RKCIF_MIPI_CTRL0] = 0x10, + [RKCIF_MIPI_CTRL1] = 0x14, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x4c, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x54, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x5c, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x50, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x58, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x94, + }, + [RKCIF_ID3] = { + [RKCIF_MIPI_CTRL0] = 0x18, + [RKCIF_MIPI_CTRL1] = 0x1c, + [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x60, + [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x68, + [RKCIF_MIPI_FRAME0_VLW_Y] = 0x70, + [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x64, + [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x6c, + [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, + [RKCIF_MIPI_CROP_START] = 0x98, + }, + }, + .blocks = { + { + .offset = 0x100, + }, + { + .offset = 0x200, + }, + { + .offset = 0x300, + }, + { + .offset = 0x400, + }, + { + .offset = 0x500, + }, + }, +}; + const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = { .mipi_num = 6, .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h index 7edaca44f653ca..4e17cbc04b76e9 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h @@ -13,6 +13,7 @@ #include "rkcif-common.h" extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data; +extern const struct rkcif_mipi_match_data rkcif_rk3576_vicap_mipi_match_data; extern const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data; int rkcif_mipi_register(struct rkcif_device *rkcif); diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c index 86575d398f807f..cfd6115c815dec 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c @@ -53,6 +53,20 @@ static const struct rkcif_match_data rk3568_vicap_match_data = { .mipi = &rkcif_rk3568_vicap_mipi_match_data, }; +static const char *const rk3576_vicap_clks[] = { + "aclk", + "hclk", + "dclk", + "iclk_host0", + "iclk_host1", +}; + +static const struct rkcif_match_data rk3576_vicap_match_data = { + .clks = rk3576_vicap_clks, + .clks_num = ARRAY_SIZE(rk3576_vicap_clks), + .mipi = &rkcif_rk3576_vicap_mipi_match_data, +}; + static const char *const rk3588_vicap_clks[] = { "aclk", "hclk", @@ -76,6 +90,10 @@ static const struct of_device_id rkcif_plat_of_match[] = { .compatible = "rockchip,rk3568-vicap", .data = &rk3568_vicap_match_data, }, + { + .compatible = "rockchip,rk3576-vicap", + .data = &rk3576_vicap_match_data, + }, { .compatible = "rockchip,rk3588-vicap", .data = &rk3588_vicap_match_data, From c1e5a2cc3f92fd834f9606ef51e384ce8f583ae3 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Fri, 22 May 2026 09:12:15 -0700 Subject: [PATCH 204/208] arm64: dts: rockchip: add camera pipeline nodes to rk3576 Add device tree nodes for the RK3576 camera capture pipeline: - Two Innosilicon CSI D-PHY instances (csi_dphy0, csi_dphy1) - Two MIPI D-PHY GRF syscon nodes (mipidphy0_grf, mipidphy1_grf) - Five MIPI CSI-2 receiver nodes (csi0 through csi4) - Video Capture (VICAP) unit with five MIPI input ports - VICAP IOMMU The CSI hosts are connected to the VICAP ports and wired to the Innosilicon D-PHY instances. All nodes are disabled by default and must be enabled by board device trees that have camera connectors. Signed-off-by: Jason Devers --- arch/arm64/boot/dts/rockchip/rk3576.dtsi | 266 +++++++++++++++++++++++ 1 file changed, 266 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index 239196b137aef0..0dea3ec6df8700 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -988,11 +988,22 @@ reg = <0x0 0x26038000 0x0 0x1000>; }; + mipidphy0_grf: syscon@2603a000 { + compatible = "rockchip,rk3576-csidphy-grf", "syscon"; + reg = <0x0 0x2603a000 0x0 0x2000>; + clocks = <&cru PCLK_PMUPHY_ROOT>; + }; + ioc_grf: syscon@26040000 { compatible = "rockchip,rk3576-ioc-grf", "syscon", "simple-mfd"; reg = <0x0 0x26040000 0x0 0xc000>; }; + mipidphy1_grf: syscon@2604c000 { + compatible = "rockchip,rk3576-csidphy-grf", "syscon"; + reg = <0x0 0x2604c000 0x0 0x2000>; + }; + cru: clock-controller@27200000 { compatible = "rockchip,rk3576-cru"; reg = <0x0 0x27200000 0x0 0x50000>; @@ -1355,6 +1366,235 @@ #iommu-cells = <0>; }; + vicap: video-capture@27c10000 { + compatible = "rockchip,rk3576-vicap"; + reg = <0x0 0x27c10000 0x0 0x800>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, + <&cru DCLK_VICAP>, <&cru ICLK_CSIHOST0>, + <&cru ICLK_CSIHOST01>; + clock-names = "aclk", "hclk", "dclk", "iclk_host0", "iclk_host1"; + iommus = <&vicap_mmu>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, + <&cru SRST_D_VICAP>, <&cru SRST_VICAP_I0CLK>, + <&cru SRST_VICAP_I1CLK>, <&cru SRST_VICAP_I2CLK>, + <&cru SRST_VICAP_I3CLK>, <&cru SRST_VICAP_I4CLK>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vicap_mipi0: port@1 { + reg = <1>; + + vicap_mipi0_input: endpoint { + remote-endpoint = <&csi0_output>; + }; + }; + + vicap_mipi1: port@2 { + reg = <2>; + + vicap_mipi1_input: endpoint { + remote-endpoint = <&csi1_output>; + }; + }; + + vicap_mipi2: port@3 { + reg = <3>; + + vicap_mipi2_input: endpoint { + remote-endpoint = <&csi2_output>; + }; + }; + + vicap_mipi3: port@4 { + reg = <4>; + + vicap_mipi3_input: endpoint { + remote-endpoint = <&csi3_output>; + }; + }; + + vicap_mipi4: port@5 { + reg = <5>; + + vicap_mipi4_input: endpoint { + remote-endpoint = <&csi4_output>; + }; + }; + }; + }; + + vicap_mmu: iommu@27c10800 { + compatible = "rockchip,rk3576-iommu", "rockchip,rk3568-iommu"; + reg = <0x0 0x27c10800 0x0 0x100>; + interrupts = ; + clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>; + clock-names = "aclk", "iface"; + power-domains = <&power RK3576_PD_VI>; + rockchip,disable-mmu-reset; + #iommu-cells = <0>; + status = "disabled"; + }; + + csi0: csi@27c80000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27c80000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_0>, <&cru ICLK_CSIHOST0>; + clock-names = "pclk", "iclk"; + phys = <&csi_dphy0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi0_in: port@0 { + reg = <0>; + }; + + csi0_out: port@1 { + reg = <1>; + + csi0_output: endpoint { + remote-endpoint = <&vicap_mipi0_input>; + }; + }; + }; + }; + + csi1: csi@27c90000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27c90000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_1>; + clock-names = "pclk"; + phys = <&csi_dphy0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi1_in: port@0 { + reg = <0>; + }; + + csi1_out: port@1 { + reg = <1>; + + csi1_output: endpoint { + remote-endpoint = <&vicap_mipi1_input>; + }; + }; + }; + }; + + csi2: csi@27ca0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27ca0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_2>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi2_in: port@0 { + reg = <0>; + }; + + csi2_out: port@1 { + reg = <1>; + + csi2_output: endpoint { + remote-endpoint = <&vicap_mipi2_input>; + }; + }; + }; + }; + + csi3: csi@27cb0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27cb0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_3>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_3>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi3_in: port@0 { + reg = <0>; + }; + + csi3_out: port@1 { + reg = <1>; + + csi3_output: endpoint { + remote-endpoint = <&vicap_mipi3_input>; + }; + }; + }; + }; + + csi4: csi@27cc0000 { + compatible = "rockchip,rk3576-mipi-csi2", "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0x27cc0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI_HOST_4>; + clock-names = "pclk"; + phys = <&csi_dphy1>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSI_HOST_4>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi4_in: port@0 { + reg = <0>; + }; + + csi4_out: port@1 { + reg = <1>; + + csi4_output: endpoint { + remote-endpoint = <&vicap_mipi4_input>; + }; + }; + }; + }; + vop: vop@27d00000 { compatible = "rockchip,rk3576-vop"; reg = <0x0 0x27d00000 0x0 0x3000>, <0x0 0x27d05000 0x0 0x1000>; @@ -2974,6 +3214,19 @@ status = "disabled"; }; + csi_dphy0: phy@2b030000 { + compatible = "rockchip,rk3576-csi-dphy"; + reg = <0x0 0x2b030000 0x0 0x8000>; + clocks = <&cru PCLK_CSIDPHY>; + clock-names = "pclk"; + #phy-cells = <0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSIPHY>, <&cru SRST_SCAN_CSIPHY>; + reset-names = "apb", "phy"; + rockchip,grf = <&mipidphy0_grf>; + status = "disabled"; + }; + combphy0_ps: phy@2b050000 { compatible = "rockchip,rk3576-naneng-combphy"; reg = <0x0 0x2b050000 0x0 0x100>; @@ -3010,6 +3263,19 @@ status = "disabled"; }; + csi_dphy1: phy@2b070000 { + compatible = "rockchip,rk3576-csi-dphy"; + reg = <0x0 0x2b070000 0x0 0x8000>; + clocks = <&cru PCLK_CSIDPHY1>; + clock-names = "pclk"; + #phy-cells = <0>; + power-domains = <&power RK3576_PD_VI>; + resets = <&cru SRST_P_CSIDPHY1>, <&cru SRST_SCAN_CSIDPHY1>; + reset-names = "apb", "phy"; + rockchip,grf = <&mipidphy1_grf>; + status = "disabled"; + }; + usbdp_phy: phy@2b010000 { compatible = "rockchip,rk3576-usbdp-phy"; reg = <0x0 0x2b010000 0x0 0x10000>; From 7ee1920f7590af0be7484312a900ccaf34d65d67 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Sat, 23 May 2026 13:40:01 -0700 Subject: [PATCH 205/208] dt-bindings: phy: rockchip-inno-csi-dphy: add rk3576 support Add rockchip,rk3576-csi-dphy to the compatible enum. The RK3576 has two Innosilicon CSI D-PHY instances and follows the same two-reset pattern (apb, phy) as the existing rk3588-csi-dphy entry. Signed-off-by: Jason Devers --- .../devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml index 03950b3cad08c1..243863d62d8312 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml +++ b/Documentation/devicetree/bindings/phy/rockchip-inno-csi-dphy.yaml @@ -21,6 +21,7 @@ properties: - rockchip,rk3326-csi-dphy - rockchip,rk3368-csi-dphy - rockchip,rk3568-csi-dphy + - rockchip,rk3576-csi-dphy - rockchip,rk3588-csi-dphy reg: From 7a72cdfe1852214abe7e173a67b87d81176826e4 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Sat, 23 May 2026 13:40:08 -0700 Subject: [PATCH 206/208] dt-bindings: media: rockchip,rk3568-mipi-csi2: add rk3576 support Add rockchip,rk3576-mipi-csi2 as a compatible that falls back to rockchip,rk3568-mipi-csi2, following the same pattern as the existing rk3588-mipi-csi2 entry. Signed-off-by: Jason Devers --- .../devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml index aa9a41927410bb..836c1301d05f42 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -20,6 +20,7 @@ properties: - const: fsl,imx93-mipi-csi2 - items: - enum: + - rockchip,rk3576-mipi-csi2 - rockchip,rk3588-mipi-csi2 - const: rockchip,rk3568-mipi-csi2 - const: rockchip,rk3568-mipi-csi2 From 17e41c20b0ccbbded22fcde03217eb7906ce7e5b Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Mon, 1 Jun 2026 10:30:58 -0700 Subject: [PATCH 207/208] dt-bindings: media: rockchip,rk3588-vicap: add rk3576 support The RK3576 VICAP uses the same register layout and clock setup as the RK3588 one, with two differences: it has five MIPI CSI-2 input ports instead of six and no DVP parallel port. Extend the existing RK3588 binding rather than adding a separate file: add the rockchip,rk3576-vicap compatible and an allOf/if-then block that drops the DVP port@0 and the sixth MIPI port@6, and caps resets at eight, when that compatible is used. Signed-off-by: Jason Devers --- .../bindings/media/rockchip,rk3588-vicap.yaml | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml index 0911a6b07d1819..5ffd41054491c6 100644 --- a/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,rk3588-vicap.yaml @@ -4,21 +4,23 @@ $id: http://devicetree.org/schemas/media/rockchip,rk3588-vicap.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Rockchip RK3588 Video Capture (VICAP) +title: Rockchip RK3576 and RK3588 Video Capture (VICAP) maintainers: - Michael Riesch description: - The Rockchip RK3588 Video Capture (VICAP) block features a digital video - port (DVP, a parallel video interface) and six MIPI CSI-2 ports. It receives - the data from camera sensors, video decoders, or other companion ICs and - transfers it into system main memory by AXI bus and/or passes it the image - signal processing (ISP) blocks. + The Rockchip Video Capture (VICAP) block receives data from camera sensors, + video decoders, or other companion ICs and transfers it into system main + memory by AXI bus and/or passes it to the image signal processing (ISP) + blocks. On RK3588 it features a digital video port (DVP, a parallel video + interface) and six MIPI CSI-2 ports. RK3576 has no DVP and five MIPI CSI-2 + ports. properties: compatible: enum: + - rockchip,rk3576-vicap - rockchip,rk3588-vicap reg: @@ -63,7 +65,8 @@ properties: port@0: $ref: /schemas/graph.yaml#/$defs/port-base unevaluatedProperties: false - description: The digital video port (DVP, a parallel video interface). + description: + The digital video port (DVP, a parallel video interface). RK3588 only. properties: endpoint: @@ -124,7 +127,8 @@ properties: port@6: $ref: /schemas/graph.yaml#/properties/port - description: Port connected to the MIPI CSI-2 receiver 5 output. + description: + Port connected to the MIPI CSI-2 receiver 5 output. RK3588 only. properties: endpoint: @@ -138,6 +142,21 @@ required: - clocks - ports +allOf: + - if: + properties: + compatible: + contains: + const: rockchip,rk3576-vicap + then: + properties: + resets: + maxItems: 8 + ports: + properties: + port@0: false + port@6: false + additionalProperties: false examples: From 56c5e08d13b86ecb9a9d929ef40d165ce620f115 Mon Sep 17 00:00:00 2001 From: Jason Devers Date: Mon, 1 Jun 2026 10:57:05 -0700 Subject: [PATCH 208/208] dt-bindings: soc: rockchip: grf: add rk3576 CSI D-PHY GRF The RK3576 has two GRF blocks for its Innosilicon CSI D-PHY instances, analogous to the rk3588-csidphy-grf. Document the compatible so the rk3576 camera pipeline nodes validate. Signed-off-by: Jason Devers --- Documentation/devicetree/bindings/soc/rockchip/grf.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml index 2cc43742b8e3b8..1dc4f0844517d7 100644 --- a/Documentation/devicetree/bindings/soc/rockchip/grf.yaml +++ b/Documentation/devicetree/bindings/soc/rockchip/grf.yaml @@ -32,6 +32,7 @@ properties: - rockchip,rk3568-usb2phy-grf - rockchip,rk3576-bigcore-grf - rockchip,rk3576-cci-grf + - rockchip,rk3576-csidphy-grf - rockchip,rk3576-dcphy-grf - rockchip,rk3576-gpu-grf - rockchip,rk3576-hdptxphy-grf