From ef8e7f32bccc433309cd1df44a9887a7ac1f2d7e Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sun, 8 Feb 2026 08:49:00 -0800 Subject: [PATCH 001/188] ntb_hw_amd: Fix incorrect debug message in link disable path amd_ntb_link_disable() prints "Enabling Link" which is misleading. Update the message to reflect that the link is being disabled. Signed-off-by: Alok Tiwari Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index 1a163596ddf54..df9150669ad3a 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -376,7 +376,7 @@ static int amd_ntb_link_disable(struct ntb_dev *ntb) if (ndev->ntb.topo == NTB_TOPO_SEC) return -EINVAL; - dev_dbg(&ntb->pdev->dev, "Enabling Link.\n"); + dev_dbg(&ntb->pdev->dev, "Disabling Link.\n"); return 0; } From dcf1125795923caff840d0c224c2aaa54f4e410c Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 4 Mar 2026 11:05:27 +0900 Subject: [PATCH 002/188] NTB: epf: Avoid pci_iounmap() with offset when PEER_SPAD and CONFIG share BAR When BAR_PEER_SPAD and BAR_CONFIG share one PCI BAR, the module teardown path ends up calling pci_iounmap() on the same iomem with some offset, which is unnecessary and triggers a kernel warning like the following: Trying to vunmap() nonexistent vm area (0000000069a5ffe8) WARNING: mm/vmalloc.c:3470 at vunmap+0x58/0x68, CPU#5: modprobe/2937 [...] Call trace: vunmap+0x58/0x68 (P) iounmap+0x34/0x48 pci_iounmap+0x2c/0x40 ntb_epf_pci_remove+0x44/0x80 [ntb_hw_epf] pci_device_remove+0x48/0xf8 device_remove+0x50/0x88 device_release_driver_internal+0x1c8/0x228 driver_detach+0x50/0xb0 bus_remove_driver+0x74/0x100 driver_unregister+0x34/0x68 pci_unregister_driver+0x34/0xa0 ntb_epf_pci_driver_exit+0x14/0xfe0 [ntb_hw_epf] [...] Fix it by unmapping only when PEER_SPAD and CONFIG use difference bars. Cc: stable@vger.kernel.org Fixes: e75d5ae8ab88 ("NTB: epf: Allow more flexibility in the memory BAR map method") Reviewed-by: Frank Li Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/epf/ntb_hw_epf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index af57554728429..c47607d4f8a7f 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -729,7 +729,8 @@ static void ntb_epf_deinit_pci(struct ntb_epf_dev *ndev) struct pci_dev *pdev = ndev->ntb.pdev; pci_iounmap(pdev, ndev->ctrl_reg); - pci_iounmap(pdev, ndev->peer_spad_reg); + if (ndev->barno_map[BAR_PEER_SPAD] != ndev->barno_map[BAR_CONFIG]) + pci_iounmap(pdev, ndev->peer_spad_reg); pci_iounmap(pdev, ndev->db_reg); pci_release_regions(pdev); From a51f1fc66ec8aad75a9aa2cf20640d141b533721 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 11 Mar 2026 22:14:15 -0700 Subject: [PATCH 003/188] NTB: fix kernel-doc warnings in ntb.h Correct a function name and function parameter name to avoid kernel-doc warnings: Warning: include/linux/ntb.h:575 expecting prototype for ntb_default_port_count(). Prototype was for ntb_default_peer_port_count() instead Warning: include/linux/ntb.h:590 function parameter 'pidx' not described in 'ntb_default_peer_port_number' Signed-off-by: Randy Dunlap Acked-by: Dave Jiang Signed-off-by: Jon Mason --- include/linux/ntb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ntb.h b/include/linux/ntb.h index 879c3e89e026f..ae72caa038597 100644 --- a/include/linux/ntb.h +++ b/include/linux/ntb.h @@ -567,7 +567,7 @@ void ntb_msg_event(struct ntb_dev *ntb); int ntb_default_port_number(struct ntb_dev *ntb); /** - * ntb_default_port_count() - get the default number of peer device ports + * ntb_default_peer_port_count() - get the default number of peer device ports * @ntb: NTB device context. * * By default hardware driver supports just one peer device. @@ -582,7 +582,7 @@ int ntb_default_peer_port_count(struct ntb_dev *ntb); /** * ntb_default_peer_port_number() - get the default peer port by given index * @ntb: NTB device context. - * @idx: Peer port index (should not differ from zero). + * @pidx: Peer port index (should not differ from zero). * * By default hardware driver supports just one peer device, so this method * shall return the corresponding value from enum ntb_default_port. From 9cebe6f6baef705b73810a107b99c7fcb7159f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 7 May 2026 09:41:01 +0200 Subject: [PATCH 004/188] ntb: amd: Use named initializer for pci_device_id::driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current list initialisation depends on the well hidden two zeros in the PCI_VDEVICE macro. Instead use a named initialisation that is more robust and easier to understand. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index df9150669ad3a..36bee0d8ac6cd 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -1328,15 +1328,15 @@ static const struct ntb_dev_data dev_data[] = { }; static const struct pci_device_id amd_ntb_pci_tbl[] = { - { PCI_VDEVICE(AMD, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { PCI_VDEVICE(AMD, 0x148b), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c0), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c3), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x155a), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d4), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d7), (kernel_ulong_t)&dev_data[2] }, - { PCI_VDEVICE(HYGON, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { 0, } + { PCI_VDEVICE(AMD, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { PCI_VDEVICE(AMD, 0x148b), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c0), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c3), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x155a), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d4), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d7), .driver_data = (kernel_ulong_t)&dev_data[2] }, + { PCI_VDEVICE(HYGON, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { } }; MODULE_DEVICE_TABLE(pci, amd_ntb_pci_tbl); From 40b19af2ef1373e389f2e2645d668226abac421d Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 28 Jun 2026 12:01:31 -0700 Subject: [PATCH 005/188] Linux 7.2-rc1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 12fd3d2d47b58..b9c5792c79e01 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 7 -PATCHLEVEL = 1 +PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = +EXTRAVERSION = -rc1 NAME = Baby Opossum Posse # *DOCUMENTATION* From d5785a4992833570a6ea60ec4a5c7282e6534fce Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Tue, 3 Feb 2026 15:46:32 +0800 Subject: [PATCH 006/188] FROMLIST: arm64: dts: qcom: sm8750: enable ETR and CTCU devices Embedded Trace Router(ETR) is working as a DDR memory sink to collect tracing data from source device and the CTCU device serves as the control unit for the ETR device. Link: https://lore.kernel.org/all/20260204-enable-ctcu-and-etr-v3-3-0bb95c590ae1@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Signed-off-by: Jie Gan --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 177 +++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index fafed417c66fc..20b483a73fa34 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -4106,6 +4106,35 @@ #reset-cells = <1>; }; + ctcu@10001000 { + compatible = "qcom,sm8750-ctcu", "qcom,sa8775p-ctcu"; + reg = <0x0 0x10001000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb"; + + in-ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ctcu_in0: endpoint { + remote-endpoint = <&etr0_out>; + }; + }; + + port@1 { + reg = <1>; + + ctcu_in1: endpoint { + remote-endpoint = <&etr1_out>; + }; + }; + }; + }; + stm@10002000 { compatible = "arm,coresight-stm", "arm,primecell"; reg = <0x0 0x10002000 0x0 0x1000>, @@ -4219,6 +4248,122 @@ }; }; + replicator@10046000 { + compatible = "arm,coresight-dynamic-replicator", "arm,primecell"; + reg = <0x0 0x10046000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + in-ports { + port { + qdss_rep_in: endpoint { + remote-endpoint = <&swao_rep_out0>; + }; + }; + }; + + out-ports { + port { + qdss_rep_out0: endpoint { + remote-endpoint = <&etr_rep_in>; + }; + }; + }; + }; + + tmc@10048000 { + compatible = "arm,coresight-tmc", "arm,primecell"; + reg = <0x0 0x10048000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + iommus = <&apps_smmu 0x04e0 0x0>; + arm,scatter-gather; + + in-ports { + port { + etr0_in: endpoint { + remote-endpoint = <&etr_rep_out0>; + }; + }; + }; + + out-ports { + port { + etr0_out: endpoint { + remote-endpoint = <&ctcu_in0>; + }; + }; + }; + }; + + replicator@1004e000 { + compatible = "arm,coresight-dynamic-replicator", "arm,primecell"; + reg = <0x0 0x1004e000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + in-ports { + port { + etr_rep_in: endpoint { + remote-endpoint = <&qdss_rep_out0>; + }; + }; + }; + + out-ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + etr_rep_out0: endpoint { + remote-endpoint = <&etr0_in>; + }; + }; + + port@1 { + reg = <1>; + + etr_rep_out1: endpoint { + remote-endpoint = <&etr1_in>; + }; + }; + }; + }; + + tmc_etr1: tmc@1004f000 { + compatible = "arm,coresight-tmc", "arm,primecell"; + reg = <0x0 0x1004f000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + iommus = <&apps_smmu 0x0500 0x0>; + arm,scatter-gather; + arm,buffer-size = <0x400000>; + + in-ports { + port { + etr1_in: endpoint { + remote-endpoint = <&etr_rep_out1>; + }; + }; + }; + + out-ports { + port { + etr1_out: endpoint { + remote-endpoint = <&ctcu_in1>; + }; + }; + }; + }; + tpdm@10800000 { compatible = "qcom,coresight-tpdm", "arm,primecell"; reg = <0x0 0x10800000 0x0 0x1000>; @@ -4889,6 +5034,38 @@ }; }; }; + + out-ports { + port { + tmc_etf_out: endpoint { + remote-endpoint = <&swao_rep_in>; + }; + }; + }; + }; + + replicator@10b06000 { + compatible = "arm,coresight-dynamic-replicator", "arm,primecell"; + reg = <0x0 0x10b06000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + in-ports { + port { + swao_rep_in: endpoint { + remote-endpoint = <&tmc_etf_out>; + }; + }; + }; + + out-ports { + port { + swao_rep_out0: endpoint { + remote-endpoint = <&qdss_rep_in>; + }; + }; + }; }; tpda@10b08000 { From eef8defbf67bc47223ee2fa1ca4769048cd73e9f Mon Sep 17 00:00:00 2001 From: Ayushi Makhija Date: Mon, 23 Mar 2026 16:33:37 +0530 Subject: [PATCH 007/188] FROMLIST: arm64: dts: qcom: sm8750-mtp: Set sufficient voltage for panel nt37801 The NT37801 Sepc V1.0 chapter "5.7.1 Power On Sequence" states VDDI=1.65V~1.95V, so set sufficient voltage for panel nt37801. Signed-off-by: Ayushi Makhija Link: https://lore.kernel.org/all/20260323102229.1546504-1-quic_amakhija@quicinc.com/ Signed-off-by: Arpit Saini --- arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts index 3837f6785320d..6ba4e69bf377a 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts @@ -462,7 +462,7 @@ vreg_l12b_1p8: ldo12 { regulator-name = "vreg_l12b_1p8"; - regulator-min-microvolt = <1200000>; + regulator-min-microvolt = <1650000>; regulator-max-microvolt = <1800000>; regulator-initial-mode = ; regulator-allow-set-load; From 734c20bb63e1db202b17b442bdc90315aaadaa66 Mon Sep 17 00:00:00 2001 From: Kuldeep Singh Date: Mon, 4 May 2026 17:37:47 +0530 Subject: [PATCH 008/188] FROMLIST: arm64: dts: qcom: sm8750: wire UFS to ice instance The Inline Crypto Engine (ICE) exists as a standalone DT node, but the UFS node lacks the required qcom,ice phandle reference. Add the qcom,ice property to explicitly associate the UFS controller with its ICE instance. Fixes: d288abc3a70e ("arm64: dts: qcom: sm8750: Add UFS nodes for SM8750 SoC") Signed-off-by: Kuldeep Singh Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/lkml/20260429-sm8750_ice_dt_fix-v1-1-2540dc337082@oss.qualcomm.com/ Signed-off-by: Kumar Kartik --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index 20b483a73fa34..154431fadd164 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -5622,6 +5622,7 @@ phy-names = "ufsphy"; #reset-cells = <1>; + qcom,ice = <&ice>; status = "disabled"; From a312ae92039b8d8be1b9d502be94f67a7538eece Mon Sep 17 00:00:00 2001 From: Anurag Pateriya Date: Fri, 29 May 2026 19:53:11 +0530 Subject: [PATCH 009/188] FROMLIST: arm64: dts: qcom: sm8750: add reboot-mode support Add PSCI SYSTEM_RESET2 reboot-modes for sm8750-mtp and sm8750-qrd for use by the psci-reboot-mode driver. The following modes are defined: - bootloader: reboot into fastboot mode for fastboot flashing. - edl: reboot into emergency download mode for image loading via the Firehose protocol. Link: https://lore.kernel.org/r/20260529-psci_sys_reset-dt-changes-for-pakala-v2-1-1964ebf1924c@oss.qualcomm.com Signed-off-by: Anurag Pateriya --- arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 7 +++++++ arch/arm64/boot/dts/qcom/sm8750-qrd.dts | 7 +++++++ arch/arm64/boot/dts/qcom/sm8750.dtsi | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts index 6ba4e69bf377a..4343ec5ca201c 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts @@ -1426,3 +1426,10 @@ status = "okay"; }; + +&psci { + reboot-mode { + mode-bootloader = <0x80010001 0x2>; + mode-edl = <0x80000000 0x1>; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/sm8750-qrd.dts b/arch/arm64/boot/dts/qcom/sm8750-qrd.dts index 801c46d556022..795d0d34e47ef 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-qrd.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-qrd.dts @@ -1154,3 +1154,10 @@ status = "okay"; }; + +&psci { + reboot-mode { + mode-bootloader = <0x80010001 0x2>; + mode-edl = <0x80000000 0x1>; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index 154431fadd164..be55e53fcea16 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -261,7 +261,7 @@ interrupts = ; }; - psci { + psci: psci { compatible = "arm,psci-1.0"; method = "smc"; From 7f5c7fd3efbdd9566be86d27397ee527150d5aaf Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Thu, 18 Jun 2026 05:03:50 -0700 Subject: [PATCH 010/188] FROMLIST: arm64: dts: qcom: sm8750: Add camss node Add node for the SM8750 camera subsystem. Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260618-pakala-camera-v2-1-9fbb729fd242@oss.qualcomm.com/ --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 203 +++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index be55e53fcea16..2ac7d3cd47504 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -3037,6 +3037,209 @@ #power-domain-cells = <1>; }; + camss: isp@ad27000 { + compatible = "qcom,sm8750-camss"; + + reg = <0x0 0x0ad27000 0x0 0x2b00>, + <0x0 0x0ad2a000 0x0 0x2b00>, + <0x0 0x0ad2d000 0x0 0x2b00>, + <0x0 0x0ad6d000 0x0 0xa00>, + <0x0 0x0ad72000 0x0 0xa00>, + <0x0 0x0ada9000 0x0 0x2000>, + <0x0 0x0adab000 0x0 0x2000>, + <0x0 0x0adad000 0x0 0x2000>, + <0x0 0x0adaf000 0x0 0x2000>, + <0x0 0x0adb1000 0x0 0x2000>, + <0x0 0x0adb3000 0x0 0x2000>, + <0x0 0x0ad8b000 0x0 0x400>, + <0x0 0x0ad8c000 0x0 0x400>, + <0x0 0x0ad8d000 0x0 0x400>, + <0x0 0x0ac86000 0x0 0x10000>, + <0x0 0x0ac96000 0x0 0x10000>, + <0x0 0x0aca6000 0x0 0x10000>, + <0x0 0x0ad6e000 0x0 0x3000>, + <0x0 0x0ad73000 0x0 0x3000>; + reg-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy2", + "csiphy3", + "csiphy4", + "csiphy5", + "csitpg0", + "csitpg1", + "csitpg2", + "vfe0", + "vfe1", + "vfe2", + "vfe_lite0", + "vfe_lite1"; + + clocks = <&camcc CAM_CC_CAMNOC_NRT_AXI_CLK>, + <&camcc CAM_CC_CAMNOC_RT_AXI_CLK>, + <&camcc CAM_CC_CAM_TOP_AHB_CLK>, + <&camcc CAM_CC_CAM_TOP_FAST_AHB_CLK>, + <&camcc CAM_CC_CAMNOC_RT_TFE_0_MAIN_CLK>, + <&camcc CAM_CC_CAMNOC_RT_TFE_1_MAIN_CLK>, + <&camcc CAM_CC_CAMNOC_RT_TFE_2_MAIN_CLK>, + <&camcc CAM_CC_CAMNOC_RT_IFE_LITE_CLK>, + <&camcc CAM_CC_CSID_CLK>, + <&camcc CAM_CC_CSID_CSIPHY_RX_CLK>, + <&camcc CAM_CC_CSIPHY0_CLK>, + <&camcc CAM_CC_CSI0PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY1_CLK>, + <&camcc CAM_CC_CSI1PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY2_CLK>, + <&camcc CAM_CC_CSI2PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY3_CLK>, + <&camcc CAM_CC_CSI3PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY4_CLK>, + <&camcc CAM_CC_CSI4PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY5_CLK>, + <&camcc CAM_CC_CSI5PHYTIMER_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&gcc GCC_CAMERA_SF_AXI_CLK>, + <&camcc CAM_CC_TFE_0_MAIN_CLK>, + <&camcc CAM_CC_TFE_0_MAIN_FAST_AHB_CLK>, + <&camcc CAM_CC_TFE_1_MAIN_CLK>, + <&camcc CAM_CC_TFE_1_MAIN_FAST_AHB_CLK>, + <&camcc CAM_CC_TFE_2_MAIN_CLK>, + <&camcc CAM_CC_TFE_2_MAIN_FAST_AHB_CLK>, + <&camcc CAM_CC_IFE_LITE_CLK>, + <&camcc CAM_CC_IFE_LITE_AHB_CLK>, + <&camcc CAM_CC_IFE_LITE_CPHY_RX_CLK>, + <&camcc CAM_CC_IFE_LITE_CSID_CLK>, + <&camcc CAM_CC_QDSS_DEBUG_XO_CLK>; + clock-names = "camnoc_nrt_axi", + "camnoc_rt_axi", + "cpas_ahb", + "cpas_fast_ahb", + "cpas_vfe0", + "cpas_vfe1", + "cpas_vfe2", + "cpas_vfe_lite", + "csid", + "csid_csiphy_rx", + "csiphy0", + "csiphy0_timer", + "csiphy1", + "csiphy1_timer", + "csiphy2", + "csiphy2_timer", + "csiphy3", + "csiphy3_timer", + "csiphy4", + "csiphy4_timer", + "csiphy5", + "csiphy5_timer", + "gcc_axi_hf", + "gcc_axi_sf", + "vfe0", + "vfe0_fast_ahb", + "vfe1", + "vfe1_fast_ahb", + "vfe2", + "vfe2_fast_ahb", + "vfe_lite", + "vfe_lite_ahb", + "vfe_lite_cphy_rx", + "vfe_lite_csid", + "qdss_debug_xo"; + + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy2", + "csiphy3", + "csiphy4", + "csiphy5", + "vfe0", + "vfe1", + "vfe2", + "vfe_lite0", + "vfe_lite1"; + + interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_CAMERA_CFG QCOM_ICC_TAG_ACTIVE_ONLY>, + <&mmss_noc MASTER_CAMNOC_HF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_NRT_ICP_SF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "ahb", + "hf_mnoc", + "sf_mnoc", + "sf_icp_mnoc"; + + iommus = <&apps_smmu 0x1c00 0x00>; + + power-domains = <&camcc CAM_CC_TFE_0_GDSC>, + <&camcc CAM_CC_TFE_1_GDSC>, + <&camcc CAM_CC_TFE_2_GDSC>, + <&camcc CAM_CC_TITAN_TOP_GDSC>; + power-domain-names = "ife0", + "ife1", + "ife2", + "top"; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + }; + + port@1 { + reg = <1>; + }; + + port@2 { + reg = <2>; + }; + + port@3 { + reg = <3>; + }; + + port@4 { + reg = <4>; + }; + + port@5 { + reg = <5>; + }; + }; + + }; + mdss: display-subsystem@ae00000 { compatible = "qcom,sm8750-mdss"; reg = <0x0 0x0ae00000 0x0 0x1000>; From 060d30a27761ed117774bfccd6238e5b8af28f82 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Thu, 18 Jun 2026 05:03:51 -0700 Subject: [PATCH 011/188] FROMLIST: arm64: dts: qcom: sm8750: Add CCI definitions Qualcomm SM8750 SoC has three Camera Control Interface (CCI). Each controller contains two I2C hosts. Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260618-pakala-camera-v2-2-9fbb729fd242@oss.qualcomm.com/ --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 282 +++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index 2ac7d3cd47504..42f0e5c546394 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -3037,6 +3037,96 @@ #power-domain-cells = <1>; }; + cci0: cci@ac7b000 { + compatible = "qcom,sm8750-cci", "qcom,msm8996-cci"; + reg = <0x0 0x0ac7b000 0x0 0x1000>; + interrupts = ; + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; + clocks = <&camcc CAM_CC_CAM_TOP_AHB_CLK>, + <&camcc CAM_CC_CCI_0_CLK>; + clock-names = "ahb", "cci"; + pinctrl-0 = <&cci0_0_default &cci0_1_default>; + pinctrl-1 = <&cci0_0_sleep &cci0_1_sleep>; + pinctrl-names = "default", "sleep"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + + cci0_i2c0: i2c-bus@0 { + reg = <0>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + + cci0_i2c1: i2c-bus@1 { + reg = <1>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + + cci1: cci@ac7c000 { + compatible = "qcom,sm8750-cci", "qcom,msm8996-cci"; + reg = <0x0 0x0ac7c000 0x0 0x1000>; + interrupts = ; + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; + clocks = <&camcc CAM_CC_CAM_TOP_AHB_CLK>, + <&camcc CAM_CC_CCI_1_CLK>; + clock-names = "ahb", "cci"; + pinctrl-0 = <&cci1_0_default &cci1_1_default>; + pinctrl-1 = <&cci1_0_sleep &cci1_1_sleep>; + pinctrl-names = "default", "sleep"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + + cci1_i2c0: i2c-bus@0 { + reg = <0>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + + cci1_i2c1: i2c-bus@1 { + reg = <1>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + + cci2: cci@ac7d000 { + compatible = "qcom,sm8750-cci", "qcom,msm8996-cci"; + reg = <0x0 0x0ac7d000 0x0 0x1000>; + interrupts = ; + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; + clocks = <&camcc CAM_CC_CAM_TOP_AHB_CLK>, + <&camcc CAM_CC_CCI_2_CLK>; + clock-names = "ahb", "cci"; + pinctrl-0 = <&cci2_0_default &cci2_1_default>; + pinctrl-1 = <&cci2_0_sleep &cci2_1_sleep>; + pinctrl-names = "default", "sleep"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + + cci2_i2c0: i2c-bus@0 { + reg = <0>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + + cci2_i2c1: i2c-bus@1 { + reg = <1>; + clock-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + camss: isp@ad27000 { compatible = "qcom,sm8750-camss"; @@ -3809,6 +3899,198 @@ gpio-ranges = <&tlmm 0 0 216>; wakeup-parent = <&pdc>; + cci0_0_default: cci0-0-default-state { + sda-pins { + pins = "gpio113"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio114"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci0_0_sleep: cci0-0-sleep-state { + sda-pins { + pins = "gpio113"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio114"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci0_1_default: cci0-1-default-state { + sda-pins { + pins = "gpio115"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio116"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci0_1_sleep: cci0-1-sleep-state { + sda-pins { + pins = "gpio115"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio116"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci1_0_default: cci1-0-default-state { + sda-pins { + pins = "gpio117"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio118"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci1_0_sleep: cci1-0-sleep-state { + sda-pins { + pins = "gpio117"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio118"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci1_1_default: cci1-1-default-state { + sda-pins { + pins = "gpio111"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio164"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci1_1_sleep: cci1-1-sleep-state { + sda-pins { + pins = "gpio111"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio164"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci2_0_default: cci2-0-default-state { + sda-pins { + pins = "gpio112"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio153"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci2_0_sleep: cci2-0-sleep-state { + sda-pins { + pins = "gpio112"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio153"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci2_1_default: cci2-1-default-state { + sda-pins { + pins = "gpio119"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up; + }; + + scl-pins { + pins = "gpio120"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + cci2_1_sleep: cci2-1-sleep-state { + sda-pins { + pins = "gpio119"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio120"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + hub_i2c0_data_clk: hub-i2c0-data-clk-state { /* SDA, SCL */ pins = "gpio64", "gpio65"; From b2e5e9630f3ff8f681ae77aaab3ef852c4896df4 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Thu, 18 Jun 2026 05:03:52 -0700 Subject: [PATCH 012/188] FROMLIST: arm64: dts: qcom: sm8750: Add camera MCLK pinctrl Define pinctrl definitions to enable camera master clocks on SM8750. Reviewed-by: Konrad Dybcio Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260618-pakala-camera-v2-3-9fbb729fd242@oss.qualcomm.com/ --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index 42f0e5c546394..250d1a1818a26 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -3899,6 +3899,62 @@ gpio-ranges = <&tlmm 0 0 216>; wakeup-parent = <&pdc>; + cam0_default: cam0-default-state { + pins = "gpio89"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam1_default: cam1-default-state { + pins = "gpio90"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam2_default: cam2-default-state { + pins = "gpio91"; + function = "cam_aon_mclk2"; + drive-strength = <2>; + bias-disable; + }; + + cam3_default: cam3-default-state { + pins = "gpio92"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam4_default: cam4-default-state { + pins = "gpio93"; + function = "cam_aon_mclk4"; + drive-strength = <2>; + bias-disable; + }; + + cam5_default: cam5-default-state { + pins = "gpio94"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam6_default: cam6-default-state { + pins = "gpio95"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam7_default: cam7-default-state { + pins = "gpio96"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + cci0_0_default: cci0-0-default-state { sda-pins { pins = "gpio113"; From a3abff0ed93da426c683ccd620d99afb93d039ce Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 18 Jun 2026 16:49:34 +0200 Subject: [PATCH 013/188] i2c: mpc: Fix timeout calculations At first glance the harmless cleanup of the driver does nothing bad. However, as the operator precedence list states the '*' (multiplication) and '/' division operators have order 5 with left-to-right associativity the *= has order 17 and associativity right-to-left. It wouldn't be a problem to replace foo = foo * HZ / 1000000; with foo *= HZ / 1000000; if HZ constant is in Hertz. The problem is that in the Linux kernel HZ is defined in jiffy units, which is order of magnitude smaller than a million. That's why operator precedence has a crucial role here. Fix the regression by reverting pre-optimized calculations. Fixes: be40a3ae719f ("i2c: mpc: Use of_property_read_u32 instead of of_get_property") Signed-off-by: Andy Shevchenko Cc: # v6.4+ Reviewed-by: Chris Packham Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260618144934.3249950-1-andriy.shevchenko@linux.intel.com --- drivers/i2c/busses/i2c-mpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-mpc.c b/drivers/i2c/busses/i2c-mpc.c index 28c5c5c1fb7a6..a21fa45bd64c4 100644 --- a/drivers/i2c/busses/i2c-mpc.c +++ b/drivers/i2c/busses/i2c-mpc.c @@ -844,7 +844,7 @@ static int fsl_i2c_probe(struct platform_device *op) "fsl,timeout", &mpc_ops.timeout); if (!result) { - mpc_ops.timeout *= HZ / 1000000; + mpc_ops.timeout = mpc_ops.timeout * HZ / 1000000; if (mpc_ops.timeout < 5) mpc_ops.timeout = 5; } else { From 216fa0d1486fc69c8164719e8d49ab6a67f27038 Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Tue, 12 May 2026 17:35:34 +0800 Subject: [PATCH 014/188] i2c: i801: fix hardware state machine corruption in error path A severe livelock and subsequent Hung Task panic were observed in the i2c-i801 driver during concurrent Fuzzing. The crash is caused by an unconditional hardware register cleanup in the error handling path of i801_access(). When i801_check_pre() fails (e.g., returning -EBUSY because the SMBus controller is actively used by BIOS/ACPI), the kernel does not actually acquire the hardware ownership. However, the code jumps to the 'out' label and executes: iowrite8(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv)); This forcefully clears the INUSE_STS lock and resets the hardware status flags without owning the controller. Doing so interrupts ongoing BIOS/ACPI transactions and totally corrupts the SMBus hardware state machine. Consequently, all subsequent i801_access() calls fail at the pre-check stage, triggering an endless stream of "SMBus is busy, can't use it!" error logs. Over a slow serial console, this printk flood monopolizes the CPU (Console Livelock), starving other processes trying to acquire the mmap_lock down_read semaphore, ultimately triggering the hung task watchdog. Fix this by moving the 'out' label below the hardware register cleanup. If i801_check_pre() fails, we safely bypass the iowrite8() and only release the software locks (pm_runtime and mutex), strictly adhering to the rule of not releasing resources that were never acquired. Fixes: 1f760b87e54c ("i2c: i801: Call i801_check_pre() from i801_access()") Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Cc: # v6.3+ Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260512093534.348655-1-w15303746062@163.com --- drivers/i2c/busses/i2c-i801.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index 32a3cef02c7b5..b29c99ed38839 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -931,13 +931,13 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr, */ if (hwpec) iowrite8(ioread8(SMBAUXCTL(priv)) & ~SMBAUXCTL_CRC, SMBAUXCTL(priv)); -out: /* * Unlock the SMBus device for use by BIOS/ACPI, * and clear status flags if not done already. */ iowrite8(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv)); +out: pm_runtime_put_autosuspend(&priv->pci_dev->dev); mutex_unlock(&priv->acpi_lock); return ret; From 1f44e993a91e2f885f3a388f87b55c74cbc5b9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 15 Jun 2026 10:37:26 -0300 Subject: [PATCH 015/188] ALSA: compress: Fix task creation error unwind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_compr_task_new() allocates the driver task before validating the returned DMA buffers and reserving file descriptors. When either of those later steps fails, the core frees its task wrapper and DMA-buffer references without calling the driver's task_free() callback. Any driver resources allocated by task_create() are therefore leaked. The dual-fd allocation path also jumps to cleanup without storing the negative get_unused_fd_flags() result in retval. Since retval still contains the successful task_create() return value, TASK_CREATE can incorrectly report success although the task was discarded. Preserve the fd allocation errors and call task_free() when failure occurs after a successful task_create() callback. Fixes: 04177158cf98 ("ALSA: compress_offload: introduce accel operation mode") Fixes: 3d3f43fab4cf ("ALSA: compress_offload: improve file descriptors installation for dma-buf") Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260615-alsa-compress-task-unwind-v1-1-39e8ad3ddb27@gmail.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index fd63d219bf866..ea699491f0c36 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -1083,15 +1083,18 @@ static int snd_compr_task_new(struct snd_compr_stream *stream, struct snd_compr_ file descriptors are allocated before fd_install() */ if (!task->input || !task->input->file || !task->output || !task->output->file) { retval = -EINVAL; - goto cleanup; + goto free_driver_task; } fd_i = get_unused_fd_flags(O_WRONLY|O_CLOEXEC); - if (fd_i < 0) - goto cleanup; + if (fd_i < 0) { + retval = fd_i; + goto free_driver_task; + } fd_o = get_unused_fd_flags(O_RDONLY|O_CLOEXEC); if (fd_o < 0) { + retval = fd_o; put_unused_fd(fd_i); - goto cleanup; + goto free_driver_task; } /* keep dmabuf reference until freed with task free ioctl */ get_dma_buf(task->input); @@ -1103,6 +1106,8 @@ static int snd_compr_task_new(struct snd_compr_stream *stream, struct snd_compr_ list_add_tail(&task->list, &stream->runtime->tasks); stream->runtime->total_tasks++; return 0; +free_driver_task: + stream->ops->task_free(stream, task); cleanup: snd_compr_task_free(task); return retval; From 08f8dc088f21cb218eb134fd1c8bd5fd0ffd04f2 Mon Sep 17 00:00:00 2001 From: Galen Hassen Date: Tue, 16 Jun 2026 10:32:57 -0700 Subject: [PATCH 016/188] ALSA: hda/conexant: Add pin config quirk for Lenovo IdeaPad Slim 5 16AKP10 The Lenovo IdeaPad Slim 5 16AKP10 (PCI SSID 17aa:38b6) uses the Conexant SN6140 codec. The internal microphone is on pin 0x1a but the BIOS configures it with pin default 0x95a60120, which includes a jack detection bit that causes the kernel to treat it as an unplugged external mic rather than a fixed internal mic. Add a pin config quirk that overrides pin 0x1a to 0x95a60130, setting the connectivity bits to indicate a fixed/always-connected device. This allows the internal microphone to be correctly identified and used. Signed-off-by: Galen Hassen Link: https://patch.msgid.link/20260616173257.37373-1-rwekyes@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index e3b6aaabe3a9c..3d92262763f62 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -291,6 +291,7 @@ enum { CXT_FIXUP_HEADSET_MIC, CXT_FIXUP_HP_MIC_NO_PRESENCE, CXT_PINCFG_SWS_JS201D, + CXT_PINCFG_LENOVO_IDEAPAD_SLIM5_16AKP10, CXT_PINCFG_TOP_SPEAKER, CXT_FIXUP_HP_A_U, CXT_FIXUP_ACER_SWIFT_HP, @@ -826,6 +827,12 @@ static const struct hda_pintbl cxt_pincfg_lemote[] = { {} }; +/* Lenovo IdeaPad Slim 5 16AKP10 with SN6140 */ +static const struct hda_pintbl cxt_pincfg_lenovo_ideapad_slim5_16akp10[] = { + { 0x1a, 0x95a60130 }, /* Internal mic, fixed/always-connected */ + {} +}; + /* SuoWoSi/South-holding JS201D with sn6140 */ static const struct hda_pintbl cxt_pincfg_sws_js201d[] = { { 0x16, 0x03211040 }, /* hp out */ @@ -1006,6 +1013,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_PINS, .v.pins = cxt_pincfg_sws_js201d, }, + [CXT_PINCFG_LENOVO_IDEAPAD_SLIM5_16AKP10] = { + .type = HDA_FIXUP_PINS, + .v.pins = cxt_pincfg_lenovo_ideapad_slim5_16akp10, + }, [CXT_PINCFG_TOP_SPEAKER] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -1114,6 +1125,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x21da, "Lenovo X220", CXT_PINCFG_LENOVO_TP410), SND_PCI_QUIRK(0x17aa, 0x21db, "Lenovo X220-tablet", CXT_PINCFG_LENOVO_TP410), SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo IdeaPad Z560", CXT_FIXUP_MUTE_LED_EAPD), + SND_PCI_QUIRK(0x17aa, 0x38b6, "Lenovo IdeaPad Slim 5 16AKP10", CXT_PINCFG_LENOVO_IDEAPAD_SLIM5_16AKP10), SND_PCI_QUIRK(0x17aa, 0x3905, "Lenovo G50-30", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x390b, "Lenovo G50-80", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC), From 23ec67d207a7e7d2466f2a5b38f358b569f30892 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 16 Jun 2026 19:59:16 +0800 Subject: [PATCH 017/188] ALSA: usb-audio: qcom: Free sideband sg_table objects The Qualcomm USB audio offload driver obtains an endpoint transfer-ring table by calling xhci_sideband_get_endpoint_buffer(). This getter passes the endpoint ring to xhci_ring_to_sgtable(), which allocates the outer struct sg_table with kzalloc_obj(*sgt). The event-ring path is equivalent: xhci_sideband_get_event_buffer() also returns the result of xhci_ring_to_sgtable(). Inside xhci_ring_to_sgtable(), sg_alloc_table_from_pages() separately allocates the scatterlist storage referenced by sgt->sgl. The returned object therefore has two allocation layers: the outer struct sg_table and its internal scatterlist storage. The Qualcomm caller only invokes sg_free_table(sgt). sg_free_table() releases the scatterlist storage owned by the table, but it does not free the separately allocated outer struct sg_table. The local sgt pointer is then discarded, so every successful endpoint or event-ring query leaks the outer object. Call kfree(sgt) after sg_free_table(sgt) in both setup paths, after the required page and DMA addresses have been copied out. Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Signed-off-by: Xu Rao Link: https://patch.msgid.link/90B353283AA150C4+20260616115916.1222915-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index a3f90cc7c6cad..436d6821c5c97 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1160,6 +1160,7 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs, tr_pa = page_to_phys(pg); mem_info->dma = sg_dma_address(sgt->sgl); sg_free_table(sgt); + kfree(sgt); /* data transfer ring */ iova = uaudio_iommu_map_pa(MEM_XFER_RING, dma_coherent, tr_pa, @@ -1229,6 +1230,7 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs, er_pa = page_to_phys(pg); mem_info->dma = sg_dma_address(sgt->sgl); sg_free_table(sgt); + kfree(sgt); iova = uaudio_iommu_map_pa(MEM_EVENT_RING, dma_coherent, er_pa, PAGE_SIZE); From 27e3cc1fdedb1ebfe3f4fe7de5fff1d36c8159dd Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Wed, 17 Jun 2026 10:52:34 +0800 Subject: [PATCH 018/188] ALSA: usb-audio: Add quirk flags for SC13A The SC13A ( VID 0x1ff7, PID 0x0f81) not support reading the current sample rate and results in an error message printed to kmsg. Set QUIRK_FLAG_GET_SAMPLE_RATE to skip the sample rate check. Quirky device sample: usb 3-5.2.4.1: new high-speed USB device number 11 using xhci_hcd usb 3-5.2.4.1: New USB device found, idVendor=1ff7, idProduct=0f81 usb 3-5.2.4.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 3-5.2.4.1: Product: SC13A usb 3-5.2.4.1: Manufacturer: Linux Foundation usb 3-5.2.4.1: SerialNumber: 000002 usb 3-5.2.4.1: Found UVC 1.50 device SC13A (1ff7:0f81) usb 3-5.2.4.1: 3:1: cannot get freq at ep 0x86 usb 3-5.2.4.1: Warning! Unlikely big volume range (=4096), cval->res is probably wrong. usb 3-5.2.4.1: [5] FU [Mic Capture Volume] ch = 1, val = 0/4096/1 usbcore: registered new interface driver snd-usb-audio usb 3-5.2.4.1: 3:1: cannot get freq at ep 0x86 usb 3-5.2.4.1: 3:1: cannot get freq at ep 0x86 Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260617025234.3344935-1-aichao@kylinos.cn Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 30f6d572e4d1b..1cb588691e16d 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2413,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x2040, 0x7201, /* Hauppauge HVR-950Q-MXL */ From d367e90d324b7451e4c2a1792302acd23454c36b Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 17 Jun 2026 08:42:18 +0200 Subject: [PATCH 019/188] ALSA: sh: Use more common error handling code in snd_aica_probe() Use an additional label so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Link: https://patch.msgid.link/47413de1-015b-4543-8e8c-25e41dfa9e39@web.de Signed-off-by: Takashi Iwai --- sound/sh/aica.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/sh/aica.c b/sound/sh/aica.c index 9438c3a68ee9e..8196e1bf04164 100644 --- a/sound/sh/aica.c +++ b/sound/sh/aica.c @@ -564,10 +564,9 @@ static int snd_aica_probe(struct platform_device *devptr) return -ENOMEM; err = snd_card_new(&devptr->dev, index, SND_AICA_DRIVER, THIS_MODULE, 0, &dreamcastcard->card); - if (unlikely(err < 0)) { - kfree(dreamcastcard); - return err; - } + if (unlikely(err < 0)) + goto free_card; + strscpy(dreamcastcard->card->driver, "snd_aica"); strscpy(dreamcastcard->card->shortname, SND_AICA_DRIVER); strscpy(dreamcastcard->card->longname, @@ -593,6 +592,7 @@ static int snd_aica_probe(struct platform_device *devptr) return 0; freedreamcast: snd_card_free(dreamcastcard->card); +free_card: kfree(dreamcastcard); return err; } From 6c10215d4f4d71a820c6284e3476463a8eb9d52c Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Wed, 17 Jun 2026 11:29:43 +0100 Subject: [PATCH 020/188] ALSA: pcm: fix __le32 cast warning in snd_pcm_set_sync_per_card In snd_pcm_set_sync_per_card() the le32 value is written to an u32 instead of an __le32 pointer. Fix the following warning by fixing the type: sound/soc/soc-pcm.c:2166:9: warning: incorrect type in argument 7 (different base types) sound/soc/soc-pcm.c:2166:9: expected int sound/soc/soc-pcm.c:2166:9: got restricted snd_pcm_format_t Signed-off-by: Ben Dooks Link: https://patch.msgid.link/20260617102943.893950-1-ben.dooks@codethink.co.uk Signed-off-by: Takashi Iwai --- sound/core/pcm_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index fe597f7d522de..4d665b4148d70 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -545,7 +545,7 @@ void snd_pcm_set_sync_per_card(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, const unsigned char *id, unsigned int len) { - *(__u32 *)params->sync = cpu_to_le32(substream->pcm->card->number); + *(__le32 *)params->sync = cpu_to_le32(substream->pcm->card->number); len = min(12, len); memcpy(params->sync + 4, id, len); memset(params->sync + 4 + len, 0, 12 - len); From eb44de981774741ce2d7117a811e6ee3719e80a3 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Thu, 18 Jun 2026 14:03:15 +0800 Subject: [PATCH 021/188] ALSA: caiaq: fix out-of-bounds read in the Traktor Kontrol S4 input parser snd_usb_caiaq_tks4_dispatch() decodes the Traktor Kontrol S4 input stream in fixed 16-byte (TKS4_MSGBLOCK_SIZE) message blocks. On every iteration it advances buf and subtracts the block size while looping on "while (len)". len is urb->actual_length. That value is supplied by the device and is not guaranteed to be a multiple of 16. When a final short block leaves len between 1 and 15, the loop runs once more, reads up to buf[15], and then does "len -= TKS4_MSGBLOCK_SIZE". As len is unsigned this underflows to a huge value. The loop then keeps iterating and walking buf far past the end of the 512-byte ep4_in_buf, reading out of bounds until a bogus block id happens to be hit. Iterate only while a full message block is available. This stops the unsigned underflow and silently drops any trailing partial block, which carries no complete control value anyway. The sibling endpoint-4 parsers are not affected. The Traktor Kontrol X1 and Maschine arms in snd_usb_caiaq_ep4_reply_dispatch() floor urb->actual_length before dispatching. Fixes: 15c5ab607045 ("ALSA: snd-usb-caiaq: Add support for Traktor Kontrol S4") Cc: stable@vger.kernel.org Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/178176259547.3343534.2724779296835237429@maoyixie.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 5c70fdf61cc13..2db4d1332df1c 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -330,7 +330,7 @@ static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev, { struct device *dev = caiaqdev_to_dev(cdev); - while (len) { + while (len >= TKS4_MSGBLOCK_SIZE) { unsigned int i, block_id = (buf[0] << 8) | buf[1]; switch (block_id) { From 70253b5af4f82904385a445ff4d1459637e661f7 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Thu, 18 Jun 2026 14:03:15 +0800 Subject: [PATCH 022/188] ALSA: caiaq: bound the length in the EP1 input parsers snd_caiaq_input_read_erp() and snd_caiaq_input_read_io() can be reached from snd_usb_caiaq_input_dispatch(). They read fixed byte offsets from the reply buffer without checking the reported length. On a short reply they decode stale bytes left from a previous, longer report and feed them to the input layer. This is not an out-of-bounds access. Every offset is a compile-time driver constant. The largest is buf[21] in the Maschine ERP case. The EP1 transfer buffer ep1_in_buf is EP1_BUFSIZE (64) bytes, and the USB core caps actual_length at 64, so a short reply only reads in-bounds stale data. Acting on data the device did not send is still wrong, so bail out per usb_id case when the reply is shorter than the bytes that case consumes. read_erp: AK1 needs 2 bytes, Kore needs 16, Maschine needs 22. read_io: the Kore case needs 5 bytes (buf[4]) and the Traktor Kontrol X1 case needs 7 (buf[5]/buf[6]). The preceding key bit loop is already bounded by "i < len * 8" and is left untouched. snd_caiaq_input_read_analog() and snd_usb_caiaq_maschine_dispatch() are not changed. Their callers already floor the reply length. Suggested-by: Takashi Iwai Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/178176259547.3343534.6659489917322808916@maoyixie.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/input.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 2db4d1332df1c..eabbf41fdfb2b 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -237,12 +237,16 @@ static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev, switch (cdev->chip.usb_id) { case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + if (len < 2) + return; i = decode_erp(buf[0], buf[1]); input_report_abs(input_dev, ABS_X, i); input_sync(input_dev); break; case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 16) + return; i = decode_erp(buf[7], buf[5]); input_report_abs(input_dev, ABS_HAT0X, i); i = decode_erp(buf[12], buf[14]); @@ -263,6 +267,8 @@ static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev, break; case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (len < 22) + return; /* 4 under the left screen */ input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20])); input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14])); @@ -308,9 +314,13 @@ static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev, switch (cdev->chip.usb_id) { case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 5) + return; input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]); break; case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (len < 7) + return; /* rotary encoders */ input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf); input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4); From a8f57565b4a98c3af9bd663028965d2ea6ec29d9 Mon Sep 17 00:00:00 2001 From: Haowen Tu Date: Thu, 18 Jun 2026 15:54:31 +0800 Subject: [PATCH 023/188] ALSA: hda/realtek: Add headset mic quirk for Acer S40-54 Acer S40-54 with ALC256 does not restore headset mic detection properly after S4 resume. After resume, headset plug events may no longer update the headset mic state, leaving the headset microphone unavailable. The system uses subsystem ID 1025:161f. Applying the existing ALC256_FIXUP_ACER_MIC_NO_PRESENCE fixup restores headset mic detection on this machine. Add a machine-specific quirk for this system. Signed-off-by: Haowen Tu Link: https://patch.msgid.link/20260618075431.1116988-1-tuhaowen@uniontech.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5d09baa6d32e9..d209efdb82724 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6807,6 +6807,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), From fa00f95807ba217e3adeb58c5df9ff3f5c78f64f Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 17 Jun 2026 22:51:25 -0400 Subject: [PATCH 024/188] ALSA: usb-audio: qcom: reject stream disable with no active interface handle_uaudio_stream_req() resolves an interface index with info_idx_from_ifnum(), which returns -EINVAL when no interface matches. The enable branch and the response: cleanup label both guard against a negative index, but the disable branch does not: it forms info = &uadev[pcm_card_num].info[info_idx] and dereferences it. uadev[].info is a pointer allocated only when a stream is first enabled, so a negative info_idx on the disable path is unsafe in two ways: - If the card was never enabled, .info is NULL and &info[-EINVAL] is a wild pointer; reading info->data_ep_pipe faults (kernel oops). - If the card was enabled at least once (.info allocated) and the disable names an interface that does not match, &info[-EINVAL] points before the allocation; info->data_ep_pipe / info->sync_ep_pipe are an out-of-bounds slab read and, when non-zero, an out-of-bounds 4-byte write (both pipe fields are cleared to 0). That is memory corruption, not just a NULL dereference. The request is reachable from unprivileged local userspace over AF_QIPCRTR. Reject a disable request with no resolved interface, matching the guard the enable path already has. Fixes: 326bbc348298a ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260618025126.1862954-2-michael.bommarito@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 436d6821c5c97..3b05dadbbeae2 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1642,6 +1642,11 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, subs->opened = 0; } } else { + if (info_idx < 0) { + ret = -EINVAL; + goto response; + } + info = &uadev[pcm_card_num].info[info_idx]; if (info->data_ep_pipe) { ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, From b6874fdba1167fa3c8cee9c042bd3aa91139ab37 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 17 Jun 2026 22:51:26 -0400 Subject: [PATCH 025/188] ALSA: usb-audio: qcom: clear opened when stream enable fails On enable, subs->opened is set before the service_interval is validated; an invalid interval jumps to the response label without clearing it, so the substream is wedged at -EBUSY until a disable or disconnect. Clear subs->opened on the enable error path. Fixes: 326bbc348298a ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260618025126.1862954-3-michael.bommarito@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 3b05dadbbeae2..3a586fd16e729 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1620,8 +1620,13 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, if (req_msg->service_interval_valid) { ret = get_data_interval_from_si(subs, req_msg->service_interval); - if (ret == -EINVAL) + if (ret == -EINVAL) { + if (req_msg->enable) { + guard(mutex)(&chip->mutex); + subs->opened = 0; + } goto response; + } datainterval = ret; } From bafb82cccaf8bfc1d90407e4cdcc0aae3634e5b1 Mon Sep 17 00:00:00 2001 From: Jean-Louis Colaco Date: Thu, 18 Jun 2026 13:32:02 +0200 Subject: [PATCH 026/188] ALSA: usb-audio: Add quirk for YAMAHA CDS3000 This quirk is identical to the one for the Yamaha Steinberg UR22, here applied to a CD player that also uses the Steinberg USB interface. This quirk is necessary to avoid sporadic "clic" noise when using the DAC of the player. Signed-off-by: Jean-Louis Colaco Link: https://patch.msgid.link/20260618113202.8363-1-jean-louis.colaco@orange.fr Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 97f28c53d2015..71444c2898b4b 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -390,6 +390,20 @@ YAMAHA_DEVICE(0x105d, NULL), } } }, +{ + USB_DEVICE(0x0499, 0x150d), + QUIRK_DRIVER_INFO { + /* .vendor_name = "Yamaha", */ + /* .product_name = "CDS3000", */ + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END + } + } +}, { USB_DEVICE(0x0499, 0x1718), QUIRK_DRIVER_INFO { From c6c4c6c008e22eee7e9ad2fda7d2bba310d07ef3 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Thu, 18 Jun 2026 12:24:36 +0200 Subject: [PATCH 027/188] ALSA: emu10k1: Use common error handling code in snd_emu10k1_playback_open() Use an additional label so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Link: https://patch.msgid.link/d709474d-62b0-4f7e-9011-a0f716b35383@web.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9023f3444d205..b8749e0131adc 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1181,19 +1181,17 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_playback; err = snd_emu10k1_playback_set_constraints(runtime); - if (err < 0) { - kfree(epcm); - return err; - } + if (err < 0) + goto free_epcm; + if (emu->card_capabilities->emu_model) sample_rate = emu->emu1010.word_clock; else sample_rate = 48000; err = snd_pcm_hw_rule_noresample(runtime, sample_rate); - if (err < 0) { - kfree(epcm); - return err; - } + if (err < 0) + goto free_epcm; + mix = &emu->pcm_mixer[substream->number]; for (i = 0; i < 8; i++) mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i; @@ -1204,6 +1202,10 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) mix->epcm = epcm; snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1); return 0; + +free_epcm: + kfree(epcm); + return err; } static int snd_emu10k1_playback_close(struct snd_pcm_substream *substream) From b64192d0b8d25d0e0e826bf41f64b1e106db246b Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 19 Jun 2026 01:00:10 +0800 Subject: [PATCH 028/188] ALSA: usb-audio: Kill MIDI 2.0 URBs before freeing endpoints MIDI 2.0 input URBs are started during snd_usb_midi_v2_create(). A later setup failure can still jump to snd_usb_midi_v2_free(), which currently frees each endpoint and its coherent URB buffers without first stopping the submitted URBs. A completion can then dereference the embedded URB context and endpoint state after they have been freed, or try to resubmit from the stale endpoint. This was observed as a KASAN slab-use-after-free in input_urb_complete(). The buggy scenario involves two paths, with each column showing the order within that path: probe error path: USB completion path: 1. start_input_streams() submits 1. The HCD still owns a input URBs. submitted input URB. 2. A later setup helper returns 2. input_urb_complete() runs an error. with urb->context in ep. 3. snd_usb_midi_v2_free() frees 3. The completion reads ep endpoint storage and URB buffers. state and can requeue URBs. Make the endpoint destructor follow the same teardown ordering used for disconnect when the endpoint has not already been disconnected: publish ep->disconnected, kill the URBs synchronously, and drain the endpoint before freeing URB buffers and endpoint storage. The guard avoids repeating the stop sequence after the normal snd_usb_midi_v2_disconnect_all() path, while still synchronizing the direct MIDI 2.0 create-error free path. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in input_urb_complete+0x37/0x1b0 Workqueue: usb_hub_wq hub_event RIP: 0010:_raw_spin_unlock_irq+0x2e/0x50 Read of size 8 Call trace: dump_stack_lvl+0x77/0xb0 print_report+0xce/0x5f0 input_urb_complete+0x37/0x1b0 (sound/usb/midi2.c:186) srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x19f/0x330 kasan_report+0xe0/0x110 __usb_hcd_giveback_urb+0x112/0x1d0 dummy_timer+0xaaa/0x19a0 lock_is_held_type+0x9a/0x110 __lock_acquire+0x467/0x28b0 mark_held_locks+0x40/0x70 _raw_spin_unlock_irqrestore+0x44/0x60 lockdep_hardirqs_on_prepare+0xbb/0x1a0 __hrtimer_run_queues+0x101/0x520 hrtimer_run_softirq+0xd0/0x130 handle_softirqs+0x15b/0x670 __irq_exit_rcu+0xd0/0x170 irq_exit_rcu+0xe/0x20 sysvec_apic_timer_interrupt+0x6c/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 Fixes: d9c99876868c ("ALSA: usb-audio: Create UMP blocks from USB MIDI GTBs") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260618170010.191433-1-zzzccc427@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 04aeb9052f139..3ec6332917726 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -470,6 +470,11 @@ static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, static void free_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) { list_del(&ep->list); + if (!ep->disconnected) { + ep->disconnected = 1; + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + } free_midi_urbs(ep); kfree(ep); } From 5816ed288fcd608dfa4c97f0f741bb562241878f Mon Sep 17 00:00:00 2001 From: Ramcharan Rajpurohit Date: Fri, 19 Jun 2026 11:54:35 +0530 Subject: [PATCH 029/188] ALSA: hda/realtek: Enable mute LED on HP EliteBook 840 G6 The HP EliteBook 840 G6 (PCI SSID 103c:854d) has an ALC215 codec whose mic-mute and audio-mute LEDs are wired to the same GPIOs as the already supported EliteBook 830 G6 and 840 G7. Without a matching quirk the LEDs are never registered, so the front-panel mic-mute LED stays permanently lit and does not track the mute state. Apply ALC285_FIXUP_HP_GPIO_LED, mirroring the sibling EliteBook entries. With this fixup the codec registers an "hda::micmute" LED class device and the LED correctly follows the capture-mute state. This was verified on the affected machine by forcing the same fixup at runtime via snd_sof_intel_hda_generic.hda_model=103c:8548, which made the LED work as expected. Signed-off-by: Ramcharan Rajpurohit Link: https://patch.msgid.link/20260619062435.26256-1-b23ci1032@iitj.ac.in Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index d209efdb82724..2df491335a760 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6987,6 +6987,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8537, "HP ProBook 440 G6", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8548, "HP EliteBook x360 830 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), From 08bfbaa09f1d185286f836126fb2a12836047044 Mon Sep 17 00:00:00 2001 From: Chris Aherin Date: Sun, 21 Jun 2026 19:12:10 -0500 Subject: [PATCH 030/188] ALSA: hda/realtek: Add quirk for Lenovo Yoga 7 16IAP7 The Yoga 7 16IAP7 (board LNVNB161216, codec SSID 17aa:386a) has pin complex 0x17 (bass speakers) wrongly reported as unconnected, causing only one of four speaker pins (0x14) to be configured and resulting in mono/tinny audio. SOF corrupts the PCI subsystem ID to 17aa:0000, preventing SND_PCI_QUIRK from matching. HDA_CODEC_QUIRK is used instead, which matches against codec->core.subsystem_id read directly from the HDA codec register and unaffected by the SOF bug. Applies ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, the same fixup used for the Yoga 7 14IAL7, which corrects pin 0x17's default configuration and enables both speaker pairs. Signed-off-by: Chris Aherin Link: https://patch.msgid.link/20260622001210.20553-1-chrisaherin@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2df491335a760..c83c1654ed45c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7755,6 +7755,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ From e2898ecdc7b16b570c6eaa9fdc20b5165dda74c2 Mon Sep 17 00:00:00 2001 From: Dirk Su Date: Mon, 22 Jun 2026 15:20:04 +0800 Subject: [PATCH 031/188] ALSA: hda/realtek: Add LED fixup for HP EliteBook 6 G2i Laptops The HP EliteBook 6 G2i laptops requires specific LED control method ALC236_FIXUP_HP_GPIO_LED to work Signed-off-by: Dirk Su Link: https://patch.msgid.link/20260622072019.56351-1-dirk.su@canonical.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index c83c1654ed45c..e88ca720f4e51 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7328,6 +7328,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8f0e, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f2d, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f2e, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f37, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8f38, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8f39, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8f3a, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f3c, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8f3d, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), From 23b84d9670a1b0ec31c9ea9e42589cf5a138b727 Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Tue, 19 May 2026 17:49:48 +0200 Subject: [PATCH 032/188] ALSA: hda/realtek: Fix noisy mic for Clevo V6xxAW Add a PCI quirk to reduce the volume of the internal microphone to prevent extremely noisy signal. Signed-off-by: Aaron Erhardt Signed-off-by: Werner Sembach Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260519155047.106096-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e88ca720f4e51..0d932f6ae3e88 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7603,6 +7603,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x51b3, "Clevo NS70AU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x5630, "Clevo NP50RNJS", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x5700, "Clevo X560WN[RST]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x6480, "Clevo V6xxAW", ALC245_FIXUP_CLEVO_NOISY_MIC), SND_PCI_QUIRK(0x1558, 0x70a1, "Clevo NB70T[HJK]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x70b3, "Clevo NK70SB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x70f2, "Clevo NH79EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), From 7ee465bd9770a65ccd680faf512cfade9653c6fd Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Tue, 23 Jun 2026 02:59:04 +0930 Subject: [PATCH 033/188] ALSA: FCP: Add Focusrite ISA C8X support Add USB PID 0x821e to the list of devices handled by the Focusrite Control Protocol (FCP) driver. Cc: stable@vger.kernel.org Signed-off-by: Geoffrey D. Bennett Link: https://patch.msgid.link/ajlw4HK+2RSW3nUl@m.b4.vu Signed-off-by: Takashi Iwai --- sound/usb/mixer_quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 628f841b04aae..f6f2bc8e97a7c 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4526,6 +4526,7 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x1235, 0x821b): /* Focusrite Scarlett 16i16 4th Gen */ case USB_ID(0x1235, 0x821c): /* Focusrite Scarlett 18i16 4th Gen */ case USB_ID(0x1235, 0x821d): /* Focusrite Scarlett 18i20 4th Gen */ + case USB_ID(0x1235, 0x821e): /* Focusrite ISA C8X */ err = snd_fcp_init(mixer); break; From 46ad1cef7d9383b55541e441a4a46a9d7a1f2df6 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 24 Jun 2026 08:38:40 +0900 Subject: [PATCH 034/188] ALSA: seq: Fix uninitialised heap leak in snd_seq_event_dup() snd_seq_event_dup() copies an incoming event into a pool cell and, in the UMP-enabled build, clears the trailing cell->ump.raw.extra word that the memcpy() did not cover. The guard deciding whether to clear it compares the copied size against sizeof(cell->event): memcpy(&cell->ump, event, size); if (size < sizeof(cell->event)) cell->ump.raw.extra = 0; For a legacy (non-UMP) event, size == sizeof(struct snd_seq_event) == sizeof(cell->event), so the condition is false and the extra word keeps stale data. The cell pool is allocated with kvmalloc() (not zeroed) and cells are reused via a free list, so that word holds uninitialised heap or leftover event data. When such a cell is delivered to a UMP client (client->midi_version > 0) that set SNDRV_SEQ_FILTER_NO_CONVERT -- so the legacy event reaches it unconverted -- snd_seq_read() reads it out as the larger struct snd_seq_ump_event and copies the stale word to user space, a 4-byte kernel heap infoleak to an unprivileged /dev/snd/seq client. Compare against sizeof(cell->ump) instead, so the trailing word is zeroed for every event shorter than the UMP cell. Fixes: 46397622a3fa ("ALSA: seq: Add UMP support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260623233841.853326-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/seq_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index ca9f6db0022c8..209b08c2a9408 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -364,7 +364,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, size = snd_seq_event_packet_size(event); memcpy(&cell->ump, event, size); #if IS_ENABLED(CONFIG_SND_SEQ_UMP) - if (size < sizeof(cell->event)) + if (size < sizeof(cell->ump)) cell->ump.raw.extra = 0; #endif From 7767e8c8b7227027e3e7ea8697ace9b470e0961c Mon Sep 17 00:00:00 2001 From: Oleg Kucheryavenko Date: Wed, 24 Jun 2026 22:13:01 +0300 Subject: [PATCH 035/188] ALSA: hda/realtek: Add quirk for Acer Nitro ANV15-41 The Acer Nitro ANV15-41 laptop with ALC245 codec does not detect the headset microphone in the combo jack by default. Apply the ALC2XX_FIXUP_HEADSET_MIC quirk to fix it. Signed-off-by: Oleg Kucheryavenko Link: https://patch.msgid.link/20260624191301.10162-1-oleg.kucheryavenko2018@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 0d932f6ae3e88..2a7a693ecedfd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6813,6 +6813,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), From 61065782d082c4d44bd888d224030e5aae3d3da8 Mon Sep 17 00:00:00 2001 From: Darvell Long Date: Wed, 24 Jun 2026 07:37:23 -0700 Subject: [PATCH 036/188] ALSA: usb-audio: avoid kobject path lookup in DualSense match The DualSense jack-detection input handler verifies that a matching input device belongs to the same physical controller by building kobject path strings for both the input device and the USB audio device, then comparing the path prefix. This was observed when a weak physical connection caused the controller to rapidly disconnect and reconnect. During that repeated hotplug, snd_dualsense_ih_match() can run while the controller's USB device is being disconnected. kobject_get_path() walks ancestor kobjects and dereferences their names; if the USB device kobject name is no longer valid, this can fault in strlen(): RIP: 0010:strlen+0x10/0x30 Call Trace: kobject_get_path+0x34/0x150 snd_dualsense_ih_match+0x49/0xd0 [snd_usb_audio] input_register_device+0x566/0x6a0 ps_probe+0xb89/0x1590 [hid_playstation] The same ownership check can be done without building kobject path strings. The input device is parented below the HID device, USB interface and USB device, so walking the input device parent chain and comparing against the mixer USB device preserves the check without dereferencing kobject names during disconnect. Fixes: 79d561c4ec04 ("ALSA: usb-audio: Add mixer quirk for Sony DualSense PS5") Cc: Assisted-by: Cute:gpt-5.5 Signed-off-by: Darvell Long Link: https://patch.msgid.link/20260624143723.2986353-1-contact@darvell.me Signed-off-by: Takashi Iwai --- sound/usb/mixer_quirks.c | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f6f2bc8e97a7c..10792d26fa94a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -577,46 +577,30 @@ static bool snd_dualsense_ih_match(struct input_handler *handler, { struct dualsense_mixer_elem_info *mei; struct usb_device *snd_dev; - char *input_dev_path, *usb_dev_path; - size_t usb_dev_path_len; - bool match = false; + struct device *parent; mei = container_of(handler, struct dualsense_mixer_elem_info, ih); snd_dev = mei->info.head.mixer->chip->dev; - input_dev_path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); - if (!input_dev_path) { - dev_warn(&snd_dev->dev, "Failed to get input dev path\n"); - return false; - } - - usb_dev_path = kobject_get_path(&snd_dev->dev.kobj, GFP_KERNEL); - if (!usb_dev_path) { - dev_warn(&snd_dev->dev, "Failed to get USB dev path\n"); - goto free_paths; - } - /* * Ensure the VID:PID matched input device supposedly owned by the * hid-playstation driver belongs to the actual hardware handled by - * the current USB audio device, which implies input_dev_path being - * a subpath of usb_dev_path. + * the current USB audio device. * * This verification is necessary when there is more than one identical * controller attached to the host system. + * + * The input device is registered below the HID device, USB interface and + * USB device, so compare the parent chain directly instead of building + * kobject path strings. This avoids dereferencing kobject names while the + * USB device hierarchy is being torn down during disconnect. */ - usb_dev_path_len = strlen(usb_dev_path); - if (usb_dev_path_len >= strlen(input_dev_path)) - goto free_paths; - - usb_dev_path[usb_dev_path_len] = '/'; - match = !memcmp(input_dev_path, usb_dev_path, usb_dev_path_len + 1); - -free_paths: - kfree(input_dev_path); - kfree(usb_dev_path); + for (parent = dev->dev.parent; parent; parent = parent->parent) { + if (parent == &snd_dev->dev) + return true; + } - return match; + return false; } static int snd_dualsense_ih_connect(struct input_handler *handler, From cc03d9bcccd6913be9a944d5a1430528f320d6da Mon Sep 17 00:00:00 2001 From: Kamlesh Chhetty Date: Mon, 22 Jun 2026 13:22:47 -0400 Subject: [PATCH 037/188] ALSA: hda: Add Lenovo Legion 7i 16IAX7 17AA3874 quirk Lenovo Legion 7i 16IAX7 systems with Realtek ALC287 codec SSID 17aa:3874 and CSC3551/CS35L41 speaker amps do not provide the required CS35L41 _DSD properties in ACPI. Without a quirk, cs35l41-hda fails probing the amps with missing cirrus,dev-index / Platform not supported errors, leaving the built-in speakers silent. This model is similar to the already-supported 17AA386F Legion 7i 16IAX7 variant. Add the Realtek ALC287 quirk to select ALC287_FIXUP_CS35L41_I2C_2 and add 17AA3874 to the CS35L41 property table using the same two-amp external-boost configuration. Tested on a Lenovo Legion 7 16IAX7 with Ubuntu 7.0.0-22-generic. Both CSC3551 CS35L41 amps probe and bind, firmware loads, calibration applies, built-in speaker playback works, and the cirrus,dev-index failure is gone. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221663 Link: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2157060 Signed-off-by: Kamlesh Chhetty Link: https://patch.msgid.link/20260622172247.19301-1-kamleshkc2002@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2a7a693ecedfd..71625394aa995 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7767,6 +7767,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C), diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 732ae534db360..416d7bf3e289c 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -128,6 +128,7 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, { "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3877", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3878", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA38A9", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, @@ -554,6 +555,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "17AA3866", generic_dsd_config }, { "CSC3551", "17AA386E", generic_dsd_config }, { "CSC3551", "17AA386F", generic_dsd_config }, + { "CSC3551", "17AA3874", generic_dsd_config }, { "CSC3551", "17AA3877", generic_dsd_config }, { "CSC3551", "17AA3878", generic_dsd_config }, { "CSC3551", "17AA38A9", generic_dsd_config }, From 06a327d2b824a02a00a521b6c07b76b039d48719 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 23 Jun 2026 15:13:08 +0800 Subject: [PATCH 038/188] ALSA: usb-audio: qcom: Free QMI handle qc_usb_audio_probe() allocates svc->uaudio_svc_hdl separately from the uaudio_qmi_svc object. qmi_handle_release() releases the resources owned by an initialized QMI handle, but does not free the memory containing the struct qmi_handle itself. The probe error path and the remove path currently release the handle and then free svc, losing the last pointer to the separately allocated handle. This leaks one struct qmi_handle on each affected probe unwind and on each successful probe/remove cycle. Free the handle after qmi_handle_release() in both paths. Signed-off-by: Xu Rao Link: https://patch.msgid.link/9108EC860F3F87DF+20260623071308.2549182-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 3a586fd16e729..e4bfd43a2488b 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1988,6 +1988,7 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, release_qmi: qc_usb_audio_cleanup_qmi_dev(); qmi_handle_release(svc->uaudio_svc_hdl); + kfree(svc->uaudio_svc_hdl); free_svc: kfree(svc); @@ -2012,6 +2013,7 @@ static void qc_usb_audio_remove(struct auxiliary_device *auxdev) qc_usb_audio_cleanup_qmi_dev(); qmi_handle_release(svc->uaudio_svc_hdl); + kfree(svc->uaudio_svc_hdl); kfree(svc); uaudio_svc = NULL; } From 99706679b38b05960f60378fba242b59ed62173a Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sun, 21 Jun 2026 23:09:07 +0800 Subject: [PATCH 039/188] ALSA: firewire: isight: bound the sample count to the packet payload isight_packet() takes the frame count from the device iso packet and checks it only against the device claimed iso length. count = be32_to_cpu(payload->sample_count); if (likely(count <= (length - 16) / 4)) isight_samples(isight, payload->samples, count); length is the iso header data_length. It can be up to 0xffff. So the gate allows a count up to about 16379. isight_samples() then copies count frames out of payload->samples into the PCM DMA buffer. payload->samples holds only 2 * MAX_FRAMES_PER_PACKET values. The device multiplexes two samples per frame. A count past MAX_FRAMES_PER_PACKET reads past the payload. A count past the buffer size writes past runtime->dma_area. The smallest PCM buffer is larger than MAX_FRAMES_PER_PACKET. Bounding the count to MAX_FRAMES_PER_PACKET keeps both the read and the write in range. A malicious or faulty Apple iSight on the FireWire bus reaches this during a normal capture. Add the MAX_FRAMES_PER_PACKET bound to the gate. Fixes: 3a691b28a0ca ("ALSA: add Apple iSight microphone driver") Suggested-by: Takashi Sakamoto Cc: stable@vger.kernel.org Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/178205454729.1900991.7807310178296762772@maoyixie.com Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 2b7f071d593b9..33c9dd48b3b0b 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -179,7 +179,8 @@ static void isight_packet(struct fw_iso_context *context, u32 cycle, if (likely(length >= 16 && payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { count = be32_to_cpu(payload->sample_count); - if (likely(count <= (length - 16) / 4)) { + if (likely(count <= (length - 16) / 4 && + count <= MAX_FRAMES_PER_PACKET)) { total = be32_to_cpu(payload->sample_total); if (unlikely(total != isight->total_samples)) { if (!isight->first_packet) From 2a0001d549c0fa706e797a75b3655aaefd880434 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 16 Jun 2026 18:02:57 +0100 Subject: [PATCH 040/188] ASoC: qcom: q6apm: fix NULL pointer dereference in graph_callback When q6apm_free_fragments() is called it frees rx_data.buf/tx_data.buf and sets them to NULL under graph->lock. A late DSP buffer-done response can race with this: graph_callback() passes the !graph->ar_graph guard (not yet NULL), acquires the lock, but then dereferences a now-NULL buf pointer to read buf[token].phys, crashing at virtual address 0x10. Add a NULL check for buf inside the mutex-protected section in both the write-done (DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2) and read-done (DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2) handlers and bail out cleanly if buffers have already been freed. This problem is only shown up recently while apr bus was updated to process the commands per service rather from single global queue. Fixes: 5477518b8a0e ("ASoC: qdsp6: audioreach: add q6apm support") Cc: Stable@vger.kernel.org Assisted-by: Claude:claude-4-6-sonnet Reported-by: Val Packett Closes: https://lore.kernel.org/all/133ced18-1aa9-475d-80d8-6120678bdde4@packett.cool/ Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260616170257.9381-1-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 2e5b25b8d00fd..641d6d2432299 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -587,6 +587,10 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) token = hdr->token & APM_WRITE_TOKEN_MASK; done = data->payload; + if (!graph->rx_data.buf) { + mutex_unlock(&graph->lock); + break; + } phys = graph->rx_data.buf[token].phys; mutex_unlock(&graph->lock); /* token numbering starts at 0 */ @@ -609,6 +613,10 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) client_event = APM_CLIENT_EVENT_DATA_READ_DONE; mutex_lock(&graph->lock); rd_done = data->payload; + if (!graph->tx_data.buf) { + mutex_unlock(&graph->lock); + break; + } phys = graph->tx_data.buf[hdr->token].phys; mutex_unlock(&graph->lock); /* token numbering starts at 0 */ From 3dc658921e7d7a97d3f6473d6d2e7e21913b14d4 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Wed, 17 Jun 2026 13:46:04 +0200 Subject: [PATCH 041/188] ASoC: rockchip: Drop problematic guard() changes This reverts commit f7fe9f707360 ("ASoC: rockchip: rockchip_sai: Use guard() for spin locks"). This is very noisy pointless churn that was not tested by the submitter, nor was it addressed to the driver's maintainer. It mixes unrelated whitespace changes (eliminating the blank line between the includes - why?) with hard to review diffs that add a whole indentation level to the function for no benefit, while also not following kernel code style by doing stuff like "ret == 0". The driver is better off without these changes, and they're not worth the time to validate whether they really do make no functional changes. Signed-off-by: Nicolas Frattaroli Link: https://patch.msgid.link/20260617-sai-revert-v1-1-e46adda2213b@collabora.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 262 +++++++++++++++--------------- 1 file changed, 133 insertions(+), 129 deletions(-) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index a195e96fed0ac..ed393e5034a49 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -18,6 +18,7 @@ #include #include #include + #include "rockchip_sai.h" #define DRV_NAME "rockchip-sai" @@ -215,12 +216,14 @@ static void rockchip_sai_xfer_clk_stop_and_wait(struct rk_sai_dev *sai, unsigned static int rockchip_sai_runtime_suspend(struct device *dev) { struct rk_sai_dev *sai = dev_get_drvdata(dev); + unsigned long flags; rockchip_sai_fsync_lost_detect(sai, 0); rockchip_sai_fsync_err_detect(sai, 0); - scoped_guard(spinlock_irqsave, &sai->xfer_lock) - rockchip_sai_xfer_clk_stop_and_wait(sai, NULL); + spin_lock_irqsave(&sai->xfer_lock, flags); + rockchip_sai_xfer_clk_stop_and_wait(sai, NULL); + spin_unlock_irqrestore(&sai->xfer_lock, flags); regcache_cache_only(sai->regmap, true); /* @@ -480,6 +483,7 @@ static int rockchip_sai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct rk_sai_dev *sai = snd_soc_dai_get_drvdata(dai); unsigned int mask = 0, val = 0; unsigned int clk_gates; + unsigned long flags; int ret = 0; pm_runtime_get_sync(dai->dev); @@ -495,56 +499,56 @@ static int rockchip_sai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) sai->is_master_mode = false; break; default: - pm_runtime_put(dai->dev); - return -EINVAL; + ret = -EINVAL; + goto err_pm_put; } - scoped_guard(spinlock_irqsave, &sai->xfer_lock) { - rockchip_sai_xfer_clk_stop_and_wait(sai, &clk_gates); - if (sai->initialized) { - if (sai->has_capture && sai->has_playback) - rockchip_sai_xfer_stop(sai, -1); - else if (sai->has_capture) - rockchip_sai_xfer_stop(sai, SNDRV_PCM_STREAM_CAPTURE); - else - rockchip_sai_xfer_stop(sai, SNDRV_PCM_STREAM_PLAYBACK); - } else { - rockchip_sai_clear(sai, 0); - sai->initialized = true; - } + spin_lock_irqsave(&sai->xfer_lock, flags); + rockchip_sai_xfer_clk_stop_and_wait(sai, &clk_gates); + if (sai->initialized) { + if (sai->has_capture && sai->has_playback) + rockchip_sai_xfer_stop(sai, -1); + else if (sai->has_capture) + rockchip_sai_xfer_stop(sai, SNDRV_PCM_STREAM_CAPTURE); + else + rockchip_sai_xfer_stop(sai, SNDRV_PCM_STREAM_PLAYBACK); + } else { + rockchip_sai_clear(sai, 0); + sai->initialized = true; + } - regmap_update_bits(sai->regmap, SAI_CKR, mask, val); - - mask = SAI_CKR_CKP_MASK | SAI_CKR_FSP_MASK; - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - val = SAI_CKR_CKP_NORMAL | SAI_CKR_FSP_NORMAL; - break; - case SND_SOC_DAIFMT_NB_IF: - val = SAI_CKR_CKP_NORMAL | SAI_CKR_FSP_INVERTED; - break; - case SND_SOC_DAIFMT_IB_NF: - val = SAI_CKR_CKP_INVERTED | SAI_CKR_FSP_NORMAL; - break; - case SND_SOC_DAIFMT_IB_IF: - val = SAI_CKR_CKP_INVERTED | SAI_CKR_FSP_INVERTED; - break; - default: - ret = -EINVAL; - break; - } + regmap_update_bits(sai->regmap, SAI_CKR, mask, val); - if (ret == 0) { - regmap_update_bits(sai->regmap, SAI_CKR, mask, val); - rockchip_sai_fmt_create(sai, fmt); - } - - if (clk_gates) - regmap_update_bits(sai->regmap, SAI_XFER, - SAI_XFER_CLK_MASK | SAI_XFER_FSS_MASK, - clk_gates); + mask = SAI_CKR_CKP_MASK | SAI_CKR_FSP_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = SAI_CKR_CKP_NORMAL | SAI_CKR_FSP_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + val = SAI_CKR_CKP_NORMAL | SAI_CKR_FSP_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + val = SAI_CKR_CKP_INVERTED | SAI_CKR_FSP_NORMAL; + break; + case SND_SOC_DAIFMT_IB_IF: + val = SAI_CKR_CKP_INVERTED | SAI_CKR_FSP_INVERTED; + break; + default: + ret = -EINVAL; + goto err_xfer_unlock; } + regmap_update_bits(sai->regmap, SAI_CKR, mask, val); + + rockchip_sai_fmt_create(sai, fmt); + +err_xfer_unlock: + if (clk_gates) + regmap_update_bits(sai->regmap, SAI_XFER, + SAI_XFER_CLK_MASK | SAI_XFER_FSS_MASK, + clk_gates); + spin_unlock_irqrestore(&sai->xfer_lock, flags); +err_pm_put: pm_runtime_put(dai->dev); return ret; @@ -560,6 +564,7 @@ static int rockchip_sai_hw_params(struct snd_pcm_substream *substream, unsigned int ch_per_lane, slot_width; unsigned int val, fscr, reg; unsigned int lanes, req_lanes; + unsigned long flags; int ret = 0; if (!rockchip_sai_stream_valid(substream, dai)) @@ -586,8 +591,8 @@ static int rockchip_sai_hw_params(struct snd_pcm_substream *substream, dev_err(sai->dev, "not enough lanes (%d) for requested number of %s channels (%d)\n", lanes, reg == SAI_TXCR ? "playback" : "capture", params_channels(params)); - pm_runtime_put(sai->dev); - return -EINVAL; + ret = -EINVAL; + goto err_pm_put; } else { lanes = req_lanes; } @@ -613,88 +618,84 @@ static int rockchip_sai_hw_params(struct snd_pcm_substream *substream, val = SAI_XCR_VDW(32); break; default: - pm_runtime_put(sai->dev); - return -EINVAL; + ret = -EINVAL; + goto err_pm_put; } val |= SAI_XCR_CSR(lanes); - scoped_guard(spinlock_irqsave, &sai->xfer_lock) { + spin_lock_irqsave(&sai->xfer_lock, flags); + + regmap_update_bits(sai->regmap, reg, SAI_XCR_VDW_MASK | SAI_XCR_CSR_MASK, val); - regmap_update_bits(sai->regmap, reg, SAI_XCR_VDW_MASK | SAI_XCR_CSR_MASK, val); + if (!sai->is_tdm) + regmap_update_bits(sai->regmap, reg, SAI_XCR_SBW_MASK, + SAI_XCR_SBW(params_physical_width(params))); - if (!sai->is_tdm) - regmap_update_bits(sai->regmap, reg, SAI_XCR_SBW_MASK, - SAI_XCR_SBW(params_physical_width(params))); + regmap_read(sai->regmap, reg, &val); - regmap_read(sai->regmap, reg, &val); + slot_width = SAI_XCR_SBW_V(val); + ch_per_lane = params_channels(params) / lanes; - slot_width = SAI_XCR_SBW_V(val); - ch_per_lane = params_channels(params) / lanes; + regmap_update_bits(sai->regmap, reg, SAI_XCR_SNB_MASK, + SAI_XCR_SNB(ch_per_lane)); - regmap_update_bits(sai->regmap, reg, SAI_XCR_SNB_MASK, - SAI_XCR_SNB(ch_per_lane)); + fscr = SAI_FSCR_FW(sai->fw_ratio * slot_width * ch_per_lane); - fscr = SAI_FSCR_FW(sai->fw_ratio * slot_width * ch_per_lane); + switch (sai->fpw) { + case FPW_ONE_BCLK_WIDTH: + fscr |= SAI_FSCR_FPW(1); + break; + case FPW_ONE_SLOT_WIDTH: + fscr |= SAI_FSCR_FPW(slot_width); + break; + case FPW_HALF_FRAME_WIDTH: + fscr |= SAI_FSCR_FPW(sai->fw_ratio * slot_width * ch_per_lane / 2); + break; + default: + dev_err(sai->dev, "Invalid Frame Pulse Width %d\n", sai->fpw); + ret = -EINVAL; + goto err_xfer_unlock; + } - switch (sai->fpw) { - case FPW_ONE_BCLK_WIDTH: - fscr |= SAI_FSCR_FPW(1); - break; - case FPW_ONE_SLOT_WIDTH: - fscr |= SAI_FSCR_FPW(slot_width); - break; - case FPW_HALF_FRAME_WIDTH: - fscr |= SAI_FSCR_FPW(sai->fw_ratio * slot_width * ch_per_lane / 2); - break; - default: - dev_err(sai->dev, "Invalid Frame Pulse Width %d\n", sai->fpw); + regmap_update_bits(sai->regmap, SAI_FSCR, + SAI_FSCR_FW_MASK | SAI_FSCR_FPW_MASK, fscr); + + if (sai->is_master_mode) { + bclk_rate = sai->fw_ratio * slot_width * ch_per_lane * params_rate(params); + ret = clk_set_rate(sai->mclk, sai->mclk_rate); + if (ret) { + dev_err(sai->dev, "Failed to set mclk to %u: %pe\n", + sai->mclk_rate, ERR_PTR(ret)); + goto err_xfer_unlock; + } + + mclk_rate = clk_get_rate(sai->mclk); + if (mclk_rate < bclk_rate) { + dev_err(sai->dev, "Mismatch mclk: %u, at least %u\n", + mclk_rate, bclk_rate); ret = -EINVAL; - break; + goto err_xfer_unlock; } - if (ret == 0) { - regmap_update_bits(sai->regmap, SAI_FSCR, - SAI_FSCR_FW_MASK | SAI_FSCR_FPW_MASK, fscr); - - if (sai->is_master_mode) { - bclk_rate = sai->fw_ratio * slot_width * - ch_per_lane * params_rate(params); - ret = clk_set_rate(sai->mclk, sai->mclk_rate); - if (ret) - dev_err(sai->dev, "Failed to set mclk to %u: %pe\n", - sai->mclk_rate, ERR_PTR(ret)); - else { - mclk_rate = clk_get_rate(sai->mclk); - if (mclk_rate < bclk_rate) { - dev_err(sai->dev, "Mismatch mclk: %u, at least %u\n", - mclk_rate, bclk_rate); - ret = -EINVAL; - } else { - - div_bclk = DIV_ROUND_CLOSEST(mclk_rate, bclk_rate); - mclk_req_rate = bclk_rate * div_bclk; - - if (mclk_rate < - mclk_req_rate - CLK_SHIFT_RATE_HZ_MAX || - mclk_rate > - mclk_req_rate + CLK_SHIFT_RATE_HZ_MAX) { - dev_err(sai->dev, - "Mismatch mclk: %u, expected %u (+/- %dHz)\n", - mclk_rate, mclk_req_rate, - CLK_SHIFT_RATE_HZ_MAX); - ret = -EINVAL; - } else - regmap_update_bits(sai->regmap, - SAI_CKR, - SAI_CKR_MDIV_MASK, - SAI_CKR_MDIV(div_bclk)); - } - } - } + div_bclk = DIV_ROUND_CLOSEST(mclk_rate, bclk_rate); + mclk_req_rate = bclk_rate * div_bclk; + + if (mclk_rate < mclk_req_rate - CLK_SHIFT_RATE_HZ_MAX || + mclk_rate > mclk_req_rate + CLK_SHIFT_RATE_HZ_MAX) { + dev_err(sai->dev, "Mismatch mclk: %u, expected %u (+/- %dHz)\n", + mclk_rate, mclk_req_rate, CLK_SHIFT_RATE_HZ_MAX); + ret = -EINVAL; + goto err_xfer_unlock; } + + regmap_update_bits(sai->regmap, SAI_CKR, SAI_CKR_MDIV_MASK, + SAI_CKR_MDIV(div_bclk)); } +err_xfer_unlock: + spin_unlock_irqrestore(&sai->xfer_lock, flags); +err_pm_put: pm_runtime_put(sai->dev); return ret; @@ -704,6 +705,7 @@ static int rockchip_sai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct rk_sai_dev *sai = snd_soc_dai_get_drvdata(dai); + unsigned long flags; if (!rockchip_sai_stream_valid(substream, dai)) return 0; @@ -724,12 +726,13 @@ static int rockchip_sai_prepare(struct snd_pcm_substream *substream, * udelay falls short. */ udelay(20); - scoped_guard(spinlock_irqsave, &sai->xfer_lock) - regmap_update_bits(sai->regmap, SAI_XFER, - SAI_XFER_CLK_MASK | - SAI_XFER_FSS_MASK, - SAI_XFER_CLK_EN | - SAI_XFER_FSS_EN); + spin_lock_irqsave(&sai->xfer_lock, flags); + regmap_update_bits(sai->regmap, SAI_XFER, + SAI_XFER_CLK_MASK | + SAI_XFER_FSS_MASK, + SAI_XFER_CLK_EN | + SAI_XFER_FSS_EN); + spin_unlock_irqrestore(&sai->xfer_lock, flags); } rockchip_sai_fsync_lost_detect(sai, 1); @@ -912,6 +915,7 @@ static int rockchip_sai_set_tdm_slot(struct snd_soc_dai *dai, int slots, int slot_width) { struct rk_sai_dev *sai = snd_soc_dai_get_drvdata(dai); + unsigned long flags; unsigned int clk_gates; int sw = slot_width; @@ -927,16 +931,16 @@ static int rockchip_sai_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; pm_runtime_get_sync(dai->dev); - scoped_guard(spinlock_irqsave, &sai->xfer_lock) { - rockchip_sai_xfer_clk_stop_and_wait(sai, &clk_gates); - regmap_update_bits(sai->regmap, SAI_TXCR, SAI_XCR_SBW_MASK, - SAI_XCR_SBW(sw)); - regmap_update_bits(sai->regmap, SAI_RXCR, SAI_XCR_SBW_MASK, - SAI_XCR_SBW(sw)); - regmap_update_bits(sai->regmap, SAI_XFER, - SAI_XFER_CLK_MASK | SAI_XFER_FSS_MASK, - clk_gates); - } + spin_lock_irqsave(&sai->xfer_lock, flags); + rockchip_sai_xfer_clk_stop_and_wait(sai, &clk_gates); + regmap_update_bits(sai->regmap, SAI_TXCR, SAI_XCR_SBW_MASK, + SAI_XCR_SBW(sw)); + regmap_update_bits(sai->regmap, SAI_RXCR, SAI_XCR_SBW_MASK, + SAI_XCR_SBW(sw)); + regmap_update_bits(sai->regmap, SAI_XFER, + SAI_XFER_CLK_MASK | SAI_XFER_FSS_MASK, + clk_gates); + spin_unlock_irqrestore(&sai->xfer_lock, flags); pm_runtime_put(dai->dev); return 0; From 0d9a72571e573a10edbfc2c172556c07aff5549b Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Tue, 16 Jun 2026 18:33:22 -0500 Subject: [PATCH 042/188] ASoC: tlv320aic3x: restrict CLKDIV bypass Q values in dual-rate mode The datasheet documents that when the PLL is disabled and dual-rate mode is enabled, only Q values {4, 8, 9, 12, 16} are valid for the CLKDIV bypass path; all other Q values produce invalid bitclock output. The existing loop iterates Q from 2 to 17 without this restriction, causing silent audio failure when an out-of-spec Q is picked. Restrict the Q search to the allowed set in dual-rate mode. Fixes: 4f9c16ccfa26 ("[ALSA] soc - tlv320aic3x - revisit clock setup") Suggested-by: Mir Jeffres Signed-off-by: Sen Wang Link: https://patch.msgid.link/20260616233322.873081-1-sen@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index b12c1952823ba..b38393a8130ff 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1049,11 +1049,13 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + static const u8 dual_rate_q[] = {4, 8, 9, 12, 16}; struct snd_soc_component *component = dai->component; struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; u16 d, pll_d = 1; + bool dual_rate; int clk; int width = aic3x->slot_width; @@ -1079,14 +1081,25 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, /* Fsref can be 44100 or 48000 */ fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + dual_rate = params_rate(params) >= 64000; /* Try to find a value for Q which allows us to bypass the PLL and * generate CODEC_CLK directly. */ - for (pll_q = 2; pll_q < 18; pll_q++) - if (aic3x->sysclk / (128 * pll_q) == fsref) { - bypass_pll = 1; - break; + if (dual_rate) { + for (int i = 0; i < ARRAY_SIZE(dual_rate_q); i++) { + pll_q = dual_rate_q[i]; + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } } + } else { + for (pll_q = 2; pll_q < 18; pll_q++) + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + } if (bypass_pll) { pll_q &= 0xf; @@ -1106,13 +1119,13 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, * right DAC to right channel input */ data = (LDAC2LCH | RDAC2RCH); data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; - if (params_rate(params) >= 64000) + if (dual_rate) data |= DUAL_RATE_MODE; snd_soc_component_write(component, AIC3X_CODEC_DATAPATH_REG, data); /* codec sample rate select */ data = (fsref * 20) / params_rate(params); - if (params_rate(params) < 64000) + if (!dual_rate) data /= 2; data /= 5; data -= 2; From 40cccc2602baf2b6f67f11f34618a5b90fc990ea Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Mon, 15 Jun 2026 17:10:12 +0800 Subject: [PATCH 043/188] ASoC: rt5650: enhance spk protection function This patch adjusts several default settings to ensure the speaker protection function can be enabled safely. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260615091012.718168-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 6 ++---- sound/soc/codecs/rt5645.h | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 8a9af260e5f7f..e9819653b30d6 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -83,6 +83,8 @@ static const struct reg_sequence rt5650_init_list[] = { {RT5645_PWR_ANLG1, 0x02}, {RT5645_IL_CMD3, 0x6728}, {RT5645_PR_BASE + 0x3a, 0x0000}, + {RT5645_CLSD_OUT_CTRL1, 0x4059}, + {RT5645_GEN_CTRL3, 0x0200}, }; static const struct reg_default rt5645_reg[] = { @@ -1855,13 +1857,9 @@ static int rt5645_spk_event(struct snd_soc_dapm_widget *w, RT5645_PWR_CLS_D_L, RT5645_PWR_CLS_D | RT5645_PWR_CLS_D_R | RT5645_PWR_CLS_D_L); - snd_soc_component_update_bits(component, RT5645_GEN_CTRL3, - RT5645_DET_CLK_MASK, RT5645_DET_CLK_MODE1); break; case SND_SOC_DAPM_PRE_PMD: - snd_soc_component_update_bits(component, RT5645_GEN_CTRL3, - RT5645_DET_CLK_MASK, RT5645_DET_CLK_DIS); snd_soc_component_write(component, RT5645_EQ_CTRL2, 0); snd_soc_component_update_bits(component, RT5645_PWR_DIG1, RT5645_PWR_CLS_D | RT5645_PWR_CLS_D_R | diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h index bef74b29fd541..a5bfe9861b8ba 100644 --- a/sound/soc/codecs/rt5645.h +++ b/sound/soc/codecs/rt5645.h @@ -118,6 +118,7 @@ #define RT5645_A_JD_CTRL1 0x94 #define RT5645_VAD_CTRL4 0x9d #define RT5645_CLSD_OUT_CTRL 0xa0 +#define RT5645_CLSD_OUT_CTRL1 0xa1 /* Function - Digital */ #define RT5645_ADC_EQ_CTRL1 0xae #define RT5645_ADC_EQ_CTRL2 0xaf From daa668dece63cf0f20c021846dbace1e3d4e0740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Wed, 17 Jun 2026 16:55:08 +0200 Subject: [PATCH 044/188] ASoC: audio-graph-card2: Drop warning for manually selected DAI formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes the DAI format must be specified in the audio-graph-card2 device tree, so emitting a warning can be misleading. Revert back to emitting no warning. A few examples where automatic format selection might not be applicable: - For DPCM, where the other side of the DAI link is not apparent, no proper selection can actually be made. This can lead to disagreeing formats. - Due to hardware peculiarities, some ostensibly supported formats might not work in practice. In either case, the only correct solution is for the sound card to set the format Link: https://lore.kernel.org/all/87ik7s36k2.wl-kuninori.morimoto.gx@renesas.com/ Signed-off-by: Alvin Šipraga Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260617145508.327213-1-alvin@pqrs.dk Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 6894bb936cfd2..0202ed0ee78e8 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -778,18 +778,6 @@ static void graph_link_init(struct simple_util_priv *priv, graph_parse_daifmt(ports_cpu, &daifmt); graph_parse_daifmt(ports_codec, &daifmt); graph_parse_daifmt(lnk, &daifmt); - if (daifmt) { - struct device *dev = simple_priv_to_dev(priv); - - /* - * Recommend to use Auto Select by using .auto_selectable_formats. - * linux/sound/soc/renesas/rcar/core.c can be good sample for it. - * - * One note is that Audio Graph Card2 still keeps compatible to set - * DAI format via DT. - */ - dev_warn_once(dev, "use .auto_selectable_formats on each corresponding CPU/Codec driver"); - } graph_util_parse_link_direction(lnk, &playback_only, &capture_only); graph_util_parse_link_direction(ports_cpu, &playback_only, &capture_only); From b85b023309e58a86b9791d2b0b75ac6a330f3728 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 17 Jun 2026 16:47:53 +0200 Subject: [PATCH 045/188] ASoC: cs530x: Fix expected MCLK rates for CS5302/4/8 When this driver was first added, it accepted rates of 24.56 MHz and 22.572 MHz for the MCLK when PLL bypass is enabled. These rates seem to have no basis in the datasheets and were thus replaced with 45.1584 MHz and 49.152 MHz, respectively, in commit e7ab858390f2 ("ASoC: cs530x: Correct MCLK reference frequency values"). While the new rates are indeed correct for the CS4xxx ICs[0][1][2][3], they are incorrect for the CS530x ICs the driver was originally written to support as the MCLK frequencies are halved there[4][5][6]. Fix this by checking against the correct type-appropriate rates. While at it, drop the CS530X_SYSCLK_REF_* macros. They arguably confuse more than they help, especially as they are not applicable to the cs5302/4/8. [0]: https://statics.cirrus.com/pubs/proDatasheet/CS4282P_DS1318F1.pdf [1]: https://statics.cirrus.com/pubs/proDatasheet/CS4302P_DS1315F1.pdf [2]: https://statics.cirrus.com/pubs/proDatasheet/CS4304P_DS1316F1.pdf [3]: https://statics.cirrus.com/pubs/proDatasheet/CS4308P_DS1317F1.pdf [4]: https://statics.cirrus.com/pubs/proDatasheet/CS5302P_DS1312F1.pdf [5]: https://statics.cirrus.com/pubs/proDatasheet/CS5304P_DS1313F1.pdf [6]: https://statics.cirrus.com/pubs/proDatasheet/CS5308P_DS1314F1.pdf Fixes: 2884c29152c0 ("ASoC: cs530x: Support for cs530x ADCs") Signed-off-by: Ahmad Fatoum Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260617-cs530x-mclk-v1-1-0215b5f1a0a4@pengutronix.de Signed-off-by: Mark Brown --- sound/soc/codecs/cs530x.c | 29 ++++++++++++++++++++++++----- sound/soc/codecs/cs530x.h | 6 ------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/cs530x.c b/sound/soc/codecs/cs530x.c index 18b5ff75feec4..2c7e331359117 100644 --- a/sound/soc/codecs/cs530x.c +++ b/sound/soc/codecs/cs530x.c @@ -1093,6 +1093,29 @@ static int cs530x_component_probe(struct snd_soc_component *component) return 0; } +static bool cs530x_mclk_freq_is_valid(struct cs530x_priv *cs530x, + unsigned int freq) +{ + /* + * All these chips support 48 kHz- and 44.1 kHz-related sample rates, + * but they differ in what MCLK frequency is required for achieving + * the sample rate. + */ + switch (cs530x->devtype) { + case CS4282: + case CS4302: + case CS4304: + case CS4308: + return freq == 49152000 || freq == 45158400; + case CS5302: + case CS5304: + case CS5308: + return freq == 24576000 || freq == 22579200; + } + + return false; +} + static int cs530x_set_sysclk(struct snd_soc_component *component, int clk_id, int source, unsigned int freq, int dir) { @@ -1101,11 +1124,7 @@ static int cs530x_set_sysclk(struct snd_soc_component *component, int clk_id, switch (source) { case CS530X_SYSCLK_SRC_MCLK: - switch (freq) { - case CS530X_SYSCLK_REF_45_1MHZ: - case CS530X_SYSCLK_REF_49_1MHZ: - break; - default: + if (!cs530x_mclk_freq_is_valid(cs530x, freq)) { dev_err(component->dev, "Invalid MCLK source rate %d\n", freq); return -EINVAL; } diff --git a/sound/soc/codecs/cs530x.h b/sound/soc/codecs/cs530x.h index 1e2f6a7a589c1..18aa4dfd0c860 100644 --- a/sound/soc/codecs/cs530x.h +++ b/sound/soc/codecs/cs530x.h @@ -200,12 +200,6 @@ /* IN_VOL_CTL5 and OUT_VOL_CTL5 */ #define CS530X_INOUT_VU BIT(0) -/* MCLK Reference Source Frequency */ -/* 41KHz related */ -#define CS530X_SYSCLK_REF_45_1MHZ 45158400 -/* 48KHz related */ -#define CS530X_SYSCLK_REF_49_1MHZ 49152000 - /* System Clock Source */ #define CS530X_SYSCLK_SRC_MCLK 0 #define CS530X_SYSCLK_SRC_PLL 1 From 4fe9cfd1789976284e5daf1bd8d930b110ac380f Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 23 Jun 2026 11:18:10 +0100 Subject: [PATCH 046/188] soundwire: Move wait for initialisation helper to header As SoundWire devices tend to enumerate on the bus after probe, drivers frequently need to wait for the device to initialise from common driver code. The common system is to split drivers into a core module and then a module for each communication bus. These two facts tend to cause Kconfig issues, the issue tends to be when SOUNDWIRE=m and DRIVER_I2C=y, this usually selects DRIVER=y. The driver code then wants to call sdw_slave_wait_for_init(), but this results in calling a module function from built in code. A depends on SOUNDWIRE | !SOUNDWIRE could be added to the end driver but this seems slightly off as it adds a lot of counter intuitive depends. A simpler solution is to make sdw_slave_wait_for_init() a static inline function. As part of doing this add a check for the slave device being NULL acknowledging that this is likely called from code that is shared between control buses. It does require dropping the call to sdw_show_ping_status() but this can be added back in end drivers that used it originally. Currently this is causing rand config issues on RT5682 and will soon also cause similar problems on cs42l43. Acked-by: Vinod Koul Acked-by: Arnd Bergmann Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260623101814.24044-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/soundwire/bus.c | 28 -------------------------- include/linux/soundwire/sdw.h | 38 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index f38a0f7d0f27e..0490777fa4067 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -1372,34 +1372,6 @@ int sdw_slave_get_current_bank(struct sdw_slave *slave) } EXPORT_SYMBOL_GPL(sdw_slave_get_current_bank); -/** - * sdw_slave_wait_for_init - Wait for device initialisation - * @slave: Pointer to the SoundWire peripheral. - * @timeout_ms: Timeout in milliseconds. - * - * Wait for a peripheral device to enumerate and be initialised by the - * SoundWire core. - * - * Return: Zero on success, and a negative error code on failure. - */ -int sdw_slave_wait_for_init(struct sdw_slave *slave, int timeout_ms) -{ - unsigned long time; - - time = wait_for_completion_timeout(&slave->initialization_complete, - msecs_to_jiffies(timeout_ms)); - if (!time) { - dev_err(&slave->dev, "Initialization not complete\n"); - sdw_show_ping_status(slave->bus, true); - return -ETIMEDOUT; - } - - slave->unattach_request = 0; - - return 0; -} -EXPORT_SYMBOL_GPL(sdw_slave_wait_for_init); - static int sdw_slave_set_frequency(struct sdw_slave *slave) { int scale_index; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index a46cbaec59491..b484784e2690d 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1093,8 +1094,6 @@ int sdw_slave_get_current_bank(struct sdw_slave *sdev); int sdw_slave_get_scale_index(struct sdw_slave *slave, u8 *base); -int sdw_slave_wait_for_init(struct sdw_slave *slave, int timeout_ms); - /* messaging and data APIs */ int sdw_read(struct sdw_slave *slave, u32 addr); int sdw_write(struct sdw_slave *slave, u32 addr, u8 value); @@ -1138,12 +1137,6 @@ static inline int sdw_slave_get_current_bank(struct sdw_slave *sdev) return -EINVAL; } -static inline int sdw_slave_wait_for_init(struct sdw_slave *slave, int timeout_ms) -{ - WARN_ONCE(1, "SoundWire API is disabled"); - return -EINVAL; -} - /* messaging and data APIs */ static inline int sdw_read(struct sdw_slave *slave, u32 addr) { @@ -1207,4 +1200,33 @@ static inline int sdw_update_no_pm(struct sdw_slave *slave, u32 addr, u8 mask, u #endif /* CONFIG_SOUNDWIRE */ +/** + * sdw_slave_wait_for_init - Wait for device initialisation + * @slave: Pointer to the SoundWire peripheral. + * @timeout_ms: Timeout in milliseconds. + * + * Wait for a peripheral device to enumerate and be initialised by the + * SoundWire core. + * + * Return: Zero on success, and a negative error code on failure. + */ +static inline int sdw_slave_wait_for_init(struct sdw_slave *slave, int timeout_ms) +{ + unsigned long time; + + if (!slave) + return 0; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(timeout_ms)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete\n"); + return -ETIMEDOUT; + } + + slave->unattach_request = 0; + + return 0; +} + #endif /* __SOUNDWIRE_H */ From a6be59319b531d4a369eccd68070f77246a2441c Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 23 Jun 2026 11:18:11 +0100 Subject: [PATCH 047/188] ASoC: es9356: Add back local call to sdw_show_ping_status() As the core no longer calls this debug helper add it back to the drivers that originally called it. Acked-by: Arnd Bergmann Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260623101814.24044-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/es9356.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c index 670e918b56a46..8db81d5746240 100644 --- a/sound/soc/codecs/es9356.c +++ b/sound/soc/codecs/es9356.c @@ -1111,8 +1111,10 @@ static int es9356_sdca_dev_resume(struct device *dev) es9356->disable_irq = false; ret = sdw_slave_wait_for_init(slave, es9356_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(es9356->regmap, false); regcache_sync(es9356->regmap); From b926495b6aadcc47a75b1c0758d4360e22112fa2 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 23 Jun 2026 11:18:12 +0100 Subject: [PATCH 048/188] ASoC: max98373: Add back local call to sdw_show_ping_status() As the core no longer calls this debug helper add it back to the drivers that originally called it. Acked-by: Arnd Bergmann Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260623101814.24044-4-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98373-sdw.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c index 6829fa07c9ecb..7a42052dc0516 100644 --- a/sound/soc/codecs/max98373-sdw.c +++ b/sound/soc/codecs/max98373-sdw.c @@ -272,8 +272,10 @@ static int max98373_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, MAX98373_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(max98373->regmap, false); regcache_sync(max98373->regmap); From f0b8d1b332246747fd629273181c7e6e7396b170 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 23 Jun 2026 11:18:13 +0100 Subject: [PATCH 049/188] ASoC: ti: Add back local call to sdw_show_ping_status() As the core no longer calls this debug helper add it back to the drivers that originally called it. Acked-by: Arnd Bergmann Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260623101814.24044-5-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/tac5xx2-sdw.c | 4 +++- sound/soc/codecs/tas2783-sdw.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c index bb12cfb6da12b..ace06f5ab58c1 100644 --- a/sound/soc/codecs/tac5xx2-sdw.c +++ b/sound/soc/codecs/tac5xx2-sdw.c @@ -1445,8 +1445,10 @@ static s32 tac5xx2_sdca_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, TAC5XX2_PROBE_TIMEOUT_MS); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(tac_dev->regmap, false); regcache_mark_dirty(tac_dev->regmap); diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 7d70e7e3f24f4..1127ea59b5e4c 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -1083,8 +1083,10 @@ static s32 tas2783_sdca_dev_resume(struct device *dev) int ret; ret = sdw_slave_wait_for_init(slave, TAS2783_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(tas_dev->regmap, false); regcache_sync(tas_dev->regmap); From 1150a3b4750ba9a5261b48bc634c0f6ba6581cb1 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 23 Jun 2026 11:18:14 +0100 Subject: [PATCH 050/188] ASoC: realtek: Add back local call to sdw_show_ping_status() As the core no longer calls this debug helper add it back to the drivers that originally called it. Acked-by: Arnd Bergmann Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260623101814.24044-6-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1017-sdca-sdw.c | 4 +++- sound/soc/codecs/rt1308-sdw.c | 4 +++- sound/soc/codecs/rt1316-sdw.c | 4 +++- sound/soc/codecs/rt5682-sdw.c | 4 +++- sound/soc/codecs/rt700-sdw.c | 4 +++- sound/soc/codecs/rt711-sdca-sdw.c | 4 +++- sound/soc/codecs/rt712-sdca-dmic.c | 4 +++- sound/soc/codecs/rt712-sdca-sdw.c | 4 +++- sound/soc/codecs/rt715-sdca-sdw.c | 4 +++- sound/soc/codecs/rt715-sdw.c | 4 +++- sound/soc/codecs/rt721-sdca-sdw.c | 4 +++- sound/soc/codecs/rt722-sdca-sdw.c | 4 +++- 12 files changed, 36 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c index d62e8a2536767..91d3d43cd9988 100644 --- a/sound/soc/codecs/rt1017-sdca-sdw.c +++ b/sound/soc/codecs/rt1017-sdca-sdw.c @@ -779,8 +779,10 @@ static int rt1017_sdca_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT1017_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt1017->regmap, false); regcache_sync(rt1017->regmap); diff --git a/sound/soc/codecs/rt1308-sdw.c b/sound/soc/codecs/rt1308-sdw.c index 39e06a3a75609..60e5040b6dd9d 100644 --- a/sound/soc/codecs/rt1308-sdw.c +++ b/sound/soc/codecs/rt1308-sdw.c @@ -774,8 +774,10 @@ static int rt1308_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT1308_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt1308->regmap, false); regcache_sync_region(rt1308->regmap, 0xc000, 0xcfff); diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c index 1828fd9d5af6a..5e8eda6a5f7f8 100644 --- a/sound/soc/codecs/rt1316-sdw.c +++ b/sound/soc/codecs/rt1316-sdw.c @@ -751,8 +751,10 @@ static int rt1316_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT1316_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt1316->regmap, false); regcache_sync(rt1316->regmap); diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c index ec2a35a0cacde..dec8c2147d684 100644 --- a/sound/soc/codecs/rt5682-sdw.c +++ b/sound/soc/codecs/rt5682-sdw.c @@ -769,8 +769,10 @@ static int rt5682_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt5682->sdw_regmap, false); regcache_cache_only(rt5682->regmap, false); diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c index 30fcca210f051..6bc636c86f427 100644 --- a/sound/soc/codecs/rt700-sdw.c +++ b/sound/soc/codecs/rt700-sdw.c @@ -528,8 +528,10 @@ static int rt700_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT700_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt700->regmap, false); regcache_sync_region(rt700->regmap, 0x3000, 0x8fff); diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c index a8164fc3979ab..461315844ba99 100644 --- a/sound/soc/codecs/rt711-sdca-sdw.c +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -454,8 +454,10 @@ static int rt711_sdca_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt711->regmap, false); regcache_sync(rt711->regmap); diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index 4c5c2f5ba5edf..8b7d50a80ff98 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -911,8 +911,10 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt712->regmap, false); regcache_sync(rt712->regmap); diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index 5817321804736..2787524c796e8 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -467,8 +467,10 @@ static int rt712_sdca_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt712->regmap, false); regcache_sync(rt712->regmap); diff --git a/sound/soc/codecs/rt715-sdca-sdw.c b/sound/soc/codecs/rt715-sdca-sdw.c index 4b9815b5628db..fabd21bbbe5b9 100644 --- a/sound/soc/codecs/rt715-sdca-sdw.c +++ b/sound/soc/codecs/rt715-sdca-sdw.c @@ -230,8 +230,10 @@ static int rt715_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT715_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt715->regmap, false); regcache_sync_region(rt715->regmap, diff --git a/sound/soc/codecs/rt715-sdw.c b/sound/soc/codecs/rt715-sdw.c index 7f83a8f1a06e9..a4a3945522e81 100644 --- a/sound/soc/codecs/rt715-sdw.c +++ b/sound/soc/codecs/rt715-sdw.c @@ -507,8 +507,10 @@ static int rt715_dev_resume(struct device *dev) return 0; ret = sdw_slave_wait_for_init(slave, RT715_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt715->regmap, false); regcache_sync_region(rt715->regmap, 0x3000, 0x8fff); diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index 58606209316a4..02df04a0ddad4 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -505,8 +505,10 @@ static int rt721_sdca_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt721->regmap, false); regcache_sync(rt721->regmap); diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c index 0f76492ff915c..284900933ebf4 100644 --- a/sound/soc/codecs/rt722-sdca-sdw.c +++ b/sound/soc/codecs/rt722-sdca-sdw.c @@ -552,8 +552,10 @@ static int rt722_sdca_dev_resume(struct device *dev) } ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); - if (ret) + if (ret) { + sdw_show_ping_status(slave->bus, true); return ret; + } regcache_cache_only(rt722->regmap, false); regcache_sync(rt722->regmap); From 9b45ee2ca9d2ac222cfea85da82d5e806daf4d82 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 23 Jun 2026 20:05:26 +0900 Subject: [PATCH 051/188] ASoC: SDCA: Validate written enum value in ge_put_enum_double() ge_put_enum_double() passes the user-supplied enumeration index item[0] to snd_soc_enum_item_to_val() without checking it against the number of items in the enum: ret = snd_soc_enum_item_to_val(e, item[0]); snd_soc_enum_item_to_val() indexes the heap-allocated e->values[] array with that index (e->values is set from a devm_kcalloc() of e->items entries), so a control write with an out-of-range item[0] reads past the end of the values buffer. The bounds check in snd_soc_dapm_put_enum_double() only runs afterwards, so it does not prevent the read here. Reject an out-of-range item before using it, matching the other enum put handlers. This issue was pointed out by the Sashiko AI review bot while reviewing a related enum-validation series: https://lore.kernel.org/all/20260609125735.CEB651F00893@smtp.kernel.org/ Fixes: 812ff1baa764 ("ASoC: SDCA: Limit values user can write to Selected Mode") Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260623110526.813217-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_asoc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index e76afa396b0ab..b4dedba719dc6 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -160,6 +160,9 @@ static int ge_put_enum_double(struct snd_kcontrol *kcontrol, unsigned int reg = e->reg; int ret; + if (item[0] >= e->items) + return -EINVAL; + reg &= ~SDW_SDCA_CTL_CSEL(0x3F); reg |= SDW_SDCA_CTL_CSEL(SDCA_CTL_GE_DETECTED_MODE); From a6976112e85ff764a29696233dc20bb1c4101189 Mon Sep 17 00:00:00 2001 From: Bartosz Juraszewski Date: Mon, 22 Jun 2026 20:27:33 +0200 Subject: [PATCH 052/188] ASoC: tas2783: Update loaded firmware names to linux-firmware 20260519 In linux-firmware commit from 2026-05-19 `2f90f4fe5c67f51a8410907a...` `ASoC: tas2783: Add Firmware files for tas2783A projects` by Baojun Xu 138 new firmware files for tas2783 were added, none of which are loaded by the kernel. Kernel expects files to be named with the following convention: "%04X-%1X-%1X.bin". However the added firmware files follow "-0x%1X.bin" naming instead with `0x` hex prefix, which fails to load resulting in following dmesg log: slave-tas2783 sdw:0:1:0102:0000:01:8: Direct firmware load for 1714-1-8.bin failed with error -2 slave-tas2783 sdw:0:1:0102:0000:01:8: Failed to read fw binary 1714-1-8.bin slave-tas2783 sdw:0:1:0102:0000:01:b: Direct firmware load for 1714-1-B.bin failed with error -2 slave-tas2783 sdw:0:1:0102:0000:01:b: Failed to read fw binary 1714-1-B.bin slave-tas2783 sdw:0:1:0102:0000:01:8: error playback without fw download slave-tas2783 sdw:0:1:0102:0000:01:8: ASoC error (-22): at snd_soc_dai_hw_params() on tas2783-codec This same commit removes all 22 symlinks from WHENCE, that used naming without the '0x' prefix to only 6 prevoiusly existing .bin files. This patch adds `0x` prefix explicitly to the generated firmware name allowing file to successfully load. In case prefixed firmware is missing due to out of date linux-firmware, we set the fallback flag and attempt to load firmware again based on the old file names. This prefix change results in functioning firmware loading on ASUS ProArt PX13 HN7306EAC, which uses 1714-1-0x8.bin and 1714-1-0xB.bin firmware files. Tested on top of 7.1 and next-20260619 with SND_SOC_AMD_ACP7X set to no. Signed-off-by: Bartosz Juraszewski Link: https://patch.msgid.link/20260622182733.23947-1-bjuraszewski@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 73 +++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 1127ea59b5e4c..3d0b116544cc4 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -100,6 +100,8 @@ struct tas2783_prv { wait_queue_head_t fw_wait; bool fw_dl_task_done; bool fw_dl_success; + /* use fallback fw name */ + bool fw_use_fallback; }; static const struct reg_default tas2783_reg_default[] = { @@ -740,11 +742,19 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) goto out; } + /* firmware binary not found*/ if (!fmw || !fmw->data) { - /* firmware binary not found*/ - dev_err(tas_dev->dev, - "Failed to read fw binary %s\n", - tas_dev->rca_binaryname); + if (!tas_dev->fw_use_fallback) { + tas_dev->fw_use_fallback = true; + dev_info(tas_dev->dev, + "Failed to read preferred fw binary: %s, attempting fallback binary load\n", + tas_dev->rca_binaryname); + } else { + dev_err(tas_dev->dev, + "Failed to read fallback fw binary %s\n", + tas_dev->rca_binaryname); + } + ret = -EINVAL; goto out; } @@ -1105,13 +1115,16 @@ static void tas_generate_fw_name(struct sdw_slave *slave, char *name, size_t siz bool pci_found = false; #if IS_ENABLED(CONFIG_PCI) struct device *dev = &slave->dev; + struct tas2783_prv *tas_dev = dev_get_drvdata(&slave->dev); struct pci_dev *pci = NULL; + const char *fw_uid_prefix = tas_dev->fw_use_fallback ? "" : "0x"; for (; dev; dev = dev->parent) { if (dev->bus == &pci_bus_type) { pci = to_pci_dev(dev); - scnprintf(name, size, "%04X-%1X-%1X.bin", - pci->subsystem_device, bus->link_id, unique_id); + scnprintf(name, size, "%04X-%1X-%s%1X.bin", + pci->subsystem_device, bus->link_id, + fw_uid_prefix, unique_id); pci_found = true; break; } @@ -1123,28 +1136,15 @@ static void tas_generate_fw_name(struct sdw_slave *slave, char *name, size_t siz bus->link_id, unique_id); } -static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) +static s32 tas_fw_load(struct tas2783_prv *tas_dev, struct sdw_slave *slave) { - struct tas2783_prv *tas_dev = dev_get_drvdata(dev); s32 ret; u8 unique_id = tas_dev->sdw_peripheral->id.unique_id; - if (tas_dev->hw_init) - return 0; - - tas_dev->fw_dl_task_done = false; - tas_dev->fw_dl_success = false; - - ret = regmap_write(tas_dev->regmap, TAS2783_SW_RESET, 0x1); - if (ret) { - dev_err(dev, "sw reset failed, err=%d", ret); - return ret; - } - usleep_range(2000, 2200); - tas_generate_fw_name(slave, tas_dev->rca_binaryname, sizeof(tas_dev->rca_binaryname)); + tas_dev->fw_dl_task_done = false; ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, tas_dev->rca_binaryname, tas_dev->dev, GFP_KERNEL, tas_dev, tas2783_fw_ready); @@ -1159,8 +1159,35 @@ static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) msecs_to_jiffies(TIMEOUT_FW_DL_MS)); if (!ret) { dev_err(tas_dev->dev, "fw request, wait_event timeout\n"); - ret = -EAGAIN; - } else { + return -EAGAIN; + } + + return 0; +} + +static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct tas2783_prv *tas_dev = dev_get_drvdata(dev); + s32 ret; + + if (tas_dev->hw_init) + return 0; + + tas_dev->fw_dl_success = false; + + ret = regmap_write(tas_dev->regmap, TAS2783_SW_RESET, 0x1); + if (ret) { + dev_err(dev, "sw reset failed, err=%d", ret); + return ret; + } + usleep_range(2000, 2200); + + tas_dev->fw_use_fallback = false; + ret = tas_fw_load(tas_dev, slave); + if (!ret && tas_dev->fw_use_fallback) + ret = tas_fw_load(tas_dev, slave); + + if (!ret) { if (tas_dev->sa_func_data) ret = sdca_regmap_write_init(dev, tas_dev->regmap, tas_dev->sa_func_data); From a96ac071f6e29c5afb6bd58f21e3843ba411da06 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Tue, 23 Jun 2026 18:25:14 +0800 Subject: [PATCH 053/188] ASoC: rt5575: Use __le32 for SPI burst write address The addr field in the SPI burst write buffer represents on-wire little-endian data. Define it as __le32 so that the assignment of cpu_to_le32() is type correct and avoids sparse endian warnings. Closes: https://lore.kernel.org/oe-kbuild-all/202606230139.rFZUVpCa-lkp@intel.com/ Fixes: 420739112e95 ("ASoC: rt5575: Add the codec driver for the ALC5575") Reported-by: kernel test robot Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260623102514.2422990-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5575-spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c index 9a349965435b6..d5b3a57c88666 100644 --- a/sound/soc/codecs/rt5575-spi.c +++ b/sound/soc/codecs/rt5575-spi.c @@ -17,7 +17,7 @@ struct rt5575_spi_burst_write { u8 cmd; - u32 addr; + __le32 addr; u8 data[RT5575_SPI_BUF_LEN]; u8 dummy; } __packed; From cda34fdb8f761c2ba6a1098dd57e993aca4b9e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 24 Jun 2026 10:37:07 +0200 Subject: [PATCH 054/188] ASoC: rockchip: rockchip_sai: #include explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently that header is only included via: -> -> which doesn't look reliable, still more in the presence of the comment: /* For the current users of sound/soc.h to avoid build issues */ in . Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Nicolas Frattaroli Link: https://patch.msgid.link/20260624083708.254517-2-u.kleine-koenig@baylibre.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index ed393e5034a49..585e89f61f0d9 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From 5ab4416ccdeb38fa429d930b3042df389709ee83 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 23 Jun 2026 14:58:21 +0100 Subject: [PATCH 055/188] ASoC: soc-core: Don't fail if device_link could not be created If device_link_add() fails in snd_soc_bind_card() just skip that driver pair and carry on. This means that ASoC must now keep track of which components it was able to device_link to the card->dev. The new card_device_link member of struct snd_soc_component is non-NULL if a device_link exists. The intent of the device link is to ensure that the machine driver system-suspends before the component drivers, to prevent ASoC suspend attempting to reconfigure a driver that has already suspended. It isn't possible to create this device link if the machine driver is a parent of the component driver or already has a device_link in the opposite direction. In this case skip the link. A warn is placed in kernel log since this might indicate a genuine design problem with those two drivers (this can be downgraded to dbg in future when people are happy that all these special drivers correctly handle their reversed shutdown order). Fixes: 0f54ce994b23 ("ASoC: soc-core: Create device_link to ensure correct suspend order") Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/linux-sound/61bd38e7-5eb9-4448-a93f-afa2ccbd1c9d@opensource.cirrus.com/T/#m496fe5a11b0a3649afd2e85da5e1cea82bb16d8a Tested-by: Marek Szyprowski Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260623135821.4125543-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 4 ++++ sound/soc/soc-core.c | 35 +++++++++++++++-------------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 11bc9527653ff..aa423865dbe7c 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -10,6 +10,8 @@ #include +struct device_link; + /* * Component probe and remove ordering levels for components with runtime * dependencies. @@ -216,6 +218,8 @@ struct snd_soc_component { struct list_head card_aux_list; /* for auxiliary bound components */ struct list_head card_list; + struct device_link *card_device_link; + const struct snd_soc_component_driver *driver; struct list_head dai_list; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 86b6c752a56b7..7817beea5b3bc 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1980,19 +1980,15 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) } } -static void snd_soc_remove_device_links(struct snd_soc_card *card, - struct snd_soc_component *stop_at) +static void snd_soc_remove_device_links(struct snd_soc_card *card) { struct snd_soc_component *component; for_each_card_components(card, component) { - if (card->dev == component->dev) - continue; - - device_link_remove(card->dev, component->dev); - - if (component == stop_at) - return; + if (component->card_device_link) { + device_link_del(component->card_device_link); + component->card_device_link = NULL; + } } } @@ -2001,7 +1997,7 @@ static void snd_soc_unbind_card(struct snd_soc_card *card) if (snd_soc_card_is_instantiated(card)) { card->instantiated = false; - snd_soc_remove_device_links(card, NULL); + snd_soc_remove_device_links(card); soc_cleanup_card_resources(card); } @@ -2012,7 +2008,6 @@ static int snd_soc_bind_card(struct snd_soc_card *card) struct snd_soc_pcm_runtime *rtd; struct snd_soc_component *component; struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); - struct snd_soc_component *last_devlinked_component = NULL; int ret; snd_soc_card_mutex_lock_root(card); @@ -2141,19 +2136,21 @@ static int snd_soc_bind_card(struct snd_soc_card *card) * Add device_link from card to component so that system_suspend * will be done in the correct order. The card must suspend first * to stop audio activity before the components suspend. + * + * If a driver pair already have a link in the opposite direction + * they must manage their own suspend order. */ for_each_card_components(card, component) { if (card->dev == component->dev) continue; - if (!device_link_add(card->dev, component->dev, DL_FLAG_STATELESS)) { - dev_warn(card->dev, "Failed to create device link to %s\n", + component->card_device_link = device_link_add(card->dev, + component->dev, + DL_FLAG_STATELESS); + if (!component->card_device_link) { + dev_warn(card->dev, "Could not create device link to %s\n", dev_name(component->dev)); - ret = -EINVAL; - goto probe_end; } - - last_devlinked_component = component; } ret = snd_soc_card_late_probe(card); @@ -2185,9 +2182,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) probe_end: if (ret < 0) { - if (last_devlinked_component) - snd_soc_remove_device_links(card, last_devlinked_component); - + snd_soc_remove_device_links(card); soc_cleanup_card_resources(card); } From 8084bfecdeea3433efb9abac58cce8c82edf06dc Mon Sep 17 00:00:00 2001 From: Baojun Xu Date: Thu, 25 Jun 2026 18:28:15 +0800 Subject: [PATCH 056/188] ASoC: tas2781: Update default register address to TAS2563 The previous TAS2781 address is overwritten during calibration, so switching to the TAS2563 address is required. Signed-off-by: Baojun Xu Link: https://patch.msgid.link/20260625102815.20043-1-baojun.xu@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index d45f805b5d18b..b763da6137699 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -59,9 +59,9 @@ #define TASDEVICE_CHECKSUM_REG TASDEVICE_REG(0x0, 0x0, 0x7e) /* XM_340 */ -#define TASDEVICE_XM_A1_REG TASDEVICE_REG(0x64, 0x63, 0x3c) +#define TASDEVICE_XM_A1_REG TASDEVICE_REG(0x64, 0x02, 0x4c) /* XM_341 */ -#define TASDEVICE_XM_A2_REG TASDEVICE_REG(0x64, 0x63, 0x38) +#define TASDEVICE_XM_A2_REG TASDEVICE_REG(0x64, 0x02, 0x64) /* Volume control */ #define TAS2563_DVC_LVL TASDEVICE_REG(0x00, 0x02, 0x0c) From 6542d1ac5b8567b25523f31692ce3f6c663d12d7 Mon Sep 17 00:00:00 2001 From: Christian Hewitt Date: Thu, 25 Jun 2026 12:28:11 +0000 Subject: [PATCH 057/188] ASoC: codecs: pcm512x: only print info once on no sclk If sclk is not provided the driver falls back to using bclk and prints an info message in the system log. Under normal operations the message is repeated many times: [ 17.929576] pcm512x 0-004c: No SCLK, using BCLK: -2 [ 17.949172] pcm512x 0-004c: No SCLK, using BCLK: -2 [ 17.953029] pcm512x 0-004c: No SCLK, using BCLK: -2 [ 17.965059] pcm512x 0-004c: No SCLK, using BCLK: -2 [ 82.592980] pcm512x 0-004c: No SCLK, using BCLK: -2 [ 82.866293] pcm512x 0-004c: No SCLK, using BCLK: -2 Switch from dev_info to dev_info_once to reduce log noise. Signed-off-by: Christian Hewitt Link: https://patch.msgid.link/20260625122811.4056274-1-christianshewitt@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm512x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index fdef98ce52f19..fe3b5011fa167 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -633,7 +633,7 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, struct regmap *regmap = pcm512x->regmap; if (IS_ERR(pcm512x->sclk)) { - dev_info(dev, "No SCLK, using BCLK: %ld\n", + dev_info_once(dev, "No SCLK, using BCLK: %ld\n", PTR_ERR(pcm512x->sclk)); /* Disable reporting of missing SCLK as an error */ From f5a2fb8c900d0a082a9fa7a5789f8785abaaf509 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Thu, 25 Jun 2026 18:24:16 +0800 Subject: [PATCH 058/188] ASoC: fsl_asrc_dma: fix eDMA maxburst misalignment with channel count The back-end consumes data in units of the number of channels. When the maxburst value is not evenly divisible by the channel count, the DMA transfer length does not align with the FIFO frame boundary, causing wrong data to be copied and audible noise at the end of the stream. This is specific to eDMA: eDMA only responds to DMA requests from the back-end, whereas SDMA handles requests from both the front-end and the back-end and is not affected. For eDMA, when the back-end maxburst is not evenly divisible by the channel count, align it to the nearest valid boundary: - If maxburst >= channel count, override to the channel count so each transfer corresponds to exactly one audio frame. - If maxburst < channel count, override to 1 to avoid partial-frame transfers. Retain the original maxburst for SDMA or when it already aligns with the channel count. Fixes: c05f10f28ef6 ("ASoC: fsl_asrc: Add support for imx8qm & imx8qxp") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260625102416.424911-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_dma.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 5aa96af994c41..38f2b7c63133a 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -288,6 +288,26 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, config_be.dst_addr_width = buswidth; config_be.dst_maxburst = dma_params_be->maxburst; + /* + * For eDMA, the back-end may report a maxburst size that is not evenly + * divisible by the channel count. This causes the DMA transfer length + * to misalign with the FIFO boundary, resulting in wrong data and + * audible noise. Align maxburst to the nearest valid boundary: + * - If maxburst >= channel count, override to the channel count so + * each transfer equals exactly one audio frame. + * - If maxburst < channel count, override to 1 to avoid partial-frame + * transfers. + */ + if (asrc->use_edma && (dma_params_be->maxburst % params_channels(params))) { + if (dma_params_be->maxburst >= params_channels(params)) { + config_be.src_maxburst = params_channels(params); + config_be.dst_maxburst = params_channels(params); + } else { + config_be.src_maxburst = 1; + config_be.dst_maxburst = 1; + } + } + memset(&audio_config, 0, sizeof(audio_config)); config_be.peripheral_config = &audio_config; config_be.peripheral_size = sizeof(audio_config); From 87aad8b49aec042b3f8d46bfa303432218c8f7c0 Mon Sep 17 00:00:00 2001 From: Oleg Kucheryavenko Date: Thu, 25 Jun 2026 16:49:55 +0300 Subject: [PATCH 059/188] ALSA: hda/realtek: Update Acer Nitro ANV15-41 quirk to enable mute LED The laptop has a microphone mute LED on the F4 key, but it was not taken in mind when the previous quirk was added in commit 00e44a68efef50f65b12854b41f098b4d50f10be ("ALSA: hda/realtek: Add quirk for Acer Nitro ANV15-41"). Replace ALC2XX_FIXUP_HEADSET_MIC with ALC245_FIXUP_ACER_MICMUTE_LED, which enables the LED and chains the previous quirk for the headset microphone. Fixes: 00e44a68efef ("ALSA: hda/realtek: Add quirk for Acer Nitro ANV15-41") Signed-off-by: Oleg Kucheryavenko Link: https://patch.msgid.link/20260625134955.27465-1-oleg.kucheryavenko2018@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 71625394aa995..d9e2384fc0bad 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6813,7 +6813,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), - SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), From 92646a94fa4aed1485870ac769c6d3e737d15cb1 Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Thu, 25 Jun 2026 21:49:33 +0800 Subject: [PATCH 060/188] ALSA: FCP: Fix NULL pointer dereference in interface lookup A malformed USB device can provide a vendor-specific interface without any endpoint descriptors. fcp_find_fc_interface() currently selects the first vendor-specific interface and reads endpoint 0 from it, without checking whether the interface actually has any endpoints. When bNumEndpoints is zero, no endpoint array is allocated for the parsed alternate setting, so get_endpoint(..., 0) yields an invalid endpoint descriptor pointer. Dereferencing it through usb_endpoint_num() then triggers a NULL pointer dereference. Skip vendor-specific interfaces that do not have any endpoints. Fixes: 46757a3e7d50 ("ALSA: FCP: Add Focusrite Control Protocol driver") Reported-by: Jiaming Zhang Closes: https://lore.kernel.org/lkml/CANypQFb1EHj0xX8bA1WxSOSK-5xca6ZNKzOQcp12=s=puY7VFw@mail.gmail.com/ Signed-off-by: Jiaming Zhang Link: https://patch.msgid.link/20260625134933.425785-1-r772577952@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/fcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c index ea746bdb36ffc..6f5dcd35e1d4a 100644 --- a/sound/usb/fcp.c +++ b/sound/usb/fcp.c @@ -1083,6 +1083,8 @@ static int fcp_find_fc_interface(struct usb_mixer_interface *mixer) if (desc->bInterfaceClass != 255) continue; + if (desc->bNumEndpoints < 1) + continue; epd = get_endpoint(intf->altsetting, 0); private->bInterfaceNumber = desc->bInterfaceNumber; From 4472fe930326dde1ad15900317488ea653b68bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 17 Jun 2026 11:44:08 +0200 Subject: [PATCH 061/188] fbdev: vga16fb: Drop unused assignment of platform_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver explicitly sets the .driver_data member of struct platform_device_id to zero without relying on that value. Drop these unused assignments. While touching this array unify spacing and usage of commas and use named initializers for .name. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Helge Deller --- drivers/video/fbdev/vga16fb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/vga16fb.c b/drivers/video/fbdev/vga16fb.c index 22085d3668e87..cdd6b8de0ceb2 100644 --- a/drivers/video/fbdev/vga16fb.c +++ b/drivers/video/fbdev/vga16fb.c @@ -1421,8 +1421,8 @@ static void vga16fb_remove(struct platform_device *dev) } static const struct platform_device_id vga16fb_driver_id_table[] = { - {"ega-framebuffer", 0}, - {"vga-framebuffer", 0}, + { .name = "ega-framebuffer" }, + { .name = "vga-framebuffer" }, { } }; MODULE_DEVICE_TABLE(platform, vga16fb_driver_id_table); From 6b569a15a53640cab865d7fc8d4e2662232aeb3b Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sun, 21 Jun 2026 15:19:35 +0800 Subject: [PATCH 062/188] fbdev: pm2fb: unwind WC setup on probe failure Add arch_phys_wc_del() on error path to keep the write-combining setup balanced when later probe steps fail. Signed-off-by: Haoxiang Li Signed-off-by: Helge Deller --- drivers/video/fbdev/pm2fb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/fbdev/pm2fb.c b/drivers/video/fbdev/pm2fb.c index 412ff249b5c79..058075a34c5dc 100644 --- a/drivers/video/fbdev/pm2fb.c +++ b/drivers/video/fbdev/pm2fb.c @@ -1711,6 +1711,7 @@ static int pm2fb_probe(struct pci_dev *pdev, const struct pci_device_id *id) err_exit_both: kfree(info->pixmap.addr); err_exit_pixmap: + arch_phys_wc_del(default_par->wc_cookie); iounmap(info->screen_base); release_mem_region(pm2fb_fix.smem_start, pm2fb_fix.smem_len); err_exit_mmio: From 44d1b9ee36b42df7a7a17f27781ad7fd4f161568 Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Fri, 26 Jun 2026 00:03:06 +0800 Subject: [PATCH 063/188] fbdev: fbcon: fix out-of-bounds read in err_out of fbcon_do_set_font() When fbcon_do_set_font() fails (e.g., due to a memory allocation failure inside vc_resize() under heavy memory pressure), it jumps to the `err_out` label to roll back the console state. However, the current rollback logic forgets to restore the `hi_font` state, leading to a severe state machine corruption. Earlier in the function, `set_vc_hi_font()` might be called to change `vc->vc_hi_font_mask` and mutate the screen buffer. If `vc_resize()` subsequently fails, the `err_out` path restores `vc_font.charcount` but entirely skips rolling back the `vc_hi_font_mask` and the screen buffer. This mismatch leaves the terminal in a desynchronized state. Because `vc_hi_font_mask` remains set, the VT subsystem will still accept character indices greater than 255 from userspace and write them to the screen buffer. Subsequent rendering calls (e.g., `fbcon_putcs()`) will then use these inflated indices to access the reverted, 256-character font array, leading to a deterministic out-of-bounds read and potential kernel memory disclosure. Fix this by adding the missing rollback logic for the `hi_font` mask and screen buffer in the error path. Fixes: a5a923038d70 ("fbdev: fbcon: Properly revert changes when vc_resize() failed") Cc: stable@vger.kernel.org Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Reviewed-by: Thomas Zimmermann Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbcon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 9077d3b99357a..37beb93045af7 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -2405,6 +2405,7 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount, int resize, ret, old_width, old_height, old_charcount; font_data_t *old_fontdata = p->fontdata; const u8 *old_data = vc->vc_font.data; + unsigned short old_hi_font_mask = vc->vc_hi_font_mask; font_data_get(data); @@ -2451,6 +2452,12 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount, vc->vc_font.height = old_height; vc->vc_font.charcount = old_charcount; + /* Restore the hi_font state and screen buffer */ + if (old_hi_font_mask && !vc->vc_hi_font_mask) + set_vc_hi_font(vc, true); + else if (!old_hi_font_mask && vc->vc_hi_font_mask) + set_vc_hi_font(vc, false); + font_data_put(data); return ret; From d2ff3412decdbb053957ace9dd8f9cd8981282b5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 25 Jun 2026 11:01:02 +0800 Subject: [PATCH 064/188] fbdev: goldfishfb: fail pan display on base-update timeout goldfish_fb_pan_display() waits for the device to acknowledge the new framebuffer base, but it only logs a timeout and still reports success. The probe path also ignores the initial pan-display result before registering the framebuffer. Return -ETIMEDOUT when the base-update acknowledgment does not arrive, and propagate that error from the initial probe-time base update before the framebuffer is published. Signed-off-by: Pengpeng Hou Signed-off-by: Helge Deller --- drivers/video/fbdev/goldfishfb.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/video/fbdev/goldfishfb.c b/drivers/video/fbdev/goldfishfb.c index c9871281bc1da..e5d79886ea66b 100644 --- a/drivers/video/fbdev/goldfishfb.c +++ b/drivers/video/fbdev/goldfishfb.c @@ -138,10 +138,12 @@ static int goldfish_fb_pan_display(struct fb_var_screeninfo *var, writel(fb->fb.fix.smem_start + fb->fb.var.xres * 2 * var->yoffset, fb->reg_base + FB_SET_BASE); spin_unlock_irqrestore(&fb->lock, irq_flags); - wait_event_timeout(fb->wait, - fb->base_update_count != base_update_count, HZ / 15); - if (fb->base_update_count == base_update_count) + if (!wait_event_timeout(fb->wait, + fb->base_update_count != base_update_count, + HZ / 15)) { pr_err("%s: timeout waiting for base update\n", __func__); + return -ETIMEDOUT; + } return 0; } @@ -251,7 +253,9 @@ static int goldfish_fb_probe(struct platform_device *pdev) goto err_request_irq_failed; writel(FB_INT_BASE_UPDATE_DONE, fb->reg_base + FB_INT_ENABLE); - goldfish_fb_pan_display(&fb->fb.var, &fb->fb); /* updates base */ + ret = goldfish_fb_pan_display(&fb->fb.var, &fb->fb); /* updates base */ + if (ret) + goto err_pan_display_failed; ret = register_framebuffer(&fb->fb); if (ret) @@ -259,6 +263,7 @@ static int goldfish_fb_probe(struct platform_device *pdev) return 0; err_register_framebuffer_failed: +err_pan_display_failed: free_irq(fb->irq, fb); err_request_irq_failed: err_fb_set_var_failed: From ade09d6ee2760c6a6d77a78543a247a0a9b08ca4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 22:43:13 +0800 Subject: [PATCH 065/188] fbdev: viafb: return an error when DMA copy times out viafb_dma_copy_out_sg() logs a VIA DMA timeout when the DONE bit is not set after the completion wait and grace delay, but still returns success to the caller. Preserve the existing cleanup sequence and return -ETIMEDOUT when the DMA engine did not report completion. Signed-off-by: Pengpeng Hou Signed-off-by: Helge Deller --- drivers/video/fbdev/via/via-core.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/via/via-core.c b/drivers/video/fbdev/via/via-core.c index a8d4a3e2c65ed..1632a669941ab 100644 --- a/drivers/video/fbdev/via/via-core.c +++ b/drivers/video/fbdev/via/via-core.c @@ -234,6 +234,7 @@ int viafb_dma_copy_out_sg(unsigned int offset, struct scatterlist *sg, int nsg) dma_addr_t descr_handle; unsigned long flags; int i; + int ret = 0; struct scatterlist *sgentry; dma_addr_t nextdesc; @@ -290,8 +291,10 @@ int viafb_dma_copy_out_sg(unsigned int offset, struct scatterlist *sg, int nsg) */ wait_for_completion_timeout(&viafb_dma_completion, 1); msleep(1); - if ((viafb_mmio_read(VDMA_CSR0)&VDMA_C_DONE) == 0) + if ((viafb_mmio_read(VDMA_CSR0) & VDMA_C_DONE) == 0) { printk(KERN_ERR "VIA DMA timeout!\n"); + ret = -ETIMEDOUT; + } /* * Clean up and we're done. */ @@ -301,7 +304,7 @@ int viafb_dma_copy_out_sg(unsigned int offset, struct scatterlist *sg, int nsg) dma_free_coherent(&global_dev.pdev->dev, nsg*sizeof(struct viafb_vx855_dma_descr), descrpages, descr_handle); - return 0; + return ret; } EXPORT_SYMBOL_GPL(viafb_dma_copy_out_sg); #endif /* CONFIG_VIDEO_VIA_CAMERA */ From 1faf48c40cd2e3df931fb3f4f2c42734e2406589 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Thu, 25 Jun 2026 23:50:48 -0500 Subject: [PATCH 066/188] fbdev: fix use-after-free in store_modes() store_modes() replaces a framebuffer's modelist with modes from userspace. On success it frees the old modelist with fb_destroy_modelist(). Two fields still point into that freed list. One pointer is fb_display[i].mode, the mode a console is using. fbcon_new_modelist() moves these pointers to the new list. It only does so for consoles still mapped to the framebuffer. An unmapped console is skipped and keeps its stale pointer. Unbinding fbcon, for example, sets con2fb_map[i] to -1 but leaves fb_display[i].mode set. An FBIOPUT_VSCREENINFO ioctl with FB_ACTIVATE_INV_MODE later reaches fbcon_mode_deleted(). That function reads the stale fb_display[i].mode through fb_mode_is_equal(). The read is a use-after-free. The other pointer is fb_info->mode, the current mode. It is set through the mode sysfs attribute. store_modes() does not update fb_info->mode, so it is left pointing into the freed list. show_mode(), the attribute's read handler, dereferences the stale fb_info->mode through mode_string(). The read is a use-after-free. Clear both pointers before freeing the list. Commit a1f305893074 ("fbcon: Set fb_display[i]->mode to NULL when the mode is released") added the helper fbcon_delete_modelist(). It clears every fb_display[i].mode that points into a given list. So far it is called only from the unregister path. Call it from store_modes() too, and set fb_info->mode to NULL. Reported-by: syzbot+81c7c6b52649fd07299d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=81c7c6b52649fd07299d Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/ajjoDhAi2y4ArSlz@dev/ Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ian Bridges Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbsysfs.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index d9743ef353552..ea196603c7a87 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -10,6 +10,7 @@ #include #include "fb_internal.h" +#include "fbcon.h" static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) { @@ -108,8 +109,15 @@ static ssize_t store_modes(struct device *device, if (fb_new_modelist(fb_info)) { fb_destroy_modelist(&fb_info->modelist); list_splice(&old_list, &fb_info->modelist); - } else + } else { + /* + * fb_display[i].mode and fb_info->mode both point into the old + * list. Clear them before it is freed. + */ + fbcon_delete_modelist(&old_list); + fb_info->mode = NULL; fb_destroy_modelist(&old_list); + } unlock_fb_info(fb_info); console_unlock(); From 8f555f97d154bbe11df386f59363926787a9dfe8 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 24 Jun 2026 16:11:36 -0500 Subject: [PATCH 067/188] fbcon: fix NULL pointer dereference for a console without vc_data fbcon_new_modelist() runs when a framebuffer's modelist changes. For each console mapped to it with fb_display[i].mode set, it reads vc_cons[i].d and passes the vc_num to fbcon_set_disp(). This assumes a console with a mode set has a vc_data, but it can be NULL. fbcon_set_disp() sets fb_display[i].mode before it checks vc_data, and fbcon_deinit() leaves the mode set after the vc_data is freed. fbcon_new_modelist() then dereferences the NULL vc_data. Keep fb_display[i].mode set only while the console has a vc_data. Check vc_data before setting the mode in fbcon_set_disp(), and clear the mode in fbcon_deinit(). The existing mode check in fbcon_new_modelist() then skips such consoles. Reported-by: syzbot+42525d636f430fd5d983@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=42525d636f430fd5d983 Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ian Bridges Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbcon.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 37beb93045af7..9f5c4c101581d 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -1273,6 +1273,7 @@ static void fbcon_deinit(struct vc_data *vc) int idx; fbcon_free_font(p); + p->mode = NULL; idx = con2fb_map[vc->vc_num]; if (idx == -1) @@ -1443,14 +1444,14 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, p = &fb_display[unit]; - if (var_to_display(p, var, info)) - return; - vc = vc_cons[unit].d; if (!vc) return; + if (var_to_display(p, var, info)) + return; + default_mode = vc->vc_display_fg; svc = *default_mode; t = &fb_display[svc->vc_num]; From 24f002b828369e37f71fc9eba7f0ab9b4796d558 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 24 Jun 2026 23:13:12 -0500 Subject: [PATCH 068/188] fbdev: Fix fb_new_modelist to prevent null-ptr-deref in fb_videomode_to_var info->var, a framebuffer's current mode, is expected to have a matching entry in info->modelist. var_to_display() relies on this and treats a failed fb_match_mode() as "This should not happen". fb_set_var() keeps it true by adding the mode to the list on every change, and do_register_framebuffer() does the same at registration. store_modes() replaces the modelist from userspace. fb_new_modelist() validates the new modes but does not check that info->var still has a match. It relies on fbcon_new_modelist() to re-point consoles, but that only handles consoles mapped to the framebuffer. With fbcon unbound there are none, so info->var is left describing a mode that is no longer in the list. A later console takeover runs var_to_display(), where fb_match_mode() returns NULL and leaves fb_display[i].mode NULL. fbcon_switch() passes it to display_to_var(), and fb_videomode_to_var() dereferences the NULL mode. Keep the current mode in the list in fb_new_modelist(), the same way fb_set_var() does. Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ian Bridges Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbmem.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index e5221653ec2b9..2f1c56e5a7a23 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -767,6 +767,18 @@ int fb_new_modelist(struct fb_info *info) if (list_empty(&info->modelist)) return 1; + /* + * The new modelist may not contain the current mode (info->var), and + * fbcon_new_modelist() below only re-points consoles mapped to this + * framebuffer. Add the current mode here so info->var keeps a match + * even when fbcon is unbound. + */ + if (!fb_match_mode(&info->var, &info->modelist)) { + fb_var_to_videomode(&mode, &info->var); + if (fb_add_videomode(&mode, &info->modelist)) + return 1; + } + fbcon_new_modelist(info); return 0; From 52cced077098f41a30280c6b3f7694211ae6eaa8 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 4 Jun 2026 10:56:31 +0100 Subject: [PATCH 069/188] pwm: rzg2l-gpt: Fix period_ticks type from u32 to u64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit period_ticks is used to store PWM period values that can exceed the 32-bit range, so change its type from u32 to u64 to prevent overflow. Cc: stable@kernel.org Fixes: 061f087f5d0b ("pwm: Add support for RZ/G2L GPT") Signed-off-by: Biju Das Link: https://patch.msgid.link/20260604095647.108654-2-biju.das.jz@bp.renesas.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-rzg2l-gpt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c index 4856af080e8e9..c9dfa59bc1ead 100644 --- a/drivers/pwm/pwm-rzg2l-gpt.c +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -81,7 +81,7 @@ struct rzg2l_gpt_chip { void __iomem *mmio; struct mutex lock; /* lock to protect shared channel resources */ unsigned long rate_khz; - u32 period_ticks[RZG2L_MAX_HW_CHANNELS]; + u64 period_ticks[RZG2L_MAX_HW_CHANNELS]; u32 channel_request_count[RZG2L_MAX_HW_CHANNELS]; u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS]; }; From a60c3430a69d78e8ef04c88121dd28da5cd9f32e Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 4 Jun 2026 10:56:34 +0100 Subject: [PATCH 070/188] pwm: rzg2l-gpt: Add missing newlines to dev_err_probe() messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev_err_probe() internally calls dev_err() which uses pr_fmt() and printk(). Kernel log messages should end with a newline character to ensure proper log formatting. Add missing '\n' at the end of the error strings in rzg2l_gpt_probe(). Signed-off-by: Biju Das Link: https://patch.msgid.link/20260604095647.108654-5-biju.das.jz@bp.renesas.com Fixes: 061f087f5d0b ("pwm: Add support for RZ/G2L GPT") Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-rzg2l-gpt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c index c9dfa59bc1ead..dfa1d11a48a82 100644 --- a/drivers/pwm/pwm-rzg2l-gpt.c +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -408,14 +408,14 @@ static int rzg2l_gpt_probe(struct platform_device *pdev) rate = clk_get_rate(clk); if (!rate) - return dev_err_probe(dev, -EINVAL, "The gpt clk rate is 0"); + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is 0\n"); /* * Refuse clk rates > 1 GHz to prevent overflow later for computing * period and duty cycle. */ if (rate > NSEC_PER_SEC) - return dev_err_probe(dev, -EINVAL, "The gpt clk rate is > 1GHz"); + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is > 1GHz\n"); /* * Rate is in MHz and is always integer for peripheral clk @@ -424,7 +424,7 @@ static int rzg2l_gpt_probe(struct platform_device *pdev) */ rzg2l_gpt->rate_khz = rate / KILO; if (rzg2l_gpt->rate_khz * KILO != rate) - return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000"); + return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000\n"); mutex_init(&rzg2l_gpt->lock); From 0f46414087e54d8ab771f30a10d86d9ff4e68f06 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 14 Jun 2026 00:36:11 -0500 Subject: [PATCH 071/188] Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count rmi_f3a_initialize() takes the GPIO count from the device query register (f3a->gpio_count = buf & RMI_F3A_GPIO_COUNT, range 0..127). rmi_f3a_map_gpios() then allocates gpio_key_map with min(gpio_count, TRACKSTICK_RANGE_END) == at most 6 entries, but rmi_f3a_attention() iterates the full gpio_count and dereferences gpio_key_map[i], and input->keycodemax is set to the full gpio_count while input->keycode points at the 6-entry allocation. A device that reports gpio_count > 6 therefore causes an out-of-bounds read of gpio_key_map[] on every attention interrupt, and out-of-bounds accesses through the input core's default keymap ioctls: EVIOCGKEYCODE reads past the buffer (leaking adjacent slab memory to user space) and EVIOCSKEYCODE writes a caller-controlled value past it, for any process able to open the evdev node, since input_default_getkeycode() and input_default_setkeycode() only bound the index against keycodemax. Size the keymap for the full gpio_count. The mapping loop is unchanged: it still assigns only the first min(gpio_count, TRACKSTICK_RANGE_END) entries; the remaining slots stay KEY_RESERVED (devm_kcalloc zero-fills) and are skipped when reporting. Fixes: 9e4c596bfd00 ("Input: synaptics-rmi4 - add support for F3A") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260614-b4-disp-818d6bda-v1-1-cf39a3615085@proton.me Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f3a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_f3a.c b/drivers/input/rmi4/rmi_f3a.c index 0e8baed84dbb5..a0777644eef07 100644 --- a/drivers/input/rmi4/rmi_f3a.c +++ b/drivers/input/rmi4/rmi_f3a.c @@ -132,7 +132,7 @@ static int rmi_f3a_map_gpios(struct rmi_function *fn, struct f3a_data *f3a, int button_count = min_t(u8, f3a->gpio_count, TRACKSTICK_RANGE_END); f3a->gpio_key_map = devm_kcalloc(&fn->dev, - button_count, + f3a->gpio_count, sizeof(f3a->gpio_key_map[0]), GFP_KERNEL); if (!f3a->gpio_key_map) { From 0e364fd8bf440336ecc346cd9cca028caa543738 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 14 Jun 2026 00:36:12 -0500 Subject: [PATCH 072/188] Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count rmi_f30_map_gpios() allocates gpioled_key_map with min(gpioled_count, TRACKSTICK_RANGE_END) == at most 6 entries, but rmi_f30_attention() iterates the full f30->gpioled_count (device query register, range 0..31) and dereferences gpioled_key_map[i], and input->keycodemax is set to the full gpioled_count while input->keycode points at the 6-entry allocation. A device that reports gpioled_count > 6 with GPIO support enabled therefore causes an out-of-bounds read on the attention interrupt and out-of-bounds read/write through the EVIOCGKEYCODE/EVIOCSKEYCODE ioctls, which bound the index only against keycodemax. This is the same defect as the F3A handler, which was copied from F30. Size the keymap for the full gpioled_count; the mapping loop still assigns only the first min(gpioled_count, TRACKSTICK_RANGE_END) entries. Fixes: 3e64fcbdbd10 ("Input: synaptics-rmi4 - limit the range of what GPIOs are buttons") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260614-b4-disp-818d6bda-v1-2-cf39a3615085@proton.me Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f30.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c index 35045f161dc2f..b2155c8e20e75 100644 --- a/drivers/input/rmi4/rmi_f30.c +++ b/drivers/input/rmi4/rmi_f30.c @@ -233,7 +233,7 @@ static int rmi_f30_map_gpios(struct rmi_function *fn, int button_count = min_t(u8, f30->gpioled_count, TRACKSTICK_RANGE_END); f30->gpioled_key_map = devm_kcalloc(&fn->dev, - button_count, + f30->gpioled_count, sizeof(f30->gpioled_key_map[0]), GFP_KERNEL); if (!f30->gpioled_key_map) { From 781a474595d83727667bd4bacb7e5af60866e3d6 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 26 Jun 2026 17:42:10 -0700 Subject: [PATCH 073/188] Revert "Input: rmi4 - fix register descriptor address calculation" The register descriptor presence register is a packet register, which means its bytes share a single RMI address. It does not occupy consecutive addresses, and the register structure that follows it is located at the next RMI address (presence_address + 1), not (presence_address + presence_size). Revert the incorrect address calculation introduced in commit a98518e72439. Reported-by: "Barry K. Nathan" Tested-by: "Barry K. Nathan" Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index 49a59da6a8411..a28eef1b765e5 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -643,7 +643,7 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, ret = rmi_read_block(d, addr, buf, size_presence_reg); if (ret) return ret; - addr += size_presence_reg; + ++addr; if (buf[0] == 0) { if (size_presence_reg < 3) From 02df8a7b69230a64c56fbdc2b64027a82451c195 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 26 Jun 2026 16:33:21 -0700 Subject: [PATCH 074/188] Input: rmi4 - tolerate short register descriptor structure Some touchpads (e.g. ThinkPad T14 Gen 1) have buggy firmware that reports a register descriptor structure size that is too small for the number of registers it claims to have in the presence map. The remaining bytes in the structure are 0, which with the new strict bounds checking causes the parser to fail with -EIO, aborting the device probe. Tolerate such short reads by dropping the remaining (unparseable or 0-size) registers from the list instead of failing the probe, preventing the driver from trying to use them. Fixes: 0adb483fbf2d ("Input: rmi4 - refactor register descriptor parsing") Reported-by: Barry K. Nathan Tested-by: Barry K. Nathan Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 37 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index a28eef1b765e5..5d49a9021c7d0 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -616,8 +616,8 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, unsigned int presence_offset; unsigned int map_offset; unsigned int offset; + unsigned int num_registers; unsigned int reg; - int i; int b; int ret; @@ -657,7 +657,7 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, memset(presence_map, 0, sizeof(presence_map)); map_offset = 0; - for (i = presence_offset; i < size_presence_reg; i++) { + for (int i = presence_offset; i < size_presence_reg; i++) { for (b = 0; b < 8; b++) { if (buf[i] & BIT(b)) { if (map_offset >= RMI_REG_DESC_PRESENCE_BITS) @@ -697,28 +697,41 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, if (ret) return ret; - reg = find_first_bit(presence_map, RMI_REG_DESC_PRESENCE_BITS); offset = 0; - for (i = 0; i < rdesc->num_registers; i++) { - struct rmi_register_desc_item *item = &rdesc->registers[i]; + num_registers = 0; + for_each_set_bit(reg, presence_map, RMI_REG_DESC_PRESENCE_BITS) { + struct rmi_register_desc_item *item = &rdesc->registers[num_registers]; int item_size; + if (offset >= rdesc->struct_size) + break; + item_size = rmi_parse_register_desc_item(item, &struct_buf[offset], rdesc->struct_size - offset); - if (item_size < 0) - return item_size; + if (item_size < 0) { + dev_warn(&d->dev, + "%s: Failed to parse register %d descriptor, ignoring it\n", + __func__, reg); + break; + } item->reg = reg; offset += item_size; - rmi_dbg(RMI_DEBUG_CORE, &d->dev, - "%s: reg: %d reg size: %u subpackets: %d\n", __func__, - item->reg, item->reg_size, item->num_subpackets); + if (item->reg_size == 0) { + dev_warn(&d->dev, + "%s: Register %d has 0 size, ignoring it\n", + __func__, item->reg); + } else { + rmi_dbg(RMI_DEBUG_CORE, &d->dev, + "%s: reg: %d reg size: %u subpackets: %d\n", __func__, + item->reg, item->reg_size, item->num_subpackets); - reg = find_next_bit(presence_map, - RMI_REG_DESC_PRESENCE_BITS, reg + 1); + num_registers++; + } } + rdesc->num_registers = num_registers; return 0; } From 13517ac9d4698a898a16d1f7630387fb441232fa Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Wed, 24 Jun 2026 17:47:39 +0800 Subject: [PATCH 075/188] Input: gscps2 - advance receive buffer write index Commit 44f920069911 ("Input: gscps2 - use guard notation when acquiring spinlock") moved the receive loop into gscps2_read_data() and gscps2_report_data(). While moving the code, it preserved the writes to buffer[ps2port->append], but omitted the following producer index update from the original loop: ps2port->append = (ps2port->append + 1) & BUFFER_SIZE; As a result, append never advances. Since gscps2_report_data() only reports bytes while act != append, the receive buffer always appears empty and no keyboard or mouse data reaches the serio core. Restore the omitted index update. Fixes: 44f920069911 ("Input: gscps2 - use guard notation when acquiring spinlock") Cc: stable@vger.kernel.org # 6.13+ Signed-off-by: Xu Rao Link: https://patch.msgid.link/460B5655BA580C60+20260624094739.850306-1-raoxu@uniontech.com Signed-off-by: Dmitry Torokhov --- drivers/input/serio/gscps2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c index 22b2f57fd91f5..bf9b993f57330 100644 --- a/drivers/input/serio/gscps2.c +++ b/drivers/input/serio/gscps2.c @@ -219,6 +219,7 @@ static void gscps2_read_data(struct gscps2port *ps2port) ps2port->buffer[ps2port->append].str = status; ps2port->buffer[ps2port->append].data = gscps2_readb_input(ps2port->addr); + ps2port->append = (ps2port->append + 1) & BUFFER_SIZE; } while (true); } From 163294571323a5a22763ab1346a73a8da2d1eb0f Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sun, 8 Feb 2026 08:49:00 -0800 Subject: [PATCH 076/188] ntb_hw_amd: Fix incorrect debug message in link disable path amd_ntb_link_disable() prints "Enabling Link" which is misleading. Update the message to reflect that the link is being disabled. Signed-off-by: Alok Tiwari Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index 1a163596ddf54..df9150669ad3a 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -376,7 +376,7 @@ static int amd_ntb_link_disable(struct ntb_dev *ntb) if (ndev->ntb.topo == NTB_TOPO_SEC) return -EINVAL; - dev_dbg(&ntb->pdev->dev, "Enabling Link.\n"); + dev_dbg(&ntb->pdev->dev, "Disabling Link.\n"); return 0; } From 814af522523944124e3abb1f1d1fb55862505410 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 4 Mar 2026 11:05:27 +0900 Subject: [PATCH 077/188] NTB: epf: Avoid pci_iounmap() with offset when PEER_SPAD and CONFIG share BAR When BAR_PEER_SPAD and BAR_CONFIG share one PCI BAR, the module teardown path ends up calling pci_iounmap() on the same iomem with some offset, which is unnecessary and triggers a kernel warning like the following: Trying to vunmap() nonexistent vm area (0000000069a5ffe8) WARNING: mm/vmalloc.c:3470 at vunmap+0x58/0x68, CPU#5: modprobe/2937 [...] Call trace: vunmap+0x58/0x68 (P) iounmap+0x34/0x48 pci_iounmap+0x2c/0x40 ntb_epf_pci_remove+0x44/0x80 [ntb_hw_epf] pci_device_remove+0x48/0xf8 device_remove+0x50/0x88 device_release_driver_internal+0x1c8/0x228 driver_detach+0x50/0xb0 bus_remove_driver+0x74/0x100 driver_unregister+0x34/0x68 pci_unregister_driver+0x34/0xa0 ntb_epf_pci_driver_exit+0x14/0xfe0 [ntb_hw_epf] [...] Fix it by unmapping only when PEER_SPAD and CONFIG use difference bars. Cc: stable@vger.kernel.org Fixes: e75d5ae8ab88 ("NTB: epf: Allow more flexibility in the memory BAR map method") Reviewed-by: Frank Li Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/epf/ntb_hw_epf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index af57554728429..c47607d4f8a7f 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -729,7 +729,8 @@ static void ntb_epf_deinit_pci(struct ntb_epf_dev *ndev) struct pci_dev *pdev = ndev->ntb.pdev; pci_iounmap(pdev, ndev->ctrl_reg); - pci_iounmap(pdev, ndev->peer_spad_reg); + if (ndev->barno_map[BAR_PEER_SPAD] != ndev->barno_map[BAR_CONFIG]) + pci_iounmap(pdev, ndev->peer_spad_reg); pci_iounmap(pdev, ndev->db_reg); pci_release_regions(pdev); From aa0f804a4f29f75bdb6e41939a06100297d19832 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 11 Mar 2026 22:14:15 -0700 Subject: [PATCH 078/188] NTB: fix kernel-doc warnings in ntb.h Correct a function name and function parameter name to avoid kernel-doc warnings: Warning: include/linux/ntb.h:575 expecting prototype for ntb_default_port_count(). Prototype was for ntb_default_peer_port_count() instead Warning: include/linux/ntb.h:590 function parameter 'pidx' not described in 'ntb_default_peer_port_number' Signed-off-by: Randy Dunlap Acked-by: Dave Jiang Signed-off-by: Jon Mason --- include/linux/ntb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ntb.h b/include/linux/ntb.h index 879c3e89e026f..ae72caa038597 100644 --- a/include/linux/ntb.h +++ b/include/linux/ntb.h @@ -567,7 +567,7 @@ void ntb_msg_event(struct ntb_dev *ntb); int ntb_default_port_number(struct ntb_dev *ntb); /** - * ntb_default_port_count() - get the default number of peer device ports + * ntb_default_peer_port_count() - get the default number of peer device ports * @ntb: NTB device context. * * By default hardware driver supports just one peer device. @@ -582,7 +582,7 @@ int ntb_default_peer_port_count(struct ntb_dev *ntb); /** * ntb_default_peer_port_number() - get the default peer port by given index * @ntb: NTB device context. - * @idx: Peer port index (should not differ from zero). + * @pidx: Peer port index (should not differ from zero). * * By default hardware driver supports just one peer device, so this method * shall return the corresponding value from enum ntb_default_port. From 7b92541d21f300d6b355d43a2c211c12cbbcb2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 7 May 2026 09:41:01 +0200 Subject: [PATCH 079/188] ntb: amd: Use named initializer for pci_device_id::driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current list initialisation depends on the well hidden two zeros in the PCI_VDEVICE macro. Instead use a named initialisation that is more robust and easier to understand. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index df9150669ad3a..36bee0d8ac6cd 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -1328,15 +1328,15 @@ static const struct ntb_dev_data dev_data[] = { }; static const struct pci_device_id amd_ntb_pci_tbl[] = { - { PCI_VDEVICE(AMD, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { PCI_VDEVICE(AMD, 0x148b), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c0), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c3), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x155a), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d4), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d7), (kernel_ulong_t)&dev_data[2] }, - { PCI_VDEVICE(HYGON, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { 0, } + { PCI_VDEVICE(AMD, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { PCI_VDEVICE(AMD, 0x148b), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c0), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c3), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x155a), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d4), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d7), .driver_data = (kernel_ulong_t)&dev_data[2] }, + { PCI_VDEVICE(HYGON, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { } }; MODULE_DEVICE_TABLE(pci, amd_ntb_pci_tbl); From c8aa1c84342ff8168cb18487d213243840f58ec1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 28 Jun 2026 12:01:31 -0700 Subject: [PATCH 080/188] Linux 7.2-rc1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 12fd3d2d47b58..b9c5792c79e01 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 7 -PATCHLEVEL = 1 +PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = +EXTRAVERSION = -rc1 NAME = Baby Opossum Posse # *DOCUMENTATION* From 42c2f5ba420018099ff6e96616695c1f3da0a368 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:23 +0530 Subject: [PATCH 081/188] FROMLIST: arm64: dts: qcom: lemans: Add PSCI SYSTEM_RESET2 types Add support for SYSTEM_RESET2 vendor-specific resets as reboot-modes in the psci node. Describe the resets: "bootloader" will cause device to reboot and stop in the bootloader's fastboot mode. "edl" will cause device to reboot into "emergency download mode", which permits loading images via the Firehose protocol. Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-10-46e085bca4cc@oss.qualcomm.com Signed-off-by: Elliot Berman Signed-off-by: Shivendra Pratap --- arch/arm64/boot/dts/qcom/lemans.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 353a6e6fd3acb..f37512eefdb8e 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -635,6 +635,11 @@ #power-domain-cells = <0>; domain-idle-states = <&cluster_sleep_apss_rsc_pc>; }; + + reboot-mode { + mode-bootloader = <0x10001 0x2>; + mode-edl = <0 0x1>; + }; }; reserved-memory { From 3d35b8c34b4739368861b43e37b09756166d7807 Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Wed, 12 Nov 2025 12:08:26 +0530 Subject: [PATCH 082/188] FROMLIST: arm64: dts: qcom: lemans-evk: Enable Bluetooth support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's a WCN6855 WiFi/Bluetooth module on an M.2 card. To make Bluetooth work, we need to define the necessary device tree nodes, including UART configuration and power supplies. Since there is no standard M.2 binding in the device tree at present, the PMU is described using dedicated PMU nodes to represent the internal regulators required by the module. The module provides a 3.3V supply, which originates from the main board’s 12V rail. To represent this power hierarchy in the device tree, add a fixed 12V regulator node as the DC-IN source and link it to the 3.3V regulator node. Link: https://lore.kernel.org/all/20251112071147.1450258-2-wei.deng@oss.qualcomm.com/ Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 99 +++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 34dfc8d22b6a5..34ebbe1eac0a0 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -21,6 +21,7 @@ ethernet0 = ðernet0; mmc1 = &sdhc; serial0 = &uart10; + serial1 = &uart17; serial2 = &uart0; }; @@ -178,6 +179,17 @@ regulator-max-microvolt = <2950000>; }; + vreg_dcin_12v: regulator-dcin-12v { + compatible = "regulator-fixed"; + + regulator-name = "VREG_DCIN_12V"; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + + regulator-boot-on; + regulator-always-on; + }; + vreg_sdc: regulator-vreg-sdc { compatible = "regulator-gpio"; @@ -191,6 +203,75 @@ startup-delay-us = <100>; }; + + vreg_wcn_3p3: regulator-wcn-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + vin-supply = <&vreg_dcin_12v>; + + regulator-boot-on; + }; + + wcn6855-pmu { + compatible = "qcom,wcn6855-pmu"; + + vddio-supply = <&vreg_wcn_3p3>; + vddaon-supply = <&vreg_wcn_3p3>; + vddpmu-supply = <&vreg_wcn_3p3>; + vddpmumx-supply = <&vreg_wcn_3p3>; + vddpmucx-supply = <&vreg_wcn_3p3>; + vddrfa0p95-supply = <&vreg_wcn_3p3>; + vddrfa1p3-supply = <&vreg_wcn_3p3>; + vddrfa1p9-supply = <&vreg_wcn_3p3>; + vddpcielp3-supply = <&vreg_wcn_3p3>; + vddpcielp9-supply = <&vreg_wcn_3p3>; + + regulators { + vreg_pmu_rfa_cmn: ldo0 { + regulator-name = "vreg_pmu_rfa_cmn"; + }; + + vreg_pmu_aon_0p59: ldo1 { + regulator-name = "vreg_pmu_aon_0p59"; + }; + + vreg_pmu_wlcx_0p8: ldo2 { + regulator-name = "vreg_pmu_wlcx_0p8"; + }; + + vreg_pmu_wlmx_0p85: ldo3 { + regulator-name = "vreg_pmu_wlmx_0p85"; + }; + + vreg_pmu_btcmx_0p85: ldo4 { + regulator-name = "vreg_pmu_btcmx_0p85"; + }; + + vreg_pmu_rfa_0p8: ldo5 { + regulator-name = "vreg_pmu_rfa_0p8"; + }; + + vreg_pmu_rfa_1p2: ldo6 { + regulator-name = "vreg_pmu_rfa_1p2"; + }; + + vreg_pmu_rfa_1p8: ldo7 { + regulator-name = "vreg_pmu_rfa_1p8"; + }; + + vreg_pmu_pcie_0p9: ldo8 { + regulator-name = "vreg_pmu_pcie_0p9"; + }; + + vreg_pmu_pcie_1p8: ldo9 { + regulator-name = "vreg_pmu_pcie_1p8"; + }; + }; + }; }; &apps_rsc { @@ -970,6 +1051,24 @@ status = "okay"; }; +&uart17 { + status = "okay"; + + bluetooth: bluetooth { + compatible = "qcom,wcn6855-bt"; + max-speed = <3200000>; + + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddbtcmx-supply = <&vreg_pmu_btcmx_0p85>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + }; +}; + &ufs_mem_hc { reset-gpios = <&tlmm 149 GPIO_ACTIVE_LOW>; vcc-supply = <&vreg_l8a>; From bbba6d436025752b9b67544ab254270ff0663255 Mon Sep 17 00:00:00 2001 From: Gaurav Kohli Date: Tue, 9 Jun 2026 15:53:00 +0530 Subject: [PATCH 083/188] FROMLIST: arm64: dts: qcom: lemans: Enable CDSP cooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike the CPU, the CDSP does not throttle its speed automatically when it reaches high temperatures in lemans. Set up CDSP cooling for both instances by throttling the cdsp, when it reaches 105°C. Signed-off-by: Gaurav Kohli Link: https://lore.kernel.org/r/20260609-qmi-tmd-v3-5-291a2ff4c634@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/lemans.dtsi | 126 ++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index f37512eefdb8e..850330752e117 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -7782,6 +7782,9 @@ qcom,smem-states = <&smp2p_cdsp0_out 0>; qcom,smem-state-names = "stop"; + #cooling-cells = <2>; + tmd-names = "cdsp_sw"; + status = "disabled"; glink-edge { @@ -7921,6 +7924,9 @@ qcom,smem-states = <&smp2p_cdsp1_out 0>; qcom,smem-state-names = "stop"; + #cooling-cells = <2>; + tmd-names = "cdsp_sw"; + status = "disabled"; glink-edge { @@ -8769,7 +8775,7 @@ thermal-sensors = <&tsens2 5>; trips { - trip-point0 { + nsp_0_0_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8781,6 +8787,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_0_0_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-0-1-0-thermal { @@ -8789,7 +8803,7 @@ thermal-sensors = <&tsens2 6>; trips { - trip-point0 { + nsp_0_1_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8801,6 +8815,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_1_0_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-0-2-0-thermal { @@ -8809,7 +8831,7 @@ thermal-sensors = <&tsens2 7>; trips { - trip-point0 { + nsp_0_2_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8821,6 +8843,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_2_0_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-0-0-thermal { @@ -8829,7 +8859,7 @@ thermal-sensors = <&tsens2 8>; trips { - trip-point0 { + nsp_1_0_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8841,6 +8871,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_0_0_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-1-0-thermal { @@ -8849,7 +8887,7 @@ thermal-sensors = <&tsens2 9>; trips { - trip-point0 { + nsp_1_1_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8861,6 +8899,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_1_0_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-2-0-thermal { @@ -8869,7 +8915,7 @@ thermal-sensors = <&tsens2 10>; trips { - trip-point0 { + nsp_1_2_0_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -8881,6 +8927,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_2_0_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; ddrss-0-thermal { @@ -9023,7 +9077,7 @@ thermal-sensors = <&tsens3 5>; trips { - trip-point0 { + nsp_0_0_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9035,6 +9089,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_0_1_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-0-1-1-thermal { @@ -9043,7 +9105,7 @@ thermal-sensors = <&tsens3 6>; trips { - trip-point0 { + nsp_0_1_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9055,6 +9117,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_1_1_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-0-2-1-thermal { @@ -9063,7 +9133,7 @@ thermal-sensors = <&tsens3 7>; trips { - trip-point0 { + nsp_0_2_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9075,6 +9145,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_0_2_1_alert0>; + cooling-device = <&remoteproc_cdsp0 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-0-1-thermal { @@ -9083,7 +9161,7 @@ thermal-sensors = <&tsens3 8>; trips { - trip-point0 { + nsp_1_0_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9095,6 +9173,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_0_1_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-1-1-thermal { @@ -9103,7 +9189,7 @@ thermal-sensors = <&tsens3 9>; trips { - trip-point0 { + nsp_1_1_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9115,6 +9201,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_1_1_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; nsp-1-2-1-thermal { @@ -9123,7 +9217,7 @@ thermal-sensors = <&tsens3 10>; trips { - trip-point0 { + nsp_1_2_1_alert0: trip-point0 { temperature = <105000>; hysteresis = <5000>; type = "passive"; @@ -9135,6 +9229,14 @@ type = "passive"; }; }; + + cooling-maps { + map0 { + trip = <&nsp_1_2_1_alert0>; + cooling-device = <&remoteproc_cdsp1 THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + }; + }; }; ddrss-1-thermal { From 45648e644db4fcfe2c4ed42695fa8188417ee31d Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Mon, 12 Jan 2026 13:05:20 +0530 Subject: [PATCH 084/188] WORKAROUND: arm64: dts: qcom: lemans-evk: Remove WCN6855 PMU node to bypass pwrseq flow There is a conflict between the current DTS configuration and the driver behavior for the WCN6855 Bluetooth path. With the PMU node in place, the driver takes the pwrseq code path unintentionally, which leads to Bluetooth failing to power up during an on -> off -> on transition. To unblock function, temporarily remove the WCN6855 PMU node so that the driver follows the non-pwrseq path and avoids the unexpected sequence. This is a TEMPORARY WORKAROUND. Once a proper M.2 binding/solution is upstreamed, will re-submit both DTS and driver changes aligned with the M.2 model. Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 73 +++---------------------- 1 file changed, 8 insertions(+), 65 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 34ebbe1eac0a0..23543d35ec5b3 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -215,63 +215,6 @@ regulator-boot-on; }; - - wcn6855-pmu { - compatible = "qcom,wcn6855-pmu"; - - vddio-supply = <&vreg_wcn_3p3>; - vddaon-supply = <&vreg_wcn_3p3>; - vddpmu-supply = <&vreg_wcn_3p3>; - vddpmumx-supply = <&vreg_wcn_3p3>; - vddpmucx-supply = <&vreg_wcn_3p3>; - vddrfa0p95-supply = <&vreg_wcn_3p3>; - vddrfa1p3-supply = <&vreg_wcn_3p3>; - vddrfa1p9-supply = <&vreg_wcn_3p3>; - vddpcielp3-supply = <&vreg_wcn_3p3>; - vddpcielp9-supply = <&vreg_wcn_3p3>; - - regulators { - vreg_pmu_rfa_cmn: ldo0 { - regulator-name = "vreg_pmu_rfa_cmn"; - }; - - vreg_pmu_aon_0p59: ldo1 { - regulator-name = "vreg_pmu_aon_0p59"; - }; - - vreg_pmu_wlcx_0p8: ldo2 { - regulator-name = "vreg_pmu_wlcx_0p8"; - }; - - vreg_pmu_wlmx_0p85: ldo3 { - regulator-name = "vreg_pmu_wlmx_0p85"; - }; - - vreg_pmu_btcmx_0p85: ldo4 { - regulator-name = "vreg_pmu_btcmx_0p85"; - }; - - vreg_pmu_rfa_0p8: ldo5 { - regulator-name = "vreg_pmu_rfa_0p8"; - }; - - vreg_pmu_rfa_1p2: ldo6 { - regulator-name = "vreg_pmu_rfa_1p2"; - }; - - vreg_pmu_rfa_1p8: ldo7 { - regulator-name = "vreg_pmu_rfa_1p8"; - }; - - vreg_pmu_pcie_0p9: ldo8 { - regulator-name = "vreg_pmu_pcie_0p9"; - }; - - vreg_pmu_pcie_1p8: ldo9 { - regulator-name = "vreg_pmu_pcie_1p8"; - }; - }; - }; }; &apps_rsc { @@ -1058,14 +1001,14 @@ compatible = "qcom,wcn6855-bt"; max-speed = <3200000>; - vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; - vddaon-supply = <&vreg_pmu_aon_0p59>; - vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; - vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; - vddbtcmx-supply = <&vreg_pmu_btcmx_0p85>; - vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; - vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; - vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + vddrfacmn-supply = <&vreg_wcn_3p3>; + vddaon-supply = <&vreg_wcn_3p3>; + vddwlcx-supply = <&vreg_wcn_3p3>; + vddwlmx-supply = <&vreg_wcn_3p3>; + vddbtcmx-supply = <&vreg_wcn_3p3>; + vddrfa0p8-supply = <&vreg_wcn_3p3>; + vddrfa1p2-supply = <&vreg_wcn_3p3>; + vddrfa1p8-supply = <&vreg_wcn_3p3>; }; }; From 9a09561de60fbc50db918d3b1d00643d66360ce0 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Wed, 11 Feb 2026 13:49:19 +0530 Subject: [PATCH 085/188] PENDING: arm64: dts: qcom: Add EL2 support for Iris for lemans Add support for IRIS on lemans when Linux host running at EL2. Signed-off-by: Vikash Garodia --- arch/arm64/boot/dts/qcom/lemans-el2.dtso | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-el2.dtso b/arch/arm64/boot/dts/qcom/lemans-el2.dtso index 621ad930cf547..3efbdda9348b3 100644 --- a/arch/arm64/boot/dts/qcom/lemans-el2.dtso +++ b/arch/arm64/boot/dts/qcom/lemans-el2.dtso @@ -15,7 +15,9 @@ }; &iris { - status = "disabled"; + video-firmware { + iommus = <&apps_smmu 0x0882 0x0400>; + }; }; &remoteproc_adsp { From 136818c02fc8c75f31e26694c450dc697c126c74 Mon Sep 17 00:00:00 2001 From: Swati Agarwal Date: Mon, 16 Feb 2026 00:23:43 +0530 Subject: [PATCH 086/188] FROMLIST: arm64: dts: qcom: lemans-evk: Enable wakeup for primary USB controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the "wakeup-source" property to the primary port controller node so its interrupt can wake the system from low‑power states on lemans EVK platform. Link: https://lore.kernel.org/all/20260215183325.3836178-3-swati.agarwal@oss.qualcomm.com/ Signed-off-by: Swati Agarwal --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 23543d35ec5b3..c810de4c043f3 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -568,6 +568,8 @@ pinctrl-0 = <&usb_id>, <&usb0_intr_state>; pinctrl-names = "default"; + wakeup-source; + ports { #address-cells = <1>; #size-cells = <0>; From e1af6fe04342d72e5dde3a95bd9fb3f7df3aaff7 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Tue, 7 Apr 2026 11:07:38 +0530 Subject: [PATCH 087/188] PENDING: arm64: dts: qcom: lemans: switch iris iommus to iommu-map Switch the iris video codec node in sa8775p (lemans) from the legacy 'iommus' property to 'iommu-map', using IRIS_NON_PIXEL_VCODEC and IRIS_PIXEL function IDs to identify the non-pixel and pixel context bank SMMU stream mappings respectively. Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Signed-off-by: Gourav Kumar --- arch/arm64/boot/dts/qcom/lemans.dtsi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 850330752e117..b88d0904fac99 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -4970,8 +4971,8 @@ resets = <&gcc GCC_VIDEO_AXI0_CLK_ARES>; reset-names = "bus"; - iommus = <&apps_smmu 0x0880 0x0400>, - <&apps_smmu 0x0887 0x0400>; + iommu-map = , + ; dma-coherent; status = "disabled"; From 566634db79a4995e494b6c7d1d6ec34554ed891a Mon Sep 17 00:00:00 2001 From: Gourav Kumar Date: Fri, 10 Apr 2026 22:37:06 +0530 Subject: [PATCH 088/188] PENDING: arm64: dts: qcom: lemans-ride: Drop video firmware from common As per memory map, video firmware memory region is 7MB for sa8775* variants while it is 16MB for qcs9100* variants. Keeping the 7MB variant of video firmware in ride common DTS does not allow qcs9100* to pick the 16MB variant. Drop it from common and define in respective variant DTS. Signed-off-by: Gourav Kumar Signed-off-by: Vikash Garodia Signed-off-by: Venkata Siva Pavan KumarVenkatapatigari --- arch/arm64/boot/dts/qcom/lemans-auto.dtsi | 4 ++++ arch/arm64/boot/dts/qcom/lemans-ride-common.dtsi | 2 -- arch/arm64/boot/dts/qcom/lemans.dtsi | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-auto.dtsi b/arch/arm64/boot/dts/qcom/lemans-auto.dtsi index 8db958d60fd1d..07fa9cb148ae6 100644 --- a/arch/arm64/boot/dts/qcom/lemans-auto.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans-auto.dtsi @@ -102,3 +102,7 @@ }; }; }; + +&iris { + firmware-name = "qcom/vpu/vpu30_p4_s6.mbn"; +}; diff --git a/arch/arm64/boot/dts/qcom/lemans-ride-common.dtsi b/arch/arm64/boot/dts/qcom/lemans-ride-common.dtsi index 40f88498999bd..dc19ced80b9b6 100644 --- a/arch/arm64/boot/dts/qcom/lemans-ride-common.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans-ride-common.dtsi @@ -582,8 +582,6 @@ }; &iris { - firmware-name = "qcom/vpu/vpu30_p4_s6.mbn"; - status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index b88d0904fac99..3fd99b131b29a 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -4968,6 +4968,8 @@ memory-region = <&pil_video_mem>; + firmware-name = "qcom/vpu/vpu30_p4_s6_16mb.mbn"; + resets = <&gcc GCC_VIDEO_AXI0_CLK_ARES>; reset-names = "bus"; From 275ba344b67bfc09426cb8c83d7d493bbc1a02f6 Mon Sep 17 00:00:00 2001 From: Swati Agarwal Date: Mon, 6 Apr 2026 16:31:12 +0530 Subject: [PATCH 089/188] FROMLIST: arm64: dts: qcom: lemans-evk: Rename hd3ss3220_ instance for primary port controller Rename the hd3ss3220_ instance to improve clarity and simplify usage when adding a secondary port controller. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Swati Agarwal Link: https://lore.kernel.org/r/20260406110113.1709886-2-swati.agarwal@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index c810de4c043f3..11da42d88884a 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -64,7 +64,7 @@ reg = <1>; usb0_con_ss_ep: endpoint { - remote-endpoint = <&hd3ss3220_in_ep>; + remote-endpoint = <&hd3ss3220_0_in_ep>; }; }; }; @@ -577,7 +577,7 @@ port@0 { reg = <0>; - hd3ss3220_in_ep: endpoint { + hd3ss3220_0_in_ep: endpoint { remote-endpoint = <&usb0_con_ss_ep>; }; }; @@ -585,7 +585,7 @@ port@1 { reg = <1>; - hd3ss3220_out_ep: endpoint { + hd3ss3220_0_out_ep: endpoint { remote-endpoint = <&usb_0_dwc3_ss>; }; }; @@ -1040,7 +1040,7 @@ }; &usb_0_dwc3_ss { - remote-endpoint = <&hd3ss3220_out_ep>; + remote-endpoint = <&hd3ss3220_0_out_ep>; }; &usb_0_hsphy { From e18e905ca3c9a13c2080200794d420ca150c86cc Mon Sep 17 00:00:00 2001 From: Swati Agarwal Date: Mon, 6 Apr 2026 16:31:13 +0530 Subject: [PATCH 090/188] FROMLIST: arm64: dts: qcom: lemans-evk: Enable secondary USB controller in host mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable secondary USB controller in host mode on lemans EVK Platform. Secondary USB controller is connected to a Genesys Logic USB HUB GL3590 having 4 ports. The ports of hub that are present on lemans EVK standalone board are used as follows:- 1) port-1 is connected to HD3SS3220 Type-C port controller. 2) port-4 is used for the M.2 E key on corekit. Standard core kit uses UART for Bluetooth. This port is to be used only if user optionally replaces the WiFi card with the NFA765 chip which uses USB for Bluetooth. Remaining 2 ports will become functional when the interface plus mezzanine board is stacked on top of corekit: 3) port-2 is connected to another hub which is present on the mezz through which 4 type-A ports are connected. 4) port-3 is used for the M.2 B key for a 5G card when the mezz is connected. Secondary USB Controller ↓ GL3590 USB Hub (4 ports) | |-- Port 1 → HD3SS3220 Type‑C Port Controller → USB‑C Connector | |-- Port 2 → Mezzanine USB Hub (when mezz attached) | |-- Port 3 → M.2 B‑Key Slot (when mezz attached) | |-- Port 4 → M.2 E‑Key Slot (Default: BT via UART; USB only if NFA765 module is installed) Mark the second USB controller as host only capable and add the HD3SS3220 Type-C port controller along with Type-C connector for controlling vbus supply. Signed-off-by: Swati Agarwal Link: https://lore.kernel.org/r/20260406110113.1709886-3-swati.agarwal@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 168 ++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 11da42d88884a..152352799dbc4 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -70,6 +70,37 @@ }; }; + connector-1 { + compatible = "usb-c-connector"; + label = "USB1-Type-C"; + data-role = "host"; + power-role = "source"; + + vbus-supply = <&usb1_vbus>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb1_con_hs_ep: endpoint { + remote-endpoint = <&usb_hub_2_1>; + }; + }; + + port@1 { + reg = <1>; + + usb1_con_ss_ep: endpoint { + remote-endpoint = <&hd3ss3220_1_in_ep>; + }; + + }; + }; + }; + connector-2 { compatible = "gpio-usb-b-connector", "usb-b-connector"; label = "micro-USB"; @@ -162,6 +193,15 @@ enable-active-high; }; + usb1_vbus: regulator-usb1-vbus { + compatible = "regulator-fixed"; + regulator-name = "usb1_vbus"; + gpio = <&expander1 3 GPIO_ACTIVE_HIGH>; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + enable-active-high; + }; + usb2_vbus: regulator-usb2-vbus { compatible = "regulator-fixed"; regulator-name = "usb2_vbus"; @@ -591,6 +631,39 @@ }; }; }; + + usb-typec@47 { + compatible = "ti,hd3ss3220"; + reg = <0x47>; + + interrupts-extended = <&pmm8654au_2_gpios 6 IRQ_TYPE_EDGE_FALLING>; + + id-gpios = <&tlmm 51 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&usb1_id>, <&usb1_intr>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hd3ss3220_1_in_ep: endpoint { + remote-endpoint = <&usb1_con_ss_ep>; + }; + }; + + port@1 { + reg = <1>; + + hd3ss3220_1_out_ep: endpoint { + remote-endpoint = <&usb_hub_3_1>; + }; + }; + }; + }; }; &i2c18 { @@ -782,6 +855,14 @@ power-source = <0>; }; + usb1_intr: usb1-intr-state { + pins = "gpio6"; + function = "normal"; + input-enable; + bias-pull-up; + power-source = <0>; + }; + usb2_id: usb2-id-state { pins = "gpio11"; function = "normal"; @@ -982,6 +1063,12 @@ function = "gpio"; bias-pull-up; }; + + usb1_id: usb1-id-state { + pins = "gpio51"; + function = "gpio"; + bias-pull-up; + }; }; &uart0 { @@ -1058,6 +1145,87 @@ status = "okay"; }; +&usb_1 { + dr_mode = "host"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + usb_hub_2_x: hub@1 { + compatible = "usb5e3,610"; + reg = <1>; + + peer-hub = <&usb_hub_3_x>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + usb_hub_2_1: endpoint { + remote-endpoint = <&usb1_con_hs_ep>; + }; + }; + + /* + * Port-4 is connected to M.2 E key connector on corekit. + */ + port@4 { + reg = <4>; + + usb_hub_2_4: endpoint { + }; + }; + }; + }; + + usb_hub_3_x: hub@2 { + compatible = "usb5e3,625"; + reg = <2>; + + peer-hub = <&usb_hub_2_x>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + usb_hub_3_1: endpoint { + remote-endpoint = <&hd3ss3220_1_out_ep>; + }; + }; + + port@4 { + reg = <4>; + + usb_hub_3_4: endpoint { + }; + }; + }; + }; +}; + +&usb_1_hsphy { + vdda-pll-supply = <&vreg_l7a>; + vdda18-supply = <&vreg_l6c>; + vdda33-supply = <&vreg_l9a>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l1c>; + vdda-pll-supply = <&vreg_l7a>; + + status = "okay"; +}; + &usb_2 { status = "okay"; }; From ba86b22dbc102aa440baed30ebf94ffacaf8ae0e Mon Sep 17 00:00:00 2001 From: Swati Agarwal Date: Fri, 6 Mar 2026 14:43:55 +0530 Subject: [PATCH 091/188] FROMLIST: arm64: dts: qcom: lemans-evk: Enable the tertiary USB controller Enable the tertiary usb controller connected to micro usb port in OTG mode on Lemans EVK platform. Reviewed-by: Konrad Dybcio Signed-off-by: Swati Agarwal Link: https://lore.kernel.org/r/20260306091355.1178333-5-swati.agarwal@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 152352799dbc4..b9d55aca3f6df 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -120,6 +120,25 @@ }; }; + connector-2 { + compatible = "gpio-usb-b-connector", "usb-b-connector"; + label = "micro-USB"; + type = "micro"; + + id-gpios = <&pmm8654au_2_gpios 11 GPIO_ACTIVE_HIGH>; + vbus-gpios = <&expander3 3 GPIO_ACTIVE_HIGH>; + vbus-supply = <&usb2_vbus>; + + pinctrl-0 = <&usb2_id>; + pinctrl-names = "default"; + + port { + usb2_con_hs_ep: endpoint { + remote-endpoint = <&usb_2_dwc3_hs>; + }; + }; + }; + edp0-connector { compatible = "dp-connector"; label = "EDP0"; @@ -211,6 +230,15 @@ enable-active-high; }; + usb2_vbus: regulator-usb2-vbus { + compatible = "regulator-fixed"; + regulator-name = "usb2_vbus"; + gpio = <&pmm8654au_1_gpios 9 GPIO_ACTIVE_HIGH>; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + enable-active-high; + }; + vmmc_sdc: regulator-vmmc-sdc { compatible = "regulator-fixed"; @@ -870,6 +898,14 @@ bias-pull-up; power-source = <0>; }; + + usb2_id: usb2-id-state { + pins = "gpio11"; + function = "normal"; + input-enable; + bias-pull-up; + power-source = <0>; + }; }; &qup_i2c19_default { @@ -1242,6 +1278,22 @@ status = "okay"; }; +&usb_2 { + status = "okay"; +}; + +&usb_2_dwc3_hs { + remote-endpoint = <&usb2_con_hs_ep>; +}; + +&usb_2_hsphy { + vdda-pll-supply = <&vreg_l7a>; + vdda18-supply = <&vreg_l6c>; + vdda33-supply = <&vreg_l9a>; + + status = "okay"; +}; + &xo_board_clk { clock-frequency = <38400000>; }; From f786978d8712396119965c000fbead2a24bfc778 Mon Sep 17 00:00:00 2001 From: Karthik S Date: Wed, 20 May 2026 13:45:57 +0530 Subject: [PATCH 092/188] FROMLIST: arm64: dts: qcom: lemans: Enable audio over DISPLAY-PORT Add dailinks for DISPLAY-PORT to enable audio functionality on edp0. Link: https://lore.kernel.org/all/20260413043713.1659-1-kumar.singh@oss.qualcomm.com/ Signed-off-by: Karthik S --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index b9d55aca3f6df..a320f45b1bc71 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -201,6 +201,22 @@ sound-dai = <&q6apm>; }; }; + + dp0-dai-link { + link-name = "DisplayPort0 Playback"; + + codec { + sound-dai = <&mdss0_dp0>; + }; + + cpu { + sound-dai = <&q6apmbedai DISPLAY_PORT_RX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; }; usb0_vbus: regulator-usb0-vbus { From a20db8c22730d7ede7f102811604345e0a293938 Mon Sep 17 00:00:00 2001 From: Monish Chunara Date: Fri, 27 Feb 2026 00:29:32 +0530 Subject: [PATCH 093/188] FROMLIST: arm64: dts: qcom: lemans-evk: Move SD card support to overlay The lemans EVK board supports either eMMC or SD-card, but only one can be active at a time. Move the SDHC node for SD card into a dedicated device tree overlay. This decoupling allows the storage medium to be selected dynamically by applying the appropriate overlay for either SD card or eMMC support. Link: https://lore.kernel.org/all/20260227102405.2339544-2-monish.chunara@oss.qualcomm.com/ Signed-off-by: Monish Chunara --- arch/arm64/boot/dts/qcom/Makefile | 3 +++ .../boot/dts/qcom/lemans-evk-sd-card.dtso | 25 +++++++++++++++++++ arch/arm64/boot/dts/qcom/lemans-evk.dts | 16 ------------ 3 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 arch/arm64/boot/dts/qcom/lemans-evk-sd-card.dtso diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 6f33c4e2f09c3..d2ead612044b8 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -44,6 +44,9 @@ dtb-$(CONFIG_ARCH_QCOM) += kaanapali-mtp.dtb dtb-$(CONFIG_ARCH_QCOM) += kaanapali-qrd.dtb dtb-$(CONFIG_ARCH_QCOM) += lemans-evk.dtb +lemans-evk-sd-card-dtbs := lemans-evk.dtb lemans-evk-sd-card.dtbo +dtb-$(CONFIG_ARCH_QCOM) += lemans-evk-sd-card.dtb + lemans-evk-camera-csi1-imx577-dtbs := lemans-evk.dtb lemans-evk-camera-csi1-imx577.dtbo lemans-evk-camera-dtbs := lemans-evk.dtb lemans-evk-camera.dtbo diff --git a/arch/arm64/boot/dts/qcom/lemans-evk-sd-card.dtso b/arch/arm64/boot/dts/qcom/lemans-evk-sd-card.dtso new file mode 100644 index 0000000000000..007ab63473495 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/lemans-evk-sd-card.dtso @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; +/plugin/; + +#include + +&sdhc { + vmmc-supply = <&vmmc_sdc>; + vqmmc-supply = <&vreg_sdc>; + + pinctrl-0 = <&sdc_default>, <&sd_cd>; + pinctrl-1 = <&sdc_sleep>, <&sd_cd>; + pinctrl-names = "default", "sleep"; + + bus-width = <4>; + cd-gpios = <&tlmm 36 GPIO_ACTIVE_LOW>; + no-mmc; + no-sdio; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index a320f45b1bc71..4d10ab997c7da 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -974,22 +974,6 @@ status = "okay"; }; -&sdhc { - vmmc-supply = <&vmmc_sdc>; - vqmmc-supply = <&vreg_sdc>; - - pinctrl-0 = <&sdc_default>, <&sd_cd>; - pinctrl-1 = <&sdc_sleep>, <&sd_cd>; - pinctrl-names = "default", "sleep"; - - bus-width = <4>; - cd-gpios = <&tlmm 36 GPIO_ACTIVE_LOW>; - no-mmc; - no-sdio; - - status = "okay"; -}; - &serdes0 { phy-supply = <&vreg_l5a>; From ed35e67de90e25dab6f1f775614fd54422f59691 Mon Sep 17 00:00:00 2001 From: Monish Chunara Date: Fri, 27 Feb 2026 00:33:16 +0530 Subject: [PATCH 094/188] FROMLIST: arm64: dts: qcom: lemans-evk: Add SDHCI support for eMMC via overlay Enable the SDHCI controller for eMMC functionality on the lemans EVK using a device tree overlay. Configure the corresponding addresse space and resources for eMMC. Link: https://lore.kernel.org/all/20260227102405.2339544-3-monish.chunara@oss.qualcomm.com/ Signed-off-by: Monish Chunara --- arch/arm64/boot/dts/qcom/Makefile | 3 + arch/arm64/boot/dts/qcom/lemans-evk-emmc.dtso | 64 +++++++++++++++++++ arch/arm64/boot/dts/qcom/lemans.dtsi | 10 ++- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 arch/arm64/boot/dts/qcom/lemans-evk-emmc.dtso diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index d2ead612044b8..3cc716919c7e6 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -44,6 +44,9 @@ dtb-$(CONFIG_ARCH_QCOM) += kaanapali-mtp.dtb dtb-$(CONFIG_ARCH_QCOM) += kaanapali-qrd.dtb dtb-$(CONFIG_ARCH_QCOM) += lemans-evk.dtb +lemans-evk-emmc-dtbs := lemans-evk.dtb lemans-evk-emmc.dtbo +dtb-$(CONFIG_ARCH_QCOM) += lemans-evk-emmc.dtb + lemans-evk-sd-card-dtbs := lemans-evk.dtb lemans-evk-sd-card.dtbo dtb-$(CONFIG_ARCH_QCOM) += lemans-evk-sd-card.dtb diff --git a/arch/arm64/boot/dts/qcom/lemans-evk-emmc.dtso b/arch/arm64/boot/dts/qcom/lemans-evk-emmc.dtso new file mode 100644 index 0000000000000..52739d39a1ff8 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/lemans-evk-emmc.dtso @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; +/plugin/; + +#include +#include +#include + +/ { + vmmc_sdc1: regulator-l8c { + compatible = "regulator-fixed"; + regulator-name = "vreg-sdc1"; + + regulator-min-microvolt = <2960000>; + regulator-max-microvolt = <2960000>; + }; + + vqmmc_sdc1: regulator-s4a { + compatible = "regulator-fixed"; + regulator-name = "vqmmc-sdc1"; + + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; +}; + +&sdhc { + vmmc-supply = <&vmmc_sdc1>; + vqmmc-supply = <&vqmmc_sdc1>; + + pinctrl-0 = <&sdc_default>, <&sdc_rclk>; + pinctrl-1 = <&sdc_sleep>, <&sdc_rclk_sleep>; + + pinctrl-names = "default", "sleep"; + + supports-cqe; + non-removable; + + qcom,dll-config = <0x000F64EC>; + max-frequency = <50000000>; + + bus-width = <8>; + no-sd; + no-sdio; + + status = "okay"; +}; + +&tlmm { + sdc_rclk: sdc1-rclk-state { + pins = "sdc1_rclk"; + bias-pull-down; + }; + + sdc_rclk_sleep: sdc1-rclk-sleep-state { + pins = "sdc1_rclk"; + drive-strength = <2>; + bias-bus-hold; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 3fd99b131b29a..6f0ae27e78074 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -4309,7 +4309,9 @@ sdhc: mmc@87c4000 { compatible = "qcom,sa8775p-sdhci", "qcom,sdhci-msm-v5"; - reg = <0x0 0x087c4000 0x0 0x1000>; + reg = <0x0 0x87C4000 0x0 0x1000>, + <0x0 0x87C5000 0x0 0x1000>; + reg-names = "hc", "cqhci"; interrupts = , ; @@ -4317,9 +4319,11 @@ "pwr_irq"; clocks = <&gcc GCC_SDCC1_AHB_CLK>, - <&gcc GCC_SDCC1_APPS_CLK>; + <&gcc GCC_SDCC1_APPS_CLK>, + <&rpmhcc RPMH_CXO_CLK>; clock-names = "iface", - "core"; + "core", + "xo"; interconnects = <&aggre1_noc MASTER_SDC QCOM_ICC_TAG_ALWAYS &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, From 784a7d2f4fccdc9120bcb412dade055967b2dbbb Mon Sep 17 00:00:00 2001 From: Umang Chheda Date: Tue, 5 May 2026 17:53:51 +0530 Subject: [PATCH 095/188] FROMLIST: arm64: dts: qcom: lemans: add AEST error nodes Add AEST RAS error source nodes for the Lemans SoC. The DT describes a processor error source covering all CPU cores and a shared L3 cache error source for the cluster. These nodes model the hardware error reporting blocks and associated interrupts as required by the Arm AEST specification. Link: https://lore.kernel.org/lkml/20260505-aest-devicetree-support-v1-7-d5d6ffacf0a5@oss.qualcomm.com/ Co-developed-by: Faruque Ansari Signed-off-by: Faruque Ansari Signed-off-by: Umang Chheda --- arch/arm64/boot/dts/qcom/lemans.dtsi | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 6f0ae27e78074..ca9e30fbf8b8c 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -4,6 +4,7 @@ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. */ +#include #include #include #include @@ -30,6 +31,46 @@ #address-cells = <2>; #size-cells = <2>; + aest { + compatible = "arm,aest"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + aest-processor-0 { + compatible = "arm,aest-processor"; + arm,num-records = <1>; + arm,record-impl = /bits/ 64 <0x0>; + arm,status-reporting = /bits/ 64 <0x0>; + arm,addressing-mode = /bits/ 64 <0x0>; + arm,processor-flags = ; + interrupts = ; + interrupt-names = "fhi"; + }; + + aest-l3-cluster0 { + compatible = "arm,aest-processor"; + arm,num-records = <2>; + arm,record-impl = /bits/ 64 <0x1>; + arm,status-reporting = /bits/ 64 <0x0>; + arm,addressing-mode = /bits/ 64 <0x0>; + arm,processor-flags = ; + interrupts = ; + interrupt-names = "fhi"; + }; + + aest-l3-cluster1 { + compatible = "arm,aest-processor"; + arm,num-records = <2>; + arm,record-impl = /bits/ 64 <0x1>; + arm,status-reporting = /bits/ 64 <0x0>; + arm,addressing-mode = /bits/ 64 <0x0>; + arm,processor-flags = ; + interrupts = ; + interrupt-names = "fhi"; + }; + }; + clocks { xo_board_clk: xo-board-clk { compatible = "fixed-clock"; From 94dd0a2da91cc32bab177ceb4b72579377122519 Mon Sep 17 00:00:00 2001 From: Bibek Kumar Patro Date: Tue, 26 May 2026 20:05:18 +0530 Subject: [PATCH 096/188] FROMLIST: arm64: dts: qcom: lemans: Add GEM_NOC interconnect for adreno SMMU On Lemans platforms, the Adreno SMMU requires a bandwidth vote on the GEM_NOC path (MASTER_GPU_TCU -> SLAVE_EBI1) before its registers are accessible. Without this vote, the SMMU may become unreachable, leading to intermittent probe failures and runtime issues. Add the required interconnect to ensure reliable register access. Link: https://lore.kernel.org/all/20260526-smmu_interconnect_addition-v2-4-2a6d8ca30d63@oss.qualcomm.com/ Signed-off-by: Bibek Kumar Patro --- arch/arm64/boot/dts/qcom/lemans.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index ca9e30fbf8b8c..fa376be108d26 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -4847,6 +4847,8 @@ , , ; + interconnects = <&gem_noc MASTER_GPU_TCU QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; }; serdes0: phy@8901000 { From 04a7f651b341dc402501724d58c6e4decddb4c65 Mon Sep 17 00:00:00 2001 From: Abhinaba Rakshit Date: Tue, 9 Jun 2026 03:17:28 +0530 Subject: [PATCH 097/188] FROMLIST: arm64: dts: qcom: lemans: Add OPP-table for ICE UFS device node Qualcomm Inline Crypto Engine (ICE) platform driver now supports an optional OPP-table. Add OPP-table for ICE UFS device nodes for LeMans platform. Signed-off-by: Abhinaba Rakshit Link: https://lore.kernel.org/r/20260609-enable-ice-clock-scaling-v11-6-1cebc8b3275b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/lemans.dtsi | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index fa376be108d26..fcfba7d08caaa 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -3156,6 +3156,32 @@ clock-names = "core", "iface"; power-domains = <&gcc UFS_PHY_GDSC>; + + operating-points-v2 = <&ice_opp_table>; + + ice_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-75000000 { + opp-hz = /bits/ 64 <75000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; + + opp-150000000 { + opp-hz = /bits/ 64 <150000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&rpmhpd_opp_nom>; + }; + + opp-600000000 { + opp-hz = /bits/ 64 <600000000>; + required-opps = <&rpmhpd_opp_nom>; + }; + }; }; cryptobam: dma-controller@1dc4000 { From 15e3601d844b3a8b1222b0b67950e4c5479c2fb2 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Tue, 14 Apr 2026 19:35:59 +0800 Subject: [PATCH 098/188] FROMLIST: arm64: dts: qcom: lemans: Add label properties to CoreSight devices Add label properties to TPDM and CTI nodes in the lemans device tree to provide human-readable identifiers for each CoreSight device. These labels allow userspace tools and the CoreSight framework to identify devices by name rather than by base address. Link: https://lore.kernel.org/linux-arm-msm/20260414-add-label-to-coresight-device-v2-1-5017d07358f2@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Signed-off-by: Jie Gan --- arch/arm64/boot/dts/qcom/lemans.dtsi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index fcfba7d08caaa..5a219d5d6543b 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -3270,6 +3270,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_dcc"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -3329,6 +3330,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_spdm"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -3797,6 +3799,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_0"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -3817,6 +3820,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_1"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -3837,6 +3841,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_2"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -3857,6 +3862,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_3"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -3877,6 +3883,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_1"; qcom,dsb-element-bits = <32>; qcom,dsb-msrs-num = <32>; @@ -3897,6 +3904,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "cti_aoss"; }; funnel@4b83000 { @@ -4218,6 +4226,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_actpm"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -4238,6 +4247,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_apss"; qcom,dsb-element-bits = <32>; qcom,dsb-msrs-num = <32>; @@ -4320,6 +4330,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_llm_silver"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -4340,6 +4351,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_llm_gold"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -4360,6 +4372,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_llm_ext"; qcom,dsb-element-bits = <32>; qcom,dsb-msrs-num = <32>; @@ -9362,6 +9375,7 @@ turing-llm-tpdm { compatible = "qcom,coresight-static-tpdm"; + label = "tpdm_cdsp_llm_0"; qcom,cmb-element-bits = <32>; From 1eb1b8481de618ca3c55f7ea32bb675398df1ee7 Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Wed, 24 Jun 2026 18:20:50 +0530 Subject: [PATCH 099/188] FROMLIST: arm64: dts: qcom: lemans: Add compatible to the PCIe Root Port Add 'compatible = "pciclass,0604"' to the pcieport0 node in lemans.dtsi to allow the PCI subsystem to associate the DT node with the PCI-to-PCI bridge device. This is required for downstream DT nodes (such as M.2 connectors described as graph endpoints of the Root Port) to be matched to PCI devices. Link: https://lore.kernel.org/r/20260622-v3-lemans-split-v3-1-d26bb22594e3@oss.qualcomm.com Reviewed-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Reviewed-by: Manivannan Sadhasivam Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/lemans.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 5a219d5d6543b..53d0347762f1d 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -2826,6 +2826,7 @@ status = "disabled"; pcieport0: pcie@0 { + compatible = "pciclass,0604"; device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; bus-range = <0x01 0xff>; From 166356cc489f837fbca276a0977ff5b8cf86e098 Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Wed, 24 Jun 2026 18:21:03 +0530 Subject: [PATCH 100/188] FROMLIST: arm64: dts: qcom: lemans-evk: Describe the PCIe M.2 Key E connector The lemans EVK has the PCIe M.2 Mechanical Key E connector to connect wireless connectivity cards over PCIe and UART interfaces. Hence, describe the connector node and link it with the PCIe 0 Root Port and UART17 nodes through graph port/endpoint. The M.2 Key E connector is powered by a 3.3V fixed regulator (vreg_wcn_3p3) which is sourced from the board's 12V DC input rail (vreg_dcin_12v). Both regulators are always-on and are required by the pcie-m2-e-connector binding. Also add the serial1 = &uart17 alias, which is required for the Bluetooth serdev device to be enumerated on the UART17 interface. Link: https://lore.kernel.org/r/20260622-v3-lemans-split-v3-2-d26bb22594e3@oss.qualcomm.com Reviewed-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Reviewed-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 59 +++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index 4d10ab997c7da..d1979ed3744fd 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -139,6 +139,38 @@ }; }; + connector-3 { + compatible = "pcie-m2-e-connector"; + vpcie3v3-supply = <&vreg_wcn_3p3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + m2_e_pcie_ep: endpoint@0 { + reg = <0>; + remote-endpoint = <&pcieport0_ep>; + }; + }; + + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + m2_e_uart_ep: endpoint@0 { + reg = <0>; + remote-endpoint = <&uart17_ep>; + }; + }; + }; + }; + edp0-connector { compatible = "dp-connector"; label = "EDP0"; @@ -270,8 +302,8 @@ regulator-min-microvolt = <12000000>; regulator-max-microvolt = <12000000>; - regulator-boot-on; regulator-always-on; + regulator-boot-on; }; vreg_sdc: regulator-vreg-sdc { @@ -297,6 +329,7 @@ vin-supply = <&vreg_dcin_12v>; + regulator-always-on; regulator-boot-on; }; }; @@ -885,6 +918,14 @@ status = "okay"; }; +&pcieport0 { + port { + pcieport0_ep: endpoint { + remote-endpoint = <&m2_e_pcie_ep>; + }; + }; +}; + &pmm8654au_0_pon_resin { linux,code = ; status = "okay"; @@ -1122,18 +1163,10 @@ &uart17 { status = "okay"; - bluetooth: bluetooth { - compatible = "qcom,wcn6855-bt"; - max-speed = <3200000>; - - vddrfacmn-supply = <&vreg_wcn_3p3>; - vddaon-supply = <&vreg_wcn_3p3>; - vddwlcx-supply = <&vreg_wcn_3p3>; - vddwlmx-supply = <&vreg_wcn_3p3>; - vddbtcmx-supply = <&vreg_wcn_3p3>; - vddrfa0p8-supply = <&vreg_wcn_3p3>; - vddrfa1p2-supply = <&vreg_wcn_3p3>; - vddrfa1p8-supply = <&vreg_wcn_3p3>; + port { + uart17_ep: endpoint { + remote-endpoint = <&m2_e_uart_ep>; + }; }; }; From 960924d516b2476657df65d735d81844d9b5bb60 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Fri, 10 Jul 2026 10:39:03 +0800 Subject: [PATCH 101/188] FROMLIST: arm64: dts: qcom: sm8750: bind traceNoC on the platform bus The traceNoC node used the "qcom,coresight-tnoc", "arm,primecell" compatible, which places the device on the AMBA bus. To bind an AMBA device, the bus reads the peripheral and component ID registers (PID/CID) at the top of the device's register block and matches them against the primecell ID. The traceNoC exposes the CID registers, but the Component ID value returned by the hardware is 0x00000000 instead of a valid AMBA Component ID, so the match never succeeds, the AMBA probe fails, and the device is left stuck in deferred probe indefinitely. Drop the "arm,primecell" entry and use the standalone "qcom,coresight-tnoc" compatible, which binds via the platform driver by compatible string and does not rely on the component ID register at all. This lets the device probe on hardware that does not return a valid CID, while remaining an Aggregator TNOC that retains ATID functionality. Link: https://lore.kernel.org/all/20260710-fix-tracenoc-probe-issue-v6-4-41eb36fef8d9@oss.qualcomm.com/ Fixes: ebd1eb365cae ("arm64: qcom: dts: sm8750: add coresight nodes") Signed-off-by: Jie Gan --- arch/arm64/boot/dts/qcom/sm8750.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sm8750.dtsi b/arch/arm64/boot/dts/qcom/sm8750.dtsi index 250d1a1818a26..c8cc979a60494 100644 --- a/arch/arm64/boot/dts/qcom/sm8750.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8750.dtsi @@ -5373,7 +5373,7 @@ }; tn@109ab000 { - compatible = "qcom,coresight-tnoc", "arm,primecell"; + compatible = "qcom,coresight-tnoc"; reg = <0x0 0x109ab000 0x0 0x4200>; clocks = <&aoss_qmp>; From ee94e61eb7fe9e0ab753fdfd242a382963d93e2b Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Fri, 17 Jul 2026 15:33:54 +0530 Subject: [PATCH 102/188] FROMLIST: dt-bindings: remoteproc: qcom,pas-common: allow up to 3 iommus The ADSP PAS on several Qualcomm SoCs (e.g. SA8775P/lemans, QCS8300, Monaco) needs more than a single SMMU stream ID for the audio ML (machine-learning) offload use case. In addition to the primary ADSP stream ID, the DSP/SPF issues DMA transactions through extra stream IDs that are not covered by the existing single-entry mapping. Relax the iommus constraint to allow between 1 and 3 entries so the additional ML offload stream IDs can be described, fixing dtb_check schema violations on device trees that add them. Link: https://lore.kernel.org/all/20260717095825.3962666-2-pratyush.meduri@oss.qualcomm.com/ Signed-off-by: Pratyush Meduri --- .../devicetree/bindings/remoteproc/qcom,pas-common.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml index 4607b459131b4..873f3ddada20f 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml @@ -45,7 +45,8 @@ properties: - const: shutdown-ack iommus: - maxItems: 1 + minItems: 1 + maxItems: 3 power-domains: minItems: 1 From f75b90a9cc0f349dce8f51620df7ca257b3dcc55 Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Thu, 16 Jul 2026 20:05:31 +0530 Subject: [PATCH 103/188] FROMLIST: arm64: dts: qcom: lemans: enable audio ML offload memory and SMMU mappings The audio ML (machine-learning) offload use case requires a contiguous, physically addressable buffer shared with the audio DSP/SPF, and issues DMA transactions through additional SMMU stream IDs that are not covered by the existing ADSP mapping. Add a dedicated reusable shared-dma-pool CMA region (16 MiB, 4 MiB aligned) and wire it to the q6apm DAIs node via memory-region. A dedicated pool guarantees the alignment and contiguity the DSP expects and isolates these allocations from the default CMA region. Under the EL2 (Gunyah/hypervisor) configuration the SMMU is fully enforcing, so the ML transactions are otherwise blocked and faulted: arm-smmu 15000000.iommu: Blocked unknown Stream ID 0x3060 arm-smmu 15000000.iommu: Blocked unknown Stream ID 0x3062 Add the ML-related stream IDs (0x3060 mask 0x9, 0x3062 mask 0x1) to the remoteproc_adsp iommus property in the EL2 overlay so these buffers are translated by the SMMU instead of being rejected. Link: https://lore.kernel.org/all/20260717095825.3962666-3-pratyush.meduri@oss.qualcomm.com/ Signed-off-by: Pratyush Meduri --- arch/arm64/boot/dts/qcom/lemans-el2.dtso | 4 +++- arch/arm64/boot/dts/qcom/lemans.dtsi | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-el2.dtso b/arch/arm64/boot/dts/qcom/lemans-el2.dtso index 3efbdda9348b3..4f7395bccfaef 100644 --- a/arch/arm64/boot/dts/qcom/lemans-el2.dtso +++ b/arch/arm64/boot/dts/qcom/lemans-el2.dtso @@ -21,7 +21,9 @@ }; &remoteproc_adsp { - iommus = <&apps_smmu 0x3000 0x0>; + iommus = <&apps_smmu 0x3000 0x0>, + <&apps_smmu 0x3060 0x9>, + <&apps_smmu 0x3062 0x1>; }; &remoteproc_cdsp0 { diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index 53d0347762f1d..f4f0f13f2dc3c 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -689,6 +689,14 @@ #size-cells = <2>; ranges; + audio_cma_mem: qcom,audio-ml { + compatible = "shared-dma-pool"; + alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>; + reusable; + alignment = <0x0 0x400000>; + size = <0x0 0x1000000>; + }; + sail_ss_mem: sail-ss@80000000 { reg = <0x0 0x80000000 0x0 0x10000000>; no-map; @@ -8239,6 +8247,7 @@ q6apmdai: dais { compatible = "qcom,q6apm-dais"; iommus = <&apps_smmu 0x3001 0x0>; + memory-region = <&audio_cma_mem>; }; }; From 780644b8fade0488affc0320406d931a598d704b Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Tue, 28 Jul 2026 12:47:50 +0530 Subject: [PATCH 104/188] Revert "PENDING: arm64: dts: qcom: lemans: switch iris iommus to iommu-map" This reverts commit e1af6fe04342d72e5dde3a95bd9fb3f7df3aaff7. Signed-off-by: Vishnu Reddy --- arch/arm64/boot/dts/qcom/lemans.dtsi | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index f4f0f13f2dc3c..f2e59883dc294 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -5068,8 +5067,8 @@ resets = <&gcc GCC_VIDEO_AXI0_CLK_ARES>; reset-names = "bus"; - iommu-map = , - ; + iommus = <&apps_smmu 0x0880 0x0400>, + <&apps_smmu 0x0887 0x0400>; dma-coherent; status = "disabled"; From 0a48d61247aa4306f7ff83ceb8fdcbb7c06e55cc Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Thu, 9 Jul 2026 18:05:55 +0530 Subject: [PATCH 105/188] FROMLIST: arm64: dts: qcom: lemans: Move Iris IOMMUs to child nodes The Iris VPU has separate streams with different IOVA constraints. The non-pixel stream must be limited to the 0-600 MB IOVA range, while the pixel stream can use the full IOVA space. Using a single set of IOMMU entries for the Iris node does not describe these per-stream limits and can allow accesses outside the supported range, which may lead to device crashes. One such issue was reported at: https://gitlab.freedesktop.org/drm/msm/-/work_items/100 Add non-pixel and pixel child nodes, move each stream ID to its corresponding child node, and add a reserved IOVA range for the non-pixel stream. Link: https://lore.kernel.org/all/20260709-vpu_iommu_iova_handling-v1-9-72bb62cb2dfd@oss.qualcomm.com Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Tested-by: Daniel J Blueman --- arch/arm64/boot/dts/qcom/lemans.dtsi | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index f2e59883dc294..ee65c912e9721 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -933,6 +933,10 @@ reg = <0x0 0xdb200000 0x0 0x100000>; no-map; }; + + iris_resv: reservation-iris { + iommu-addresses = <&iris_non_pixel 0x0 0x0 0x0 0x25800000>; + }; }; smp2p-adsp { @@ -5067,12 +5071,22 @@ resets = <&gcc GCC_VIDEO_AXI0_CLK_ARES>; reset-names = "bus"; - iommus = <&apps_smmu 0x0880 0x0400>, - <&apps_smmu 0x0887 0x0400>; dma-coherent; + #address-cells = <2>; + #size-cells = <2>; + status = "disabled"; + iris_non_pixel: non-pixel { + iommus = <&apps_smmu 0x0880 0x0400>; + memory-region = <&iris_resv>; + }; + + iris_pixel: pixel { + iommus = <&apps_smmu 0x0887 0x0400>; + }; + iris_opp_table: opp-table { compatible = "operating-points-v2"; From 33ecf6f10af6a446cb234b92f5579e23d7d0b8e8 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:14 +0530 Subject: [PATCH 106/188] FROMLIST: arm64: dts: qcom: pmih0108: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for pmih0108 PMIC. The BCL monitors battery voltage and current, providing hardware interrupts when configurable thresholds are violated. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-9-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pmih0108.dtsi b/arch/arm64/boot/dts/qcom/pmih0108.dtsi index 1c875995d8813..1f91e788010b1 100644 --- a/arch/arm64/boot/dts/qcom/pmih0108.dtsi +++ b/arch/arm64/boot/dts/qcom/pmih0108.dtsi @@ -49,6 +49,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pmih0108-bcl"; + reg = <0x4700>; + interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pmih0108_gpios: gpio@8800 { compatible = "qcom,pmih0108-gpio", "qcom,spmi-gpio"; reg = <0x8800>; From c4e303d381337ee98fb5721d44aafecb1ed6251d Mon Sep 17 00:00:00 2001 From: Yi Zhang Date: Mon, 23 Mar 2026 12:38:52 +0530 Subject: [PATCH 107/188] FROMLIST: dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support Add binding for the Lontium LT9211C bridge chip. Signed-off-by: Yi Zhang Signed-off-by: Nilesh Laad Signed-off-by: Gopi Botlagunta Link: https://lore.kernel.org/all/20260323-add-lt9211c-bridge-v5-1-9c63bb035c17@oss.qualcomm.com/ --- .../devicetree/bindings/display/bridge/lontium,lt9211.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml index 7cfe92a8bcd72..de61bda09db96 100644 --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml @@ -4,19 +4,20 @@ $id: http://devicetree.org/schemas/display/bridge/lontium,lt9211.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Lontium LT9211 DSI/LVDS/DPI to DSI/LVDS/DPI bridge. +title: Lontium LT9211/LT9211C DSI/LVDS/DPI to DSI/LVDS/DPI bridge. maintainers: - Marek Vasut description: | - The LT9211 are bridge devices which convert Single/Dual-Link DSI/LVDS + The LT9211 and LT9211C are bridge devices which convert Single/Dual-Link DSI/LVDS or Single DPI to Single/Dual-Link DSI/LVDS or Single DPI. properties: compatible: enum: - lontium,lt9211 + - lontium,lt9211c reg: maxItems: 1 From 00b4b8bd450192483ad6f3773b97bcff63d31de8 Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 28 Jan 2026 18:28:20 +0530 Subject: [PATCH 108/188] FROMLIST: drm/bridge: lt9611uxc: reset edid_read on disconnect Currently edid_read has value from previous connect session and resulting in drm using older edid before new edid is available in lt9611uxc. Reset edid_read so that correct status is updated and correct edid is available for drm. Link: https://lore.kernel.org/lkml/20260202-lt9611uxc-reset-edid-v2-1-b1e1d72edc90@oss.qualcomm.com/ Signed-off-by: Ravi Agola Signed-off-by: Nilesh Laad --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 9427cc2358ae1..2e59318ccf2ba 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -169,6 +169,9 @@ static void lt9611uxc_hpd_work(struct work_struct *work) connected = lt9611uxc->hdmi_connected; mutex_unlock(<9611uxc->ocm_lock); + if (!connected) + lt9611uxc->edid_read = false; + drm_bridge_hpd_notify(<9611uxc->bridge, connected ? connector_status_connected : From 715c8640213c1809c28bd31833c7ed29c80923e2 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 23 Mar 2026 19:36:04 +0800 Subject: [PATCH 109/188] FROMLIST: drm/bridge: display-connector: don't autoenable HPD IRQ If HPD IRQ is enabled in the display_connector's probe, it can be triggered too early, before the DRM connector is completely setup. Use the enable_hpd / disable_hpd callbacks to control enablement of the HPD IRQ. Fixes: 0c275c30176b ("drm/bridge: Add bridge driver for display connectors") Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260314-dp-connector-hpd-v1-1-786044cedc17@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/display-connector.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c index 6b128fabe3a97..4567e402f2224 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -94,6 +94,20 @@ display_connector_bridge_detect(struct drm_bridge *bridge, struct drm_connector return display_connector_detect(bridge); } +static void display_connector_hpd_enable(struct drm_bridge *bridge) +{ + struct display_connector *conn = to_display_connector(bridge); + + enable_irq(conn->hpd_irq); +} + +static void display_connector_hpd_disable(struct drm_bridge *bridge) +{ + struct display_connector *conn = to_display_connector(bridge); + + disable_irq(conn->hpd_irq); +} + static const struct drm_edid *display_connector_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -186,6 +200,8 @@ static const struct drm_bridge_funcs display_connector_bridge_funcs = { .attach = display_connector_attach, .destroy = display_connector_destroy, .detect = display_connector_bridge_detect, + .hpd_enable = display_connector_hpd_enable, + .hpd_disable = display_connector_hpd_disable, .edid_read = display_connector_edid_read, .atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts, .atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts, @@ -315,6 +331,7 @@ static int display_connector_probe(struct platform_device *pdev) NULL, display_connector_hpd_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_NO_AUTOEN | IRQF_ONESHOT, "HPD", conn); if (ret) { From 6913cf17cf88db00c0cbb72437ab8c6818cc91de Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 23 Mar 2026 19:37:45 +0800 Subject: [PATCH 110/188] FROMLIST: drm/bridge: display-connector: trigger initial HPD event for DP If the DisplayPort drivers use display-connector for the HPD detection, the internal HPD state machine might be not active and thus the hardware might be not able to handle cable detection correctly. Instead it will depend on the externall HPD notifications to set the cable state, bypassing the internal HPD state machine (for example this is the case for the msm DP driver). However if the cable has been plugged before the HPD IRQ has been enabled, there will be no HPD event coming. The drivers might fail detection in such a case. Trigger the HPD notification after enabling the HPD IRQ, propagating the cable insertion state. Fixes: 2e2bf3a5584d ("drm/bridge: display-connector: add DP support") Reported-by: Yongxing Mou Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260314-dp-connector-hpd-v1-2-786044cedc17@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/display-connector.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c index 4567e402f2224..441355ace2bb8 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,8 @@ struct display_connector { struct regulator *supply; struct gpio_desc *ddc_en; + + struct work_struct hpd_work; }; static inline struct display_connector * @@ -99,15 +102,29 @@ static void display_connector_hpd_enable(struct drm_bridge *bridge) struct display_connector *conn = to_display_connector(bridge); enable_irq(conn->hpd_irq); + + if (conn->bridge.type == DRM_MODE_CONNECTOR_DisplayPort) + schedule_work(&conn->hpd_work); } static void display_connector_hpd_disable(struct drm_bridge *bridge) { struct display_connector *conn = to_display_connector(bridge); + if (conn->bridge.type == DRM_MODE_CONNECTOR_DisplayPort) + cancel_work_sync(&conn->hpd_work); + disable_irq(conn->hpd_irq); } +static void display_connector_hpd_work(struct work_struct *work) +{ + struct display_connector *conn = container_of(work, struct display_connector, hpd_work); + struct drm_bridge *bridge = &conn->bridge; + + drm_bridge_hpd_notify(bridge, display_connector_detect(bridge)); +} + static const struct drm_edid *display_connector_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -403,6 +420,8 @@ static int display_connector_probe(struct platform_device *pdev) conn->bridge.ops |= DRM_BRIDGE_OP_DETECT; if (conn->hpd_irq >= 0) conn->bridge.ops |= DRM_BRIDGE_OP_HPD; + if (conn->hpd_irq >= 0 && type == DRM_MODE_CONNECTOR_DisplayPort) + INIT_WORK(&conn->hpd_work, display_connector_hpd_work); dev_dbg(&pdev->dev, "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n", From e6cfcfcd7d8a8f99ebe3b24c9dfae134fa1e381a Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Sun, 19 Apr 2026 15:45:56 +0530 Subject: [PATCH 111/188] FROMLIST: phy: qualcomm: qmp-combo: update DP PHY PLL programming on Glymur The existing DP PHY PLL and AUX configuration for the Glymur platform does not fully follow the Hardware Programming Guide requirements for DP over Type-C, which results in DP link bring-up failures. Update the DP PHY programming sequence and PLL-related register settings to align with the latest HPG recommendations. With this change, DP link training completes successfully on Glymur-based platforms. Fixes: d10736db98d2 ("phy: qualcomm: qmp-combo: Add DP offsets and settings for Glymur platforms") Link: https://lore.kernel.org/r/20260419-glymur_dp-v1-1-ad1067a8e8ae@oss.qualcomm.com Signed-off-by: Ritesh Kumar Signed-off-by: Mahadevan P --- drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 213 +++++++++++++----- .../qualcomm/phy-qcom-qmp-qserdes-com-v8.h | 2 + 2 files changed, 154 insertions(+), 61 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index cdcfad2e86b1d..6c5f56c2e1ffc 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -283,8 +283,8 @@ static const unsigned int qmp_v8_n3_usb43dpphy_regs_layout[QPHY_LAYOUT_SIZE] = { [QPHY_DP_AON_TOGGLE_ENABLE] = QPHY_V8_PCS_AON_DP_AON_TOGGLE_ENABLE, [QPHY_COM_RESETSM_CNTRL] = QSERDES_V8_COM_RESETSM_CNTRL, - [QPHY_COM_C_READY_STATUS] = QSERDES_V8_COM_C_READY_STATUS, - [QPHY_COM_CMN_STATUS] = QSERDES_V8_COM_CMN_STATUS, + [QPHY_COM_C_READY_STATUS] = QSERDES_V8_COM_C_READY_STATUS_N3, + [QPHY_COM_CMN_STATUS] = QSERDES_V8_COM_CMN_STATUS_N3, [QPHY_COM_BIAS_EN_CLKBUFLR_EN] = QSERDES_V8_COM_BIAS_EN_CLKBUFLR_EN, [QPHY_DP_PHY_STATUS] = QSERDES_V8_DP_PHY_STATUS, @@ -1386,10 +1386,10 @@ static const struct qmp_phy_init_tbl qmp_v6_n4_dp_serdes_tbl[] = { }; static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CP_CTRL_MODE0, 0x06), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_RCTRL_MODE1, 0x10), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_CCTRL_MODE1, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_CCTRL_MODE0, 0x36), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CORECLK_DIV_MODE0, 0x0a), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START1_MODE0, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), @@ -1404,12 +1404,13 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYSCLK_BUF_ENABLE, 0x06), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_IVCO, 0x07), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYSCLK_EN_SEL, 0x3b), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_RESETSM_CNTRL, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP_EN, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE_CTRL, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE_MAP, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_SELECT, 0x30), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CORE_CLK_EN, 0x00), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_CONFIG_1, 0x56), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_CONFIG_1, 0x16), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SVS_MODE_CLK_SEL, 0x15), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_MODE_CONTD1, 0x24), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DCC_CAL_1, 0x40), @@ -1445,26 +1446,26 @@ static const struct qmp_phy_init_tbl qmp_v6_n4_dp_tx_tbl[] = { }; static const struct qmp_phy_init_tbl qmp_v8_n3p_dp_tx_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TRANSMITTER_EN_CTRL, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TRANSMITTER_EN_CTRL, 0x1a), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_VMODE_CTRL1, 0x40), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_ANA_INTERFACE_SELECT1, 0x07), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_ANA_INTERFACE_SELECT2, 0x18), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_PCS_INTERFACE_SELECT1, 0x50), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_LANE_MODE_1, 0x0d), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_CLKBUF_ENABLE, 0x07), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_RESET_TSYNC_EN_CTRL, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_CLKBUF_ENABLE, 0x87), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_RESET_TSYNC_EN_CTRL, 0x0f), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX_LVL_UPDATE_CTRL, 0x0f), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TRAN_DRVR_EMP_EN, 0x5f), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_EMP_POST1_LVL, 0x20), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX1_EMP_POST1_LVL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_EMP_POST1_LVL, 0x2b), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX1_EMP_POST1_LVL, 0x2b), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_PRE1_EMPH, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX1_PRE1_EMPH, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_DRV_LVL, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX1_DRV_LVL, 0x00), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_HIGHZ_DRVR_EN, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_HIGHZ_DRVR_EN, 0x3f), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_LANE_MODE_2, 0x50), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_LANE_MODE_3, 0x51), - QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX_DCC_ANA_CTRL2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX_DCC_ANA_CTRL2, 0x0c), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_RESTRIM_CAL_CTRL, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX1_RESTRIM_CAL_CTRL, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_LALB_TX0_RESTRIM_POST_CAL_OFFSET, 0x10), @@ -1589,11 +1590,12 @@ static const struct qmp_phy_init_tbl qmp_v6_n4_dp_serdes_tbl_hbr3[] = { static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_rbr[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_HSCLK_SEL_1, 0x05), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0x7a), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0x8d), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x27), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE1_MODE0, 0x83), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x37), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP2_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP2_MODE0, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DEC_START_MODE0, 0x54), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START2_MODE0, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START3_MODE0, 0x06), @@ -1601,16 +1603,17 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_rbr[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE2_MODE0, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_HSCLK_SEL_1, 0x05), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_MODE_CONTD3, 0x07), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1, 0x30), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0xa4), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0xa7), }; static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_HSCLK_SEL_1, 0x04), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0x21), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xf6), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE1_MODE0, 0x18), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x08), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP2_MODE0, 0x07), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DEC_START_MODE0, 0x46), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START2_MODE0, 0x00), @@ -1627,7 +1630,8 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr2[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_HSCLK_SEL_1, 0x03), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xf6), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x20), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE1_MODE0, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE1_MODE0, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_RCTRL_MODE0, 0x16), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_CCTRL_MODE0, 0x36), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x10), @@ -1637,9 +1641,9 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr2[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START3_MODE0, 0x05), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE1_MODE0, 0xae), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE2_MODE0, 0x02), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_HSCLK_SEL_1, 0x00), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0xbf), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIAS_EN_CLKBUFLR_EN, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_HSCLK_SEL_1, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIAS_EN_CLKBUFLR_EN, 0x17), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_RESETSM_CNTRL, 0x20), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_MODE_CONTD3, 0x03), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1, 0x3f), @@ -1647,8 +1651,8 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr2[] = { static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr3[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_HSCLK_SEL_1, 0x02), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0x63), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x25), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE1_MODE0, 0x5b), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SSC_STEP_SIZE2_MODE0, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CP_CTRL_MODE0, 0x06), @@ -1656,7 +1660,7 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr3[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_CCTRL_MODE0, 0x36), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CORECLK_DIV_MODE0, 0x0a), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x17), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP1_MODE0, 0x18), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_LOCK_CMP2_MODE0, 0x15), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DEC_START_MODE0, 0x4f), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_DIV_FRAC_START1_MODE0, 0x00), @@ -1675,19 +1679,19 @@ static const struct qmp_phy_init_tbl qmp_v8_dp_serdes_tbl_hbr3[] = { QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYS_CLK_CTRL, 0x02), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYSCLK_BUF_ENABLE, 0x06), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_PLL_IVCO, 0x07), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYSCLK_EN_SEL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SYSCLK_EN_SEL, 0x3b), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE_CTRL, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_VCO_TUNE_MAP, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_SELECT, 0x30), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CORE_CLK_EN, 0x00), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_CONFIG_1, 0x16), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_SVS_MODE_CLK_SEL, 0x15), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1, 0x30), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIAS_EN_CLKBUFLR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIAS_EN_CLKBUFLR_EN, 0x17), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_MODE_CONTD3, 0x05), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_CMN_MODE_CONTD1, 0x24), QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_BIN_VCOCAL_HSCLK_SEL_1, 0x02), - QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0x84), + QMP_PHY_INIT_CFG(QSERDES_V8_USB43_COM_IP_CTRL_AND_DP_SEL, 0x87), }; static const struct qmp_phy_init_tbl sc8280xp_usb43dp_serdes_tbl[] = { @@ -3145,6 +3149,30 @@ static int qmp_combo_configure_dp_swing(struct qmp_combo *qmp) return 0; } +static bool qmp_v8_combo_configure_dp_mode(struct qmp_combo *qmp) +{ + bool reverse = (qmp->orientation == TYPEC_ORIENTATION_REVERSE); + const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; + u32 val; + + val = DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | + DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | + DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN; + + if (dp_opts->lanes == 1 || dp_opts->lanes == 2) { + if (reverse) + val &= ~DP_PHY_PD_CTL_LANE_2_3_PWRDN; + else + val &= ~DP_PHY_PD_CTL_LANE_0_1_PWRDN; + } + + writel(val, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); + + writel(0x5c, qmp->dp_dp_phy + QSERDES_DP_PHY_MODE); + + return reverse; +} + static void qmp_v3_configure_dp_tx(struct qmp_combo *qmp) { const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; @@ -3324,16 +3352,70 @@ static void qmp_v4_dp_aux_init(struct qmp_combo *qmp) qmp->dp_dp_phy + QSERDES_V4_DP_PHY_AUX_INTERRUPT_MASK); } +static int qmp_v8_helper_configure_dp_phy(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + u32 status; + int ret; + + writel(0x0f, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG_1); + + qmp_v8_combo_configure_dp_mode(qmp); + + writel(0x13, qmp->dp_dp_phy + QSERDES_DP_PHY_AUX_CFG1); + writel(0xa4, qmp->dp_dp_phy + QSERDES_DP_PHY_AUX_CFG2); + + writel(0x05, qmp->dp_dp_phy + QSERDES_V4_DP_PHY_TX0_TX1_LANE_CTL); + writel(0x05, qmp->dp_dp_phy + QSERDES_V4_DP_PHY_TX2_TX3_LANE_CTL); + + ret = qmp->cfg->configure_dp_clocks(qmp); + if (ret) + return ret; + + writel(0x01, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); + writel(0x05, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); + writel(0x01, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); + writel(0x09, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); + + writel(0x20, qmp->dp_serdes + cfg->regs[QPHY_COM_RESETSM_CNTRL]); + + if (readl_poll_timeout(qmp->dp_serdes + cfg->regs[QPHY_COM_C_READY_STATUS], + status, + ((status & BIT(0)) > 0), + 500, + 10000)) + return -ETIMEDOUT; + + if (readl_poll_timeout(qmp->dp_serdes + cfg->regs[QPHY_COM_CMN_STATUS], + status, + ((status & BIT(0)) > 0), + 500, + 10000)) + return -ETIMEDOUT; + + if (readl_poll_timeout(qmp->dp_serdes + cfg->regs[QPHY_COM_CMN_STATUS], + status, + ((status & BIT(1)) > 0), + 500, + 10000)) + return -ETIMEDOUT; + + return 0; +} + static void qmp_v8_dp_aux_init(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; - writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_PSR_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | - DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, - qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); + writel(0x3f, qmp->dp_serdes + QSERDES_V8_USB43_COM_CLK_FWD_CONFIG_1); + + writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | + DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | + DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, + qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); /* Turn on BIAS current for PHY/PLL */ - writel(0x1c, qmp->dp_serdes + cfg->regs[QPHY_COM_BIAS_EN_CLKBUFLR_EN]); + writel(0x17, qmp->dp_serdes + cfg->regs[QPHY_COM_BIAS_EN_CLKBUFLR_EN]); writel(0x00, qmp->dp_dp_phy + QSERDES_DP_PHY_AUX_CFG0); writel(0x13, qmp->dp_dp_phy + QSERDES_DP_PHY_AUX_CFG1); @@ -3361,8 +3443,8 @@ static void qmp_v4_configure_dp_tx(struct qmp_combo *qmp) writel(0x27, qmp->dp_tx + cfg->regs[QPHY_TX_TX_DRV_LVL]); writel(0x27, qmp->dp_tx2 + cfg->regs[QPHY_TX_TX_DRV_LVL]); - writel(0x20, qmp->dp_tx + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); - writel(0x20, qmp->dp_tx2 + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); + writel(0x2b, qmp->dp_tx + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); + writel(0x2b, qmp->dp_tx2 + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); qmp_combo_configure_dp_swing(qmp); } @@ -3370,48 +3452,41 @@ static void qmp_v4_configure_dp_tx(struct qmp_combo *qmp) static int qmp_v8_configure_dp_clocks(struct qmp_combo *qmp) { const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; - u32 phy_vco_div; unsigned long pixel_freq; - const struct qmp_phy_cfg *cfg = qmp->cfg; switch (dp_opts->link_rate) { case 1620: - phy_vco_div = 0x4; pixel_freq = 1620000000UL / 2; break; case 2700: - phy_vco_div = 0x2; pixel_freq = 2700000000UL / 2; break; case 5400: - phy_vco_div = 0x4; pixel_freq = 5400000000UL / 4; break; case 8100: - phy_vco_div = 0x3; pixel_freq = 8100000000UL / 6; break; default: /* Other link rates aren't supported */ return -EINVAL; } - writel(phy_vco_div, qmp->dp_dp_phy + cfg->regs[QPHY_DP_PHY_VCO_DIV]); /* disable core reset tsync */ writel(0x09, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); - writel(0x04, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_AUXLESS_SETUP_CYC); - writel(0x08, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_AUXLESS_SILENCE_CYC); + writel(0x09, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_AUXLESS_SETUP_CYC); + writel(0x11, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_AUXLESS_SILENCE_CYC); writel(0x08, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LFPS_CYC); - writel(0x11, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LFPS_PERIOD); + writel(0x33, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LFPS_PERIOD); writel(0x3e, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TSYNC_OVRD); writel(0x05, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TX2_TX3_LANE_CTL); writel(0x05, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TX0_TX1_LANE_CTL); writel(0x01, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_AUXLESS_CFG1); - writel(0x11, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LFPS_PERIOD); + writel(0x33, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LFPS_PERIOD); writel(0x1f, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LN0_DRV_LVL); - writel(0x1f, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LN1_DRV_LVL); + writel(0x02, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_LN1_DRV_LVL); clk_set_rate(qmp->dp_link_hw.clk, dp_opts->link_rate * 100000); clk_set_rate(qmp->dp_pixel_hw.clk, pixel_freq); @@ -3558,7 +3633,7 @@ static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp) u32 status; int ret; - ret = qmp_v456_configure_dp_phy(qmp); + ret = qmp_v8_helper_configure_dp_phy(qmp); if (ret < 0) return ret; @@ -3570,13 +3645,13 @@ static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp) } else if (dp_opts->lanes == 2) { bias0_en = reverse ? 0x3f : 0x15; bias1_en = reverse ? 0x15 : 0x3f; - drvr0_en = 0x10; - drvr1_en = 0x10; + drvr0_en = 0x30; + drvr1_en = 0x30; } else { bias0_en = 0x3f; bias1_en = 0x3f; - drvr0_en = 0x34; - drvr1_en = 0x34; + drvr0_en = 0x30; + drvr1_en = 0x30; } writel(drvr0_en, qmp->dp_tx + cfg->regs[QPHY_TX_HIGHZ_DRVR_EN]); @@ -3584,6 +3659,14 @@ static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp) writel(drvr1_en, qmp->dp_tx2 + cfg->regs[QPHY_TX_HIGHZ_DRVR_EN]); writel(bias1_en, qmp->dp_tx2 + cfg->regs[QPHY_TX_TRANSCEIVER_BIAS_EN]); + writel(0x03, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TSYNC_OVRD); + writel(0x23, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TSYNC_OVRD); + writel(0x22, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TSYNC_OVRD); + + writel(0x0a, qmp->dp_tx + QSERDES_V8_LALB_RESET_TSYNC_EN_CTRL); + writel(0x0a, qmp->dp_tx2 + QSERDES_V8_LALB_RESET_TSYNC_EN_CTRL); + + writel(0x3e, qmp->dp_dp_phy + QSERDES_V8_DP_PHY_TSYNC_OVRD); writel(0x08, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); udelay(100); writel(0x09, qmp->dp_dp_phy + QSERDES_DP_PHY_CFG); @@ -3591,16 +3674,24 @@ static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp) if (readl_poll_timeout(qmp->dp_dp_phy + cfg->regs[QPHY_DP_PHY_STATUS], status, - ((status & BIT(1)) > 0), + ((status & BIT(0)) > 0), 500, 10000)) return -ETIMEDOUT; - writel(0x00, qmp->dp_tx + cfg->regs[QPHY_TX_TX_DRV_LVL]); - writel(0x00, qmp->dp_tx2 + cfg->regs[QPHY_TX_TX_DRV_LVL]); + if (readl_poll_timeout(qmp->dp_serdes + cfg->regs[QPHY_COM_CMN_STATUS], + status, + ((status & BIT(0)) > 0), + 500, + 10000)) + return -ETIMEDOUT; - writel(0x2b, qmp->dp_tx + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); - writel(0x2b, qmp->dp_tx2 + cfg->regs[QPHY_TX_TX_EMP_POST1_LVL]); + if (readl_poll_timeout(qmp->dp_serdes + cfg->regs[QPHY_COM_CMN_STATUS], + status, + ((status & BIT(1)) > 0), + 500, + 10000)) + return -ETIMEDOUT; return 0; } diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-v8.h b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-v8.h index d8ac4c4a2c316..b35b486cfa566 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-v8.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp-qserdes-com-v8.h @@ -71,5 +71,7 @@ #define QSERDES_V8_COM_ADDITIONAL_MISC 0x1b4 #define QSERDES_V8_COM_CMN_STATUS 0x2c8 #define QSERDES_V8_COM_C_READY_STATUS 0x2f0 +#define QSERDES_V8_COM_CMN_STATUS_N3 0x314 +#define QSERDES_V8_COM_C_READY_STATUS_N3 0x33c #endif From 95094684c25b1950492be493a496c2bfb84f9a5c Mon Sep 17 00:00:00 2001 From: Yi Zhang Date: Mon, 4 May 2026 13:50:52 +0530 Subject: [PATCH 112/188] FROMLIST: drm/bridge: add support for lontium lt9211c bridge LT9211c is a Single/Dual-Link DSI/LVDS or Single DPI input to Single-link/Dual-Link DSI/LVDS or Single DPI output bridge chip. Extend the existing lontium-lt9211 driver to support DSI-to-LVDS bridge configuration. Signed-off-by: Yi Zhang Signed-off-by: Nilesh Laad Signed-off-by: Gopi Botlagunta Link:https://lore.kernel.org/all/20260323-add-lt9211c-bridge-v5-2-9c63bb035c17@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/lontium-lt9211.c | 842 ++++++++++++++++++++++-- 1 file changed, 802 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index 870175a848390..2d14fa0b3a9a2 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -36,20 +37,39 @@ #define REG_CHIPID2 0x8102 #define REG_CHIPID2_VALUE 0xe3 +/* LT9211C chip ID values */ +#define REG_CHIPID0_LT9211C_VALUE 0x21 +#define REG_CHIPID1_LT9211C_VALUE 0x03 +#define REG_CHIPID2_LT9211C_VALUE 0xe1 + #define REG_DSI_LANE 0xd000 /* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */ #define REG_DSI_LANE_COUNT(n) ((n) & 3) +/* Chip type enum */ +enum lt9211_chip_type { + LT9211, + LT9211C, +}; + struct lt9211 { struct drm_bridge bridge; struct device *dev; struct regmap *regmap; - struct mipi_dsi_device *dsi; + struct mipi_dsi_device *dsi; struct drm_bridge *panel_bridge; struct gpio_desc *reset_gpio; struct regulator *vccio; - bool lvds_dual_link; - bool lvds_dual_link_even_odd_swap; + bool lvds_dual_link; + bool lvds_dual_link_even_odd_swap; + /* LT9211C specific fields */ + enum lt9211_chip_type chip_type; + struct workqueue_struct *wq; + struct delayed_work lt9211_dw; + struct drm_display_mode mode; + bool bpp24; + bool jeida; + bool de; }; static const struct regmap_range lt9211_rw_ranges[] = { @@ -93,6 +113,49 @@ static const struct regmap_config lt9211_regmap_config = { .max_register = 0xda00, }; +static const struct regmap_range lt9211c_rw_ranges[] = { + regmap_reg_range(0xff, 0xff), + regmap_reg_range(0x8100, 0x8182), + regmap_reg_range(0x8200, 0x82aa), + regmap_reg_range(0x8500, 0x85ff), + regmap_reg_range(0x8600, 0x86a0), + regmap_reg_range(0x8700, 0x8746), + regmap_reg_range(0xd000, 0xd0a7), + regmap_reg_range(0xd400, 0xd42c), + regmap_reg_range(0xd800, 0xd838), + regmap_reg_range(0xd9c0, 0xd9d5), +}; + +static const struct regmap_access_table lt9211c_rw_table = { + .yes_ranges = lt9211c_rw_ranges, + .n_yes_ranges = ARRAY_SIZE(lt9211c_rw_ranges), +}; + +static const struct regmap_range_cfg lt9211c_range = { + .name = "lt9211c", + .range_min = 0x0000, + .range_max = 0xda00, + .selector_reg = REG_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, +}; + +static const struct regmap_config lt9211c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = <9211c_rw_table, + .wr_table = <9211c_rw_table, + .volatile_table = <9211c_rw_table, + .ranges = <9211c_range, + .num_ranges = 1, + .cache_type = REGCACHE_RBTREE, + .max_register = 0xda00, +}; + +static void lt9211_delayed_work_func(struct work_struct *work); + static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge) { return container_of(bridge, struct lt9211, bridge); @@ -120,14 +183,21 @@ static int lt9211_read_chipid(struct lt9211 *ctx) return ret; } - /* Test for known Chip ID. */ - if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE) { - dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n", - chipid[0], chipid[1], chipid[2]); - return -EINVAL; + /* Test for LT9211 Chip ID. */ + if (chipid[0] == REG_CHIPID0_VALUE && chipid[1] == REG_CHIPID1_VALUE) { + dev_dbg(ctx->dev, "Detected LT9211 chip\n"); + return 0; } - return 0; + /* Test for LT9211C Chip ID. */ + if (chipid[0] == REG_CHIPID0_LT9211C_VALUE && chipid[1] == REG_CHIPID1_LT9211C_VALUE) { + dev_dbg(ctx->dev, "Detected LT9211C chip\n"); + return 0; + } + + dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x\n", chipid[0], chipid[1]); + + return -EINVAL; } static int lt9211_system_init(struct lt9211 *ctx) @@ -504,8 +574,8 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge, lvds_format_24bpp = true; lvds_format_jeida = false; dev_warn(ctx->dev, - "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", - bridge_state->output_bus_cfg.format); + "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", + bridge_state->output_bus_cfg.format); break; } @@ -518,35 +588,42 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge, crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); mode = &crtc_state->adjusted_mode; - ret = lt9211_read_chipid(ctx); if (ret) return; - ret = lt9211_system_init(ctx); - if (ret) + if (ctx->chip_type == LT9211C && ctx->wq) { + drm_mode_copy(&ctx->mode, mode); + /* LT9211C must enable after mipi clock enable */ + queue_delayed_work(ctx->wq, &ctx->lt9211_dw, + msecs_to_jiffies(100)); + dev_dbg(ctx->dev, "LT9211C enabled.\n"); return; + } + ret = lt9211_system_init(ctx); + if (ret) + return; - ret = lt9211_configure_rx(ctx); - if (ret) - return; + ret = lt9211_configure_rx(ctx); + if (ret) + return; - ret = lt9211_autodetect_rx(ctx, mode); - if (ret) - return; + ret = lt9211_autodetect_rx(ctx, mode); + if (ret) + return; - ret = lt9211_configure_timing(ctx, mode); - if (ret) - return; + ret = lt9211_configure_timing(ctx, mode); + if (ret) + return; - ret = lt9211_configure_plls(ctx, mode); - if (ret) - return; + ret = lt9211_configure_plls(ctx, mode); + if (ret) + return; ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp, - bus_flags & DRM_BUS_FLAG_DE_HIGH); - if (ret) - return; + bus_flags & DRM_BUS_FLAG_DE_HIGH); + if (ret) + return; dev_dbg(ctx->dev, "LT9211 enabled.\n"); } @@ -671,11 +748,6 @@ static int lt9211_parse_dt(struct lt9211 *ctx) static int lt9211_host_attach(struct lt9211 *ctx) { - const struct mipi_dsi_device_info info = { - .type = "lt9211", - .channel = 0, - .node = NULL, - }; struct device *dev = ctx->dev; struct device_node *host_node; struct device_node *endpoint; @@ -684,6 +756,8 @@ static int lt9211_host_attach(struct lt9211 *ctx) int dsi_lanes; int ret; + /* Check if the compatible string matches lt9211c */ + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); host_node = of_graph_get_remote_port_parent(endpoint); @@ -697,7 +771,22 @@ static int lt9211_host_attach(struct lt9211 *ctx) if (dsi_lanes < 0) return dsi_lanes; - dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (ctx->chip_type == LT9211C) { + const struct mipi_dsi_device_info info = { + .type = "lt9211c", + .channel = 0, + .node = NULL, + }; + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + } else { + const struct mipi_dsi_device_info info = { + .type = "lt9211", + .channel = 0, + .node = NULL, + }; + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + } + if (IS_ERR(dsi)) return dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi device\n"); @@ -706,10 +795,16 @@ static int lt9211_host_attach(struct lt9211 *ctx) dsi->lanes = dsi_lanes; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | - MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO_NO_HSA | - MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP | - MIPI_DSI_MODE_NO_EOT_PACKET; + + if (ctx->chip_type == LT9211C) { + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + } else { + dsi->mode_flags = + MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO_NO_HSA | + MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP | + MIPI_DSI_MODE_NO_EOT_PACKET; + } ret = devm_mipi_dsi_attach(dev, dsi); if (ret < 0) { @@ -731,6 +826,7 @@ static int lt9211_probe(struct i2c_client *client) return PTR_ERR(ctx); ctx->dev = dev; + ctx->chip_type = LT9211; /* * Put the chip in reset, pull nRST line low, @@ -744,13 +840,29 @@ static int lt9211_probe(struct i2c_client *client) usleep_range(10000, 11000); /* Very long reset duration. */ ret = lt9211_parse_dt(ctx); + if (ret) return ret; - ctx->regmap = devm_regmap_init_i2c(client, <9211_regmap_config); + if (of_device_is_compatible(dev->of_node, "lontium,lt9211c")) { + ctx->chip_type = LT9211C; + ctx->regmap = + devm_regmap_init_i2c(client, <9211c_regmap_config); + } else { + ctx->chip_type = LT9211; + ctx->regmap = + devm_regmap_init_i2c(client, <9211_regmap_config); + } if (IS_ERR(ctx->regmap)) return PTR_ERR(ctx->regmap); + /* Initialize LT9211C-specific fields */ + ctx->wq = create_workqueue("lt9211_work"); + if (!ctx->wq) + return -ENOMEM; + + INIT_DELAYED_WORK(&ctx->lt9211_dw, lt9211_delayed_work_func); + dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx); @@ -768,17 +880,667 @@ static void lt9211_remove(struct i2c_client *client) { struct lt9211 *ctx = i2c_get_clientdata(client); + if (ctx->wq) + destroy_workqueue(ctx->wq); + drm_bridge_remove(&ctx->bridge); } +static int lt9211c_configure_rx(struct lt9211 *ctx) +{ + unsigned int pval; + + const struct reg_sequence lt9211c_rx_phy_seq[] = { + { REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) }, + { 0x8201, 0x11 }, + { 0x8218, 0x48 }, + { 0x8201, 0x91 }, + { 0x8202, 0x00 }, + { 0x8203, 0xee }, + { 0x8209, 0x21 }, + { 0x8204, 0x44 }, + { 0x8205, 0xc4 }, + { 0x8206, 0x44 }, + { 0x8213, 0x0c }, + + { 0xd001, 0x00 }, + { 0xd002, 0x0e }, + { 0xd005, 0x00 }, + { 0xd00a, 0x59 }, + { 0xd00b, 0x20 }, + }; + + const struct reg_sequence lt9211c_rx_phy_reset_seq[] = { + { 0x8109, 0xde }, + { 0x8109, 0xdf }, + }; + + const struct reg_sequence lt9211c_rx_clk_sel_seq[] = { + { 0x85e9, 0x88 }, + { 0x8180, 0x51 }, + { 0x8181, 0x10 }, + { 0x8632, 0x03 }, + }; + + const struct reg_sequence lt9211c_rx_input_sel_seq[] = { + { 0xd004, 0x00 }, + { 0xd021, 0x46 }, + }; + + const struct reg_sequence lt9211c_rx_dig_seq[] = { + { 0x853f, 0x08 }, + { 0x8540, 0x04 }, + { 0x8541, 0x03 }, + { 0x8542, 0x02 }, + { 0x8543, 0x01 }, + { 0x8545, 0x04 }, + { 0x8546, 0x03 }, + { 0x8547, 0x02 }, + { 0x8548, 0x01 }, + { 0x8544, 0x00 }, + { 0x8549, 0x00 }, + }; + + int ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_phy_seq, + ARRAY_SIZE(lt9211c_rx_phy_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_phy_reset_seq, + ARRAY_SIZE(lt9211c_rx_phy_reset_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_clk_sel_seq, + ARRAY_SIZE(lt9211c_rx_clk_sel_seq)); + if (ret) + return ret; + + ret = regmap_read(ctx->regmap, 0x8180, &pval); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x8180, ((pval & 0xfc) | 0x03)); + if (ret) + return ret; + + ret = regmap_read(ctx->regmap, 0x8680, &pval); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x863f, (pval & 0xf8)); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x863f, 0x05); + if (ret) + return ret; + + ret = regmap_read(ctx->regmap, 0x8530, &pval); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x8530, ((pval & 0xf8) | 0x11)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_input_sel_seq, + ARRAY_SIZE(lt9211c_rx_input_sel_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_dig_seq, + ARRAY_SIZE(lt9211c_rx_dig_seq)); + if (ret) + return ret; + + /* Give the chip time to lock onto RX stream. */ + msleep(100); + + return 0; +} + +static int lt9211c_autodetect_rx(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + u16 width, height; + u8 buf[5]; + u8 format; + u8 sot[8]; + int ret; + + /* Read the SOT from the chip. */ + ret = regmap_bulk_read(ctx->regmap, 0xd088, sot, sizeof(sot)); + if (ret) + return ret; + + dev_dbg(ctx->dev, "Sot Num = 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", sot[0], + sot[2], sot[4], sot[6]); + + dev_dbg(ctx->dev, "Sot Data = 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", sot[1], + sot[3], sot[5], sot[7]); + /* HS Settle Set */ + if ((sot[0] > 0x10) && (sot[0] < 0x50)) + regmap_write(ctx->regmap, 0xd002, sot[0] - 5); + else + regmap_write(ctx->regmap, 0xd002, 0x08); + + /* Width/Height/Format Auto-detection */ + ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf)); + if (ret) + return ret; + + width = (buf[0] << 8) | buf[1]; + height = (buf[3] << 8) | buf[4]; + format = buf[2] & 0xf; + + if (format == 0x3) { /* YUV422 16bit */ + width /= 2; + } else if (format == 0xa) { /* RGB888 24bit */ + width /= 3; + } else { + dev_err(ctx->dev, "Unsupported DSI format 0x%01x\n", format); + return -EINVAL; + } + + if (width != mode->hdisplay) { + dev_err(ctx->dev, + "RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n", + width, mode->hdisplay); + return -EINVAL; + } + + if (height != mode->vdisplay) { + dev_err(ctx->dev, + "RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n", + height, mode->vdisplay); + return -EINVAL; + } + + dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x\n", width, height, format); + return 0; +} + +static int lt9211c_configure_timing(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + const struct reg_sequence lt9211c_timing[] = { + { 0xd00d, (mode->vtotal >> 8) & 0xff }, + { 0xd00e, mode->vtotal & 0xff }, + { 0xd00f, (mode->vdisplay >> 8) & 0xff }, + { 0xd010, mode->vdisplay & 0xff }, + { 0xd011, (mode->htotal >> 8) & 0xff }, + { 0xd012, mode->htotal & 0xff }, + { 0xd013, (mode->hdisplay >> 8) & 0xff }, + { 0xd014, mode->hdisplay & 0xff }, + { 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff }, + { 0xd04c, ((mode->hsync_end - mode->hsync_start) >> 8) & 0xff }, + { 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff }, + { 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff }, + { 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff }, + { 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff }, + { 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff }, + }; + + return regmap_multi_reg_write(ctx->regmap, lt9211c_timing, + ARRAY_SIZE(lt9211c_timing)); +} + +static int lt9211c_configure_plls(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + const struct reg_sequence lt9211c_dessc_pll_reset[] = { + { 0x8103, 0xfe, 2000 }, + { 0x8103, 0xff, 0 }, + }; + + const struct reg_sequence lt9211c_pcr_cali_seq[] = { + { 0xd00a, 0x5f }, + { 0xd01e, 0x51 }, + { 0xd023, 0x80 }, + { 0xd024, 0x70 }, + { 0xd025, 0x80 }, + { 0xd02a, 0x10 }, + { 0xd021, 0x4f }, + { 0xd022, 0xf0 }, + { 0xd038, 0x04 }, + { 0xd039, 0x08 }, + { 0xd03a, 0x10 }, + { 0xd03b, 0x20 }, + { 0xd03f, 0x04 }, + { 0xd040, 0x08 }, + { 0xd041, 0x10 }, + { 0xd042, 0x20 }, + { 0xd02b, 0xA0 }, + }; + + const struct reg_sequence lt9211c_pcr_reset_seq[] = { + { 0xd009, 0xdb }, + { 0xd009, 0xdf }, + { 0xd008, 0x80 }, + { 0xd008, 0x00 }, + }; + + unsigned int pval; + int ret; + u8 div; + u32 pcr_m; + u32 pcr_k; + u32 pcr_up; + u32 pcr_down; + + /* DeSSC PLL reference clock is 25 MHz XTal. */ + ret = regmap_write(ctx->regmap, 0x8226, 0x20); + if (ret) + return ret; + + /* Prediv = 0 */ + ret = regmap_write(ctx->regmap, 0x8227, 0x40); + if (ret) + return ret; + + if (mode->clock < 22000) { + ret = regmap_write(ctx->regmap, 0x822f, 0x07); + ret |= regmap_write(ctx->regmap, 0x822c, 0x01); + div = 16; + } else if (mode->clock < 44000) { + ret = regmap_write(ctx->regmap, 0x822f, 0x07); + div = 16; + } else if (mode->clock < 88000) { + ret = regmap_write(ctx->regmap, 0x822f, 0x06); + div = 8; + } else if (mode->clock < 176000) { + ret = regmap_write(ctx->regmap, 0x822f, 0x05); + div = 4; + } else { + ret = regmap_write(ctx->regmap, 0x822f, 0x04); + div = 2; + } + + if (ret) + return ret; + + pcr_m = (mode->clock * div) / 25; + pcr_k = pcr_m % 1000; + pcr_m /= 1000; + + pcr_up = pcr_m + 1; + pcr_down = pcr_m - 1; + + pcr_k <<= 14; + + ret = regmap_write(ctx->regmap, 0xd008, 0x00); + if (ret < 0) + return ret; + + /* 0xd026: pcr_m */ + ret = regmap_write(ctx->regmap, 0xd026, (0x80 | (u8)pcr_m) & 0x7f); + if (ret < 0) + return ret; + + /* 0xd027 0xd028 0xd029: pcr_k */ + ret = regmap_write(ctx->regmap, 0xd027, (pcr_k >> 16) & 0xff); + if (ret < 0) + return ret; + + ret = regmap_write(ctx->regmap, 0xd028, (pcr_k >> 8) & 0xff); + if (ret < 0) + return ret; + + ret = regmap_write(ctx->regmap, 0xd029, pcr_k & 0xff); + if (ret < 0) + return ret; + + /* 0xd02d: pcr_m overflow limit setting */ + ret = regmap_write(ctx->regmap, 0xd02d, pcr_up); + if (ret < 0) + return ret; + + /* 0xd031: pcr_m underflow limit setting */ + ret = regmap_write(ctx->regmap, 0xd031, pcr_down); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_dessc_pll_reset, + ARRAY_SIZE(lt9211c_dessc_pll_reset)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_pcr_cali_seq, + ARRAY_SIZE(lt9211c_pcr_cali_seq)); + if (ret) + return ret; + + if (mode->clock < 44000) { + ret = regmap_write(ctx->regmap, 0xd00c, 0x60); + ret |= regmap_write(ctx->regmap, 0xd01b, 0x00); + ret |= regmap_write(ctx->regmap, 0xd01c, 0x60); + } else { + ret = regmap_write(ctx->regmap, 0xd00c, 0x40); + ret |= regmap_write(ctx->regmap, 0xd01b, 0x00); + ret |= regmap_write(ctx->regmap, 0xd01c, 0x40); + } + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_pcr_reset_seq, + ARRAY_SIZE(lt9211c_pcr_reset_seq)); + if (ret) + return ret; + + /* PCR stability test takes seconds. */ + ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval, + ((pval & 0x18) == 0x18), 20000, 3000000); + if (ret) + dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret); + + ret = regmap_write(ctx->regmap, 0x8180, 0x51); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x863f, 0x00); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x863f, 0x01); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(ctx->regmap, 0x8640, pval, + ((pval & 0x01) == 0x01), 50000, 250000); + if (ret) + dev_err(ctx->dev, "Video check not stable, ret=%i\n", ret); + + return ret; +} + +static int lt9211c_configure_tx(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + const struct reg_sequence lt9211c_tx_phy_off_seq[] = { + { 0x8236, 0x00 }, + { 0x8237, 0x00 }, + { 0x8108, 0x6f }, + { 0x8103, 0xbf }, + }; + + const struct reg_sequence lt9211c_tx_phy_seq[] = { + { 0x8236, 0x03 }, + { 0x8237, 0x44 }, + { 0x8238, 0x14 }, + { 0x8239, 0x31 }, + { 0x823a, 0xc8 }, + { 0x823b, 0x00 }, + { 0x823c, 0x0f }, + { 0x8246, 0x40 }, + { 0x8247, 0x40 }, + { 0x8248, 0x40 }, + { 0x8249, 0x40 }, + { 0x824a, 0x40 }, + { 0x824b, 0x40 }, + { 0x824c, 0x40 }, + { 0x824d, 0x40 }, + { 0x824e, 0x40 }, + { 0x824f, 0x40 }, + { 0x8250, 0x40 }, + { 0x8251, 0x40 }, + }; + + const struct reg_sequence lt9211c_tx_mltx_reset[] = { + { 0x8103, 0xbf }, + { 0x8103, 0xff }, + }; + + const struct reg_sequence lt9211c_tx_dig_seq[] = { + { 0x854a, 0x01 }, + { 0x854b, 0x00 }, + { 0x854c, 0x10 }, + { 0x854d, 0x20 }, + { 0x854e, 0x50 }, + { 0x854f, 0x30 }, + { 0x8550, 0x46 }, + { 0x8551, 0x10 }, + { 0x8552, 0x20 }, + { 0x8553, 0x50 }, + { 0x8554, 0x30 }, + { 0x8555, 0x00 }, + { 0x8556, 0x20 }, + + { 0x8568, 0x00 }, + { 0x856e, 0x10 | (ctx->de ? BIT(6) : 0) }, + { 0x856f, 0x81 | (ctx->jeida ? BIT(6) : 0) | + (ctx->lvds_dual_link ? BIT(4) : 0) | + (ctx->bpp24 ? BIT(2) : 0) }, + }; + + const struct reg_sequence lt9211c_tx_ssc_seq[] = { + { 0x8234, 0x00 }, + { 0x856e, 0x10 }, + { 0x8181, 0x15 }, + { 0x871e, 0x00 }, + { 0x8717, 0x02 }, + { 0x8718, 0x04 }, + { 0x8719, 0xd4 }, + { 0x871A, 0x00 }, + { 0x871B, 0x12 }, + { 0x871C, 0x00 }, + { 0x871D, 0x24 }, + { 0x871F, 0x1c }, + { 0x8720, 0x00 }, + { 0x8721, 0x00 }, + { 0x871e, 0x02 }, + }; + + const struct reg_sequence lt9211c_tx_pll_reset_seq[] = { + { 0x810c, 0xfe, 2000 }, + { 0x810c, 0xff, 0 }, + }; + + const struct reg_sequence lt9211c_tx_sw_reset_seq[] = { + { 0x8108, 0x6f, 2000 }, + { 0x8108, 0x7f, 0 }, + }; + + unsigned int pval; + int ret; + u32 phy_clk; + u8 pixclk_div; + u8 pre_div; + u8 div_set; + u8 sericlk_div; + u8 val; + + dev_info(ctx->dev, + "dual_link=%d,even_odd_swap=%d,bpp24=%d,jeida=%d,de=%d\n", + ctx->lvds_dual_link, ctx->lvds_dual_link_even_odd_swap, + ctx->bpp24, ctx->jeida, ctx->de); + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_phy_off_seq, + ARRAY_SIZE(lt9211c_tx_phy_off_seq)); + if (ret) + return ret; + + ret = regmap_read(ctx->regmap, 0x8530, &pval); + if (ret) + return ret; + + ret = regmap_write(ctx->regmap, 0x8530, ((pval & 0x3f) | 0x40)); + if (ret) + return ret; + + /* [7]0:txpll normal work; txpll ref clk sel pix clk */ + ret = regmap_write(ctx->regmap, 0x8230, 0x00); + if (ret) + return ret; + + if (ctx->lvds_dual_link) + phy_clk = (u32)(mode->clock * 7 / 2); + else + phy_clk = (u32)(mode->clock * 7); + + /* 0x8231: prediv sel */ + if (mode->clock < 20000) { + val = 0x28; + pre_div = 1; + } else if (mode->clock < 40000) { + val = 0x28; + pre_div = 1; + } else if (mode->clock < 80000) { + val = 0x29; + pre_div = 2; + } else if (mode->clock < 160000) { + val = 0x2a; + pre_div = 4; + } else if (mode->clock < 320000) { + val = 0x2b; + pre_div = 8; + } else { + val = 0x2f; + pre_div = 16; + } + ret = regmap_write(ctx->regmap, 0x8231, val); + if (ret < 0) + return ret; + + /* 0x8232: serickdiv sel */ + if (phy_clk < 80000) { + val = 0x32; + sericlk_div = 16; + } else if (phy_clk < 160000) { + val = 0x22; + sericlk_div = 8; + } else if (phy_clk < 320000) { + val = 0x12; + sericlk_div = 4; + } else if (phy_clk < 640000) { + val = 0x02; + sericlk_div = 2; + } else { + val = 0x42; + sericlk_div = 1; + } + ret = regmap_write(ctx->regmap, 0x8232, val); + if (ret < 0) + return ret; + + /* 0x8233: pix_mux sel & pix_div sel + * To avoid floating point operations, The pixclk_div is enlarged by 10 times + */ + if (mode->clock > 150000) { + val = 0x04; + pixclk_div = 35; + } else { + pixclk_div = + (u8)((phy_clk * sericlk_div * 10) / (mode->clock * 7)); + if (pixclk_div <= 10) + val = 0x00; + else if (pixclk_div <= 20) + val = 0x01; + else if (pixclk_div <= 40) + val = 0x02; + else + val = 0x03; + } + ret = regmap_write(ctx->regmap, 0x8233, val); + if (ret < 0) + return ret; + + ret = regmap_write(ctx->regmap, 0x8234, 0x01); + if (ret < 0) + return ret; + + /* 0x8235: div set */ + div_set = (u8)(phy_clk * sericlk_div / mode->clock / pre_div); + ret = regmap_write(ctx->regmap, 0x8235, div_set); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_ssc_seq, + ARRAY_SIZE(lt9211c_tx_ssc_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_pll_reset_seq, + ARRAY_SIZE(lt9211c_tx_pll_reset_seq)); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(ctx->regmap, 0x8739, pval, pval & 0x04, + 10000, 1000000); + if (ret) { + dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret); + return ret; + } + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_phy_seq, + ARRAY_SIZE(lt9211c_tx_phy_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_mltx_reset, + ARRAY_SIZE(lt9211c_tx_mltx_reset)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_dig_seq, + ARRAY_SIZE(lt9211c_tx_dig_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_sw_reset_seq, + ARRAY_SIZE(lt9211c_tx_sw_reset_seq)); + if (ret) + return ret; + + return 0; +} + +static void lt9211_delayed_work_func(struct work_struct *work) +{ + struct delayed_work *dw = to_delayed_work(work); + struct lt9211 *ctx = container_of(dw, struct lt9211, lt9211_dw); + int ret; + const struct drm_display_mode *mode = &ctx->mode; + + /* For LT9211C */ + if (ctx->chip_type != LT9211C) { + dev_err(ctx->dev, "LT9211: Delayed work called for non-LT9211C chip\n"); + return; + } + ret = lt9211c_configure_rx(ctx); + if (ret) + return; + + ret = lt9211c_autodetect_rx(ctx, mode); + if (ret) + return; + + ret = lt9211c_configure_timing(ctx, mode); + if (ret) + return; + + ret = lt9211c_configure_plls(ctx, mode); + if (ret) + return; + + ret = lt9211c_configure_tx(ctx, mode); + if (ret) + return; + +} + static const struct i2c_device_id lt9211_id[] = { { "lontium,lt9211" }, + { "lontium,lt9211c" }, {}, }; MODULE_DEVICE_TABLE(i2c, lt9211_id); static const struct of_device_id lt9211_match_table[] = { { .compatible = "lontium,lt9211" }, + { .compatible = "lontium,lt9211c" }, {}, }; MODULE_DEVICE_TABLE(of, lt9211_match_table); From 688ed4dc318a8fe99882f1bf2e326c34e8fa968c Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 26 Nov 2025 17:01:18 +0530 Subject: [PATCH 113/188] FROMLIST: drm/bridge: lt9611uxc: extend mode valid checks Currently valid mode checks are only for hdisplay and vdisplay, add htotal and vtotal to filter only specific modes. Link:https://lore.kernel.org/lkml/20251126-lt9611uxc-modes-v2-1-34bf9b351921@oss.qualcomm.com/ Signed-off-by: Nilesh Laad Signed-off-by: Mahadevan P --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 2e59318ccf2ba..89786af7cdcc7 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -87,7 +87,9 @@ static const struct regmap_config lt9611uxc_regmap_config = { struct lt9611uxc_mode { u16 hdisplay; + u16 htotal; u16 vdisplay; + u16 vtotal; u8 vrefresh; }; @@ -96,22 +98,22 @@ struct lt9611uxc_mode { * Enumerate them here to check whether the mode is supported. */ static struct lt9611uxc_mode lt9611uxc_modes[] = { - { 1920, 1080, 60 }, - { 1920, 1080, 30 }, - { 1920, 1080, 25 }, - { 1366, 768, 60 }, - { 1360, 768, 60 }, - { 1280, 1024, 60 }, - { 1280, 800, 60 }, - { 1280, 720, 60 }, - { 1280, 720, 50 }, - { 1280, 720, 30 }, - { 1152, 864, 60 }, - { 1024, 768, 60 }, - { 800, 600, 60 }, - { 720, 576, 50 }, - { 720, 480, 60 }, - { 640, 480, 60 }, + { 1920, 2200, 1080, 1125, 60 }, + { 1920, 2200, 1080, 1125, 30 }, + { 1920, 2640, 1080, 1125, 25 }, + { 1366, 1792, 768, 798, 60 }, + { 1360, 1792, 768, 795, 60 }, + { 1280, 1688, 1024, 1066, 60 }, + { 1280, 1680, 800, 831, 60 }, + { 1280, 1650, 720, 750, 60 }, + { 1280, 1980, 720, 750, 50 }, + { 1280, 3300, 720, 750, 30 }, + { 1152, 1600, 864, 900, 60 }, + { 1024, 1344, 768, 806, 60 }, + { 800, 1056, 600, 628, 60 }, + { 720, 864, 576, 625, 50 }, + { 720, 858, 480, 525, 60 }, + { 640, 800, 480, 525, 60 }, }; static struct lt9611uxc *bridge_to_lt9611uxc(struct drm_bridge *bridge) @@ -238,7 +240,9 @@ static struct lt9611uxc_mode *lt9611uxc_find_mode(const struct drm_display_mode for (i = 0; i < ARRAY_SIZE(lt9611uxc_modes); i++) { if (lt9611uxc_modes[i].hdisplay == mode->hdisplay && + lt9611uxc_modes[i].htotal == mode->htotal && lt9611uxc_modes[i].vdisplay == mode->vdisplay && + lt9611uxc_modes[i].vtotal == mode->vtotal && lt9611uxc_modes[i].vrefresh == drm_mode_vrefresh(mode)) { return <9611uxc_modes[i]; } From 3e6639e24b124b3d1df7a66921067711479d42a9 Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 26 Nov 2025 18:52:37 +0530 Subject: [PATCH 114/188] FROMLIST: drm/bridge: lt9611uxc: add support for 4K@30 resolution Add 3840x2160@30 mode in lt9611uxc modes to add support for 4K@30 resolution. Link:https://lore.kernel.org/r/20251126-lt9611uxc-4k30-v2-1-3de0ea58c24e@oss.qualcomm.com Signed-off-by: Nilesh Laad Signed-off-by: Mahadevan P --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 89786af7cdcc7..9bec7cf96e407 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -98,6 +98,7 @@ struct lt9611uxc_mode { * Enumerate them here to check whether the mode is supported. */ static struct lt9611uxc_mode lt9611uxc_modes[] = { + { 3840, 4400, 2160, 2250, 30 }, { 1920, 2200, 1080, 1125, 60 }, { 1920, 2200, 1080, 1125, 30 }, { 1920, 2640, 1080, 1125, 25 }, From e625381c6e9332c086e0be6158360f0d3b4d3423 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:23 +0800 Subject: [PATCH 115/188] FROMLIST: drm/msm/dp: remove cached drm_edid from panel The cached drm_edid seems unnecessary here. Use the drm_edid pointer directly in the plug stage instead of caching it. Remove the cached drm_edid and the corresponding oneliner to simplify the code. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-1-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++----- drivers/gpu/drm/msm/dp/dp_panel.c | 57 +++-------------------------- drivers/gpu/drm/msm/dp/dp_panel.h | 13 ++----- 3 files changed, 27 insertions(+), 71 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dc6f33809ca5f..bce165bac106b 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -269,6 +269,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) const struct drm_display_info *info = &connector->display_info; int rc = 0; u8 dpcd[DP_RECEIVER_CAP_SIZE]; + const struct drm_edid *drm_edid = NULL; rc = drm_dp_read_dpcd_caps(dp->aux, dpcd); if (rc) @@ -276,10 +277,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd); - rc = msm_dp_panel_read_sink_caps(dp->panel, connector); + rc = msm_dp_panel_read_link_caps(dp->panel, connector); if (rc) goto end; + drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid) { + DRM_ERROR("panel edid read failed\n"); + /* check edid read fail is due to unplug */ + if (!msm_dp_aux_is_link_connected(dp->aux)) + return -ETIMEDOUT; + } + msm_dp_link_process_request(dp->link); if (!dp->msm_dp_display.is_edp) @@ -291,7 +302,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->msm_dp_display.psr_supported = dp->panel->psr_cap.version && psr_enabled; dp->audio_supported = info->has_audio; - msm_dp_panel_handle_sink_request(dp->panel); + msm_dp_panel_handle_sink_request(dp->panel, drm_edid); /* * set sink to normal operation mode -- D0 @@ -302,6 +313,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) msm_dp_link_reset_phy_params_vx_px(dp->link); end: + drm_edid_free(drm_edid); return rc; } @@ -453,7 +465,7 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) /* Don't forget modes for eDP */ if (!dp->msm_dp_display.is_edp) - msm_dp_panel_unplugged(dp->panel, dp->msm_dp_display.connector); + drm_edid_connector_update(dp->msm_dp_display.connector, NULL); /* triggered by irq_hdp with sink_count = 0 */ if (dp->link->sink_count == 0) @@ -515,7 +527,6 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); - msm_dp_panel_put(dp->panel); msm_dp_aux_put(dp->aux); } @@ -566,7 +577,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); dp->ctrl = NULL; - goto error_ctrl; + goto error_link; } dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base); @@ -574,13 +585,11 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->audio); pr_err("failed to initialize audio, rc = %d\n", rc); dp->audio = NULL; - goto error_ctrl; + goto error_link; } return rc; -error_ctrl: - msm_dp_panel_put(dp->panel); error_link: msm_dp_aux_put(dp->aux); error: @@ -744,8 +753,7 @@ int msm_dp_display_get_modes(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - return msm_dp_panel_get_modes(msm_dp_display->panel, - dp->connector); + return drm_edid_connector_add_modes(msm_dp_display->panel->connector); } bool msm_dp_display_check_video_test(struct msm_dp *dp) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 6bb021820d7c5..bde4a772d22c0 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -232,8 +232,8 @@ static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel, return min_supported_bpp; } -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) { int rc, bw_code; int count; @@ -271,36 +271,9 @@ int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd, msm_dp_panel->downstream_ports); - if (rc) - return rc; - - drm_edid_free(msm_dp_panel->drm_edid); - - msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc); - - drm_edid_connector_update(connector, msm_dp_panel->drm_edid); - - if (!msm_dp_panel->drm_edid) { - DRM_ERROR("panel edid read failed\n"); - /* check edid read fail is due to unplug */ - if (!msm_dp_aux_is_link_connected(panel->aux)) { - rc = -ETIMEDOUT; - goto end; - } - } - -end: return rc; } -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - drm_edid_connector_update(connector, NULL); - drm_edid_free(msm_dp_panel->drm_edid); - msm_dp_panel->drm_edid = NULL; -} - u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_edid_bpp, u32 mode_pclk_khz) { @@ -324,20 +297,6 @@ u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, return bpp; } -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - if (!msm_dp_panel) { - DRM_ERROR("invalid input\n"); - return -EINVAL; - } - - if (msm_dp_panel->drm_edid) - return drm_edid_connector_add_modes(connector); - - return 0; -} - static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) { edid += edid->extensions; @@ -345,7 +304,8 @@ static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) return edid->checksum; } -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid) { struct msm_dp_panel_private *panel; @@ -358,7 +318,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { /* FIXME: get rid of drm_edid_raw() */ - const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid); + const struct edid *edid = drm_edid_raw(drm_edid); u8 checksum; if (edid) @@ -755,10 +715,3 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux return msm_dp_panel; } -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel) -{ - if (!msm_dp_panel) - return; - - drm_edid_free(msm_dp_panel->drm_edid); -} diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 9173e90a50530..53b7b4463551f 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -33,7 +33,6 @@ struct msm_dp_panel { u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; struct msm_dp_link_info link_info; - const struct drm_edid *drm_edid; struct drm_connector *connector; struct msm_dp_display_mode msm_dp_mode; struct msm_dp_panel_psr psr_cap; @@ -47,15 +46,12 @@ struct msm_dp_panel { int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp, u32 mode_pclk_khz); -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel); +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid); void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable); void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel); @@ -94,5 +90,4 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux struct msm_dp_link *link, void __iomem *link_base, void __iomem *p0_base); -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel); #endif /* _DP_PANEL_H_ */ From ffc67e39eef6e14d201514e6bfe3b0922f1efaf0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:24 +0800 Subject: [PATCH 116/188] FROMLIST: drm/msm/dp: drop deprecated .mode_set() and use .atomic_enable The bridge .mode_set() callback is deprecated. Remove it and move all mode setup logic to .atomic_enable(), where the adjusted_mode is available from the atomic CRTC state. Drop msm_dp_mode from msm_dp_display_private and store the mode directly in the panel, as it was only used as a temporary cache. Both changes are limited to msm_dp_display_set_mode and are kept in a single patch. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-2-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 81 +++++++++++------------------ drivers/gpu/drm/msm/dp/dp_drm.c | 2 - drivers/gpu/drm/msm/dp/dp_drm.h | 3 -- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index bce165bac106b..57eafd4724627 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -63,7 +63,6 @@ struct msm_dp_display_private { struct msm_dp_panel *panel; struct msm_dp_ctrl *ctrl; - struct msm_dp_display_mode msm_dp_mode; struct msm_dp msm_dp_display; /* wait for audio signaling */ @@ -597,16 +596,33 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) } static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, - struct msm_dp_display_mode *mode) + const struct drm_display_mode *adjusted_mode, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp_display_private *dp; + u32 bpp; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode); - dp->panel->msm_dp_mode.bpp = mode->bpp; - dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420; - msm_dp_panel_init_panel_info(dp->panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + if (msm_dp_display_check_video_test(msm_dp_display)) + bpp = msm_dp_display_get_test_bpp(msm_dp_display); + else + bpp = msm_dp_panel->connector->display_info.bpc * 3; + + msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + msm_dp_panel_init_panel_info(msm_dp_panel); + + /* populate wide_bus_support to different layers */ + dp->ctrl->wide_bus_en = + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported; return 0; } @@ -1309,7 +1325,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->msm_dp_mode.out_fmt_is_yuv_420) + if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420) return false; return dp->wide_bus_supported; @@ -1365,15 +1381,19 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; int rc = 0; struct msm_dp_display_private *msm_dp_display; bool force_link_train = false; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - if (!msm_dp_display->msm_dp_mode.drm_mode.clock) { - DRM_ERROR("invalid params\n"); + + crtc = drm_atomic_get_new_crtc_for_encoder(state, + drm_bridge->encoder); + if (!crtc) return; - } + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); if (dp->is_edp) msm_dp_hpd_plug_handle(msm_dp_display); @@ -1386,7 +1406,7 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, if (msm_dp_display->link->sink_count == 0) return; - rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode); + rc = msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); if (rc) { DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); return; @@ -1446,45 +1466,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, pm_runtime_put_sync(&dp->pdev->dev); } -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *msm_dp_display; - struct msm_dp_panel *msm_dp_panel; - - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - msm_dp_panel = msm_dp_display->panel; - - memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode)); - - if (msm_dp_display_check_video_test(dp)) - msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp); - else /* Default num_components per pixel = 3 */ - msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3; - - if (!msm_dp_display->msm_dp_mode.bpp) - msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */ - - drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode); - - msm_dp_display->msm_dp_mode.v_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC); - - msm_dp_display->msm_dp_mode.h_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC); - - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - - /* populate wide_bus_support to different layers */ - msm_dp_display->ctrl->wide_bus_en = - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? false : msm_dp_display->wide_bus_supported; -} - void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index b659d22f5f284..6ac5bac903d98 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -56,7 +56,6 @@ static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_enable = msm_dp_bridge_atomic_enable, .atomic_disable = msm_dp_bridge_atomic_disable, .atomic_post_disable = msm_dp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_dp_bridge_mode_valid, .get_modes = msm_dp_bridge_get_modes, .detect = msm_dp_bridge_detect, @@ -233,7 +232,6 @@ static const struct drm_bridge_funcs msm_edp_bridge_ops = { .atomic_enable = msm_edp_bridge_atomic_enable, .atomic_disable = msm_edp_bridge_atomic_disable, .atomic_post_disable = msm_edp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_edp_bridge_mode_valid, .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 041aa026ae2ee..4bd788ea05d5b 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -36,9 +36,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, const struct drm_display_mode *mode); -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From 50f9103a944db3f5b9c850853725c11222adaeb0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:25 +0800 Subject: [PATCH 117/188] FROMLIST: drm/msm/dp: move mode setup into msm_dp_panel_init_panel_info() The display layer directly assigns msm_dp_panel mode fields (bpp, sync polarity, yuv420 flag) instead of letting the panel manage its own state. Pass adjusted_mode and bpp as parameters to msm_dp_panel_init_panel_info() and move the assignments inside it. Suggested-by: Dmitry Baryshkov Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-3-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 11 +---------- drivers/gpu/drm/msm/dp/dp_panel.c | 18 +++++++++++++++--- drivers/gpu/drm/msm/dp/dp_panel.h | 4 +++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 57eafd4724627..155beefdfdef2 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -604,21 +604,12 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); if (msm_dp_display_check_video_test(msm_dp_display)) bpp = msm_dp_display_get_test_bpp(msm_dp_display); else bpp = msm_dp_panel->connector->display_info.bpc * 3; - msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ - msm_dp_panel->msm_dp_mode.v_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); - msm_dp_panel->msm_dp_mode.h_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); - msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - msm_dp_panel_init_panel_info(msm_dp_panel); + msm_dp_panel_init_panel_info(msm_dp_panel, adjusted_mode, bpp ? bpp : 24); /* populate wide_bus_support to different layers */ dp->ctrl->wide_bus_en = diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index bde4a772d22c0..e76dad0f66637 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -647,15 +647,27 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) return 0; } -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel) +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp) { struct drm_display_mode *drm_mode; struct msm_dp_panel_private *panel; - drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; - panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + msm_dp_panel->msm_dp_mode.bpp = bpp; + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + + drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; + /* * print resolution info as this is a result * of user initiated action of cable connection diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 53b7b4463551f..4519ac3742206 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -43,7 +43,9 @@ struct msm_dp_panel { u32 max_bw_code; }; -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, From a82760ff6022a89e35425c6685200346fdba52f6 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:26 +0800 Subject: [PATCH 118/188] FROMLIST: drm/msm/dp: split msm_dp_ctrl_config_ctrl() into link parts and stream parts The DP_CONFIGURATION_CTRL register contains both link-level and stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures all of them together. Separate the configuration into link parts and stream parts to support MST. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-4-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 46 +++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 86ef8c89ad443..ed2ba47881fd0 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -388,26 +388,44 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 config = 0, tbd; + + /* + * RMW: Called from atomic_enable(). Serialized by the DRM atomic framework. + */ + config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); + + if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) + config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ + + tbd = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); + + config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; + + if (msm_dp_panel->psr_cap.version) + config |= DP_CONFIGURATION_CTRL_SEND_VSC; + + drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); + + msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); +} + +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +{ + u32 config = 0; const u8 *dpcd = ctrl->panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ - /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; - tbd = msm_dp_link_get_test_bits_depth(ctrl->link, - ctrl->panel->msm_dp_mode.bpp); - - config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; - /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); @@ -421,10 +439,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; - if (ctrl->panel->psr_cap.version) - config |= DP_CONFIGURATION_CTRL_SEND_VSC; - - drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config); + drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } @@ -450,7 +465,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); @@ -1628,7 +1644,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; From e246b66406f976f1108733cfa1f0af7d563e7fc8 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:27 +0800 Subject: [PATCH 119/188] FROMLIST: drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Refactor the MISC1_MISC0 register configuration into a standalone helper function to support MST. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-5-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index ed2ba47881fd0..71d45b2c4daf8 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -458,17 +458,13 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl) ln_mapping); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 colorimetry_cfg, test_bits_depth, misc_val; - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); - - test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); + test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); @@ -482,6 +478,17 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); +} + +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +{ + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + + msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); } From cf8123ffc654b823710b1e959206a45b523cd04c Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:28 +0800 Subject: [PATCH 120/188] FROMLIST: drm/msm/dp: split link setup from source params msm_dp_ctrl_configure_source_params() should only handle stream-related configuration. Move the link setup out of it so MST can program link and stream settings separately. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-6-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 71d45b2c4daf8..1c2eccec6ec6d 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -482,10 +482,6 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) { - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); @@ -2551,6 +2547,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train */ reinit_completion(&ctrl->video_comp); + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_configure_source_params(ctrl); msm_dp_ctrl_config_msa(ctrl, From f4a062d970ba5bbca1dcbd2f5c19c2ede4c9b54e Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:29 +0800 Subject: [PATCH 121/188] FROMLIST: drm/msm/dp: move the pixel clock control to its own API Enable/Disable of DP pixel clock happens in multiple code paths leading to code duplication. Move it into individual helpers so that the helpers can be called wherever necessary. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-7-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 80 ++++++++++++++++---------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 1c2eccec6ec6d..a2c44088e6a68 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2171,6 +2171,41 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) return success; } +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate) +{ + int ret; + + ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + return ret; + } + + if (WARN_ON_ONCE(ctrl->stream_clks_on)) + return 0; + + ret = clk_prepare_enable(ctrl->pixel_clk); + if (ret) { + DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + return ret; + } + ctrl->stream_clks_on = true; + + return ret; +} + +static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (ctrl->stream_clks_on) { + clk_disable_unprepare(ctrl->pixel_clk); + ctrl->stream_clks_on = false; + } +} + static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) { int ret; @@ -2196,22 +2231,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl } pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); - return ret; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - return ret; - } - ctrl->stream_clks_on = true; - } + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); msm_dp_ctrl_send_phy_test_pattern(ctrl); @@ -2514,26 +2534,13 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); - goto end; + return ret; } } - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); - goto end; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - goto end; - } - ctrl->stream_clks_on = true; - } + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); @@ -2572,7 +2579,6 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train drm_dbg_dp(ctrl->drm_dev, "mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); -end: return ret; } @@ -2620,11 +2626,7 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - + msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); From 735f232c7ce33842d340273e9f0ecdf0e62dd7f0 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:30 +0800 Subject: [PATCH 122/188] FROMLIST: drm/msm/dp: break up dp_display_enable into two parts dp_display_enable() currently re-trains the link if needed and then enables the pixel clock, programs the controller to start sending the pixel stream. Split these two parts into prepare/enable APIs, to support MST bridges_enable insert the MST payloads funcs between enable stream_clks and program register. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-8-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 48 ++++++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 3 +- drivers/gpu/drm/msm/dp/dp_display.c | 105 ++++++++++++++++++---------- 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index a2c44088e6a68..546d899dde237 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2504,27 +2504,19 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) { int ret = 0; - bool mainlink_ready = false; struct msm_dp_ctrl_private *ctrl; - unsigned long pixel_rate; - unsigned long pixel_rate_orig; if (!msm_dp_ctrl) return -EINVAL; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; - - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - pixel_rate >>= 1; - - drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n", - ctrl->link->link_params.rate, - ctrl->link->link_params.num_lanes, pixel_rate); + drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d\n", + ctrl->link->link_params.rate, + ctrl->link->link_params.num_lanes); drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n", @@ -2538,16 +2530,40 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train } } - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); - if (ret) - return ret; - if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); /* stop txing train pattern to end link training */ msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + return ret; +} + +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +{ + int ret = 0; + bool mainlink_ready = false; + struct msm_dp_ctrl_private *ctrl; + unsigned long pixel_rate; + unsigned long pixel_rate_orig; + + if (!msm_dp_ctrl) + return -EINVAL; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = pixel_rate_orig; + + if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + pixel_rate >>= 1; + + drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); + + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; + /* * Set up transfer unit values and set controller state to send * video. diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index f68bee62713f1..1497f1a8fc2f0 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -17,7 +17,8 @@ struct msm_dp_ctrl { struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 155beefdfdef2..2cf4c984c7ca7 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -617,7 +617,40 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, return 0; } -static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train) +static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + int rc = 0; + bool force_link_train = false; + + drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); + + if (msm_dp_display->is_edp) + msm_dp_hpd_plug_handle(dp); + + rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); + if (rc) { + DRM_ERROR("failed to pm_runtime_resume\n"); + return rc; + } + + if (dp->link->sink_count == 0) + return rc; + + if (!msm_dp_display->power_on) { + msm_dp_display_host_phy_init(dp); + force_link_train = true; + } + + rc = msm_dp_ctrl_on_link(dp->ctrl); + if (rc) + DRM_ERROR("Failed link training (rc=%d)\n", rc); + // TODO: schedule drm_connector_set_link_status_property() + + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); +} + +static int msm_dp_display_enable(struct msm_dp_display_private *dp) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -628,7 +661,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_l return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, force_link_train); + rc = msm_dp_ctrl_on_stream(dp->ctrl); if (!rc) msm_dp_display->power_on = true; @@ -658,13 +691,10 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) return 0; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; - if (!msm_dp_display->power_on) - return 0; - /* wait only if audio was enabled */ if (msm_dp_display->audio_enabled) { /* signal the disconnect event */ @@ -675,6 +705,14 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) } msm_dp_display->audio_enabled = false; +} + +static int msm_dp_display_disable(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + if (!msm_dp_display->power_on) + return 0; if (dp->link->sink_count == 0) { /* @@ -1371,14 +1409,13 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; int rc = 0; - struct msm_dp_display_private *msm_dp_display; - bool force_link_train = false; + struct msm_dp_display_private *dp; - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_bridge->encoder); @@ -1386,44 +1423,29 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - if (dp->is_edp) - msm_dp_hpd_plug_handle(msm_dp_display); - - if (pm_runtime_resume_and_get(&dp->pdev->dev)) { - DRM_ERROR("failed to pm_runtime_resume\n"); - return; - } - - if (msm_dp_display->link->sink_count == 0) - return; - - rc = msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); + rc = msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel); if (rc) { DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); return; } - if (!dp->power_on) { - msm_dp_display_host_phy_init(msm_dp_display); - force_link_train = true; - } - - rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl); + rc = msm_dp_display_prepare_link(dp); if (rc) { - DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() + DRM_ERROR("DP display prepare failed, rc=%d\n", rc); return; } - msm_dp_display_enable(msm_dp_display, force_link_train); + rc = msm_dp_display_enable(dp); + if (rc) + DRM_ERROR("DP display enable failed, rc=%d\n", rc); - rc = msm_dp_display_post_enable(dp); + rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(dp); } - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -1438,6 +1460,15 @@ void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } +static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + pm_runtime_put_sync(&msm_dp_display->pdev->dev); + + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); +} + void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { @@ -1450,11 +1481,11 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, if (dp->is_edp) msm_dp_hpd_unplug_handle(msm_dp_display); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_audio_notify_disable(msm_dp_display); - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + msm_dp_display_disable(msm_dp_display); - pm_runtime_put_sync(&dp->pdev->dev); + msm_dp_display_unprepare(msm_dp_display); } void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) From 053ce6a3544456cf2f1f22e84a3d647b693e0127 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:31 +0800 Subject: [PATCH 123/188] FROMLIST: drm/msm/dp: re-arrange dp_display_disable() into functional parts dp_display_disable() handles special case of when monitor is disconnected from the dongle while the dongle stays connected thereby needing a separate function dp_ctrl_off_link_stream() for this. However with a slight rework this can still be handled by keeping common paths same for regular and special case. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-9-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 19 +------------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +- drivers/gpu/drm/msm/dp/dp_display.c | 10 +++++++++- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 546d899dde237..626b95d4d0159 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2598,7 +2598,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) return ret; } -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2606,23 +2606,6 @@ void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); - - /* set dongle to D3 (power off) mode */ - msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true); - - msm_dp_ctrl_mainlink_disable(ctrl); - - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - - dev_pm_opp_set_rate(ctrl->dev, 0); - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); - /* aux channel down, reinit phy */ phy_exit(phy); phy_init(phy); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 1497f1a8fc2f0..5d615f50d13bf 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -19,7 +19,6 @@ struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); @@ -46,4 +45,5 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 2cf4c984c7ca7..e1d90b8539c27 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -714,12 +714,20 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) if (!msm_dp_display->power_on) return 0; + msm_dp_panel_disable_vsc_sdp(dp->panel); + + /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) { /* * irq_hpd with sink_count = 0 * hdmi unplugged out of dongle */ - msm_dp_ctrl_off_link_stream(dp->ctrl); + + /* set dongle to D3 (power off) mode */ + msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_ctrl_off(dp->ctrl); + /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt From 0ef6f77d1c87f9a1e9c1a8b494ed4e2de6655ea4 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:32 +0800 Subject: [PATCH 124/188] FROMLIST: drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it With MST, multiple sinks share a single DP controller, so a cached panel in msm_dp_ctrl_private can no longer represent the per-stream sink. Drop the cache and pass panel explicitly to all stream-related dp_ctrl APIs. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-10-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 196 ++++++++++++++++------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 28 ++-- drivers/gpu/drm/msm/dp/dp_display.c | 24 ++-- 3 files changed, 140 insertions(+), 108 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 626b95d4d0159..87c3a5517911f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -114,7 +114,6 @@ struct msm_dp_ctrl_private { struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; - struct msm_dp_panel *panel; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; @@ -202,7 +201,8 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); @@ -219,7 +219,7 @@ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) if (!ctrl->hw_revision) { ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); - ctrl->panel->hw_revision = ctrl->hw_revision; + panel->hw_revision = ctrl->hw_revision; } } @@ -414,10 +414,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } -static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 config = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); @@ -480,13 +481,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + msm_dp_ctrl_config_ctrl_streams(ctrl, panel); - msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); + msm_dp_ctrl_config_misc1_misc0(ctrl, panel); - msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); + msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); } /* @@ -1256,20 +1258,21 @@ static void _dp_ctrl_calc_tu(struct msm_dp_ctrl_private *ctrl, } static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, struct msm_dp_vc_tu_mapping_table *tu_table) { struct msm_dp_tu_calc_input in; - struct drm_display_mode *drm_mode; + const struct drm_display_mode *drm_mode; - drm_mode = &ctrl->panel->msm_dp_mode.drm_mode; + drm_mode = &panel->msm_dp_mode.drm_mode; in.lclk = ctrl->link->link_params.rate / 1000; in.pclk_khz = drm_mode->clock; in.hactive = drm_mode->hdisplay; in.hporch = drm_mode->htotal - drm_mode->hdisplay; in.nlanes = ctrl->link->link_params.num_lanes; - in.bpp = ctrl->panel->msm_dp_mode.bpp; - in.pixel_enc = ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; + in.bpp = panel->msm_dp_mode.bpp; + in.pixel_enc = panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; in.dsc_en = 0; in.async_en = 0; in.fec_en = 0; @@ -1279,14 +1282,15 @@ static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, _dp_ctrl_calc_tu(ctrl, &in, tu_table); } -static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 msm_dp_tu = 0x0; u32 valid_boundary = 0x0; u32 valid_boundary2 = 0x0; struct msm_dp_vc_tu_mapping_table tu_calc_table; - msm_dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); + msm_dp_ctrl_calc_tu_parameters(ctrl, panel, &tu_calc_table); msm_dp_tu |= tu_calc_table.tu_size_minus1; valid_boundary |= tu_calc_table.valid_boundary_link; @@ -1438,6 +1442,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1446,7 +1451,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, int const maximum_retries = 4; delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); @@ -1532,14 +1537,15 @@ static int msm_dp_ctrl_link_rate_down_shift(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { if (ctrl->link->link_params.num_lanes == 1) return -1; ctrl->link->link_params.num_lanes /= 2; - ctrl->link->link_params.rate = ctrl->panel->link_info.rate; + ctrl->link->link_params.rate = panel->link_info.rate; ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; @@ -1548,6 +1554,7 @@ static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) } static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, enum drm_dp_phy dp_phy) { int delay_us; @@ -1555,11 +1562,12 @@ static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE, dp_phy); delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); fsleep(delay_us); } static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1570,16 +1578,16 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, u8 link_status[DP_LINK_STATUS_SIZE]; delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); *training_step = DP_TRAINING_2; - if (drm_dp_tps4_supported(ctrl->panel->dpcd)) { + if (drm_dp_tps4_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_4; state_ctrl_bit = 4; - } else if (drm_dp_tps3_supported(ctrl->panel->dpcd)) { + } else if (drm_dp_tps3_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_3; state_ctrl_bit = 3; } else { @@ -1616,18 +1624,19 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int ret; - ret = msm_dp_ctrl_link_train_1(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_1(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #1 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; } drm_dbg_dp(ctrl->drm_dev, "link training #1 on phy %d successful\n", dp_phy); - ret = msm_dp_ctrl_link_train_2(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_2(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #2 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; @@ -1638,16 +1647,17 @@ static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int i; int ret = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; u8 encoding[] = { 0, DP_SET_ANSI_8B10B }; u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; @@ -1670,8 +1680,8 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, for (i = ctrl->link->lttpr_count - 1; i >= 0; i--) { enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i); - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, dp_phy); - msm_dp_ctrl_clear_training_pattern(ctrl, dp_phy); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, dp_phy); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, dp_phy); if (ret) break; @@ -1682,7 +1692,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, goto end; } - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, DP_PHY_DPRX); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, DP_PHY_DPRX); if (ret) { DRM_ERROR("link training on sink failed. ret=%d\n", ret); goto end; @@ -1695,6 +1705,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int ret = 0; @@ -1710,7 +1721,7 @@ static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, * a link training pattern, we have to first do soft reset. */ - ret = msm_dp_ctrl_link_train(ctrl, training_step); + ret = msm_dp_ctrl_link_train(ctrl, panel, training_step); return ret; } @@ -1809,11 +1820,12 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) str_on_off(ctrl->core_clks_on)); } -static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; struct phy *phy = ctrl->phy; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; ctrl->phy_opts.dp.link_rate = ctrl->link->link_params.rate / 100; @@ -1865,13 +1877,14 @@ static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl) msm_dp_write_link(ctrl, REG_PSR_CMD, cmd); } -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 cfg; - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* enable PSR1 function */ @@ -1886,12 +1899,13 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) drm_dp_dpcd_write(ctrl->aux, DP_PSR_EN_CFG, &cfg, 1); } -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter) +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enter) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* @@ -1961,7 +1975,8 @@ void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl) phy_exit(phy); } -static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy = ctrl->phy; int ret = 0; @@ -1982,7 +1997,7 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) /* hw recommended delay before re-enabling clocks */ msleep(20); - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); return ret; @@ -1991,7 +2006,8 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy; @@ -1999,7 +2015,7 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); @@ -2013,7 +2029,8 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return 0; } -static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; int training_step = DP_TRAINING_NONE; @@ -2023,11 +2040,11 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; - ret = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + ret = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (ret) goto end; - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2206,7 +2223,8 @@ static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) } } -static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret; unsigned long pixel_rate; @@ -2222,15 +2240,15 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); - ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl); + ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { DRM_ERROR("failed to enable DP link controller\n"); return ret; } - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); msm_dp_ctrl_send_phy_test_pattern(ctrl); @@ -2238,7 +2256,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl return 0; } -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 sink_request = 0x0; @@ -2253,14 +2272,14 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { drm_dbg_dp(ctrl->drm_dev, "PHY_TEST_PATTERN request\n"); - if (msm_dp_ctrl_process_phy_test_request(ctrl)) { + if (msm_dp_ctrl_process_phy_test_request(ctrl, panel)) { DRM_ERROR("process phy_test_req failed\n"); return; } } if (sink_request & DP_LINK_STATUS_UPDATED) { - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2268,7 +2287,7 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_TRAINING) { msm_dp_link_send_test_response(ctrl->link); - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2304,7 +2323,8 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl) return drm_dp_channel_eq_ok(link_status, num_lanes); } -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { int rc = 0; struct msm_dp_ctrl_private *ctrl; @@ -2320,8 +2340,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - rate = ctrl->panel->link_info.rate; - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + rate = panel->link_info.rate; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl); @@ -2333,8 +2353,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } else { ctrl->link->link_params.rate = rate; ctrl->link->link_params.num_lanes = - ctrl->panel->link_info.num_lanes; - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + panel->link_info.num_lanes; + if (panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; } @@ -2342,13 +2362,13 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes, pixel_rate); - rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (rc) return rc; while (--link_train_max_retries) { training_step = DP_TRAINING_NONE; - rc = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + rc = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (rc == 0) { /* training completed successfully */ break; @@ -2367,7 +2387,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * some lanes are ready, * reduce lane number */ - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* lane == 1 already */ /* end with failure */ break; @@ -2388,7 +2408,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.num_lanes)) rc = msm_dp_ctrl_link_rate_down_shift(ctrl); else - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* end with failure */ @@ -2396,10 +2416,10 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } /* stop link training before start re training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); } - rc = msm_dp_ctrl_reinitialize_mainlink(ctrl); + rc = msm_dp_ctrl_reinitialize_mainlink(ctrl, panel); if (rc) { DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n", rc); break; @@ -2420,20 +2440,21 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * link training failed * end txing train pattern here */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); - msm_dp_ctrl_deinitialize_mainlink(ctrl); + msm_dp_ctrl_deinitialize_mainlink(ctrl, panel); rc = -ECONNRESET; } return rc; } -static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int training_step = DP_TRAINING_NONE; - return msm_dp_ctrl_setup_main_link(ctrl, &training_step); + return msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); } static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, @@ -2504,7 +2525,9 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train) { int ret = 0; struct msm_dp_ctrl_private *ctrl; @@ -2523,7 +2546,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on); if (!ctrl->link_clks_on) { /* link clk is off */ - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); return ret; @@ -2531,15 +2554,15 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li } if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) - msm_dp_ctrl_link_retrain(ctrl); + msm_dp_ctrl_link_retrain(ctrl, panel); /* stop txing train pattern to end link training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); return ret; } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) { int ret = 0; bool mainlink_ready = false; @@ -2552,10 +2575,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock; pixel_rate = pixel_rate_orig; - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + if (msm_dp_ctrl->wide_bus_en || panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); @@ -2572,18 +2595,18 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); - msm_dp_ctrl_configure_source_params(ctrl); + msm_dp_ctrl_configure_source_params(ctrl, panel); msm_dp_ctrl_config_msa(ctrl, ctrl->link->link_params.rate, pixel_rate_orig, - ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420); + panel->msm_dp_mode.out_fmt_is_yuv_420); - msm_dp_panel_clear_dsc_dto(ctrl->panel); + msm_dp_panel_clear_dsc_dto(panel); - msm_dp_ctrl_setup_tr_unit(ctrl); + msm_dp_ctrl_setup_tr_unit(ctrl, panel); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2611,7 +2634,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2619,11 +2643,11 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); + msm_dp_panel_disable_vsc_sdp(panel); msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); @@ -2632,7 +2656,8 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) phy_power_off(phy); } -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 isr; @@ -2643,7 +2668,7 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (ctrl->panel->psr_cap.version) { + if (panel->psr_cap.version) { isr = msm_dp_ctrl_get_psr_interrupt(ctrl); if (isr) @@ -2732,7 +2757,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) } struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, + struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base) @@ -2740,7 +2765,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link struct msm_dp_ctrl_private *ctrl; int ret; - if (!dev || !panel || !aux || !link) { + if (!dev || !aux || !link) { DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL); } @@ -2768,7 +2793,6 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link init_completion(&ctrl->video_comp); /* in parameters */ - ctrl->panel = panel; ctrl->aux = aux; ctrl->link = link; ctrl->dev = dev; diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 5d615f50d13bf..00b430392a52e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -16,28 +16,36 @@ struct msm_dp_ctrl { struct phy; -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train); +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl); +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base); -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_irq_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enable); -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enable); +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index e1d90b8539c27..367d1d10af471 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -359,7 +359,7 @@ static void msm_dp_display_host_init(struct msm_dp_display_private *dp) dp->phy_initialized); msm_dp_ctrl_core_clk_enable(dp->ctrl); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_enable_irq(dp->ctrl); msm_dp_aux_init(dp->aux); dp->core_initialized = true; @@ -371,7 +371,7 @@ static void msm_dp_display_host_deinit(struct msm_dp_display_private *dp) dp->msm_dp_display.connector_type, dp->core_initialized, dp->phy_initialized); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_disable_irq(dp->ctrl); msm_dp_aux_deinit(dp->aux); msm_dp_ctrl_core_clk_disable(dp->ctrl); @@ -392,7 +392,7 @@ static int msm_dp_display_handle_irq_hpd(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "%d\n", sink_request); - msm_dp_ctrl_handle_sink_request(dp->ctrl); + msm_dp_ctrl_handle_sink_request(dp->ctrl, dp->panel); if (sink_request & DP_TEST_LINK_VIDEO_PATTERN) msm_dp_display_handle_video_request(dp); @@ -570,7 +570,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, + dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux, phy, dp->ahb_base, dp->link_base); if (IS_ERR(dp->ctrl)) { rc = PTR_ERR(dp->ctrl); @@ -642,12 +642,12 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) force_link_train = true; } - rc = msm_dp_ctrl_on_link(dp->ctrl); + rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); if (rc) DRM_ERROR("Failed link training (rc=%d)\n", rc); // TODO: schedule drm_connector_set_link_status_property() - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } static int msm_dp_display_enable(struct msm_dp_display_private *dp) @@ -661,7 +661,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl); + rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); if (!rc) msm_dp_display->power_on = true; @@ -686,7 +686,7 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) msm_dp_display_handle_plugged_change(msm_dp_display, true); if (msm_dp_display->psr_supported) - msm_dp_ctrl_config_psr(dp->ctrl); + msm_dp_ctrl_config_psr(dp->ctrl, dp->panel); return 0; } @@ -725,7 +725,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); } else { @@ -733,7 +733,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } @@ -869,7 +869,7 @@ void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) } dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_set_psr(dp->ctrl, enter); + msm_dp_ctrl_set_psr(dp->ctrl, dp->panel, enter); } /** @@ -979,7 +979,7 @@ static irqreturn_t msm_dp_display_irq_handler(int irq, void *dev_id) } /* DP controller isr */ - ret |= msm_dp_ctrl_isr(dp->ctrl); + ret |= msm_dp_ctrl_isr(dp->ctrl, dp->panel); return ret; } From 94268e93cf5b5354deb5da5216b63bcb7809d234 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:33 +0800 Subject: [PATCH 125/188] FROMLIST: drm/msm/dp: split dp_ctrl_off() into stream and link parts Split dp_ctrl_off() into stream and link parts so that for MST cases we can control the link and pixel parts separately. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-11-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 10 +++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 +++-- drivers/gpu/drm/msm/dp/dp_display.c | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 87c3a5517911f..90fba03de7f0b 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2211,7 +2211,7 @@ static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned l return ret; } -static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; @@ -2240,7 +2240,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); + msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel); ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { @@ -2634,8 +2635,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2649,7 +2650,6 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 00b430392a52e..5902cf7e746a4 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -22,8 +22,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 367d1d10af471..4f14bcee35772 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -725,15 +725,16 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl, dp->panel); - /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl, dp->panel); + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } From ad76b8b8c450768f20b88e19ef06748e7147a086 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:34 +0800 Subject: [PATCH 126/188] FROMLIST: drm/msm/dp: simplify link and clock disable sequence Move the common disable steps out of the sink_count check to make the flow easier to follow. No functional change intended. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-12-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 4f14bcee35772..f1970e6dd7e46 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -716,27 +716,19 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) msm_dp_panel_disable_vsc_sdp(dp->panel); - /* dongle is still connected but sinks are disconnected */ - if (dp->link->sink_count == 0) { - /* - * irq_hpd with sink_count = 0 - * hdmi unplugged out of dongle - */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); - /* set dongle to D3 (power off) mode */ + /* dongle is still connected but sinks are disconnected */ + if (dp->link->sink_count == 0) msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + if (dp->link->sink_count == 0) + /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); - } else { - /* - * unplugged interrupt - * dongle unplugged out of DUT - */ - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + else msm_dp_display_host_phy_exit(dp); - } msm_dp_display->power_on = false; From 610bde344bf04c0cf498c1b76879471c11d4d685 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:35 +0800 Subject: [PATCH 127/188] FROMLIST: drm/msm/dp: make bridge helpers use dp_display to allow re-use dp_bridge helpers take drm_bridge as an input and extract the dp_display object to be used in the dp_display module. Rather than doing it in a roundabout way, directly pass the dp_display object to these helpers so that the MST bridge can also re-use the same helpers. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-13-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 29 +++++++-------------- drivers/gpu/drm/msm/dp/dp_display.h | 7 ++++++ drivers/gpu/drm/msm/dp/dp_drm.c | 39 ++++++++++++++++++++++++++++- drivers/gpu/drm/msm/dp/dp_drm.h | 9 ------- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index f1970e6dd7e46..cdb7dd4cd7cbd 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -738,24 +738,21 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid - * @bridge: Pointer to drm bridge structure + * @dp: Pointer to dp display structure * @info: display info * @mode: Pointer to drm mode structure * Returns: Validity status for specified mode */ -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode) +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode) { const u32 num_components = 3, default_bpp = 24; struct msm_dp_display_private *msm_dp_display; struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; - struct msm_dp *dp; int mode_pclk_khz = mode->clock; - dp = to_dp_bridge(bridge)->msm_dp_display; - if (!dp || !mode_pclk_khz || !dp->connector) { DRM_ERROR("invalid params\n"); return -EINVAL; @@ -1406,11 +1403,9 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; int rc = 0; @@ -1419,7 +1414,7 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); crtc = drm_atomic_get_new_crtc_for_encoder(state, - drm_bridge->encoder); + msm_dp_display->bridge->encoder); if (!crtc) return; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); @@ -1449,11 +1444,8 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); @@ -1470,11 +1462,8 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_post_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 0b65e16c790da..5116f7bbbd029 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -33,5 +33,12 @@ void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display); void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display); void msm_dp_display_set_psr(struct msm_dp *dp, bool enter); void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp); +void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_enable(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 6ac5bac903d98..6b8923d9dff40 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -49,6 +49,43 @@ static void msm_dp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry msm_dp_display_debugfs_init(dp, root, false); } +static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_enable(dp, state); +} + +static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_disable(dp); +} + +static void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_post_disable(dp); +} + +static enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *drm_bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + return msm_dp_display_mode_valid(dp, info, mode); +} + static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -115,7 +152,7 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - msm_dp_bridge_atomic_enable(drm_bridge, state); + msm_dp_display_atomic_enable(dp, state); } static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 4bd788ea05d5b..da412c788503c 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,15 +27,6 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From b7bca3bf9a02fa0a2b0598a1d4dd67d92016143a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:36 +0800 Subject: [PATCH 128/188] FROMLIST: drm/msm/dp: separate dp_display_prepare() into its own API For MST, the link setup should only be done once when multiple sinks are enabled, while stream setup may run multiple times for each sink. Split the link-related preparation out of msm_dp_display_atomic_enable() so it can be called separately before the per-stream enable path. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-14-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 16 +++++++++++----- drivers/gpu/drm/msm/dp/dp_display.h | 5 +++-- drivers/gpu/drm/msm/dp/dp_drm.c | 6 ++++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index cdb7dd4cd7cbd..03320071fc077 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1403,8 +1403,8 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } -void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; @@ -1426,10 +1426,16 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, } rc = msm_dp_display_prepare_link(dp); - if (rc) { + if (rc) DRM_ERROR("DP display prepare failed, rc=%d\n", rc); - return; - } +} + +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display) +{ + struct msm_dp_display_private *dp; + int rc = 0; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); rc = msm_dp_display_enable(dp); if (rc) diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 5116f7bbbd029..43ed79093e241 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -35,8 +35,9 @@ void msm_dp_display_set_psr(struct msm_dp *dp, bool enter); void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp); void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display); void msm_dp_display_atomic_disable(struct msm_dp *dp_display); -void msm_dp_display_atomic_enable(struct msm_dp *dp_display, - struct drm_atomic_commit *state); +void msm_dp_display_atomic_prepare(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +void msm_dp_display_atomic_enable(struct msm_dp *dp_display); enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, const struct drm_display_info *info, const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 6b8923d9dff40..4bf1a5b7c3f97 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -55,7 +55,8 @@ static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); struct msm_dp *dp = dp_bridge->msm_dp_display; - msm_dp_display_atomic_enable(dp, state); + msm_dp_display_atomic_prepare(dp, state); + msm_dp_display_atomic_enable(dp); } static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -152,7 +153,8 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - msm_dp_display_atomic_enable(dp, state); + msm_dp_display_atomic_prepare(dp, state); + msm_dp_display_atomic_enable(dp); } static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge, From 7c08a99d5cbb544cee0b34bd8f83d125e4540d13 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:37 +0800 Subject: [PATCH 129/188] FROMLIST: drm/msm/dp: pass panel to display enable/disable helpers Pass struct msm_dp_panel to the display enable/disable helpers to make them easier to reuse for MST stream handling. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260609-dp_mstclean-v7-15-ea04113e8233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 03320071fc077..888ecf34f1602 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -650,7 +650,8 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } -static int msm_dp_display_enable(struct msm_dp_display_private *dp) +static int msm_dp_display_enable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -661,7 +662,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); + rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel); if (!rc) msm_dp_display->power_on = true; @@ -707,20 +708,21 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d msm_dp_display->audio_enabled = false; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static int msm_dp_display_disable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; if (!msm_dp_display->power_on) return 0; - msm_dp_panel_disable_vsc_sdp(dp->panel); + msm_dp_panel_disable_vsc_sdp(msm_dp_panel); msm_dp_ctrl_off_pixel_clk(dp->ctrl); /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) - msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true); msm_dp_ctrl_off_link(dp->ctrl, dp->panel); @@ -1437,14 +1439,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - rc = msm_dp_display_enable(dp); + rc = msm_dp_display_enable(dp, dp->panel); if (rc) DRM_ERROR("DP display enable failed, rc=%d\n", rc); rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp); + msm_dp_display_disable(dp, dp->panel); } drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); @@ -1479,7 +1481,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp) msm_dp_display_audio_notify_disable(msm_dp_display); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(msm_dp_display, msm_dp_display->panel); msm_dp_display_unprepare(msm_dp_display); } From 68c24499c58d09fd94ca8454d8a14fcb45763f4e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:22 +0800 Subject: [PATCH 130/188] FROMLIST: drm/msm/dp: introduce stream_id for each DP panel With MST, each DP controller can handle multiple streams. There shall be one dp_panel for each stream but the dp_display object shall be shared among them. To represent this abstraction, create a stream_id for each DP panel which shall be dynamically assigned to actual stream IDs by the MST path. For SST, default this to stream 0. In the MST path, panels are dynamically assigned to actual stream IDs at stream enable time by the MST layer. Use the stream ID to control the pixel clock of that respective stream by extending the clock handles and state tracking of the DP pixel clock to an array of max supported streams. The maximum streams currently is 4. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-1-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 67 ++++++++++++++++++----------- drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +- drivers/gpu/drm/msm/dp/dp_display.c | 2 +- drivers/gpu/drm/msm/dp/dp_panel.c | 1 + drivers/gpu/drm/msm/dp/dp_panel.h | 11 +++++ 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 90fba03de7f0b..a475e787656eb 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -126,7 +126,7 @@ struct msm_dp_ctrl_private { unsigned int num_link_clks; struct clk_bulk_data *link_clks; - struct clk *pixel_clk; + struct clk *pixel_clk[DP_STREAM_MAX]; union phy_configure_opts phy_opts; @@ -138,7 +138,7 @@ struct msm_dp_ctrl_private { bool core_clks_on; bool link_clks_on; - bool stream_clks_on; + bool stream_clks_on[DP_STREAM_MAX]; }; static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset) @@ -1746,7 +1746,7 @@ int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "enable core clocks \n"); drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n", - str_on_off(ctrl->stream_clks_on), + str_on_off(ctrl->stream_clks_on[DP_STREAM_0]), str_on_off(ctrl->link_clks_on), str_on_off(ctrl->core_clks_on)); @@ -1765,7 +1765,7 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "disable core clocks \n"); drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n", - str_on_off(ctrl->stream_clks_on), + str_on_off(ctrl->stream_clks_on[DP_STREAM_0]), str_on_off(ctrl->link_clks_on), str_on_off(ctrl->core_clks_on)); } @@ -1796,7 +1796,7 @@ static int msm_dp_ctrl_link_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "enable link clocks\n"); drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n", - str_on_off(ctrl->stream_clks_on), + str_on_off(ctrl->stream_clks_on[DP_STREAM_0]), str_on_off(ctrl->link_clks_on), str_on_off(ctrl->core_clks_on)); @@ -1815,7 +1815,7 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "disabled link clocks\n"); drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n", - str_on_off(ctrl->stream_clks_on), + str_on_off(ctrl->stream_clks_on[DP_STREAM_0]), str_on_off(ctrl->link_clks_on), str_on_off(ctrl->core_clks_on)); } @@ -2188,38 +2188,39 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) return success; } -static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate) +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate, + enum msm_dp_stream_id stream_id) { int ret; - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + ret = clk_set_rate(ctrl->pixel_clk[stream_id], pixel_rate * 1000); if (ret) { DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); return ret; } - if (WARN_ON_ONCE(ctrl->stream_clks_on)) + if (WARN_ON_ONCE(ctrl->stream_clks_on[stream_id])) return 0; - ret = clk_prepare_enable(ctrl->pixel_clk); + ret = clk_prepare_enable(ctrl->pixel_clk[stream_id]); if (ret) { DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); return ret; } - ctrl->stream_clks_on = true; + ctrl->stream_clks_on[stream_id] = true; return ret; } -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id) { struct msm_dp_ctrl_private *ctrl; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; + if (ctrl->stream_clks_on[stream_id]) { + clk_disable_unprepare(ctrl->pixel_clk[stream_id]); + ctrl->stream_clks_on[stream_id] = false; } } @@ -2240,7 +2241,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl, panel->stream_id); msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel); ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); @@ -2250,7 +2251,7 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl } pixel_rate = panel->msm_dp_mode.drm_mode.clock; - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, panel->stream_id); msm_dp_ctrl_send_phy_test_pattern(ctrl); @@ -2542,9 +2543,8 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes); - drm_dbg_dp(ctrl->drm_dev, - "core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n", - ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on); + drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d\n", + ctrl->core_clks_on, ctrl->link_clks_on); if (!ctrl->link_clks_on) { /* link clk is off */ ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); @@ -2584,7 +2584,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate, panel->stream_id); if (ret) return ret; @@ -2644,8 +2644,6 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(panel); - msm_dp_ctrl_mainlink_disable(ctrl); msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); @@ -2716,6 +2714,13 @@ static const char *ctrl_clks[] = { "ctrl_link_iface", }; +static const char * const pixel_clks[] = { + "stream_pixel", + "stream_1_pixel", + "stream_2_pixel", + "stream_3_pixel", +}; + static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; @@ -2749,9 +2754,19 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) if (rc) return rc; - ctrl->pixel_clk = devm_clk_get(dev, "stream_pixel"); - if (IS_ERR(ctrl->pixel_clk)) - return PTR_ERR(ctrl->pixel_clk); + for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) { + ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]); + + if (i == 0 && IS_ERR(ctrl->pixel_clk[i])) + return PTR_ERR(ctrl->pixel_clk[i]); + + if (IS_ERR(ctrl->pixel_clk[i])) { + if (PTR_ERR(ctrl->pixel_clk[i]) != -ENOENT) + return PTR_ERR(ctrl->pixel_clk[i]); + DRM_DEBUG_DP("stream %d pixel clock not found", i); + break; + } + } return 0; } diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 5902cf7e746a4..be0d89d60914e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -24,7 +24,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 888ecf34f1602..9306375e0c474 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -718,7 +718,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp, msm_dp_panel_disable_vsc_sdp(msm_dp_panel); - msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id); /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index e76dad0f66637..745ee69768979 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -723,6 +723,7 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux msm_dp_panel = &panel->msm_dp_panel; msm_dp_panel->max_bw_code = DP_LINK_BW_8_1; + msm_dp_panel->stream_id = DP_STREAM_0; return msm_dp_panel; } diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 4519ac3742206..50a7214017512 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -27,6 +27,15 @@ struct msm_dp_panel_psr { u8 capabilities; }; +/* stream id */ +enum msm_dp_stream_id { + DP_STREAM_0, + DP_STREAM_1, + DP_STREAM_2, + DP_STREAM_3, + DP_STREAM_MAX, +}; + struct msm_dp_panel { /* dpcd raw data */ u8 dpcd[DP_RECEIVER_CAP_SIZE]; @@ -40,6 +49,8 @@ struct msm_dp_panel { bool vsc_sdp_supported; u32 hw_revision; + enum msm_dp_stream_id stream_id; + u32 max_bw_code; }; From df92bf3d8e67ac6407067589ab16576927589d37 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:23 +0800 Subject: [PATCH 131/188] FROMLIST: drm/msm/dp: introduce max_streams for DP controller MST support DP controllers across different SoCs vary in the number of concurrent MST streams they can support. Rather than hardcoding per-platform values, the number of available pixel clocks in DT serves as a natural indicator since each stream requires a dedicated pixel clock. Introduce max_stream to capture this at initialization time and expose it for the MST module to use during setup. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-2-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 13 +++++++++++++ drivers/gpu/drm/msm/dp/dp_ctrl.h | 1 + drivers/gpu/drm/msm/dp/dp_display.c | 12 ++++++++++++ drivers/gpu/drm/msm/dp/dp_display.h | 1 + 4 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index a475e787656eb..68fb4facb056b 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -127,6 +127,7 @@ struct msm_dp_ctrl_private { struct clk_bulk_data *link_clks; struct clk *pixel_clk[DP_STREAM_MAX]; + unsigned int num_pixel_clks; union phy_configure_opts phy_opts; @@ -2754,6 +2755,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) if (rc) return rc; + ctrl->num_pixel_clks = 0; for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) { ctrl->pixel_clk[i] = devm_clk_get(dev, pixel_clks[i]); @@ -2766,11 +2768,22 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) DRM_DEBUG_DP("stream %d pixel clock not found", i); break; } + + ctrl->num_pixel_clks++; } return 0; } +int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + return ctrl->num_pixel_clks; +} + struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, struct drm_dp_aux *aux, struct phy *phy, diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index be0d89d60914e..305add3dcd938 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -55,4 +55,5 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 9306375e0c474..9190848f23220 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -87,6 +87,8 @@ struct msm_dp_display_private { void __iomem *p0_base; size_t p0_len; + + int max_stream; }; struct msm_dp_desc { @@ -578,6 +580,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) dp->ctrl = NULL; goto error_link; } + dp->max_stream = msm_dp_ctrl_get_stream_cnt(dp->ctrl); dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base); if (IS_ERR(dp->audio)) { @@ -1178,6 +1181,15 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display) return 0; } +int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + return dp->max_stream; +} + static int msm_dp_display_probe(struct platform_device *pdev) { int rc = 0; diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 43ed79093e241..d3d4ab98089d5 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -26,6 +26,7 @@ struct msm_dp { bool psr_supported; }; +int msm_dp_get_mst_max_stream(struct msm_dp *msm_dp_display); int msm_dp_display_get_modes(struct msm_dp *msm_dp_display); bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display); int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display); From 529b775fda36af725e092b85287181d4e5bf085a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:24 +0800 Subject: [PATCH 132/188] FROMLIST: drm/msm/dp: Add support for programming p1/p2/p3 register blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for additional pixel register blocks (p1, p2, p3) to enable 4‑stream MST pixel clocks. Introduce the helper functions msm_dp_read_pn and msm_dp_write_pn for pixel register programming. All pixel clocks share the same register layout but use different base addresses. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-3-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 40 ++++++++++---- drivers/gpu/drm/msm/dp/dp_panel.c | 82 ++++++++++++++--------------- drivers/gpu/drm/msm/dp/dp_panel.h | 2 +- 3 files changed, 71 insertions(+), 53 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 9190848f23220..dd159df9cce9e 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -85,8 +85,8 @@ struct msm_dp_display_private { void __iomem *link_base; size_t link_len; - void __iomem *p0_base; - size_t p0_len; + void __iomem *pixel_base[DP_STREAM_MAX]; + size_t pixel_len; int max_stream; }; @@ -564,7 +564,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->p0_base); + dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]); if (IS_ERR(dp->panel)) { rc = PTR_ERR(dp->panel); DRM_ERROR("failed to initialize panel, rc = %d\n", rc); @@ -850,8 +850,14 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp) msm_dp_display->aux_base, "dp_aux"); msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len, msm_dp_display->link_base, "dp_link"); - msm_disp_snapshot_add_block(disp_state, msm_dp_display->p0_len, - msm_dp_display->p0_base, "dp_p0"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, + msm_dp_display->pixel_base[0], "dp_p0"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, + msm_dp_display->pixel_base[1], "dp_p1"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, + msm_dp_display->pixel_base[2], "dp_p2"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, + msm_dp_display->pixel_base[3], "dp_p3"); } void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) @@ -1131,6 +1137,7 @@ static void __iomem *msm_dp_ioremap(struct platform_device *pdev, int idx, size_ static int msm_dp_display_get_io(struct msm_dp_display_private *display) { struct platform_device *pdev = display->msm_dp_display.pdev; + int i; display->ahb_base = msm_dp_ioremap(pdev, 0, &display->ahb_len); if (IS_ERR(display->ahb_base)) @@ -1160,8 +1167,8 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display) display->aux_len = DP_DEFAULT_AUX_SIZE; display->link_base = display->ahb_base + DP_DEFAULT_LINK_OFFSET; display->link_len = DP_DEFAULT_LINK_SIZE; - display->p0_base = display->ahb_base + DP_DEFAULT_P0_OFFSET; - display->p0_len = DP_DEFAULT_P0_SIZE; + display->pixel_base[0] = display->ahb_base + DP_DEFAULT_P0_OFFSET; + display->pixel_len = DP_DEFAULT_P0_SIZE; return 0; } @@ -1172,10 +1179,21 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display) return PTR_ERR(display->link_base); } - display->p0_base = msm_dp_ioremap(pdev, 3, &display->p0_len); - if (IS_ERR(display->p0_base)) { - DRM_ERROR("unable to remap p0 region: %pe\n", display->p0_base); - return PTR_ERR(display->p0_base); + display->pixel_base[0] = msm_dp_ioremap(pdev, 3, &display->pixel_len); + if (IS_ERR(display->pixel_base[0])) { + DRM_ERROR("unable to remap p0 region: %pe\n", display->pixel_base[0]); + return PTR_ERR(display->pixel_base[0]); + } + + for (i = DP_STREAM_1; i < DP_STREAM_MAX; i++) { + /* pixels clk reg index start from 3*/ + display->pixel_base[i] = msm_dp_ioremap(pdev, i + 3, &display->pixel_len); + if (IS_ERR(display->pixel_base[i])) { + DRM_DEBUG_DP("unable to remap p%d region: %pe\n", i, + display->pixel_base[i]); + display->pixel_base[i] = NULL; + break; + } } return 0; diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 745ee69768979..238920c452613 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -25,7 +25,7 @@ struct msm_dp_panel_private { struct drm_dp_aux *aux; struct msm_dp_link *link; void __iomem *link_base; - void __iomem *p0_base; + void __iomem *pixel_base; bool panel_on; }; @@ -44,24 +44,24 @@ static inline void msm_dp_write_link(struct msm_dp_panel_private *panel, writel(data, panel->link_base + offset); } -static inline void msm_dp_write_p0(struct msm_dp_panel_private *panel, - u32 offset, u32 data) +static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel, + u32 offset, u32 data) { /* * To make sure interface reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ - writel(data, panel->p0_base + offset); + writel(data, panel->pixel_base + offset); } -static inline u32 msm_dp_read_p0(struct msm_dp_panel_private *panel, - u32 offset) +static inline u32 msm_dp_read_pn(struct msm_dp_panel_private *panel, + u32 offset) { /* * To make sure interface reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ - return readl_relaxed(panel->p0_base + offset); + return readl_relaxed(panel->pixel_base + offset); } static void msm_dp_panel_read_psr_cap(struct msm_dp_panel_private *panel) @@ -367,34 +367,34 @@ static void msm_dp_panel_tpg_enable(struct msm_dp_panel *msm_dp_panel, display_hctl = (hsync_end_x << 16) | hsync_start_x; - msm_dp_write_p0(panel, MMSS_DP_INTF_HSYNC_CTL, hsync_ctl); - msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F0, vsync_period * + msm_dp_write_pn(panel, MMSS_DP_INTF_HSYNC_CTL, hsync_ctl); + msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PERIOD_F0, vsync_period * hsync_period); - msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, v_sync_width * + msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, v_sync_width * hsync_period); - msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_HCTL, display_hctl); - msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_HCTL, 0); - msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F0, display_v_start); - msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F0, display_v_end); - msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F0, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F0, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F1, 0); - msm_dp_write_p0(panel, MMSS_DP_INTF_POLARITY_CTL, 0); - - msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL, - DP_TPG_CHECKERED_RECT_PATTERN); - msm_dp_write_p0(panel, MMSS_DP_TPG_VIDEO_CONFIG, - DP_TPG_VIDEO_CONFIG_BPP_8BIT | - DP_TPG_VIDEO_CONFIG_RGB); - msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE, - DP_BIST_ENABLE_DPBIST_EN); - msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN, - DP_TIMING_ENGINE_EN_EN); + msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_HCTL, display_hctl); + msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_HCTL, 0); + msm_dp_write_pn(panel, MMSS_INTF_DISPLAY_V_START_F0, display_v_start); + msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_V_END_F0, display_v_end); + msm_dp_write_pn(panel, MMSS_INTF_DISPLAY_V_START_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_DISPLAY_V_END_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_START_F0, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_END_F0, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_START_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_ACTIVE_V_END_F1, 0); + msm_dp_write_pn(panel, MMSS_DP_INTF_POLARITY_CTL, 0); + + msm_dp_write_pn(panel, MMSS_DP_TPG_MAIN_CONTROL, + DP_TPG_CHECKERED_RECT_PATTERN); + msm_dp_write_pn(panel, MMSS_DP_TPG_VIDEO_CONFIG, + DP_TPG_VIDEO_CONFIG_BPP_8BIT | + DP_TPG_VIDEO_CONFIG_RGB); + msm_dp_write_pn(panel, MMSS_DP_BIST_ENABLE, + DP_BIST_ENABLE_DPBIST_EN); + msm_dp_write_pn(panel, MMSS_DP_TIMING_ENGINE_EN, + DP_TIMING_ENGINE_EN_EN); drm_dbg_dp(panel->drm_dev, "%s: enabled tpg\n", __func__); } @@ -403,9 +403,9 @@ static void msm_dp_panel_tpg_disable(struct msm_dp_panel *msm_dp_panel) struct msm_dp_panel_private *panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); - msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL, 0x0); - msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE, 0x0); - msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN, 0x0); + msm_dp_write_pn(panel, MMSS_DP_TPG_MAIN_CONTROL, 0x0); + msm_dp_write_pn(panel, MMSS_DP_BIST_ENABLE, 0x0); + msm_dp_write_pn(panel, MMSS_DP_TIMING_ENGINE_EN, 0x0); } void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable) @@ -439,7 +439,7 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel) struct msm_dp_panel_private *panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); - msm_dp_write_p0(panel, MMSS_DP_DSC_DTO, 0x0); + msm_dp_write_pn(panel, MMSS_DP_DSC_DTO, 0x0); } static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp) @@ -629,7 +629,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking); msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active); - reg = msm_dp_read_p0(panel, MMSS_DP_INTF_CONFIG); + reg = msm_dp_read_pn(panel, MMSS_DP_INTF_CONFIG); if (wide_bus_en) reg |= DP_INTF_CONFIG_DATABUS_WIDEN; else @@ -637,7 +637,7 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) drm_dbg_dp(panel->drm_dev, "wide_bus_en=%d reg=%#x\n", wide_bus_en, reg); - msm_dp_write_p0(panel, MMSS_DP_INTF_CONFIG, reg); + msm_dp_write_pn(panel, MMSS_DP_INTF_CONFIG, reg); if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) msm_dp_panel_setup_vsc_sdp_yuv_420(msm_dp_panel); @@ -701,7 +701,7 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux, struct msm_dp_link *link, void __iomem *link_base, - void __iomem *p0_base) + void __iomem *pixel_base) { struct msm_dp_panel_private *panel; struct msm_dp_panel *msm_dp_panel; @@ -719,7 +719,7 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux panel->aux = aux; panel->link = link; panel->link_base = link_base; - panel->p0_base = p0_base; + panel->pixel_base = pixel_base; msm_dp_panel = &panel->msm_dp_panel; msm_dp_panel->max_bw_code = DP_LINK_BW_8_1; diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 50a7214017512..218a09a2fa655 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -102,5 +102,5 @@ static inline bool is_lane_count_valid(u32 lane_count) struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux, struct msm_dp_link *link, void __iomem *link_base, - void __iomem *p0_base); + void __iomem *pixel_base); #endif /* _DP_PANEL_H_ */ From 3932202f875e6eba2ce4ca2bb39a5ced965c077f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:25 +0800 Subject: [PATCH 133/188] FROMLIST: drm/msm/dp: use stream_id to change offsets in dp_catalog In the DP MST architecture, stream 1 shares the same link clock as stream 0 but uses different register offsets within the same link register space. Use the dp_panel's stream_id to select the correct register offsets for stream 1 in dp_catalog. Also add stream 1 register defines. Streams 2 and 3 are not covered here, as they use separate link clocks and require separate handling. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-4-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 78 +++++++++++++++++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 4 +- drivers/gpu/drm/msm/dp/dp_display.c | 24 +++++++- drivers/gpu/drm/msm/dp/dp_panel.c | 94 ++++++++++++++++++++++++++++- drivers/gpu/drm/msm/dp/dp_panel.h | 4 ++ drivers/gpu/drm/msm/dp/dp_reg.h | 44 ++++++++++++++ 6 files changed, 229 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 68fb4facb056b..5c491a925b4b8 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -117,6 +117,8 @@ struct msm_dp_ctrl_private { struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; + void __iomem *mst2link_base; + void __iomem *mst3link_base; struct phy *phy; @@ -172,6 +174,49 @@ static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl, writel(data, ctrl->link_base + offset); } +static inline u32 msm_dp_read_stream_link(struct msm_dp_ctrl_private *ctrl, + enum msm_dp_stream_id stream_id, u32 offset) +{ + offset = msm_dp_stream_reg(stream_id, offset); + switch (stream_id) { + case DP_STREAM_0: + case DP_STREAM_1: + return readl_relaxed(ctrl->link_base + offset); + case DP_STREAM_2: + return readl_relaxed(ctrl->mst2link_base + offset); + case DP_STREAM_3: + return readl_relaxed(ctrl->mst3link_base + offset); + default: + DRM_ERROR("error stream_id\n"); + return 0; + } +} + +static inline void msm_dp_write_stream_link(struct msm_dp_ctrl_private *ctrl, + enum msm_dp_stream_id stream_id, u32 offset, u32 data) +{ + /* + * To make sure link reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + offset = msm_dp_stream_reg(stream_id, offset); + switch (stream_id) { + case DP_STREAM_0: + case DP_STREAM_1: + writel(data, ctrl->link_base + offset); + break; + case DP_STREAM_2: + writel(data, ctrl->mst2link_base + offset); + break; + case DP_STREAM_3: + writel(data, ctrl->mst3link_base + offset); + break; + default: + DRM_ERROR("error stream_id\n"); + break; + } +} + static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, struct msm_dp_link_info *link) { @@ -397,7 +442,8 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, /* * RMW: Called from atomic_enable(). Serialized by the DRM atomic framework. */ - config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); + if (msm_dp_panel->stream_id == DP_STREAM_0) + config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ @@ -412,7 +458,7 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); - msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); + msm_dp_write_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_CONFIGURATION_CTRL, config); } static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, @@ -469,7 +515,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); - misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); + misc_val = msm_dp_read_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_MISC1_MISC0); /* clear bpp bits */ misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT); @@ -479,7 +525,7 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, misc_val |= DP_MISC0_SYNCHRONOUS_CLK; drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); - msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); + msm_dp_write_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_MISC1_MISC0, misc_val); } static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, @@ -2461,8 +2507,8 @@ static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl, } static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, - u32 rate, u32 stream_rate_khz, - bool is_ycbcr_420) + struct msm_dp_panel *panel, + u32 rate, u32 stream_rate_khz) { u32 pixel_m, pixel_n; u32 mvid, nvid, pixel_div, dispcc_input_rate; @@ -2514,7 +2560,7 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, nvid = temp; } - if (is_ycbcr_420) + if (panel->msm_dp_mode.out_fmt_is_yuv_420) mvid /= 2; if (link_rate_hbr2 == rate) @@ -2524,8 +2570,8 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, nvid *= 3; drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid); - msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID, mvid); - msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); + msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_MVID, mvid); + msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid); } int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, @@ -2597,14 +2643,14 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl_link(ctrl, panel); + if (panel->stream_id == DP_STREAM_0) + msm_dp_ctrl_config_ctrl_link(ctrl, panel); msm_dp_ctrl_configure_source_params(ctrl, panel); msm_dp_ctrl_config_msa(ctrl, - ctrl->link->link_params.rate, - pixel_rate_orig, - panel->msm_dp_mode.out_fmt_is_yuv_420); + panel, ctrl->link->link_params.rate, + pixel_rate_orig); msm_dp_panel_clear_dsc_dto(panel); @@ -2788,7 +2834,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, - void __iomem *link_base) + void __iomem *link_base, + void __iomem *mst2link_base, + void __iomem *mst3link_base) { struct msm_dp_ctrl_private *ctrl; int ret; @@ -2827,6 +2875,8 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link ctrl->phy = phy; ctrl->ahb_base = ahb_base; ctrl->link_base = link_base; + ctrl->mst2link_base = mst2link_base; + ctrl->mst3link_base = mst3link_base; ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl); if (ret) { diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 305add3dcd938..49d16911ae8be 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -35,7 +35,9 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, - void __iomem *link_base); + void __iomem *link_base, + void __iomem *mst2link_base, + void __iomem *mst3link_base); void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dd159df9cce9e..3b599b25f40cf 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -85,6 +85,12 @@ struct msm_dp_display_private { void __iomem *link_base; size_t link_len; + void __iomem *mst2link_base; + size_t mst2link_len; + + void __iomem *mst3link_base; + size_t mst3link_len; + void __iomem *pixel_base[DP_STREAM_MAX]; size_t pixel_len; @@ -564,7 +570,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, dp->pixel_base[0]); + dp->panel = msm_dp_panel_get(dev, dp->aux, dp->link, dp->link_base, + dp->mst2link_base, dp->mst3link_base, dp->pixel_base[0]); if (IS_ERR(dp->panel)) { rc = PTR_ERR(dp->panel); DRM_ERROR("failed to initialize panel, rc = %d\n", rc); @@ -573,7 +580,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) } dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux, - phy, dp->ahb_base, dp->link_base); + phy, dp->ahb_base, dp->link_base, + dp->mst2link_base, dp->mst3link_base); if (IS_ERR(dp->ctrl)) { rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); @@ -850,6 +858,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp) msm_dp_display->aux_base, "dp_aux"); msm_disp_snapshot_add_block(disp_state, msm_dp_display->link_len, msm_dp_display->link_base, "dp_link"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst2link_len, + msm_dp_display->mst2link_base, "dp_mst2link"); + msm_disp_snapshot_add_block(disp_state, msm_dp_display->mst3link_len, + msm_dp_display->mst3link_base, "dp_mst3link"); msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, msm_dp_display->pixel_base[0], "dp_p0"); msm_disp_snapshot_add_block(disp_state, msm_dp_display->pixel_len, @@ -1196,6 +1208,14 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display) } } + display->mst2link_base = msm_dp_ioremap(pdev, 7, &display->mst2link_len); + if (IS_ERR(display->mst2link_base)) + DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst2link_base); + + display->mst3link_base = msm_dp_ioremap(pdev, 8, &display->mst3link_len); + if (IS_ERR(display->mst3link_base)) + DRM_DEBUG_DP("unable to remap link region: %pe\n", display->mst3link_base); + return 0; } diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 238920c452613..e0c0e8c9178c8 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -25,13 +25,84 @@ struct msm_dp_panel_private { struct drm_dp_aux *aux; struct msm_dp_link *link; void __iomem *link_base; + void __iomem *mst2link_base; + void __iomem *mst3link_base; void __iomem *pixel_base; bool panel_on; }; +u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg) +{ + bool is_s1 = (id == DP_STREAM_1); + + if (id == DP_STREAM_0) + return reg; + + switch (reg) { + case REG_DP_CONFIGURATION_CTRL: + return is_s1 ? REG_DP1_CONFIGURATION_CTRL : REG_DP_MSTLINK_CONFIGURATION_CTRL; + case REG_DP_SOFTWARE_MVID: + return is_s1 ? REG_DP1_SOFTWARE_MVID : REG_MSTLINK_SOFTWARE_MVID; + case REG_DP_SOFTWARE_NVID: + return is_s1 ? REG_DP1_SOFTWARE_NVID : REG_MSTLINK_SOFTWARE_NVID; + case REG_DP_TOTAL_HOR_VER: + return is_s1 ? REG_DP1_TOTAL_HOR_VER : REG_DP_MSTLINK_TOTAL_HOR_VER; + case REG_DP_START_HOR_VER_FROM_SYNC: + return is_s1 ? REG_DP1_START_HOR_VER_FROM_SYNC + : REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC; + case REG_DP_HSYNC_VSYNC_WIDTH_POLARITY: + return is_s1 ? REG_DP1_HSYNC_VSYNC_WIDTH_POLARITY + : REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY; + case REG_DP_ACTIVE_HOR_VER: + return is_s1 ? REG_DP1_ACTIVE_HOR_VER : REG_DP_MSTLINK_ACTIVE_HOR_VER; + case REG_DP_MISC1_MISC0: + return is_s1 ? REG_DP1_MISC1_MISC0 : REG_DP_MSTLINK_MISC1_MISC0; + case MMSS_DP_SDP_CFG: + return is_s1 ? MMSS_DP1_SDP_CFG : MMSS_DP_MSTLINK_SDP_CFG; + case MMSS_DP_SDP_CFG2: + return is_s1 ? MMSS_DP1_SDP_CFG2 : MMSS_DP_MSTLINK_SDP_CFG2; + case MMSS_DP_SDP_CFG3: + return is_s1 ? MMSS_DP1_SDP_CFG3 : MMSS_DP_MSTLINK_SDP_CFG3; + case MMSS_DP_GENERIC0_0: + return is_s1 ? MMSS_DP1_GENERIC0_0 : MMSS_DP_MSTLINK_GENERIC0_0; + case MMSS_DP_GENERIC0_1: + return is_s1 ? MMSS_DP1_GENERIC0_1 : MMSS_DP_MSTLINK_GENERIC0_1; + case MMSS_DP_GENERIC0_2: + return is_s1 ? MMSS_DP1_GENERIC0_2 : MMSS_DP_MSTLINK_GENERIC0_2; + case MMSS_DP_GENERIC0_3: + return is_s1 ? MMSS_DP1_GENERIC0_3 : MMSS_DP_MSTLINK_GENERIC0_3; + case MMSS_DP_GENERIC0_4: + return is_s1 ? MMSS_DP1_GENERIC0_4 : MMSS_DP_MSTLINK_GENERIC0_4; + case MMSS_DP_GENERIC0_5: + return is_s1 ? MMSS_DP1_GENERIC0_5 : MMSS_DP_MSTLINK_GENERIC0_5; + case MMSS_DP_GENERIC0_6: + return is_s1 ? MMSS_DP1_GENERIC0_6 : MMSS_DP_MSTLINK_GENERIC0_6; + case MMSS_DP_GENERIC0_7: + return is_s1 ? MMSS_DP1_GENERIC0_7 : MMSS_DP_MSTLINK_GENERIC0_7; + case MMSS_DP_GENERIC0_8: + return is_s1 ? MMSS_DP1_GENERIC0_8 : MMSS_DP_MSTLINK_GENERIC0_8; + case MMSS_DP_GENERIC0_9: + return is_s1 ? MMSS_DP1_GENERIC0_9 : MMSS_DP_MSTLINK_GENERIC0_9; + default: + return reg; + } +} + static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset) { - return readl_relaxed(panel->link_base + offset); + offset = msm_dp_stream_reg(panel->msm_dp_panel.stream_id, offset); + switch (panel->msm_dp_panel.stream_id) { + case DP_STREAM_0: + case DP_STREAM_1: + return readl_relaxed(panel->link_base + offset); + case DP_STREAM_2: + return readl_relaxed(panel->mst2link_base + offset); + case DP_STREAM_3: + return readl_relaxed(panel->mst3link_base + offset); + default: + DRM_ERROR("error stream_id\n"); + return 0; + } } static inline void msm_dp_write_link(struct msm_dp_panel_private *panel, @@ -41,7 +112,22 @@ static inline void msm_dp_write_link(struct msm_dp_panel_private *panel, * To make sure link reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ - writel(data, panel->link_base + offset); + offset = msm_dp_stream_reg(panel->msm_dp_panel.stream_id, offset); + switch (panel->msm_dp_panel.stream_id) { + case DP_STREAM_0: + case DP_STREAM_1: + writel(data, panel->link_base + offset); + break; + case DP_STREAM_2: + writel(data, panel->mst2link_base + offset); + break; + case DP_STREAM_3: + writel(data, panel->mst3link_base + offset); + break; + default: + DRM_ERROR("error stream_id\n"); + break; + } } static inline void msm_dp_write_pn(struct msm_dp_panel_private *panel, @@ -701,6 +787,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux, struct msm_dp_link *link, void __iomem *link_base, + void __iomem *mst2link_base, + void __iomem *mst3link_base, void __iomem *pixel_base) { struct msm_dp_panel_private *panel; @@ -720,6 +808,8 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux panel->link = link; panel->link_base = link_base; panel->pixel_base = pixel_base; + panel->mst2link_base = mst2link_base; + panel->mst3link_base = mst3link_base; msm_dp_panel = &panel->msm_dp_panel; msm_dp_panel->max_bw_code = DP_LINK_BW_8_1; diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 218a09a2fa655..dc046fec24fc7 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -99,8 +99,12 @@ static inline bool is_lane_count_valid(u32 lane_count) lane_count == 4); } +u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg); + struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux, struct msm_dp_link *link, void __iomem *link_base, + void __iomem *mst2link_base, + void __iomem *mst3link_base, void __iomem *pixel_base); #endif /* _DP_PANEL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index 3689642b7fc06..310e5a1cc934a 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -332,6 +332,50 @@ #define DP_TPG_VIDEO_CONFIG_BPP_8BIT (0x00000001) #define DP_TPG_VIDEO_CONFIG_RGB (0x00000004) +#define REG_DP1_CONFIGURATION_CTRL (0x00000400) +#define REG_DP1_SOFTWARE_MVID (0x00000414) +#define REG_DP1_SOFTWARE_NVID (0x00000418) +#define REG_DP1_TOTAL_HOR_VER (0x0000041C) +#define REG_DP1_START_HOR_VER_FROM_SYNC (0x00000420) +#define REG_DP1_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424) +#define REG_DP1_ACTIVE_HOR_VER (0x00000428) +#define REG_DP1_MISC1_MISC0 (0x0000042C) +#define MMSS_DP1_GENERIC0_0 (0x00000490) +#define MMSS_DP1_GENERIC0_1 (0x00000494) +#define MMSS_DP1_GENERIC0_2 (0x00000498) +#define MMSS_DP1_GENERIC0_3 (0x0000049C) +#define MMSS_DP1_GENERIC0_4 (0x000004A0) +#define MMSS_DP1_GENERIC0_5 (0x000004A4) +#define MMSS_DP1_GENERIC0_6 (0x000004A8) +#define MMSS_DP1_GENERIC0_7 (0x000004AC) +#define MMSS_DP1_GENERIC0_8 (0x000004B0) +#define MMSS_DP1_GENERIC0_9 (0x000004B4) +#define MMSS_DP1_SDP_CFG (0x000004E0) +#define MMSS_DP1_SDP_CFG2 (0x000004E4) +#define MMSS_DP1_SDP_CFG3 (0x000004E8) + +#define REG_DP_MSTLINK_CONFIGURATION_CTRL (0x00000034) +#define REG_MSTLINK_SOFTWARE_MVID (0x00000040) +#define REG_MSTLINK_SOFTWARE_NVID (0x00000044) +#define REG_DP_MSTLINK_TOTAL_HOR_VER (0x00000048) +#define REG_DP_MSTLINK_START_HOR_VER_FROM_SYNC (0x0000004C) +#define REG_DP_MSTLINK_HSYNC_VSYNC_WIDTH_POLARITY (0x00000050) +#define REG_DP_MSTLINK_ACTIVE_HOR_VER (0x00000054) +#define REG_DP_MSTLINK_MISC1_MISC0 (0x00000058) +#define MMSS_DP_MSTLINK_GENERIC0_0 (0x000000BC) +#define MMSS_DP_MSTLINK_GENERIC0_1 (0x000000C0) +#define MMSS_DP_MSTLINK_GENERIC0_2 (0x000000C4) +#define MMSS_DP_MSTLINK_GENERIC0_3 (0x000000C8) +#define MMSS_DP_MSTLINK_GENERIC0_4 (0x000000CC) +#define MMSS_DP_MSTLINK_GENERIC0_5 (0x000000D0) +#define MMSS_DP_MSTLINK_GENERIC0_6 (0x000000D4) +#define MMSS_DP_MSTLINK_GENERIC0_7 (0x000000D8) +#define MMSS_DP_MSTLINK_GENERIC0_8 (0x000000DC) +#define MMSS_DP_MSTLINK_GENERIC0_9 (0x000000E0) +#define MMSS_DP_MSTLINK_SDP_CFG (0x0000010c) +#define MMSS_DP_MSTLINK_SDP_CFG2 (0x0000011c) +#define MMSS_DP_MSTLINK_SDP_CFG3 (0x00000114) + #define MMSS_DP_ASYNC_FIFO_CONFIG (0x00000088) #define REG_DP_PHY_AUX_INTERRUPT_CLEAR (0x0000004C) From 735d6070a76b7c52579f72df1e9d60d543fd84d2 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:26 +0800 Subject: [PATCH 134/188] FROMLIST: drm/msm/dp: add support to send ACT packets for MST Whenever virtual channel slot allocation changes, the DP source must send the action control trigger sequence to notify the sink about the same. This would be applicable during the start and stop of the pixel stream. Add the infrastructure to be able to send ACT packets for the DP controller when operating in MST mode. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-5-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 44 ++++++++++++++++++++++++++++- drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 +++- drivers/gpu/drm/msm/dp/dp_display.c | 3 +- drivers/gpu/drm/msm/dp/dp_display.h | 1 + drivers/gpu/drm/msm/dp/dp_reg.h | 2 ++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 5c491a925b4b8..6754b10d418cf 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -142,6 +142,7 @@ struct msm_dp_ctrl_private { bool core_clks_on; bool link_clks_on; bool stream_clks_on[DP_STREAM_MAX]; + bool mst_active; }; static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset) @@ -244,6 +245,37 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, return err; } +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) +{ + struct msm_dp_ctrl_private *ctrl; + const struct drm_display_mode *mode; + u32 frame_time_ms; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (!ctrl->mst_active) + return 0; + + mode = &panel->msm_dp_mode.drm_mode; + frame_time_ms = DIV_ROUND_UP((u64)mode->htotal * mode->vtotal, + mode->clock) + 1; + + msm_dp_write_link(ctrl, REG_DP_MST_ACT, 0x1); + /* make sure ACT signal is performed */ + wmb(); + + msleep(frame_time_ms); + + /* Hardware clears this bit after sending 4 ACT headers */ + if (msm_dp_read_link(ctrl, REG_DP_MST_ACT)) { + drm_dbg_dp(ctrl->drm_dev, "MST ACT trigger complete failed\n"); + return -EINVAL; + } + + return 0; +} + /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ @@ -2610,7 +2642,8 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, return ret; } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, + bool mst_active) { int ret = 0; bool mainlink_ready = false; @@ -2623,6 +2656,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + ctrl->mst_active = mst_active; + pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock; pixel_rate = pixel_rate_orig; @@ -2658,6 +2693,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); + ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel); + if (ret) + return ret; + ret = msm_dp_ctrl_wait4video_ready(ctrl); if (ret) return ret; @@ -2695,6 +2734,8 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); + ctrl->mst_active = false; + dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); @@ -2877,6 +2918,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link ctrl->link_base = link_base; ctrl->mst2link_base = mst2link_base; ctrl->mst3link_base = mst3link_base; + ctrl->mst_active = false; ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl); if (ret) { diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 49d16911ae8be..6de028da85fb9 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -18,7 +18,8 @@ struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, + bool mst_active); int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, bool force_link_train); @@ -58,4 +59,6 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl); +int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 3b599b25f40cf..ad848211096ec 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -673,7 +673,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel); + rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel, msm_dp_display->mst_active); if (!rc) msm_dp_display->power_on = true; @@ -1509,6 +1509,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel); } static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index d3d4ab98089d5..e987de80522c3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -19,6 +19,7 @@ struct msm_dp { struct drm_bridge *bridge; bool audio_enabled; bool power_on; + bool mst_active; unsigned int connector_type; bool is_edp; diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index 310e5a1cc934a..6808965878d45 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -156,6 +156,8 @@ #define DP_CONFIGURATION_CTRL_BPC_SHIFT (0x08) #define DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT (0x0D) +#define REG_DP_MST_ACT (0x00000500) + #define REG_DP_SOFTWARE_MVID (0x00000010) #define REG_DP_SOFTWARE_NVID (0x00000018) #define REG_DP_TOTAL_HOR_VER (0x0000001C) From 95f4790d4a852b9df0a0121d8773f24d097e5106 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:27 +0800 Subject: [PATCH 135/188] FROMLIST: drm/msm/dp: Add support to enable MST in mainlink control Add support to program the MST enable bit in the mainlink control register when an MST session is active or being disabled. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-6-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 17 +++++++++++++++++ drivers/gpu/drm/msm/dp/dp_reg.h | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 6754b10d418cf..e9aa0e254234a 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -276,6 +276,19 @@ int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl, return 0; } +static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable) +{ + u32 mainlink_ctrl; + + mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); + if (enable) + mainlink_ctrl |= DP_MAINLINK_CTRL_MST_EN; + else + mainlink_ctrl &= ~DP_MAINLINK_CTRL_MST_EN; + + msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); +} + /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ @@ -2678,6 +2691,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); + if (ctrl->mst_active) + msm_dp_ctrl_mst_config(ctrl, true); + if (panel->stream_id == DP_STREAM_0) msm_dp_ctrl_config_ctrl_link(ctrl, panel); @@ -2731,6 +2747,7 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, phy = ctrl->phy; msm_dp_ctrl_mainlink_disable(ctrl); + msm_dp_ctrl_mst_config(ctrl, false); msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index 6808965878d45..deb40ed246549 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -128,6 +128,10 @@ #define DP_MAINLINK_FLUSH_MODE_UPDATE_SDP FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 1) #define DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 3) #define DP_MAINLINK_FB_BOUNDARY_SEL (0x02000000) +#define DP_MAINLINK_CTRL_ECF_MODE BIT(26) +#define DP_MAINLINK_CTRL_MST_ACTIVE BIT(8) +#define DP_MAINLINK_CTRL_MST_EN (DP_MAINLINK_CTRL_ECF_MODE | \ + DP_MAINLINK_CTRL_MST_ACTIVE) #define REG_DP_STATE_CTRL (0x00000004) #define DP_STATE_CTRL_LINK_TRAINING_PATTERN1 (0x00000001) From b979ea83e5d6f55c3bc138d12d9d12a6ffb14046 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:28 +0800 Subject: [PATCH 136/188] FROMLIST: drm/msm/dp: no need to update tu calculation for mst DP stream is transmitted in transfer units only for SST case, there is no need to calculate and program TU parameters for MST case. Skip the TU programming for MST cases. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-7-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index e9aa0e254234a..5b5149b160df1 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2705,7 +2705,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * msm_dp_panel_clear_dsc_dto(panel); - msm_dp_ctrl_setup_tr_unit(ctrl, panel); + if (!ctrl->mst_active) + msm_dp_ctrl_setup_tr_unit(ctrl, panel); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); From 68057864e429224fd3a38761025e45f187132ccd Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:29 +0800 Subject: [PATCH 137/188] FROMLIST: drm/msm/dp: Add support for MST channel slot allocation DP MST streams share 64 MTP slots in a time-multiplexed manner. Add support for calculating the rate governor, slot allocation, and slot reservation in the DP controller. Each MST stream can reserve its slots by calling msm_dp_display_set_stream_info() from its bridge callbacks. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-8-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 192 ++++++++++++++++++++++++++++ drivers/gpu/drm/msm/dp/dp_ctrl.h | 4 + drivers/gpu/drm/msm/dp/dp_display.c | 17 +++ drivers/gpu/drm/msm/dp/dp_display.h | 2 + drivers/gpu/drm/msm/dp/dp_panel.c | 6 + drivers/gpu/drm/msm/dp/dp_panel.h | 1 + drivers/gpu/drm/msm/dp/dp_reg.h | 10 ++ 7 files changed, 232 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 5b5149b160df1..15df82a0caca9 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -73,6 +73,7 @@ #define MR_LINK_PRBS7 0x100 #define MR_LINK_CUSTOM80 0x200 #define MR_LINK_TRAINING4 0x40 +#define DP_MAX_TIME_SLOTS 64 enum { DP_TRAINING_NONE, @@ -109,6 +110,11 @@ struct msm_dp_vc_tu_mapping_table { u8 tu_size_minus1; }; +struct msm_dp_mst_ch_slot_info { + u32 start_slot; + u32 tot_slots; +}; + struct msm_dp_ctrl_private { struct msm_dp_ctrl msm_dp_ctrl; struct drm_device *drm_dev; @@ -143,6 +149,8 @@ struct msm_dp_ctrl_private { bool link_clks_on; bool stream_clks_on[DP_STREAM_MAX]; bool mst_active; + + struct msm_dp_mst_ch_slot_info mst_ch_info[DP_STREAM_MAX]; }; static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset) @@ -289,6 +297,44 @@ static void msm_dp_ctrl_mst_config(struct msm_dp_ctrl_private *ctrl, bool enable msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); } +static void msm_dp_ctrl_mst_channel_alloc(struct msm_dp_ctrl_private *ctrl, + enum msm_dp_stream_id stream_id, u32 ch_start_slot, + u32 tot_slot_cnt) +{ + u32 slot_reg_1 = 0, slot_reg_2 = 0; + + if (ch_start_slot > DP_MAX_TIME_SLOTS || + (ch_start_slot + tot_slot_cnt > DP_MAX_TIME_SLOTS)) { + DRM_ERROR("invalid slots start %d, tot %d\n", + ch_start_slot, tot_slot_cnt); + return; + } + + drm_dbg_dp(ctrl->drm_dev, "stream_id %d, start_slot %d, tot_slot %d\n", + stream_id, ch_start_slot, tot_slot_cnt); + + if (ch_start_slot && tot_slot_cnt) { + u64 mask = GENMASK_ULL(ch_start_slot + tot_slot_cnt - 2, ch_start_slot - 1); + + slot_reg_1 = mask & 0xFFFFFFFF; + slot_reg_2 = (mask >> 32) & 0xFFFFFFFF; + } + + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_1_32, slot_reg_1); + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_TIMESLOT_33_63, slot_reg_2); +} + +static void msm_dp_ctrl_update_rg(struct msm_dp_ctrl_private *ctrl, + enum msm_dp_stream_id stream_id, u32 x_int, u32 y_frac_enum) +{ + u32 rg = y_frac_enum | (x_int << 16); + + drm_dbg_dp(ctrl->drm_dev, "stream_id: %d x_int:%d y_frac_enum:%d rg:%d\n", + stream_id, x_int, y_frac_enum, rg); + + msm_dp_write_stream_link(ctrl, stream_id, REG_DP_DP0_RG, rg); +} + /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ @@ -2619,6 +2665,117 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_stream_link(ctrl, panel->stream_id, REG_DP_SOFTWARE_NVID, nvid); } +/* + * Calculate MST Rate Governor parameters x_int and y_frac_enum (HPG 3.8.1.2). + * + * The RG paces symbol delivery per MTP via: M = x_int + y_frac_enum/256 + * where M is the target symbol count per MTP across all lanes. + * + * min_slot_cnt = (pclk * bpp/8) / (lclk * lanes) * 64 -- slots at 1.0x BW + * max_slot_cnt = pbn * 54 / (lclk * lanes) -- slots at PBN limit + * raw_target_sc = (min + max) / 2 -- midpoint (~1.003x) + * + * Quantize raw_target_sc to 1/(256*lanes) steps, then: + * M = Chosen_TARGET_Slot_Count * lanes + * x_int = INT(M) + * y_frac_enum = CEIL(256 * MOD(M, 1)) + */ +static void msm_dp_ctrl_mst_calculate_rg(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, + u32 *p_x_int, u32 *p_y_frac_enum) +{ + u64 min_slot_cnt, max_slot_cnt; + u64 raw_target_sc, target_sc_fixp; + u64 ts_denom, ts_enum, ts_int; + u64 pclk = panel->msm_dp_mode.drm_mode.clock; + u64 lclk = 0; + u64 lanes = ctrl->link->link_params.num_lanes; + u64 bpp = panel->msm_dp_mode.bpp; + u64 pbn = panel->pbn; + u64 numerator, denominator, temp, temp1, temp2; + u32 x_int = 0, y_frac_enum = 0; + u64 target_strm_sym, ts_int_fixp, ts_frac_fixp, y_frac_enum_fixp; + + lclk = ctrl->link->link_params.rate; + + /* min_slot_cnt */ + numerator = pclk * bpp * 64 * 1000; + denominator = lclk * lanes * 8 * 1000; + min_slot_cnt = drm_fixp_from_fraction(numerator, denominator); + + /* max_slot_cnt */ + numerator = pbn * 54 * 1000; + denominator = lclk * lanes; + max_slot_cnt = drm_fixp_from_fraction(numerator, denominator); + + /* raw_target_sc */ + numerator = max_slot_cnt + min_slot_cnt; + denominator = drm_fixp_from_fraction(2, 1); + raw_target_sc = drm_fixp_div(numerator, denominator); + + /* target_sc */ + temp = drm_fixp_from_fraction(256 * lanes, 1); + numerator = drm_fixp_mul(raw_target_sc, temp); + denominator = drm_fixp_from_fraction(256 * lanes, 1); + target_sc_fixp = drm_fixp_div(numerator, denominator); + + ts_enum = 256 * lanes; + ts_denom = drm_fixp_from_fraction(256 * lanes, 1); + ts_int = drm_fixp2int(target_sc_fixp); + + temp = drm_fixp2int_ceil(raw_target_sc); + if (temp != ts_int) { + temp = drm_fixp_from_fraction(ts_int, 1); + temp1 = raw_target_sc - temp; + temp2 = drm_fixp_mul(temp1, ts_denom); + ts_enum = drm_fixp2int(temp2); + } + + /* target_strm_sym */ + ts_int_fixp = drm_fixp_from_fraction(ts_int, 1); + ts_frac_fixp = drm_fixp_from_fraction(ts_enum, drm_fixp2int(ts_denom)); + temp = ts_int_fixp + ts_frac_fixp; + temp1 = drm_fixp_from_fraction(lanes, 1); + target_strm_sym = drm_fixp_mul(temp, temp1); + + /* x_int */ + x_int = drm_fixp2int(target_strm_sym); + + /* y_enum_frac */ + temp = drm_fixp_from_fraction(x_int, 1); + temp1 = target_strm_sym - temp; + temp2 = drm_fixp_from_fraction(256, 1); + y_frac_enum_fixp = drm_fixp_mul(temp1, temp2); + + temp1 = drm_fixp2int(y_frac_enum_fixp); + temp2 = drm_fixp2int_ceil(y_frac_enum_fixp); + + y_frac_enum = (u32)((temp1 == temp2) ? temp1 : temp1 + 1); + + *p_x_int = x_int; + *p_y_frac_enum = y_frac_enum; + + drm_dbg_dp(ctrl->drm_dev, "MST lane_cnt:%llu, rate:%llu x_int:%d, y_frac:%d\n", + lanes, lclk, x_int, y_frac_enum); +} + +static void msm_dp_ctrl_mst_stream_setup(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) +{ + u32 x_int, y_frac_enum; + + if (!ctrl->mst_active) + return; + + drm_dbg_dp(ctrl->drm_dev, "MST stream channel allocation\n"); + + msm_dp_ctrl_mst_stream_channel_slot_setup(&ctrl->msm_dp_ctrl); + + msm_dp_ctrl_mst_calculate_rg(ctrl, panel, &x_int, &y_frac_enum); + + msm_dp_ctrl_update_rg(ctrl, panel->stream_id, x_int, y_frac_enum); +} + int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, bool force_link_train) @@ -2708,6 +2865,8 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * if (!ctrl->mst_active) msm_dp_ctrl_setup_tr_unit(ctrl, panel); + msm_dp_ctrl_mst_stream_setup(ctrl, panel); + msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); ret = msm_dp_ctrl_mst_send_act(msm_dp_ctrl, panel); @@ -2760,6 +2919,39 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, phy_power_off(phy); } +void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl, + enum msm_dp_stream_id stream_id, + u32 start_slot, u32 tot_slots) +{ + struct msm_dp_ctrl_private *ctrl; + + if (!msm_dp_ctrl || stream_id >= DP_STREAM_MAX) { + DRM_ERROR("invalid input\n"); + return; + } + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + ctrl->mst_ch_info[stream_id].start_slot = start_slot; + ctrl->mst_ch_info[stream_id].tot_slots = tot_slots; +} + +void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl; + int i; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (!ctrl->mst_active) + return; + + for (i = DP_STREAM_0; i < ctrl->num_pixel_clks; i++) { + msm_dp_ctrl_mst_channel_alloc(ctrl, i, ctrl->mst_ch_info[i].start_slot, + ctrl->mst_ch_info[i].tot_slots); + } +} + irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) { diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 6de028da85fb9..e1d10ae20f705 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -61,4 +61,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_get_stream_cnt(struct msm_dp_ctrl *dp_ctrl); int msm_dp_ctrl_mst_send_act(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); +void msm_dp_ctrl_mst_stream_channel_slot_setup(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_set_mst_channel_info(struct msm_dp_ctrl *msm_dp_ctrl, + enum msm_dp_stream_id stream_id, + u32 start_slot, u32 tot_slots); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index ad848211096ec..dccd3df368593 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -749,6 +749,20 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp, return 0; } +int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel, + u32 start_slot, u32 num_slots, u32 pbn) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + msm_dp_ctrl_set_mst_channel_info(dp->ctrl, panel->stream_id, start_slot, num_slots); + + panel->pbn = pbn; + + return 0; +} + /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid * @dp: Pointer to dp display structure @@ -1489,6 +1503,8 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0); + rc = msm_dp_display_enable(dp, dp->panel); if (rc) DRM_ERROR("DP display enable failed, rc=%d\n", rc); @@ -1509,6 +1525,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + msm_dp_ctrl_mst_stream_channel_slot_setup(msm_dp_display->ctrl); msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel); } diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index e987de80522c3..45e2cc2d6adda 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -43,5 +43,7 @@ void msm_dp_display_atomic_enable(struct msm_dp *dp_display); enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, const struct drm_display_info *info, const struct drm_display_mode *mode); +int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel, + u32 start_slot, u32 num_slots, u32 pbn); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index e0c0e8c9178c8..ef2ded8ec4ea5 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -57,6 +57,12 @@ u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg) return is_s1 ? REG_DP1_ACTIVE_HOR_VER : REG_DP_MSTLINK_ACTIVE_HOR_VER; case REG_DP_MISC1_MISC0: return is_s1 ? REG_DP1_MISC1_MISC0 : REG_DP_MSTLINK_MISC1_MISC0; + case REG_DP_DP0_TIMESLOT_1_32: + return is_s1 ? REG_DP_DP1_TIMESLOT_1_32 : REG_DP_MSTLINK_TIMESLOT_1_32; + case REG_DP_DP0_TIMESLOT_33_63: + return is_s1 ? REG_DP_DP1_TIMESLOT_33_63 : REG_DP_MSTLINK_TIMESLOT_33_63; + case REG_DP_DP0_RG: + return is_s1 ? REG_DP_DP1_RG : REG_DP_MSTLINK_DP_RG; case MMSS_DP_SDP_CFG: return is_s1 ? MMSS_DP1_SDP_CFG : MMSS_DP_MSTLINK_SDP_CFG; case MMSS_DP_SDP_CFG2: diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index dc046fec24fc7..3e78af9e430dc 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -50,6 +50,7 @@ struct msm_dp_panel { u32 hw_revision; enum msm_dp_stream_id stream_id; + u32 pbn; u32 max_bw_code; }; diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index deb40ed246549..f2bd96f3bbd01 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -338,7 +338,13 @@ #define DP_TPG_VIDEO_CONFIG_BPP_8BIT (0x00000001) #define DP_TPG_VIDEO_CONFIG_RGB (0x00000004) + +#define REG_DP_MSTLINK_DP_RG (0X0000011C) #define REG_DP1_CONFIGURATION_CTRL (0x00000400) +#define REG_DP_DP0_TIMESLOT_1_32 (0x00000404) +#define REG_DP_DP0_TIMESLOT_33_63 (0x00000408) +#define REG_DP_DP1_TIMESLOT_1_32 (0x0000040C) +#define REG_DP_DP1_TIMESLOT_33_63 (0x00000410) #define REG_DP1_SOFTWARE_MVID (0x00000414) #define REG_DP1_SOFTWARE_NVID (0x00000418) #define REG_DP1_TOTAL_HOR_VER (0x0000041C) @@ -359,8 +365,12 @@ #define MMSS_DP1_SDP_CFG (0x000004E0) #define MMSS_DP1_SDP_CFG2 (0x000004E4) #define MMSS_DP1_SDP_CFG3 (0x000004E8) +#define REG_DP_DP0_RG (0x000004F8) +#define REG_DP_DP1_RG (0x000004FC) #define REG_DP_MSTLINK_CONFIGURATION_CTRL (0x00000034) +#define REG_DP_MSTLINK_TIMESLOT_1_32 (0x00000038) +#define REG_DP_MSTLINK_TIMESLOT_33_63 (0x0000003C) #define REG_MSTLINK_SOFTWARE_MVID (0x00000040) #define REG_MSTLINK_SOFTWARE_NVID (0x00000044) #define REG_DP_MSTLINK_TOTAL_HOR_VER (0x00000048) From 3fe58727dc2131d70821976151f8d68e6ca700dc Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:30 +0800 Subject: [PATCH 138/188] FROMLIST: drm/msm/dp: Add support for sending VCPF packets in DP controller The VC Payload Fill (VCPF) sequence is inserted by the DP controller when stream symbols are absent, typically before a stream is disabled. Add support for triggering the VCPF sequence in the MSM DP controller. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-9-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 57 +++++++++++++++++++++++++++++ drivers/gpu/drm/msm/dp/dp_ctrl.h | 1 + drivers/gpu/drm/msm/dp/dp_display.c | 2 +- drivers/gpu/drm/msm/dp/dp_panel.c | 2 + drivers/gpu/drm/msm/dp/dp_reg.h | 8 ++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 15df82a0caca9..c4f1a68b1210c 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -65,6 +65,11 @@ (PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \ PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK) +#define DP_INTERRUPT_STATUS5 \ + (DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT) +#define DP_INTERRUPT_STATUS5_MASK \ + (DP_INTERRUPT_STATUS5 << DP_INTERRUPT_STATUS_MASK_SHIFT) + #define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0) #define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) @@ -398,6 +403,8 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl) DP_INTERRUPT_STATUS1_MASK); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, DP_INTERRUPT_STATUS2_MASK); + msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, + DP_INTERRUPT_STATUS5_MASK); } void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl) @@ -407,6 +414,7 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00); + msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, 0x00); } static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl) @@ -426,6 +434,20 @@ static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl) msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4); } +static u32 msm_dp_ctrl_get_mst_interrupt(struct msm_dp_ctrl_private *ctrl) +{ + u32 intr, intr_ack; + + intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS5); + intr &= ~DP_INTERRUPT_STATUS5_MASK; + intr_ack = (intr & DP_INTERRUPT_STATUS5) + << DP_INTERRUPT_STATUS_ACK_SHIFT; + msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS5, + intr_ack | DP_INTERRUPT_STATUS5_MASK); + + return intr; +} + static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl) { u32 val; @@ -525,6 +547,34 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } +/* Must be called with msm_dp_mst::mst_lock held */ +void msm_dp_ctrl_push_vcpf(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *msm_dp_panel) +{ + struct msm_dp_ctrl_private *ctrl; + u32 state = 0x0; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (!ctrl->mst_active) + state |= DP_STATE_CTRL_PUSH_IDLE; + else if (msm_dp_panel->stream_id == DP_STREAM_0) + state |= DP_DP0_PUSH_VCPF; + else if (msm_dp_panel->stream_id == DP_STREAM_1) + state |= DP_DP1_PUSH_VCPF; + else + state |= DP_MSTLINK_PUSH_VCPF; + + reinit_completion(&ctrl->idle_comp); + + msm_dp_write_stream_link(ctrl, msm_dp_panel->stream_id, REG_DP_STATE_CTRL, state); + + if (!wait_for_completion_timeout(&ctrl->idle_comp, + IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES)) + pr_warn("PUSH_VCPF pattern timedout\n"); + + drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n"); +} + static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *msm_dp_panel) { @@ -2994,6 +3044,13 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, ret = IRQ_HANDLED; } + isr = msm_dp_ctrl_get_mst_interrupt(ctrl); + if (isr & (DP_INTR_DP0_VCPF_SENT | DP_INTR_DP1_VCPF_SENT)) { + drm_dbg_dp(ctrl->drm_dev, "vcpf sent\n"); + complete(&ctrl->idle_comp); + ret = IRQ_HANDLED; + } + /* DP aux isr */ isr = msm_dp_ctrl_get_aux_interrupt(ctrl); if (isr) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index e1d10ae20f705..88a02d52f61c6 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -27,6 +27,7 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl, enum msm_dp_stream_id stream_id); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_push_vcpf(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dccd3df368593..6b871c0c55e1a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1524,7 +1524,7 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + msm_dp_ctrl_push_vcpf(msm_dp_display->ctrl, msm_dp_display->panel); msm_dp_ctrl_mst_stream_channel_slot_setup(msm_dp_display->ctrl); msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel); } diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index ef2ded8ec4ea5..cbbcc0dbf6520 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -39,6 +39,8 @@ u32 msm_dp_stream_reg(enum msm_dp_stream_id id, u32 reg) return reg; switch (reg) { + case REG_DP_STATE_CTRL: + return is_s1 ? REG_DP_STATE_CTRL : REG_DP_MSTLINK_STATE_CTRL; case REG_DP_CONFIGURATION_CTRL: return is_s1 ? REG_DP1_CONFIGURATION_CTRL : REG_DP_MSTLINK_CONFIGURATION_CTRL; case REG_DP_SOFTWARE_MVID: diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index f2bd96f3bbd01..ade7b362d650c 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -42,9 +42,13 @@ #define DP_INTR_FRAME_END BIT(6) #define DP_INTR_CRC_UPDATED BIT(9) +#define DP_INTR_DP0_VCPF_SENT BIT(0) +#define DP_INTR_DP1_VCPF_SENT BIT(3) + #define REG_DP_INTR_STATUS3 (0x00000028) #define REG_DP_INTR_STATUS4 (0x0000002C) +#define REG_DP_INTR_STATUS5 (0x00000034) #define PSR_UPDATE_INT (0x00000001) #define PSR_CAPTURE_INT (0x00000004) #define PSR_EXIT_INT (0x00000010) @@ -143,6 +147,8 @@ #define DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN (0x00000040) #define DP_STATE_CTRL_SEND_VIDEO (0x00000080) #define DP_STATE_CTRL_PUSH_IDLE (0x00000100) +#define DP_DP0_PUSH_VCPF BIT(12) +#define DP_DP1_PUSH_VCPF BIT(14) #define REG_DP_CONFIGURATION_CTRL (0x00000008) #define DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK (0x00000001) @@ -368,6 +374,8 @@ #define REG_DP_DP0_RG (0x000004F8) #define REG_DP_DP1_RG (0x000004FC) +#define REG_DP_MSTLINK_STATE_CTRL (0x00000000) +#define DP_MSTLINK_PUSH_VCPF BIT(12) #define REG_DP_MSTLINK_CONFIGURATION_CTRL (0x00000034) #define REG_DP_MSTLINK_TIMESLOT_1_32 (0x00000038) #define REG_DP_MSTLINK_TIMESLOT_33_63 (0x0000003C) From 6f27fbdd56f1b10a8d00e1c048ff75f88cc1a85f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:31 +0800 Subject: [PATCH 139/188] FROMLIST: drm/msm/dp: Always program MST_FIFO_CONSTANT_FILL for MST use cases As per the hardware programming guide, MST_FIFO_CONSTANT_FILL must always be programmed when operating in MST mode. Ensure the register is configured accordingly. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-10-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 2 ++ drivers/gpu/drm/msm/dp/dp_panel.c | 12 ++++++++++++ drivers/gpu/drm/msm/dp/dp_panel.h | 2 ++ 3 files changed, 16 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index c4f1a68b1210c..80116e19fbbf4 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -677,6 +677,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl msm_dp_ctrl_config_misc1_misc0(ctrl, panel); msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); + + msm_dp_panel_mst_async_fifo(panel, ctrl->mst_active); } /* diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index cbbcc0dbf6520..ba5ee2ad8924b 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -741,6 +741,18 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) return 0; } +void msm_dp_panel_mst_async_fifo(struct msm_dp_panel *msm_dp_panel, bool mst_en) +{ + struct msm_dp_panel_private *panel; + + panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); + + if (mst_en) + msm_dp_write_pn(panel, MMSS_DP_ASYNC_FIFO_CONFIG, 0x01); + else + msm_dp_write_pn(panel, MMSS_DP_ASYNC_FIFO_CONFIG, 0x00); +} + int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, const struct drm_display_mode *adjusted_mode, u32 bpp) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 3e78af9e430dc..edc39ee5268ef 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -73,6 +73,8 @@ void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel); void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sdp *vsc_sdp); void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel); +void msm_dp_panel_mst_async_fifo(struct msm_dp_panel *msm_dp_panel, bool mst_en); + /** * is_link_rate_valid() - validates the link rate * @bw_code: link rate requested by the sink From c63fa82ae6ed82a73b602ed52b7c01666cf436ad Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:32 +0800 Subject: [PATCH 140/188] FROMLIST: drm/msm/dp: move link-level teardown from display_disable to display_unprepare msm_dp_display_disable() currently mixes stream-level shutdown (disable VSC SDP, off pixel clk, clear power_on) with link-level teardown (PSM config when sink_count==0, off_link, PHY re-init or host PHY exit). For DP MST the same link is shared across multiple streams, so disabling one stream must not tear down the link. Move the link-level steps into msm_dp_display_unprepare() so that display_disable() handles only the per-stream sequence, mirroring the split already present on the prepare path (display_prepare_link vs display_set_mode / display_enable). SST behaviour is unchanged: atomic_post_disable() still calls display_disable() followed by display_unprepare() in the same order, and the cached dp->panel used inside unprepare is the same panel that was previously passed in. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-11-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 6b871c0c55e1a..8680efb766382 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -731,18 +731,6 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp, msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id); - /* dongle is still connected but sinks are disconnected */ - if (dp->link->sink_count == 0) - msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true); - - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); - - if (dp->link->sink_count == 0) - /* re-init the PHY so that we can listen to Dongle disconnect */ - msm_dp_ctrl_reinit_phy(dp->ctrl); - else - msm_dp_display_host_phy_exit(dp); - msm_dp_display->power_on = false; drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count); @@ -1533,6 +1521,18 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; + /* dongle is still connected but sinks are disconnected */ + if (dp->link->sink_count == 0) + msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + if (dp->link->sink_count == 0) + /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_reinit_phy(dp->ctrl); + else + msm_dp_display_host_phy_exit(dp); + pm_runtime_put_sync(&msm_dp_display->pdev->dev); drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); From 0284ef128c132b70593416837c950b69780a534b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:33 +0800 Subject: [PATCH 141/188] FROMLIST: drm/msm/dp: factor out _helper variants of bridge ops accepting a panel The atomic bridge callbacks (set_mode / enable / disable / post_disable) on dp_display currently hard-code dp->panel. For DP MST every stream has its own msm_dp_panel that the MST encoder owns, so the same enable/disable sequence needs to be invokable against an arbitrary panel. Introduce *_helper variants that take struct msm_dp_panel * and reduce the existing atomic_* callbacks to thin wrappers that pass dp->panel. No SST-path behaviour change. Also drop the static qualifier from msm_dp_display_prepare_link() and msm_dp_display_unprepare() and change them to take struct msm_dp * so the upcoming MST encoder code can drive link-level prepare/unprepare uniformly through the public API. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-12-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 93 +++++++++++++++++++++-------- drivers/gpu/drm/msm/dp/dp_display.h | 12 ++++ 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 8680efb766382..73e4422651973 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -628,12 +628,14 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, return 0; } -static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) +int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display) { - struct msm_dp *msm_dp_display = &dp->msm_dp_display; + struct msm_dp_display_private *dp; int rc = 0; bool force_link_train = false; + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); if (msm_dp_display->is_edp) @@ -1457,69 +1459,101 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } -void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display, - struct drm_atomic_commit *state) +int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state, + struct drm_encoder *drm_encoder, + struct msm_dp_panel *msm_dp_panel) { struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; + + crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_encoder); + if (!crtc) + return 0; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + return msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, msm_dp_panel); +} + +void msm_dp_display_atomic_prepare(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) +{ int rc = 0; struct msm_dp_display_private *dp; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - crtc = drm_atomic_get_new_crtc_for_encoder(state, - msm_dp_display->bridge->encoder); - if (!crtc) - return; - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - rc = msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel); + rc = msm_dp_display_set_mode_helper(msm_dp_display, state, + msm_dp_display->bridge->encoder, dp->panel); if (rc) { DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); return; } - rc = msm_dp_display_prepare_link(dp); + rc = msm_dp_display_prepare_link(msm_dp_display); if (rc) DRM_ERROR("DP display prepare failed, rc=%d\n", rc); } -void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display) +void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, struct msm_dp_panel *msm_dp_panel) { struct msm_dp_display_private *dp; int rc = 0; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0); + msm_dp_display_set_stream_info(msm_dp_display, msm_dp_panel, 0, 0, 0); - rc = msm_dp_display_enable(dp, dp->panel); + rc = msm_dp_display_enable(dp, msm_dp_panel); if (rc) DRM_ERROR("DP display enable failed, rc=%d\n", rc); rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp, dp->panel); + msm_dp_display_disable(dp, msm_dp_panel); } drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + msm_dp_display_set_stream_info(msm_dp_display, dp->panel, 0, 0, 0); + + msm_dp_display_enable_helper(msm_dp_display, dp->panel); +} + +void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display, + struct msm_dp_panel *msm_dp_panel) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + msm_dp_ctrl_push_vcpf(dp->ctrl, msm_dp_panel); + msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl); + msm_dp_ctrl_mst_send_act(dp->ctrl, msm_dp_panel); +} + void msm_dp_display_atomic_disable(struct msm_dp *dp) { struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_push_vcpf(msm_dp_display->ctrl, msm_dp_display->panel); - msm_dp_ctrl_mst_stream_channel_slot_setup(msm_dp_display->ctrl); - msm_dp_ctrl_mst_send_act(msm_dp_display->ctrl, msm_dp_display->panel); + msm_dp_display_disable_helper(dp, msm_dp_display->panel); } -static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) +void msm_dp_display_unprepare(struct msm_dp *msm_dp_display) { - struct msm_dp *msm_dp_display = &dp->msm_dp_display; + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) @@ -1534,11 +1568,9 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) msm_dp_display_host_phy_exit(dp); pm_runtime_put_sync(&msm_dp_display->pdev->dev); - - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_display_atomic_post_disable(struct msm_dp *dp) +void msm_dp_display_atomic_post_disable_helper(struct msm_dp *dp, struct msm_dp_panel *msm_dp_panel) { struct msm_dp_display_private *msm_dp_display; @@ -1549,7 +1581,18 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp) msm_dp_display_audio_notify_disable(msm_dp_display); - msm_dp_display_disable(msm_dp_display, msm_dp_display->panel); + msm_dp_display_disable(msm_dp_display, msm_dp_panel); + + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); +} + +void msm_dp_display_atomic_post_disable(struct msm_dp *msm_dp_display) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + msm_dp_display_atomic_post_disable_helper(msm_dp_display, dp->panel); msm_dp_display_unprepare(msm_dp_display); } diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 45e2cc2d6adda..55ffa22bb233f 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -45,5 +45,17 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, const struct drm_display_mode *mode); int msm_dp_display_set_stream_info(struct msm_dp *msm_dp_display, struct msm_dp_panel *panel, u32 start_slot, u32 num_slots, u32 pbn); +void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, + struct msm_dp_panel *msm_dp_panel); +void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display, + struct msm_dp_panel *msm_dp_panel); +void msm_dp_display_atomic_post_disable_helper(struct msm_dp *msm_dp_display, + struct msm_dp_panel *msm_dp_panel); +int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state, + struct drm_encoder *drm_encoder, + struct msm_dp_panel *msm_dp_panel); +int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display); +void msm_dp_display_unprepare(struct msm_dp *dp); #endif /* _DP_DISPLAY_H_ */ From 7eb19a2f0a698065362ae12ada7d3b84657925e5 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:34 +0800 Subject: [PATCH 142/188] FROMLIST: drm/msm/dp: replace power_on with active_stream_cnt for dp_display For DP MST, the link clock and power domain resources stay on until both streams have been disabled OR we receive hotplug. Introduce an active_stream_cnt to track the number of active streams and necessary state handling. Replace the power_on variable with active_stream_cnt as power_on boolean works only for a single stream. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-13-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_audio.c | 2 +- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++++++++++----------- drivers/gpu/drm/msm/dp/dp_display.h | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index 41018e82efa10..035e230201fd9 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -284,7 +284,7 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge, * such cases check for connection status and bail out if not * connected. */ - if (!msm_dp_display->power_on) { + if (!msm_dp_display->active_stream_cnt) { rc = -EINVAL; goto end; } diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 73e4422651973..71e0dbe80201f 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -650,7 +650,7 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display) if (dp->link->sink_count == 0) return rc; - if (!msm_dp_display->power_on) { + if (!msm_dp_display->active_stream_cnt) { msm_dp_display_host_phy_init(dp); force_link_train = true; } @@ -670,14 +670,10 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, struct msm_dp *msm_dp_display = &dp->msm_dp_display; drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); - if (msm_dp_display->power_on) { - drm_dbg_dp(dp->drm_dev, "Link already setup, return\n"); - return 0; - } rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel, msm_dp_display->mst_active); - if (!rc) - msm_dp_display->power_on = true; + + msm_dp_display->active_stream_cnt++; return rc; } @@ -726,14 +722,14 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp, { struct msm_dp *msm_dp_display = &dp->msm_dp_display; - if (!msm_dp_display->power_on) + if (!msm_dp_display->active_stream_cnt) return 0; msm_dp_panel_disable_vsc_sdp(msm_dp_panel); msm_dp_ctrl_off_pixel_clk(dp->ctrl, msm_dp_panel->stream_id); - msm_dp_display->power_on = false; + msm_dp_display->active_stream_cnt--; drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count); return 0; @@ -850,10 +846,10 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp) * if we are reading registers we need the link clocks to be on * however till DP cable is connected this will not happen as we * do not know the resolution to power up with. Hence check the - * power_on status before dumping DP registers to avoid crash due + * active_stream_cnt status before dumping DP registers to avoid crash due * to unclocked access */ - if (!dp->power_on) + if (!dp->active_stream_cnt) return; msm_disp_snapshot_add_block(disp_state, msm_dp_display->ahb_len, @@ -1535,6 +1531,11 @@ void msm_dp_display_disable_helper(struct msm_dp *msm_dp_display, dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + if (!msm_dp_display->active_stream_cnt) { + drm_dbg_dp(dp->drm_dev, "no active streams\n"); + return; + } + msm_dp_ctrl_push_vcpf(dp->ctrl, msm_dp_panel); msm_dp_ctrl_mst_stream_channel_slot_setup(dp->ctrl); msm_dp_ctrl_mst_send_act(dp->ctrl, msm_dp_panel); @@ -1555,6 +1556,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + if (msm_dp_display->active_stream_cnt) { + drm_dbg_dp(dp->drm_dev, "stream still active, return\n"); + return; + } + /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 55ffa22bb233f..75dc40261723e 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -18,7 +18,7 @@ struct msm_dp { struct drm_bridge *next_bridge; struct drm_bridge *bridge; bool audio_enabled; - bool power_on; + u32 active_stream_cnt; bool mst_active; unsigned int connector_type; bool is_edp; From 5af74261fccfc68317e6efa28ef63c46fc9fa75b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:35 +0800 Subject: [PATCH 143/188] FROMLIST: drm/msm/dp: Mark the SST bridge disconnected when mst is active The bridge detect function is only applicable for SST. In MST mode, connector detection is handled by MST bridges. Skips detection for the SST bridge when MST is active. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-14-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 71e0dbe80201f..fc47b29258ad3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -910,6 +910,9 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, priv = container_of(dp, struct msm_dp_display_private, msm_dp_display); + if (dp->mst_active) + return status; + guard(mutex)(&priv->plugged_lock); ret = pm_runtime_resume_and_get(&dp->pdev->dev); if (ret) { @@ -955,6 +958,10 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, status = connector_status_disconnected; } + /* skip for MST */ + if (priv->max_stream > 1 && drm_dp_read_mst_cap(priv->aux, dpcd)) + status = connector_status_disconnected; + end: /* * If we detected the DPRX, leave the controller on so that it doesn't From 6613c3b6ae0e514317460097b0d1d28e36b9492d Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:36 +0800 Subject: [PATCH 144/188] FROMLIST: drm/msm/dp: add an API to initialize MST on sink side If the DP controller is capable of supporting multiple streams then initialize the DP sink in MST mode by programming the DP_MSTM_CTRL DPCD register to enable MST mode. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-15-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 48 ++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index fc47b29258ad3..4e55024f92209 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "msm_drv.h" @@ -270,6 +271,31 @@ static int msm_dp_display_lttpr_init(struct msm_dp_display_private *dp, u8 *dpcd return lttpr_count; } +static void msm_dp_display_mst_init(struct msm_dp_display_private *dp) +{ + u8 old_mstm_ctrl; + struct msm_dp *msm_dp = &dp->msm_dp_display; + int ret; + + /* clear sink MST state */ + drm_dp_dpcd_read_byte(dp->aux, DP_MSTM_CTRL, &old_mstm_ctrl); + + ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, 0); + if (ret < 0) { + DRM_ERROR("failed to clear DP_MSTM_CTRL, ret=%d\n", ret); + return; + } + + ret = drm_dp_dpcd_write_byte(dp->aux, DP_MSTM_CTRL, + DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC); + if (ret < 0) { + DRM_ERROR("sink MST enablement failed\n"); + return; + } + + msm_dp->mst_active = true; +} + static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) { struct drm_connector *connector = dp->msm_dp_display.connector; @@ -288,14 +314,19 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) if (rc) goto end; - drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); - drm_edid_connector_update(connector, drm_edid); + if (!(dp->max_stream > 1) || !drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) { + drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); + drm_edid_connector_update(connector, drm_edid); - if (!drm_edid) { - DRM_ERROR("panel edid read failed\n"); - /* check edid read fail is due to unplug */ - if (!msm_dp_aux_is_link_connected(dp->aux)) - return -ETIMEDOUT; + if (!drm_edid) { + DRM_ERROR("panel edid read failed\n"); + /* check edid read fail is due to unplug */ + if (!msm_dp_aux_is_link_connected(dp->aux)) + return -ETIMEDOUT; + } + + if (rc) + goto end; } msm_dp_link_process_request(dp->link); @@ -317,6 +348,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, false); + if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) + msm_dp_display_mst_init(dp); + msm_dp_link_reset_phy_params_vx_px(dp->link); end: From 14987ffdd2cf7c8a0c445a70d46e01a54ec8c153 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:37 +0800 Subject: [PATCH 145/188] FROMLIST: drm/msm/dp: add msm_dp_display_get_panel() to initialize DP panel Add an API msm_dp_display_get_panel() to initialize and return a DP panel to be used by DP MST module. Since some of the fields of DP panel are private, dp_display module needs to initialize these parts and return the panel back. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-16-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 27 +++++++++++++++++++++++++++ drivers/gpu/drm/msm/dp/dp_display.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 4e55024f92209..528b049dec50b 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -565,6 +565,33 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) return rc; } +struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display, + enum msm_dp_stream_id stream_id) +{ + struct msm_dp_display_private *dp; + struct msm_dp_panel *dp_panel; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + if (stream_id >= DP_STREAM_MAX || stream_id >= dp->max_stream) { + DRM_ERROR("invalid stream_id %d\n", stream_id); + return NULL; + } + + dp_panel = msm_dp_panel_get(&dp->msm_dp_display.pdev->dev, dp->aux, dp->link, + dp->link_base, dp->mst2link_base, dp->mst3link_base, + dp->pixel_base[stream_id]); + + if (IS_ERR(dp_panel)) { + DRM_ERROR("failed to initialize panel\n"); + return NULL; + } + + dp_panel->stream_id = stream_id; + + return dp_panel; +} + static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 75dc40261723e..676213a48089e 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -58,4 +58,6 @@ int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display, int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display); void msm_dp_display_unprepare(struct msm_dp *dp); +struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display, + enum msm_dp_stream_id stream_id); #endif /* _DP_DISPLAY_H_ */ From 7026b61408ecd801daf843b2d98a4393b6160e02 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:38 +0800 Subject: [PATCH 146/188] FROMLIST: drm/msm/dp: add link_ready to manage link-level operations In MST mode, multiple streams share the same DP link. Track a link_ready state so msm_dp_display_prepare_link() runs only once per link and repeated calls are skipped. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-17-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 42 +++++++++++++++++++---------- drivers/gpu/drm/msm/dp/dp_display.h | 1 + 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 528b049dec50b..9fbfd9bb4d120 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -702,6 +702,9 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display) if (msm_dp_display->is_edp) msm_dp_hpd_plug_handle(dp); + if (msm_dp_display->link_ready) + return 0; + rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); if (rc) { DRM_ERROR("failed to pm_runtime_resume\n"); @@ -714,14 +717,18 @@ int msm_dp_display_prepare_link(struct msm_dp *msm_dp_display) if (!msm_dp_display->active_stream_cnt) { msm_dp_display_host_phy_init(dp); force_link_train = true; + + rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); + if (rc) + DRM_ERROR("Failed link training (rc=%d)\n", rc); + // TODO: schedule drm_connector_set_link_status_property() } - rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); - if (rc) - DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() + rc = msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); + if (!rc) + msm_dp_display->link_ready = true; - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); + return rc; } static int msm_dp_display_enable(struct msm_dp_display_private *dp, @@ -1566,16 +1573,16 @@ void msm_dp_display_enable_helper(struct msm_dp *msm_dp_display, struct msm_dp_p dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_display_set_stream_info(msm_dp_display, msm_dp_panel, 0, 0, 0); - - rc = msm_dp_display_enable(dp, msm_dp_panel); - if (rc) - DRM_ERROR("DP display enable failed, rc=%d\n", rc); + if (msm_dp_display->link_ready) { + rc = msm_dp_display_enable(dp, msm_dp_panel); + if (rc) + DRM_ERROR("DP display enable failed, rc=%d\n", rc); - rc = msm_dp_display_post_enable(msm_dp_display); - if (rc) { - DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp, msm_dp_panel); + rc = msm_dp_display_post_enable(msm_dp_display); + if (rc) { + DRM_ERROR("DP display post enable failed, rc=%d\n", rc); + msm_dp_display_disable(dp, msm_dp_panel); + } } drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); @@ -1624,6 +1631,11 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + if (!msm_dp_display->link_ready) { + drm_dbg_dp(dp->drm_dev, "Link already setup, return\n"); + return; + } + if (msm_dp_display->active_stream_cnt) { drm_dbg_dp(dp->drm_dev, "stream still active, return\n"); return; @@ -1642,6 +1654,8 @@ void msm_dp_display_unprepare(struct msm_dp *msm_dp_display) msm_dp_display_host_phy_exit(dp); pm_runtime_put_sync(&msm_dp_display->pdev->dev); + + msm_dp_display->link_ready = false; } void msm_dp_display_atomic_post_disable_helper(struct msm_dp *dp, struct msm_dp_panel *msm_dp_panel) diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 676213a48089e..0464f8941e8de 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -22,6 +22,7 @@ struct msm_dp { bool mst_active; unsigned int connector_type; bool is_edp; + bool link_ready; struct msm_dp_audio *msm_dp_audio; bool psr_supported; From 35a979e4c68b4386ecb16d8887f23e1c1ae892aa Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:39 +0800 Subject: [PATCH 147/188] FROMLIST: drm/msm/dpu: initialize encoders per stream for DP MST Simply initialize MST encoders for MST-capable DP controllers, and introduce msm_dp_get_mst_max_stream to query MST streams. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-18-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 14 +++++++++++++- drivers/gpu/drm/msm/msm_drv.h | 7 ++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index da3556eb6ecc2..7a00c4094d5c8 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -655,7 +655,7 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev, struct msm_display_info info; bool yuv_supported; int rc; - int i; + int i, stream_id, stream_cnt; for (i = 0; i < ARRAY_SIZE(priv->kms->dp); i++) { if (!priv->kms->dp[i]) @@ -678,6 +678,18 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev, DPU_ERROR("modeset_init failed for DP, rc = %d\n", rc); return rc; } + + stream_cnt = msm_dp_get_mst_max_stream(priv->kms->dp[i]); + + if (stream_cnt > 1) { + for (stream_id = 0; stream_id < stream_cnt; stream_id++) { + encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info); + if (IS_ERR(encoder)) { + DPU_ERROR("encoder init failed for dp mst display\n"); + return PTR_ERR(encoder); + } + } + } } return 0; diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 3787db8770ada..9bb9cdcec32da 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -361,7 +361,7 @@ bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display, bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode); bool msm_dp_wide_bus_available(const struct msm_dp *dp_display); - +int msm_dp_get_mst_max_stream(struct msm_dp *dp_display); #else static inline int __init msm_dp_register(void) { @@ -378,6 +378,11 @@ static inline int msm_dp_modeset_init(struct msm_dp *dp_display, return -EINVAL; } +static inline int msm_dp_get_mst_max_stream(struct msm_dp *dp_display) +{ + return -EINVAL; +} + static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display) { } From 2e4431b02df2c21795f7c2da9fa7b22d018af5b0 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:40 +0800 Subject: [PATCH 148/188] FROMLIST: drm/msm/dp: initialize dp_mst module for each DP MST controller For each MST capable DP controller, initialize a dp_mst module to manage its DP MST operations. The DP MST module for each controller is the central entity to manage its topology related operations as well as interfacing with the rest of the DP driver. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-19-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/Makefile | 3 +- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 6 +++ drivers/gpu/drm/msm/dp/dp_display.c | 18 ++++++++ drivers/gpu/drm/msm/dp/dp_display.h | 2 + drivers/gpu/drm/msm/dp/dp_mst_drm.c | 60 +++++++++++++++++++++++++ drivers/gpu/drm/msm/dp/dp_mst_drm.h | 13 ++++++ drivers/gpu/drm/msm/msm_drv.h | 6 +++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/msm/dp/dp_mst_drm.c create mode 100644 drivers/gpu/drm/msm/dp/dp_mst_drm.h diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index d0c3a4c6703be..31a15636958c8 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -145,7 +145,8 @@ msm-display-$(CONFIG_DRM_MSM_DP)+= dp/dp_aux.o \ dp/dp_link.o \ dp/dp_panel.o \ dp/dp_audio.o \ - dp/dp_utils.o + dp/dp_utils.o \ + dp/dp_mst_drm.o msm-display-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index 7a00c4094d5c8..91d33b432427f 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -682,6 +682,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev, stream_cnt = msm_dp_get_mst_max_stream(priv->kms->dp[i]); if (stream_cnt > 1) { + rc = msm_dp_mst_register(priv->kms->dp[i]); + if (rc) { + DPU_ERROR("dp_mst_init failed for DP, rc = %d\n", rc); + return rc; + } + for (stream_id = 0; stream_id < stream_cnt; stream_id++) { encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info); if (IS_ERR(encoder)) { diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 9fbfd9bb4d120..ce717f9ce5af6 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -28,6 +28,7 @@ #include "dp_drm.h" #include "dp_audio.h" #include "dp_debug.h" +#include "dp_mst_drm.h" static bool psr_enabled = false; module_param(psr_enabled, bool, 0); @@ -351,6 +352,9 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) msm_dp_display_mst_init(dp); + if (dp->msm_dp_display.mst_active) + msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, true); + msm_dp_link_reset_phy_params_vx_px(dp->link); end: @@ -522,6 +526,11 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) dp->panel->dpcd, dp->panel->downstream_ports); + if (dp->msm_dp_display.mst_active) { + msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, false); + dp->msm_dp_display.mst_active = false; + } + /* signal the disconnect event early to ensure proper teardown */ msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); @@ -1530,6 +1539,15 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } +int msm_dp_mst_register(struct msm_dp *msm_dp_display) +{ + struct msm_dp_display_private *dp; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + return msm_dp_mst_init(msm_dp_display, dp->max_stream, dp->aux); +} + int msm_dp_display_set_mode_helper(struct msm_dp *msm_dp_display, struct drm_atomic_commit *state, struct drm_encoder *drm_encoder, diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 0464f8941e8de..a185819ec57e8 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -24,6 +24,8 @@ struct msm_dp { bool is_edp; bool link_ready; + void *msm_dp_mst; + struct msm_dp_audio *msm_dp_audio; bool psr_supported; }; diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c new file mode 100644 index 0000000000000..78b8dffe111b9 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +#include "dp_mst_drm.h" +#include "dp_panel.h" + +struct msm_dp_mst { + struct drm_dp_mst_topology_mgr mst_mgr; + struct msm_dp *msm_dp; + struct drm_dp_aux *dp_aux; + u32 max_streams; +}; + +int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state) +{ + struct msm_dp_mst *mst = dp_display->msm_dp_mst; + int rc; + + rc = drm_dp_mst_topology_mgr_set_mst(&mst->mst_mgr, state); + if (rc < 0) { + drm_err(dp_display->drm_dev, + "[MST] failed to set topology mgr state to %d rc:%d\n", state, rc); + } + + drm_dbg_kms(dp_display->drm_dev, "[MST] set_mgr_state state:%d\n", state); + return rc; +} + +int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux) +{ + struct drm_device *dev = dp_display->drm_dev; + struct msm_dp_mst *mst; + int ret; + + mst = devm_kzalloc(dev->dev, sizeof(*mst), GFP_KERNEL); + if (!mst) + return -ENOMEM; + + mst->msm_dp = dp_display; + mst->max_streams = max_streams; + mst->dp_aux = drm_aux; + + ret = drm_dp_mst_topology_mgr_init(&mst->mst_mgr, dev, + drm_aux, + 16, + max_streams, + dp_display->connector->base.id); + if (ret) { + drm_err(dev, "[MST] topology manager init failed\n"); + return ret; + } + + dp_display->msm_dp_mst = mst; + return 0; +} diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h new file mode 100644 index 0000000000000..5d411529f6819 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _DP_MST_DRM_H_ +#define _DP_MST_DRM_H_ + +#include "dp_display.h" + +int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux); +int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state); + +#endif /* _DP_MST_DRM_H_ */ diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 9bb9cdcec32da..77d65d175d44e 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -362,6 +362,7 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode); bool msm_dp_wide_bus_available(const struct msm_dp *dp_display); int msm_dp_get_mst_max_stream(struct msm_dp *dp_display); +int msm_dp_mst_register(struct msm_dp *dp_display); #else static inline int __init msm_dp_register(void) { @@ -383,6 +384,11 @@ static inline int msm_dp_get_mst_max_stream(struct msm_dp *dp_display) return -EINVAL; } +static inline int msm_dp_mst_register(struct msm_dp *dp_display) +{ + return -EINVAL; +} + static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display) { } From 5a43f00801d32a5a645336ec1cb082cd0f0c3e6a Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:41 +0800 Subject: [PATCH 149/188] FROMLIST: drm/msm/dpu: expose dpu_encoder ops for DP MST reuse Export dpu_encoder_atomic_mode_set, dpu_encoder_phys_enable and dpu_encoder_phys_disable so MST encoder helper funcs can reuse them. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-20-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 20 ++++++++++---------- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h | 6 ++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 778e231d49677..1c74ff6f0dbd4 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -1149,9 +1149,9 @@ void dpu_encoder_cleanup_wb_job(struct drm_encoder *drm_enc, } } -static void dpu_encoder_virt_atomic_mode_set(struct drm_encoder *drm_enc, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) +void dpu_encoder_atomic_mode_set(struct drm_encoder *drm_enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { struct dpu_encoder_virt *dpu_enc; struct msm_drm_private *priv; @@ -1334,8 +1334,8 @@ void dpu_encoder_virt_runtime_resume(struct drm_encoder *drm_enc) mutex_unlock(&dpu_enc->enc_lock); } -static void dpu_encoder_virt_atomic_enable(struct drm_encoder *drm_enc, - struct drm_atomic_commit *state) +void dpu_encoder_phys_enable(struct drm_encoder *drm_enc, + struct drm_atomic_commit *state) { struct dpu_encoder_virt *dpu_enc = NULL; int ret = 0; @@ -1381,8 +1381,8 @@ static void dpu_encoder_virt_atomic_enable(struct drm_encoder *drm_enc, mutex_unlock(&dpu_enc->enc_lock); } -static void dpu_encoder_virt_atomic_disable(struct drm_encoder *drm_enc, - struct drm_atomic_commit *state) +void dpu_encoder_phys_disable(struct drm_encoder *drm_enc, + struct drm_atomic_commit *state) { struct dpu_encoder_virt *dpu_enc = NULL; struct drm_crtc *crtc; @@ -2739,9 +2739,9 @@ static void dpu_encoder_frame_done_timeout(struct timer_list *t) } static const struct drm_encoder_helper_funcs dpu_encoder_helper_funcs = { - .atomic_mode_set = dpu_encoder_virt_atomic_mode_set, - .atomic_disable = dpu_encoder_virt_atomic_disable, - .atomic_enable = dpu_encoder_virt_atomic_enable, + .atomic_mode_set = dpu_encoder_atomic_mode_set, + .atomic_disable = dpu_encoder_phys_disable, + .atomic_enable = dpu_encoder_phys_enable, }; static const struct drm_encoder_funcs dpu_encoder_funcs = { diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h index 4942097e76137..25ade3dbbeda8 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h @@ -98,4 +98,10 @@ void dpu_encoder_cleanup_wb_job(struct drm_encoder *drm_enc, bool dpu_encoder_is_valid_for_commit(struct drm_encoder *drm_enc); void dpu_encoder_start_frame_done_timer(struct drm_encoder *drm_enc); + +void dpu_encoder_phys_enable(struct drm_encoder *enc, struct drm_atomic_commit *state); +void dpu_encoder_phys_disable(struct drm_encoder *enc, struct drm_atomic_commit *state); +void dpu_encoder_atomic_mode_set(struct drm_encoder *enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); #endif /* __DPU_ENCODER_H__ */ From e877e9059af65a582924cfdf1e2cad47935e32d2 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:42 +0800 Subject: [PATCH 150/188] FROMLIST: drm/msm/dpu: use msm_dp_get_mst_intf_id() to get the intf id Use msm_dp_get_mst_intf_id() to get the interface ID for the DP MST controller as the intf_id is unique for each MST stream of each DP controller. For DSI/eDP/DP SST, the stream_id is always 0, so existing behavior remains unchanged. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-21-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 20 +++++++++++--------- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h | 2 ++ drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 4 ++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 1c74ff6f0dbd4..3adfaeaab71da 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -1438,18 +1438,21 @@ void dpu_encoder_phys_disable(struct drm_encoder *drm_enc, static struct dpu_hw_intf *dpu_encoder_get_intf(const struct dpu_mdss_cfg *catalog, struct dpu_rm *dpu_rm, - enum dpu_intf_type type, u32 controller_id) + struct msm_display_info *disp_info, u32 controller_id) { - int i = 0; + int i = 0, cnt = 0; + int stream_id = disp_info->stream_id; - if (type == INTF_WB) + if (disp_info->intf_type == INTF_WB) return NULL; + DPU_DEBUG("intf_type 0x%x controller_id %d stream_id %d\n", + disp_info->intf_type, controller_id, stream_id); for (i = 0; i < catalog->intf_count; i++) { - if (catalog->intf[i].type == type - && catalog->intf[i].controller_id == controller_id) { - return dpu_rm_get_intf(dpu_rm, catalog->intf[i].id); - } + if (catalog->intf[i].type == disp_info->intf_type && + controller_id == catalog->intf[i].controller_id) + if (cnt++ == stream_id) + return dpu_rm_get_intf(dpu_rm, catalog->intf[i].id); } return NULL; @@ -2675,8 +2678,7 @@ static int dpu_encoder_setup_display(struct dpu_encoder_virt *dpu_enc, i, controller_id, phys_params.split_role); phys_params.hw_intf = dpu_encoder_get_intf(dpu_kms->catalog, &dpu_kms->rm, - disp_info->intf_type, - controller_id); + disp_info, controller_id); if (disp_info->intf_type == INTF_WB && controller_id < WB_MAX) phys_params.hw_wb = dpu_rm_get_wb(&dpu_kms->rm, controller_id); diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h index 25ade3dbbeda8..861d69afbd763 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h @@ -28,6 +28,7 @@ * @h_tile_instance: Controller instance used per tile. Number of elements is * based on num_of_h_tiles * @is_cmd_mode Boolean to indicate if the CMD mode is requested + * @stream_id stream id for which the interface needs to be acquired * @vsync_source: Source of the TE signal for DSI CMD devices */ struct msm_display_info { @@ -35,6 +36,7 @@ struct msm_display_info { uint32_t num_of_h_tiles; uint32_t h_tile_instance[MAX_H_TILES_PER_DISPLAY]; bool is_cmd_mode; + int stream_id; enum dpu_vsync_source vsync_source; }; diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index 91d33b432427f..b32ecd5b07772 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -614,6 +614,7 @@ static int _dpu_kms_initialize_dsi(struct drm_device *dev, info.h_tile_instance[info.num_of_h_tiles++] = other; info.is_cmd_mode = msm_dsi_is_cmd_mode(priv->kms->dsi[i]); + info.stream_id = 0; rc = dpu_kms_dsi_set_te_source(&info, priv->kms->dsi[i]); if (rc) { @@ -689,6 +690,7 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev, } for (stream_id = 0; stream_id < stream_cnt; stream_id++) { + info.stream_id = stream_id; encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DPMST, &info); if (IS_ERR(encoder)) { DPU_ERROR("encoder init failed for dp mst display\n"); @@ -716,6 +718,7 @@ static int _dpu_kms_initialize_hdmi(struct drm_device *dev, info.num_of_h_tiles = 1; info.h_tile_instance[0] = 0; info.intf_type = INTF_HDMI; + info.stream_id = 0; encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_TMDS, &info); if (IS_ERR(encoder)) { @@ -748,6 +751,7 @@ static int _dpu_kms_initialize_writeback(struct drm_device *dev, /* use only WB idx 2 instance for DPU */ info.h_tile_instance[0] = wb_idx; info.intf_type = INTF_WB; + info.stream_id = 0; maxlinewidth = dpu_rm_get_wb(&dpu_kms->rm, info.h_tile_instance[0])->caps->maxlinewidth; From 8bd252f05326ae827e7058ebad5892271e68b850 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:43 +0800 Subject: [PATCH 151/188] FROMLIST: drm/msm/dp: wire MST helpers into atomic check and commit paths Call drm_dp_mst_atomic_check() from msm_atomic_check() so MST-specific state, such as connector and topology changes, is validated as part of the atomic check. Hook the MST helpers into atomic_commit_setup() and atomic_commit_tail() to support non-blocking atomic commits for DisplayPort MST, and ensure MST commits properly wait for dependencies. For SST, non-blocking commits are already handled via commit_tail(), which waits for dependencies in the DRM core. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-22-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/msm_atomic.c | 14 +++++++++++++- drivers/gpu/drm/msm/msm_drv.h | 1 + drivers/gpu/drm/msm/msm_kms.c | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c index a8babf1dbe0d3..e70e5088cfe53 100644 --- a/drivers/gpu/drm/msm/msm_atomic.c +++ b/drivers/gpu/drm/msm/msm_atomic.c @@ -4,6 +4,7 @@ * Author: Rob Clark */ +#include #include #include @@ -207,7 +208,11 @@ int msm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state) if (ret) return ret; - return drm_atomic_helper_check(dev, state); + ret = drm_atomic_helper_check(dev, state); + if (ret) + return ret; + + return drm_dp_mst_atomic_check(state); } void msm_atomic_commit_tail(struct drm_atomic_commit *state) @@ -221,6 +226,8 @@ void msm_atomic_commit_tail(struct drm_atomic_commit *state) trace_msm_atomic_commit_tail_start(async, crtc_mask); + drm_dp_mst_atomic_wait_for_dependencies(state); + kms->funcs->enable_commit(kms); /* @@ -322,3 +329,8 @@ void msm_atomic_commit_tail(struct drm_atomic_commit *state) trace_msm_atomic_commit_tail_finish(async, crtc_mask); } + +int msm_atomic_commit_setup(struct drm_atomic_commit *state) +{ + return drm_dp_mst_atomic_setup_commit(state); +} diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 77d65d175d44e..23e9945f49f7c 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -211,6 +211,7 @@ int msm_atomic_init_pending_timer(struct msm_pending_timer *timer, struct msm_kms *kms, int crtc_idx); void msm_atomic_destroy_pending_timer(struct msm_pending_timer *timer); void msm_atomic_commit_tail(struct drm_atomic_commit *state); +int msm_atomic_commit_setup(struct drm_atomic_commit *state); int msm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); struct drm_atomic_commit *msm_atomic_state_alloc(struct drm_device *dev); diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c index e5d0ea6294484..33ab30300e16d 100644 --- a/drivers/gpu/drm/msm/msm_kms.c +++ b/drivers/gpu/drm/msm/msm_kms.c @@ -29,6 +29,7 @@ static const struct drm_mode_config_funcs mode_config_funcs = { static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = { .atomic_commit_tail = msm_atomic_commit_tail, + .atomic_commit_setup = msm_atomic_commit_setup, }; static irqreturn_t msm_irq(int irq, void *arg) From 9eb489a7200be8dec87616e49dd07a14c60d7457 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:44 +0800 Subject: [PATCH 152/188] FROMLIST: drm/msm/dp: add dp_mst_drm to manage DP MST encoder operations drm/msm/dp: introduce dp_mst_drm for MST stream management Add a dp_mst_drm layer to manage DP MST streams with a clear ownership model between encoder, panel and connector. Each MST stream is represented by a dedicated drm_encoder. At modeset initialization time, one (encoder, dp_panel) pair is created per stream_id and remains fixed for the lifetime of the driver. The dp_panel thus carries a stable stream context, including stream_id and pixel mapping. MST connectors are created and destroyed dynamically on hotplug and are attached to a dp_panel through atomic routing. During an atomic commit, connectors are associated with encoders via atomic_best_encoder(), forming a temporary binding for the duration of the commit. Encoder helper callbacks drive the MST stream lifecycle, including timeslot allocation, link enable/disable and payload programming. A per-MST-instance lock serializes operations on shared link state across multiple streams. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-23-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 6 + drivers/gpu/drm/msm/dp/dp_display.c | 9 + drivers/gpu/drm/msm/dp/dp_display.h | 2 + drivers/gpu/drm/msm/dp/dp_mst_drm.c | 245 ++++++++++++++++++++++++ drivers/gpu/drm/msm/msm_drv.h | 7 + 5 files changed, 269 insertions(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index b32ecd5b07772..ac5dc844feada 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -696,6 +696,12 @@ static int _dpu_kms_initialize_displayport(struct drm_device *dev, DPU_ERROR("encoder init failed for dp mst display\n"); return PTR_ERR(encoder); } + + rc = msm_dp_mst_attach_encoder(priv->kms->dp[i], encoder); + if (rc) { + DPU_ERROR("dp_mst attach_encoder failed, rc = %d\n", rc); + return rc; + } } } } diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index ce717f9ce5af6..0456d76c2f7f1 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -601,6 +601,15 @@ struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display, return dp_panel; } +void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display, + struct msm_dp_link_info *dst) +{ + struct msm_dp_display_private *dp = + container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + + memcpy(dst, &dp->panel->link_info, sizeof(*dst)); +} + static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index a185819ec57e8..fb6bdd372b52f 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -63,4 +63,6 @@ void msm_dp_display_unprepare(struct msm_dp *dp); struct msm_dp_panel *msm_dp_display_get_panel(struct msm_dp *msm_dp_display, enum msm_dp_stream_id stream_id); +void msm_dp_display_set_link_info(struct msm_dp *msm_dp_display, + struct msm_dp_link_info *dst); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c index 78b8dffe111b9..6a77fdef85e97 100644 --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c @@ -4,18 +4,259 @@ */ #include +#include +#include #include #include "dp_mst_drm.h" #include "dp_panel.h" +#include "dpu_encoder.h" + +#define to_dp_mst_connector(x) \ + container_of((x), struct msm_dp_mst_connector, connector) + +struct msm_dp_mst_encoder { + struct drm_encoder *enc; + int stream_id; + struct msm_dp_panel *dp_panel; +}; + +struct msm_dp_mst_connector { + struct drm_connector connector; + struct drm_dp_mst_port *mst_port; + struct msm_dp_mst *dp_mst; +}; + struct msm_dp_mst { struct drm_dp_mst_topology_mgr mst_mgr; + struct msm_dp_mst_encoder mst_encoders[DP_STREAM_MAX]; struct msm_dp *msm_dp; struct drm_dp_aux *dp_aux; u32 max_streams; + struct mutex mst_lock; + struct msm_dp_link_info link_info; }; +static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst, + struct drm_encoder *enc) +{ + int i; + + for (i = 0; i < mst->max_streams; i++) { + if (mst->mst_encoders[i].enc == enc) + return mst->mst_encoders[i].dp_panel; + } + return NULL; +} + +static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst, + struct msm_dp_panel *panel, + struct drm_dp_mst_atomic_payload *payload) +{ + if (payload->vc_start_slot < 0) + msm_dp_display_set_stream_info(mst->msm_dp, panel, 1, 0, 0); + else + msm_dp_display_set_stream_info(mst->msm_dp, panel, + payload->vc_start_slot, + payload->time_slots, payload->pbn); + + drm_dbg_kms(mst->msm_dp->drm_dev, + "[MST] stream:%u timeslots vc_start:%d slots:%d pbn:%d\n", + panel->stream_id, payload->vc_start_slot, + payload->time_slots, payload->pbn); +} + +static void msm_dp_mst_stream_enable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_connector *connector = + drm_atomic_get_new_connector_for_encoder(state, encoder); + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct msm_dp *dp_display = mst->msm_dp; + struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder); + struct drm_dp_mst_port *port = mst_conn->mst_port; + struct drm_dp_mst_topology_state *mst_state = + drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr); + struct drm_dp_mst_atomic_payload *payload = + drm_atomic_get_mst_payload_state(mst_state, port); + int rc; + + panel->connector = connector; + + guard(mutex)(&mst->mst_lock); + + rc = msm_dp_display_set_mode_helper(dp_display, state, encoder, panel); + if (rc) { + drm_err(dp_display->drm_dev, + "[MST] stream:%u set_mode failed rc=%d\n", panel->stream_id, rc); + goto out; + } + + rc = msm_dp_display_prepare_link(dp_display); + if (rc) { + drm_err(dp_display->drm_dev, + "[MST] stream:%u prepare_link failed rc=%d\n", panel->stream_id, rc); + msm_dp_display_unprepare(dp_display); + goto out; + } + + drm_dp_mst_update_slots(mst_state, DP_CAP_ANSI_8B10B); + + rc = drm_dp_add_payload_part1(&mst->mst_mgr, mst_state, payload); + if (rc) { + drm_err(dp_display->drm_dev, + "[MST] payload allocation failure for conn:%d\n", connector->base.id); + msm_dp_display_unprepare(dp_display); + goto out; + } + + msm_dp_mst_update_timeslots(mst, panel, payload); + + msm_dp_display_enable_helper(dp_display, panel); + + drm_dp_check_act_status(&mst->mst_mgr); + + drm_dp_add_payload_part2(&mst->mst_mgr, payload); + +out: + drm_connector_get(connector); +} + +static void msm_dp_mst_stream_disable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder); + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder); + struct drm_dp_mst_topology_state *old_mst_state = + drm_atomic_get_old_mst_topology_state(state, &mst->mst_mgr); + struct drm_dp_mst_topology_state *new_mst_state = + drm_atomic_get_new_mst_topology_state(state, &mst->mst_mgr); + struct drm_dp_mst_atomic_payload *old_payload = + drm_atomic_get_mst_payload_state(old_mst_state, mst_conn->mst_port); + struct drm_dp_mst_atomic_payload *new_payload = + drm_atomic_get_mst_payload_state(new_mst_state, mst_conn->mst_port); + + guard(mutex)(&mst->mst_lock); + + drm_dp_remove_payload_part1(&mst->mst_mgr, new_mst_state, new_payload); + + drm_dp_remove_payload_part2(&mst->mst_mgr, new_mst_state, old_payload, new_payload); + + msm_dp_mst_update_timeslots(mst, panel, new_payload); + + msm_dp_display_disable_helper(mst->msm_dp, panel); + + drm_dp_check_act_status(&mst->mst_mgr); +} + +static void msm_dp_mst_stream_post_disable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_connector *connector = drm_atomic_get_old_connector_for_encoder(state, encoder); + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct msm_dp_panel *panel = msm_dp_mst_panel_from_encoder(mst, encoder); + + guard(mutex)(&mst->mst_lock); + + msm_dp_display_atomic_post_disable_helper(mst->msm_dp, panel); + + if (!mst->msm_dp->mst_active) + msm_dp_display_unprepare(mst->msm_dp); + + panel->connector = NULL; + + drm_connector_put(connector); +} + +static int msm_dp_mst_enc_atomic_check(struct drm_encoder *enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(conn_state->connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct drm_dp_mst_topology_state *mst_state; + int bpp, pbn, slots; + + if (!conn_state->crtc) + return 0; + + if (!drm_atomic_crtc_needs_modeset(crtc_state) || !crtc_state->active) + return 0; + + bpp = (conn_state->connector->display_info.bpc * 3) ?: 24; /* fallback: assume 8bpc */ + pbn = drm_dp_calc_pbn_mode(crtc_state->mode.clock, bpp << 4); + + mst_state = drm_atomic_get_mst_topology_state(crtc_state->state, &mst->mst_mgr); + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + if (!dfixed_trunc(mst_state->pbn_div)) { + mst_state->pbn_div = + drm_dp_get_vc_payload_bw(mst->link_info.rate, + mst->link_info.num_lanes); + } + + slots = drm_dp_atomic_find_time_slots(crtc_state->state, &mst->mst_mgr, + mst_conn->mst_port, pbn); + if (slots < 0) + return slots; + + return 0; +} + +static void msm_dp_mst_enc_atomic_enable(struct drm_encoder *enc, + struct drm_atomic_commit *state) +{ + msm_dp_mst_stream_enable(enc, state); + dpu_encoder_phys_enable(enc, state); +} + +static void msm_dp_mst_enc_atomic_disable(struct drm_encoder *enc, + struct drm_atomic_commit *state) +{ + msm_dp_mst_stream_disable(enc, state); + dpu_encoder_phys_disable(enc, state); + msm_dp_mst_stream_post_disable(enc, state); +} + +static const struct drm_encoder_helper_funcs msm_dp_mst_encoder_helper_funcs = { + .atomic_check = msm_dp_mst_enc_atomic_check, + .atomic_mode_set = dpu_encoder_atomic_mode_set, + .atomic_enable = msm_dp_mst_enc_atomic_enable, + .atomic_disable = msm_dp_mst_enc_atomic_disable, +}; + +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder) +{ + struct msm_dp_mst *mst = dp_display->msm_dp_mst; + struct msm_dp_panel *dp_panel; + int i; + + for (i = 0; i < mst->max_streams; i++) { + if (!mst->mst_encoders[i].enc) + break; + } + + dp_panel = msm_dp_display_get_panel(dp_display, i); + if (!dp_panel) { + drm_err(dp_display->drm_dev, + "[MST] failed to allocate panel for stream %d\n", i); + return -ENOMEM; + } + + mst->mst_encoders[i].enc = encoder; + mst->mst_encoders[i].stream_id = i; + mst->mst_encoders[i].dp_panel = dp_panel; + drm_encoder_helper_add(encoder, &msm_dp_mst_encoder_helper_funcs); + + return 0; +} + int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state) { struct msm_dp_mst *mst = dp_display->msm_dp_mst; @@ -27,6 +268,9 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state) "[MST] failed to set topology mgr state to %d rc:%d\n", state, rc); } + if (state) + msm_dp_display_set_link_info(dp_display, &mst->link_info); + drm_dbg_kms(dp_display->drm_dev, "[MST] set_mgr_state state:%d\n", state); return rc; } @@ -55,6 +299,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au return ret; } + mutex_init(&mst->mst_lock); dp_display->msm_dp_mst = mst; return 0; } diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 23e9945f49f7c..f599ebe670190 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -364,6 +364,8 @@ bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, bool msm_dp_wide_bus_available(const struct msm_dp *dp_display); int msm_dp_get_mst_max_stream(struct msm_dp *dp_display); int msm_dp_mst_register(struct msm_dp *dp_display); +int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder); + #else static inline int __init msm_dp_register(void) { @@ -390,6 +392,11 @@ static inline int msm_dp_mst_register(struct msm_dp *dp_display) return -EINVAL; } +static inline int msm_dp_mst_attach_encoder(struct msm_dp *dp_display, struct drm_encoder *encoder) +{ + return -EINVAL; +} + static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display) { } From 41eb850d2bc99ad8b0f10f13469b81527a90c1c5 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:45 +0800 Subject: [PATCH 153/188] FROMLIST: drm/msm/dp: add connector abstraction for DP MST Introduce an MST connector abstraction for DP MST, with each MST connector associated with a DP panel and connected through a DRM bridge to an MST encoder. The connector is only used for MST helper callbacks, such as detect, get_modes, and get_encoder. Display enable/disable, hotplug handling, and modeset sequencing continue to be handled by the bridge path. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-24-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_mst_drm.c | 232 ++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c index 6a77fdef85e97..12b47a413793d 100644 --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "dp_mst_drm.h" #include "dp_panel.h" @@ -50,6 +51,18 @@ static struct msm_dp_panel *msm_dp_mst_panel_from_encoder(struct msm_dp_mst *mst return NULL; } +static int msm_dp_mst_encoder_stream_id(struct msm_dp_mst *mst, + struct drm_encoder *enc) +{ + int i; + + for (i = 0; i < mst->max_streams; i++) { + if (mst->mst_encoders[i].enc == enc) + return mst->mst_encoders[i].stream_id; + } + return -1; +} + static void msm_dp_mst_update_timeslots(struct msm_dp_mst *mst, struct msm_dp_panel *panel, struct drm_dp_mst_atomic_payload *payload) @@ -275,6 +288,224 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state) return rc; } +/* DP MST Connector OPs */ +static int +msm_dp_mst_connector_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct msm_dp *dp_display = mst->msm_dp; + struct device *dev = dp_display->drm_dev->dev; + enum drm_connector_status status = connector_status_disconnected; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return status; + + if (dp_display->mst_active) + status = drm_dp_mst_detect_port(connector, + ctx, &mst->mst_mgr, mst_conn->mst_port); + + pm_runtime_put_autosuspend(dev); + + return status; +} + +static int msm_dp_mst_connector_get_modes(struct drm_connector *connector) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + const struct drm_edid *drm_edid; + int rc; + + drm_edid = drm_dp_mst_edid_read(connector, &mst->mst_mgr, mst_conn->mst_port); + drm_edid_connector_update(connector, drm_edid); + + rc = drm_edid_connector_add_modes(connector); + + drm_edid_free(drm_edid); + + return rc; +} + +static enum drm_mode_status msm_dp_mst_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct msm_dp_mst_connector *mst_conn; + struct drm_dp_mst_port *mst_port; + struct msm_dp *dp_display; + int required_pbn; + + if (drm_connector_is_unregistered(connector)) + return 0; + + mst_conn = to_dp_mst_connector(connector); + mst_port = mst_conn->mst_port; + dp_display = mst_conn->dp_mst->msm_dp; + + /* FIXME: use negotiated bpp (DSC, YUV 4:2:0, etc.); for now use + * 18bpp (6bpc) as a conservative lower bound like i915/nouveau. + */ + required_pbn = drm_dp_calc_pbn_mode(mode->clock, (6 * 3) << 4); + + if (required_pbn > mst_port->full_pbn) { + drm_dbg_dp(dp_display->drm_dev, "mode:%s not supported.\n", mode->name); + return MODE_CLOCK_HIGH; + } + + return msm_dp_display_mode_valid(dp_display, &connector->display_info, mode); +} + +static const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs; + +static struct drm_encoder * +msm_dp_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + struct drm_connector_state *conn_state; + struct drm_connector *iter; + struct drm_connector_list_iter conn_iter; + u32 stream_mask = 0; + u32 i; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return NULL; + + if (conn_state->best_encoder) + return conn_state->best_encoder; + + drm_connector_list_iter_begin(connector->dev, &conn_iter); + drm_for_each_connector_iter(iter, &conn_iter) { + struct drm_connector_state *peer_state; + int stream_id; + + if (iter == connector || + iter->funcs != &msm_dp_drm_mst_connector_funcs || + to_dp_mst_connector(iter)->dp_mst != mst) + continue; + + peer_state = drm_atomic_get_new_connector_state(state, iter) ?: iter->state; + if (!peer_state || !peer_state->crtc || !peer_state->best_encoder) + continue; + + stream_id = msm_dp_mst_encoder_stream_id(mst, peer_state->best_encoder); + if (stream_id >= 0 && stream_id < mst->max_streams) + stream_mask |= BIT(stream_id); + } + drm_connector_list_iter_end(&conn_iter); + + for (i = 0; i < mst->max_streams; i++) { + if (!(stream_mask & BIT(i))) { + conn_state->best_encoder = mst->mst_encoders[i].enc; + return mst->mst_encoders[i].enc; + } + } + + return NULL; +} + +static int msm_dp_mst_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + struct msm_dp_mst *mst = mst_conn->dp_mst; + + return drm_dp_atomic_release_time_slots(state, &mst->mst_mgr, mst_conn->mst_port); +} + +static void dp_mst_connector_destroy(struct drm_connector *connector) +{ + struct msm_dp_mst_connector *mst_conn = to_dp_mst_connector(connector); + + drm_connector_cleanup(connector); + drm_dp_mst_put_port_malloc(mst_conn->mst_port); + kfree(mst_conn); +} + +/* DRM MST callbacks */ +static const struct drm_connector_helper_funcs msm_dp_drm_mst_connector_helper_funcs = { + .get_modes = msm_dp_mst_connector_get_modes, + .detect_ctx = msm_dp_mst_connector_detect, + .mode_valid = msm_dp_mst_connector_mode_valid, + .atomic_best_encoder = msm_dp_mst_atomic_best_encoder, + .atomic_check = msm_dp_mst_connector_atomic_check, +}; + +static const struct drm_connector_funcs msm_dp_drm_mst_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .destroy = dp_mst_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct drm_connector * +msm_dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, const char *pathprop) +{ + struct msm_dp_mst *mst = container_of(mgr, struct msm_dp_mst, mst_mgr); + struct drm_device *dev = mst->msm_dp->drm_dev; + struct msm_dp_mst_connector *mst_conn; + struct drm_connector *connector; + int rc, i; + + mst_conn = kzalloc_obj(*mst_conn); + if (!mst_conn) + return NULL; + + connector = &mst_conn->connector; + rc = drm_connector_dynamic_init(dev, connector, + &msm_dp_drm_mst_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + if (rc) + goto err_free; + + mst_conn->dp_mst = mst; + + drm_connector_helper_add(connector, &msm_dp_drm_mst_connector_helper_funcs); + connector->funcs->reset(connector); + + /* add all encoders as possible encoders */ + for (i = 0; i < mst->max_streams; i++) { + rc = drm_connector_attach_encoder(connector, mst->mst_encoders[i].enc); + if (rc) { + drm_err(dev, "[MST] failed to attach encoder:%u to conn:%d rc:%d\n", + mst->mst_encoders[i].enc->base.id, + connector->base.id, rc); + goto err_connector; + } + } + + mst_conn->mst_port = port; + drm_dp_mst_get_port_malloc(port); + + drm_object_attach_property(&connector->base, + dev->mode_config.path_property, 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tile_property, 0); + drm_connector_set_path_property(connector, pathprop); + + drm_dbg_kms(dev, "[MST] add_connector done conn:%d max_streams:%u\n", + connector->base.id, mst->max_streams); + + return connector; + +err_connector: + drm_connector_cleanup(connector); +err_free: + kfree(mst_conn); + return NULL; +} + +static const struct drm_dp_mst_topology_cbs msm_dp_mst_drm_cbs = { + .add_connector = msm_dp_mst_add_connector, +}; + int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux) { struct drm_device *dev = dp_display->drm_dev; @@ -285,6 +516,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au if (!mst) return -ENOMEM; + mst->mst_mgr.cbs = &msm_dp_mst_drm_cbs; mst->msm_dp = dp_display; mst->max_streams = max_streams; mst->dp_aux = drm_aux; From f98ab73b70b89f93920e16ecdafca1d1fb444f63 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:46 +0800 Subject: [PATCH 154/188] FROMLIST: drm/msm/dp: add HPD callback for dp MST Add HPD callback for the MST module which shall be invoked from the dp_display's HPD handler to perform MST specific operations in case of HPD. In MST case, route the HPD messages to MST module. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-mst-v5-25-2ed6aee1867a@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 22 +++++++++++--- drivers/gpu/drm/msm/dp/dp_mst_drm.c | 46 +++++++++++++++++++++++++++++ drivers/gpu/drm/msm/dp/dp_mst_drm.h | 1 + 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 0456d76c2f7f1..aa440790dc959 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -455,6 +455,9 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp) dp->msm_dp_display.connector_type, dp->link->sink_count); + if (dp->plugged && dp->msm_dp_display.mst_active) + return 0; + guard(mutex)(&dp->plugged_lock); ret = pm_runtime_resume_and_get(&pdev->dev); @@ -550,12 +553,18 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) { u32 sink_request; int rc = 0; + struct msm_dp *msm_dp_display = &dp->msm_dp_display; /* irq_hpd can happen at either connected or disconnected state */ drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n", dp->msm_dp_display.connector_type, dp->link->sink_count); + if (msm_dp_display->mst_active) { + msm_dp_mst_display_hpd_irq(&dp->msm_dp_display); + return 0; + } + /* check for any test request issued by sink */ rc = msm_dp_link_process_request(dp->link); if (!rc) { @@ -1111,9 +1120,13 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id) connector_status_connected); /* Send HPD as connected and distinguish it in the notifier */ - if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) - drm_bridge_hpd_notify(dp->msm_dp_display.bridge, - connector_status_connected); + if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) { + if (dp->msm_dp_display.mst_active) + msm_dp_irq_hpd_handle(dp); + else + drm_bridge_hpd_notify(dp->msm_dp_display.bridge, + connector_status_connected); + } ret = IRQ_HANDLED; @@ -1776,7 +1789,8 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, msm_dp_hpd_plug_handle(dp); } } else { - msm_dp_hpd_unplug_handle(dp); + if (hpd_link_status == ISR_DISCONNECTED) + msm_dp_hpd_unplug_handle(dp); } pm_runtime_put_sync(&msm_dp_display->pdev->dev); diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c index 12b47a413793d..56f7a84e77d15 100644 --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c @@ -36,6 +36,8 @@ struct msm_dp_mst { struct drm_dp_aux *dp_aux; u32 max_streams; struct mutex mst_lock; + /* Serializes HPD IRQ handling between IRQ handler and poll_hpd_irq. */ + struct mutex hpd_irq_lock; struct msm_dp_link_info link_info; }; @@ -288,6 +290,41 @@ int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state) return rc; } +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display) +{ + int rc; + struct msm_dp_mst *mst = dp_display->msm_dp_mst; + u8 ack[8] = {}; + u8 esi[4]; + unsigned int esi_res = DP_SINK_COUNT_ESI + 1; + bool handled; + + guard(mutex)(&mst->hpd_irq_lock); + + rc = drm_dp_dpcd_read_data(mst->dp_aux, DP_SINK_COUNT_ESI, esi, 4); + if (rc < 0) { + DRM_ERROR("DPCD sink status read failed, rlen=%d\n", rc); + return; + } + + drm_dbg_dp(dp_display->drm_dev, "MST irq: esi1[0x%x] esi2[0x%x] esi3[%x]\n", + esi[1], esi[2], esi[3]); + + rc = drm_dp_mst_hpd_irq_handle_event(&mst->mst_mgr, esi, ack, &handled); + + /* ack the request */ + if (handled) { + rc = drm_dp_dpcd_write_byte(mst->dp_aux, esi_res, ack[1]); + if (rc < 0) { + DRM_ERROR("DPCD esi_res failed. rc=%d\n", rc); + return; + } + + drm_dp_mst_hpd_irq_send_new_request(&mst->mst_mgr); + } + drm_dbg_dp(dp_display->drm_dev, "MST display hpd_irq handled:%d rc:%d\n", handled, rc); +} + /* DP MST Connector OPs */ static int msm_dp_mst_connector_detect(struct drm_connector *connector, @@ -502,8 +539,16 @@ msm_dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr, return NULL; } +static void msm_dp_mst_poll_hpd_irq(struct drm_dp_mst_topology_mgr *mgr) +{ + struct msm_dp_mst *mst = container_of(mgr, struct msm_dp_mst, mst_mgr); + + msm_dp_mst_display_hpd_irq(mst->msm_dp); +} + static const struct drm_dp_mst_topology_cbs msm_dp_mst_drm_cbs = { .add_connector = msm_dp_mst_add_connector, + .poll_hpd_irq = msm_dp_mst_poll_hpd_irq, }; int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux) @@ -532,6 +577,7 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_au } mutex_init(&mst->mst_lock); + mutex_init(&mst->hpd_irq_lock); dp_display->msm_dp_mst = mst; return 0; } diff --git a/drivers/gpu/drm/msm/dp/dp_mst_drm.h b/drivers/gpu/drm/msm/dp/dp_mst_drm.h index 5d411529f6819..08e145399cfc7 100644 --- a/drivers/gpu/drm/msm/dp/dp_mst_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.h @@ -9,5 +9,6 @@ int msm_dp_mst_init(struct msm_dp *dp_display, u32 max_streams, struct drm_dp_aux *drm_aux); int msm_dp_mst_display_set_mgr_state(struct msm_dp *dp_display, bool state); +void msm_dp_mst_display_hpd_irq(struct msm_dp *dp_display); #endif /* _DP_MST_DRM_H_ */ From e581cd04ef4ea848cdd47925f5847815886c79ee Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:02 +0300 Subject: [PATCH 155/188] FROMLIST: drm/connector: report out-of-band IRQ_HPD events The DisplayPort standard defines a special kind of events called IRQ. These events are used to notify DP Source about the events on the Sink side. It is extremely important for DP MST handling, where the MST events are reported through this IRQ. In case of the USB-C DP AltMode there is no actual HPD pulse, but the events are reported through the bits in the AltMode VDOs. Rename drm_connector_oob_hotplug_event() to drm_connector_dp_oob_status() and extend its interface to report IRQ events to the DisplayPort Sink drivers. Acked-by: Heikki Krogerus Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-1-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/drm_connector.c | 20 ++++++++++++-------- drivers/usb/typec/altmodes/displayport.c | 23 +++++++++++++++-------- include/drm/drm_connector.h | 21 +++++++++++++++++++-- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 11646453aaac9..e7e2fc722b587 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3502,20 +3502,24 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) } /** - * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector + * drm_connector_dp_oob_status - Report out-of-band hotplug event to DisplayPort connector * @connector_fwnode: fwnode_handle to report the event on * @status: hot plug detect logical state + * @extra_status: additional information provided by the sink without changing + * the HPD state (or in addition to such a change). * - * On some hardware a hotplug event notification may come from outside the display - * driver / device. An example of this is some USB Type-C setups where the hardware - * muxes the DisplayPort data and aux-lines but does not pass the altmode HPD - * status bit to the GPU's DP HPD pin. + * In some cases when DisplayPort signals are being routed through the USB + * Type-C port the hotplug event notifications come from outside of the display + * driver / device. In this case hardware muxes the DisplayPort data and + * AUX-lines but does not pass the altmode HPD status bit to the GPU's DP HPD + * pin. * * This function can be used to report these out-of-band events after obtaining * a drm_connector reference through calling drm_connector_find_by_fwnode(). */ -void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, - enum drm_connector_status status) +void drm_connector_dp_oob_status(struct fwnode_handle *connector_fwnode, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_connector *connector; @@ -3528,7 +3532,7 @@ void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, drm_connector_put(connector); } -EXPORT_SYMBOL(drm_connector_oob_hotplug_event); +EXPORT_SYMBOL(drm_connector_dp_oob_status); /** diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 263a89c5f3243..ff3659b8f5a2c 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -187,9 +187,11 @@ static int dp_altmode_status_update(struct dp_altmode *dp) dp->pending_irq_hpd = true; } } else { - drm_connector_oob_hotplug_event(dp->connector_fwnode, + drm_connector_dp_oob_status(dp->connector_fwnode, hpd ? connector_status_connected : - connector_status_disconnected); + connector_status_disconnected, + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); dp->hpd = hpd; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); if (hpd && irq_hpd) { @@ -211,8 +213,11 @@ static int dp_altmode_configured(struct dp_altmode *dp) * configuration is complete to signal HPD. */ if (dp->pending_hpd) { - drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_connected); + drm_connector_dp_oob_status(dp->connector_fwnode, + connector_status_connected, + dp->pending_irq_hpd ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); dp->pending_hpd = false; if (dp->pending_irq_hpd) { @@ -396,8 +401,9 @@ static int dp_altmode_vdm(struct typec_altmode *alt, dp->data.status = 0; dp->data.conf = 0; if (dp->hpd) { - drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_disconnected); + drm_connector_dp_oob_status(dp->connector_fwnode, + connector_status_disconnected, + DRM_CONNECTOR_NO_EXTRA_STATUS); dp->hpd = false; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } @@ -828,8 +834,9 @@ void dp_altmode_remove(struct typec_altmode *alt) typec_altmode_put_plug(dp->plug_prime); if (dp->connector_fwnode) { - drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_disconnected); + drm_connector_dp_oob_status(dp->connector_fwnode, + connector_status_disconnected, + DRM_CONNECTOR_NO_EXTRA_STATUS); fwnode_handle_put(dp->connector_fwnode); } diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 5f5ca023cd654..577ab35de5e0b 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -91,6 +91,22 @@ enum drm_connector_status { connector_status_unknown = 3, }; +/** + * enum drm_connector_status_extra - additional events sent by the sink / + * display together or in replacement of the HPD status changes. + */ +enum drm_connector_status_extra { + /** + * @DRM_CONNECTOR_NO_EXTRA_STATUS: No additional status reported. + */ + DRM_CONNECTOR_NO_EXTRA_STATUS, + /** + * @DRM_CONNECTOR_DP_IRQ_HPD: DisplayPort Sink has sent the + * IRQ_HPD (either by the HPD short pulse or via the AltMode event). + */ + DRM_CONNECTOR_DP_IRQ_HPD, +}; + /** * enum drm_connector_registration_state - userspace registration status for * a &drm_connector @@ -2520,8 +2536,9 @@ drm_connector_is_unregistered(struct drm_connector *connector) DRM_CONNECTOR_UNREGISTERED; } -void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, - enum drm_connector_status status); +void drm_connector_dp_oob_status(struct fwnode_handle *connector_fwnode, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); const char *drm_get_connector_type_name(unsigned int connector_type); const char *drm_get_connector_status_name(enum drm_connector_status status); const char *drm_get_subpixel_order_name(enum subpixel_order order); From bf341e40c79fbfeaf0acf7ff0996aeb765028799 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:03 +0300 Subject: [PATCH 156/188] FROMLIST: drm/connector: pass down IRQ_HPD to the drivers The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Extend drm_connector_funcs::oob_hotplug_event() to pass the notifications about the IRQ_HPD events down to the individual drivers, letting them handle those as required. Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-2-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/display/drm_bridge_connector.c | 17 ++++++++++------- drivers/gpu/drm/drm_connector.c | 2 +- drivers/gpu/drm/i915/display/intel_dp.c | 3 ++- include/drm/drm_connector.h | 3 ++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 649969fca1413..046efd913064d 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -141,7 +141,8 @@ struct drm_bridge_connector { */ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); @@ -154,7 +155,8 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, } static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bridge_connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_connector *connector = &drm_bridge_connector->base; struct drm_device *dev = connector->dev; @@ -163,7 +165,7 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri connector->status = status; mutex_unlock(&dev->mode_config.mutex); - drm_bridge_connector_hpd_notify(connector, status); + drm_bridge_connector_hpd_notify(connector, status, extra_status); drm_kms_helper_connector_hotplug_event(connector); } @@ -171,16 +173,17 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri static void drm_bridge_connector_hpd_cb(void *cb_data, enum drm_connector_status status) { - drm_bridge_connector_handle_hpd(cb_data, status); + drm_bridge_connector_handle_hpd(cb_data, status, DRM_CONNECTOR_NO_EXTRA_STATUS); } static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); - drm_bridge_connector_handle_hpd(bridge_connector, status); + drm_bridge_connector_handle_hpd(bridge_connector, status, extra_status); } static void drm_bridge_connector_enable_hpd(struct drm_connector *connector) @@ -223,7 +226,7 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force) if (hdmi) drm_atomic_helper_connector_hdmi_hotplug(connector, status); - drm_bridge_connector_hpd_notify(connector, status); + drm_bridge_connector_hpd_notify(connector, status, DRM_CONNECTOR_NO_EXTRA_STATUS); } else { switch (connector->connector_type) { case DRM_MODE_CONNECTOR_DPI: diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index e7e2fc722b587..6bcd47439d5f8 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3528,7 +3528,7 @@ void drm_connector_dp_oob_status(struct fwnode_handle *connector_fwnode, return; if (connector->funcs->oob_hotplug_event) - connector->funcs->oob_hotplug_event(connector, status); + connector->funcs->oob_hotplug_event(connector, status, extra_status); drm_connector_put(connector); } diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 5733d2e7ac7f1..7f9faca52199f 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -7003,7 +7003,8 @@ static int intel_dp_connector_atomic_check(struct drm_connector *_connector, } static void intel_dp_oob_hotplug_event(struct drm_connector *_connector, - enum drm_connector_status hpd_state) + enum drm_connector_status hpd_state, + enum drm_connector_status_extra extra_status) { struct intel_connector *connector = to_intel_connector(_connector); struct intel_display *display = to_intel_display(connector); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 577ab35de5e0b..a4133a28b461a 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1720,7 +1720,8 @@ struct drm_connector_funcs { * has been received from a source outside the display driver / device. */ void (*oob_hotplug_event)(struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @debugfs_init: From a17f3554dd96a7b9209f6fb74f96dd5ffb463e8c Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:04 +0300 Subject: [PATCH 157/188] FROMLIST: drm/bridge: aux-hpd: let drivers pass IRQ_HPD events The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Let users of aux-hpd, the UCSI and PMIC GLINK drivers pass the IRQ_HPD events to the DisplayPort drivers. The drm_aux_hpd_bridge_notify() is kept to ease merging of the series, preventing extra cross-tree merges. It will be removed once all drivers are converted. The drm_bridge_hpd_notify() function is kept for the drivers which only care about the connector status and will always pass DRM_CONNECTOR_NO_EXTRA_STATUS as the extra status. Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-3-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/aux-hpd-bridge.c | 11 +++++++---- drivers/gpu/drm/drm_bridge.c | 15 +++++++++------ include/drm/bridge/aux-bridge.h | 13 +++++++++++-- include/drm/drm_bridge.h | 22 ++++++++++++++++++++-- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c index f02a38a2638ad..0e2f0b0461213 100644 --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c @@ -136,16 +136,19 @@ struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_n EXPORT_SYMBOL_GPL(drm_dp_hpd_bridge_register); /** - * drm_aux_hpd_bridge_notify - notify hot plug detection events + * drm_aux_hpd_bridge_notify_extra - notify hot plug detection events * @dev: device created for the HPD bridge * @status: output connection status + * @extra_status: extra status bits like DRM_CONNECTOR_DP_IRQ_HPD * * A wrapper around drm_bridge_hpd_notify() that is used to report hot plug * detection events for bridges created via drm_dp_hpd_bridge_register(). * * This function shall be called in a context that can sleep. */ -void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct auxiliary_device *adev = to_auxiliary_dev(dev); struct drm_aux_hpd_bridge_data *data = auxiliary_get_drvdata(adev); @@ -153,9 +156,9 @@ void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status sta if (!data) return; - drm_bridge_hpd_notify(&data->bridge, status); + drm_bridge_hpd_notify_extra(&data->bridge, status, extra_status); } -EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify); +EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify_extra); static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder, diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 687b36eea0c70..84e55db5c1fed 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1495,25 +1495,28 @@ void drm_bridge_hpd_disable(struct drm_bridge *bridge) EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable); /** - * drm_bridge_hpd_notify - notify hot plug detection events + * drm_bridge_hpd_notify_extra - notify hot plug detection and sink IRQ events * @bridge: bridge control structure * @status: output connection status + * @extra_status: additional status recorded by the sink * * Bridge drivers shall call this function to report hot plug events when they - * detect a change in the output status, when hot plug detection has been - * enabled by drm_bridge_hpd_enable(). + * detect a change in the output status or when the sink has reported extra HPD + * status events (like the IRQ_HPD in case of the DisplayPort), when hot plug + * detection has been enabled by drm_bridge_hpd_enable(). * * This function shall be called in a context that can sleep. */ -void drm_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status) +void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { mutex_lock(&bridge->hpd_mutex); if (bridge->hpd_cb) bridge->hpd_cb(bridge->hpd_data, status); mutex_unlock(&bridge->hpd_mutex); } -EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify_extra); #ifdef CONFIG_OF /** diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h index c2f5a855512f3..f9a86886b0dfa 100644 --- a/include/drm/bridge/aux-bridge.h +++ b/include/drm/bridge/aux-bridge.h @@ -25,7 +25,9 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev); struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_node *np); -void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status); +void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); #else static inline struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, struct device_node *np) @@ -44,9 +46,16 @@ static inline struct device *drm_dp_hpd_bridge_register(struct device *parent, return NULL; } -static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +static inline void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { } #endif +static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +{ + drm_aux_hpd_bridge_notify_extra(dev, status, DRM_CONNECTOR_NO_EXTRA_STATUS); +} + #endif diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 4ba3a5deef9a6..78b0d83ef2aa9 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1581,8 +1581,26 @@ void drm_bridge_hpd_enable(struct drm_bridge *bridge, enum drm_connector_status status), void *data); void drm_bridge_hpd_disable(struct drm_bridge *bridge); -void drm_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status); +void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); + +/** + * drm_bridge_hpd_notify - notify hot plug detection events + * @bridge: bridge control structure + * @status: output connection status + * + * Bridge drivers shall call this function to report hot plug events when they + * detect a change in the output status, when hot plug detection has been + * enabled by drm_bridge_hpd_enable(). + * + * This function shall be called in a context that can sleep. + */ +static inline void drm_bridge_hpd_notify(struct drm_bridge *bridge, + enum drm_connector_status status) +{ + drm_bridge_hpd_notify_extra(bridge, status, DRM_CONNECTOR_NO_EXTRA_STATUS); +} #ifdef CONFIG_DRM_PANEL_BRIDGE bool drm_bridge_is_panel(const struct drm_bridge *bridge); From 9b01e30f7c047d59a77388fa1182feae6d40575f Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:05 +0300 Subject: [PATCH 158/188] FROMLIST: drm/bridge: pass extra events to the HPD callback The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Bridge drivers report these events through the drm_bridge_hpd_notify_extra(). Pass down the extra status to the HPD callback, specified during the drm_bridge_hpd_enable(), letting underlying drivers (e.g. drm_bridge_connector) to receive these events. Signed-off-by: Dmitry Baryshkov Acked-by: Francesco Dolcini # lt8912b Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-4-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/chrontel-ch7033.c | 3 ++- drivers/gpu/drm/bridge/lontium-lt8912b.c | 3 ++- drivers/gpu/drm/bridge/ti-tfp410.c | 3 ++- drivers/gpu/drm/display/drm_bridge_connector.c | 5 +++-- drivers/gpu/drm/drm_bridge.c | 5 +++-- include/drm/drm_bridge.h | 6 ++++-- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index a237c65ebd690..35df99b02c9c7 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -258,7 +258,8 @@ static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { .best_encoder = ch7033_connector_best_encoder, }; -static void ch7033_hpd_event(void *arg, enum drm_connector_status status) +static void ch7033_hpd_event(void *arg, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct ch7033_priv *priv = arg; diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index 729b12b674706..b8e643cf1d4da 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -505,7 +505,8 @@ static int lt8912_attach_dsi(struct lt8912 *lt) return 0; } -static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status) +static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct lt8912 *lt = data; diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index bf4ab4eaf2697..e2eedaaa3552e 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -110,7 +110,8 @@ static void tfp410_hpd_work_func(struct work_struct *work) drm_helper_hpd_irq_event(dvi->bridge.dev); } -static void tfp410_hpd_callback(void *arg, enum drm_connector_status status) +static void tfp410_hpd_callback(void *arg, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct tfp410 *dvi = arg; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 046efd913064d..a34643d3ddef8 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -171,9 +171,10 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri } static void drm_bridge_connector_hpd_cb(void *cb_data, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { - drm_bridge_connector_handle_hpd(cb_data, status, DRM_CONNECTOR_NO_EXTRA_STATUS); + drm_bridge_connector_handle_hpd(cb_data, status, extra_status); } static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connector, diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 84e55db5c1fed..3fd3e7bac588c 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1445,7 +1445,8 @@ EXPORT_SYMBOL_GPL(drm_bridge_edid_read); */ void drm_bridge_hpd_enable(struct drm_bridge *bridge, void (*cb)(void *data, - enum drm_connector_status status), + enum drm_connector_status status, + enum drm_connector_status_extra extra_status), void *data) { if (!(bridge->ops & DRM_BRIDGE_OP_HPD)) @@ -1513,7 +1514,7 @@ void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, { mutex_lock(&bridge->hpd_mutex); if (bridge->hpd_cb) - bridge->hpd_cb(bridge->hpd_data, status); + bridge->hpd_cb(bridge->hpd_data, status, extra_status); mutex_unlock(&bridge->hpd_mutex); } EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify_extra); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 78b0d83ef2aa9..6a5edfda2ddd4 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1260,7 +1260,8 @@ struct drm_bridge { * @hpd_cb: Hot plug detection callback, registered with * drm_bridge_hpd_enable(). */ - void (*hpd_cb)(void *data, enum drm_connector_status status); + void (*hpd_cb)(void *data, enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @hpd_data: Private data passed to the Hot plug detection callback * @hpd_cb. @@ -1578,7 +1579,8 @@ const struct drm_edid *drm_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector); void drm_bridge_hpd_enable(struct drm_bridge *bridge, void (*cb)(void *data, - enum drm_connector_status status), + enum drm_connector_status status, + enum drm_connector_status_extra extra_status), void *data); void drm_bridge_hpd_disable(struct drm_bridge *bridge); void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, From 078fb844934b9b25f05224d06283569d324ef13e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:06 +0300 Subject: [PATCH 159/188] FROMLIST: drm/bridge: pass down IRQ_HPD to the drivers The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Pass down the extra status to the bridge drivers via the hpd_notify() callback, letting DP bridges to act accordingly. Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-5-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 ++- drivers/gpu/drm/display/drm_bridge_connector.c | 2 +- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 3 ++- drivers/gpu/drm/msm/dp/dp_display.c | 3 ++- drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 3 ++- include/drm/drm_bridge.h | 3 ++- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 9bec7cf96e407..8affa1a1545f5 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -437,7 +437,8 @@ static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *brid static void lt9611uxc_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { const struct drm_edid *drm_edid; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index a34643d3ddef8..8f7075fd2aa5c 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -150,7 +150,7 @@ static void drm_bridge_connector_hpd_notify(struct drm_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->hpd_notify) - bridge->funcs->hpd_notify(bridge, connector, status); + bridge->funcs->hpd_notify(bridge, connector, status, extra_status); } } diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 55c0601df3c62..4aecf0ffcf75c 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -323,7 +323,8 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index aa440790dc959..b930331d2173a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1762,7 +1762,8 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index da412c788503c..f6b96c27408af 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -31,6 +31,7 @@ void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); #endif /* _DP_DRM_H_ */ diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index e306247ed8a05..d02d432abde46 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -429,7 +429,8 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge, static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 6a5edfda2ddd4..9c4c88024cc50 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -615,7 +615,8 @@ struct drm_bridge_funcs { */ void (*hpd_notify)(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @hpd_enable: From 77a0140b9093e5fbc37b8c8f0a270c825b5bb0e1 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:07 +0300 Subject: [PATCH 160/188] FROMLIST: drm/msm: dp: handle the IRQ_HPD events reported by USB-C The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side, for example DP MST events. Let the MSM DisplayPort driver properly track and handle IRQ_HPD delivered over the OOB events (e.g. from the USB-C AltMode handler). Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-6-30b62b335487@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index b930331d2173a..5b2b0fd36d587 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1781,11 +1781,12 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, msm_dp_display->connector_type, hpd_link_status, status); if (status == connector_status_connected) { - if (hpd_link_status == ISR_HPD_REPLUG_COUNT) { + if (hpd_link_status == ISR_IRQ_HPD_PULSE_COUNT || + extra_status == DRM_CONNECTOR_DP_IRQ_HPD) { + msm_dp_irq_hpd_handle(dp); + } else if (hpd_link_status == ISR_HPD_REPLUG_COUNT) { msm_dp_hpd_unplug_handle(dp); msm_dp_hpd_plug_handle(dp); - } else if (hpd_link_status == ISR_IRQ_HPD_PULSE_COUNT) { - msm_dp_irq_hpd_handle(dp); } else { msm_dp_hpd_plug_handle(dp); } From 61452a1e6ab2e1aadf3741f515e4a43c2cbec4dd Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:08 +0300 Subject: [PATCH 161/188] FROMLIST: soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Pass IRQ_HPD events from the firmware to the HPD bridge, letting those to be delivered to the DisplayPort driver. Reviewed-by: Konrad Dybcio Acked-by: Bjorn Andersson Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-7-30b62b335487@oss.qualcomm.com/ --- drivers/soc/qcom/pmic_glink_altmode.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 619bad2c27eeb..946eb20b8f836 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -373,7 +373,11 @@ static void pmic_glink_altmode_worker(struct work_struct *work) else conn_status = connector_status_disconnected; - drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, conn_status); + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, + conn_status, + alt_port->hpd_irq ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { if (alt_port->svid == USB_TYPEC_TBT_SID) pmic_glink_altmode_enable_tbt(altmode, alt_port); From 133f3ddb4dd3ace402fbc49c0ecb40025bbe1a95 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:09 +0300 Subject: [PATCH 162/188] FROMLIST: usb: typec: ucsi: huawei-gaokun: pass down HPD_IRQ events The DisplayPort standard defines a special kind of HPD events called IRQ_HPD. These events are used to notify DP Source about the events on the Sink side. Pass IRQ_HPD events from the EC to the HPD bridge, letting those to be delivered to the DisplayPort driver. Reviewed-by: Pengyu Luo Acked-by: Heikki Krogerus Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-hpd-irq-events-v4-8-30b62b335487@oss.qualcomm.com/ --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index ca1b534cb183d..8dd5cdcab9f95 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -315,10 +315,13 @@ static void gaokun_ucsi_handle_usb_mode(struct gaokun_ucsi_port *port) /* UCSI callback .connector_status() have set orientation */ if (port->bridge && port->svid == USB_TYPEC_DP_SID) - drm_aux_hpd_bridge_notify(&port->bridge->dev, - port->hpd_state ? - connector_status_connected : - connector_status_disconnected); + drm_aux_hpd_bridge_notify_extra(&port->bridge->dev, + port->hpd_state ? + connector_status_connected : + connector_status_disconnected, + port->hpd_irq ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); } From ba6689c25a08c896b124926fe0494741ea00cc9d Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:03 +0800 Subject: [PATCH 163/188] FROMLIST: drm/bridge: allow hpd_notify() to suppress connector hotplug events The bridge connector framework currently invokes all bridge hpd_notify() callbacks and unconditionally emits a connector hotplug event afterwards. However, not every HPD notification requires a userspace hotplug event. In particular, DP MST bridges may use hpd_notify() to propagate HPD and IRQ notifications through the bridge chain while the actual hotplug handling is performed by the DRM DP MST core. Connector creation, removal and userspace hotplug events are already managed by the MST topology framework. Allow hpd_notify() implementations to suppress the bridge connector hotplug event by introducing a bool *send_hotplug parameter. Drivers can clear this flag when HPD processing should not result in a connector hotplug notification. A NULL pointer indicates that hotplug suppression is not supported by the caller, such as the connector detect polling path. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-msttypec-v1-1-646a10256233@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 ++- drivers/gpu/drm/display/drm_bridge_connector.c | 15 +++++++++------ drivers/gpu/drm/meson/meson_encoder_hdmi.c | 3 ++- drivers/gpu/drm/msm/dp/dp_display.c | 3 ++- drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 3 ++- include/drm/drm_bridge.h | 3 ++- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 8affa1a1545f5..30fe757e49723 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -438,7 +438,8 @@ static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *brid static void lt9611uxc_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status_extra extra_status, + bool *send_hotplug) { const struct drm_edid *drm_edid; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 8f7075fd2aa5c..5edca47a025f5 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -142,7 +142,8 @@ struct drm_bridge_connector { static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status_extra extra_status, + bool *send_hotplug) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); @@ -150,13 +151,14 @@ static void drm_bridge_connector_hpd_notify(struct drm_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->hpd_notify) - bridge->funcs->hpd_notify(bridge, connector, status, extra_status); + bridge->funcs->hpd_notify(bridge, connector, status, + extra_status, send_hotplug); } } static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bridge_connector, - enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_connector *connector = &drm_bridge_connector->base; struct drm_device *dev = connector->dev; @@ -165,7 +167,7 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri connector->status = status; mutex_unlock(&dev->mode_config.mutex); - drm_bridge_connector_hpd_notify(connector, status, extra_status); + drm_bridge_connector_hpd_notify(connector, status, extra_status, NULL); drm_kms_helper_connector_hotplug_event(connector); } @@ -227,7 +229,8 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force) if (hdmi) drm_atomic_helper_connector_hdmi_hotplug(connector, status); - drm_bridge_connector_hpd_notify(connector, status, DRM_CONNECTOR_NO_EXTRA_STATUS); + drm_bridge_connector_hpd_notify(connector, status, + DRM_CONNECTOR_NO_EXTRA_STATUS, NULL); } else { switch (connector->connector_type) { case DRM_MODE_CONNECTOR_DPI: diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 4aecf0ffcf75c..a67e7b365c5b3 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -324,7 +324,8 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status_extra extra_status, + bool *send_hotplug) { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 5b2b0fd36d587..32bb358b07871 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1763,7 +1763,8 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status_extra extra_status, + bool *send_hotplug) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index f6b96c27408af..07ddcd0559626 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -32,6 +32,7 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status); + enum drm_connector_status_extra extra_status, + bool *send_hotplug); #endif /* _DP_DRM_H_ */ diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index d02d432abde46..ad659cef16f58 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -430,7 +430,8 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge, static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status) + enum drm_connector_status_extra extra_status, + bool *send_hotplug) { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 9c4c88024cc50..e6de665ce8f65 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -616,7 +616,8 @@ struct drm_bridge_funcs { void (*hpd_notify)(struct drm_bridge *bridge, struct drm_connector *connector, enum drm_connector_status status, - enum drm_connector_status_extra extra_status); + enum drm_connector_status_extra extra_status, + bool *send_hotplug); /** * @hpd_enable: From ae3960a96166d9335e94c34cc522cea4ce5e0949 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:04 +0800 Subject: [PATCH 164/188] FROMLIST: drm/bridge_connector: preserve connector status for IRQ-only HPD events The bridge connector HPD handling path currently updates connector->status for every hpd_notify() invocation. This does not work well for IRQ-only notifications where the event being reported is carried by extra_status and no connector status transition is associated with it. One example is DP MST. HPD IRQs are propagated through drm_bridge_hpd_notify_*() so that bridge drivers can process the notification. During MST operation, however, the SST connector attached to the bridge connector is intentionally kept disconnected while the MST topology manager handles all connector creation, removal and hotplug processing. Updating connector->status for an IRQ-only MST notification may cause the SST connector state to oscillate between connected and disconnected depending on the notification path. These artificial state transitions can later be detected by the polling logic and result in unnecessary hotplug events being generated. Userspace then re-probes connector status, potentially triggering the same sequence again. Treat notifications with status == connector_status_unknown and a valid extra_status as IRQ-only events. Forward the notification to bridge drivers without modifying connector->status. This keeps IRQ delivery working while leaving connector state management to the component that actually owns it, such as the DP MST topology framework. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-msttypec-v1-2-646a10256233@oss.qualcomm.com/ --- drivers/gpu/drm/display/drm_bridge_connector.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 5edca47a025f5..7334d66776042 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -163,6 +163,18 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri struct drm_connector *connector = &drm_bridge_connector->base; struct drm_device *dev = connector->dev; + /* + * IRQ-only notification: extra_status carries the event but + * status is unknown — do not overwrite connector->status. + */ + if (status == connector_status_unknown && + extra_status != DRM_CONNECTOR_NO_EXTRA_STATUS) { + drm_bridge_connector_hpd_notify(connector, + connector->status, + extra_status, NULL); + return; + } + mutex_lock(&dev->mode_config.mutex); connector->status = status; mutex_unlock(&dev->mode_config.mutex); From f5e0b5de5f8a291854c8ab5fe0662da7398fb14c Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:05 +0800 Subject: [PATCH 165/188] FROMLIST: drm/msm/dp: suppress bridge hotplug events during MST operation The DP MST framework already generates the required hotplug events for MST topology changes. Suppress connector hotplug event generation from the bridge connector path while MST is active, and continue propagating HPD notifications to the DP driver. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-msttypec-v1-3-646a10256233@oss.qualcomm.com/ --- drivers/gpu/drm/display/drm_bridge_connector.c | 6 ++++-- drivers/gpu/drm/msm/dp/dp_display.c | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 7334d66776042..82ed0dc450abc 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -162,6 +162,7 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri { struct drm_connector *connector = &drm_bridge_connector->base; struct drm_device *dev = connector->dev; + bool send_hotplug = true; /* * IRQ-only notification: extra_status carries the event but @@ -179,9 +180,10 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri connector->status = status; mutex_unlock(&dev->mode_config.mutex); - drm_bridge_connector_hpd_notify(connector, status, extra_status, NULL); + drm_bridge_connector_hpd_notify(connector, status, extra_status, &send_hotplug); - drm_kms_helper_connector_hotplug_event(connector); + if (send_hotplug) + drm_kms_helper_connector_hotplug_event(connector); } static void drm_bridge_connector_hpd_cb(void *cb_data, diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 32bb358b07871..691dbef0e9abb 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1790,10 +1790,17 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, msm_dp_hpd_plug_handle(dp); } else { msm_dp_hpd_plug_handle(dp); + /* mst_active is set in plug_handle; suppress SST hotplug */ + if (send_hotplug && msm_dp_display->mst_active) + *send_hotplug = false; } } else { - if (hpd_link_status == ISR_DISCONNECTED) + if (!msm_dp_display->mst_active) { msm_dp_hpd_unplug_handle(dp); + } else if (send_hotplug) { + msm_dp_hpd_unplug_handle(dp); + *send_hotplug = false; + } } pm_runtime_put_sync(&msm_dp_display->pdev->dev); From 3c516320ed7ba6057e567b36adfc90c2afa89ad0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:06 +0800 Subject: [PATCH 166/188] FROMLIST: drm/msm/dp: report IRQ_HPD as an IRQ-only notification MST reuses the SST connector bridge to propagate HPD IRQ events through the bridge chain. For IRQ_HPD notifications there is no connector state transition to report. Use connector_status_unknown together with DRM_CONNECTOR_DP_IRQ_HPD so that the bridge connector framework treats them as IRQ-only notifications and forwards them without modifying connector state. The DP driver handles IRQ_HPD events based on DRM_CONNECTOR_DP_IRQ_HPD rather than connector status transitions. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-msttypec-v1-4-646a10256233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 22 +++++++++------------- drivers/soc/qcom/pmic_glink_altmode.c | 14 +++++++++----- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 691dbef0e9abb..d28369d1a5fa9 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1119,14 +1119,10 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id) drm_bridge_hpd_notify(dp->msm_dp_display.bridge, connector_status_connected); - /* Send HPD as connected and distinguish it in the notifier */ - if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) { - if (dp->msm_dp_display.mst_active) - msm_dp_irq_hpd_handle(dp); - else - drm_bridge_hpd_notify(dp->msm_dp_display.bridge, - connector_status_connected); - } + if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) + drm_bridge_hpd_notify_extra(dp->msm_dp_display.bridge, + connector_status_unknown, + DRM_CONNECTOR_DP_IRQ_HPD); ret = IRQ_HANDLED; @@ -1781,11 +1777,11 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, drm_dbg_dp(dp->drm_dev, "type=%d link hpd_link_status=0x%x, status=%d\n", msm_dp_display->connector_type, hpd_link_status, status); - if (status == connector_status_connected) { - if (hpd_link_status == ISR_IRQ_HPD_PULSE_COUNT || - extra_status == DRM_CONNECTOR_DP_IRQ_HPD) { - msm_dp_irq_hpd_handle(dp); - } else if (hpd_link_status == ISR_HPD_REPLUG_COUNT) { + if (extra_status == DRM_CONNECTOR_DP_IRQ_HPD || + hpd_link_status == ISR_IRQ_HPD_PULSE_COUNT) { + msm_dp_irq_hpd_handle(dp); + } else if (status == connector_status_connected) { + if (hpd_link_status == ISR_HPD_REPLUG_COUNT) { msm_dp_hpd_unplug_handle(dp); msm_dp_hpd_plug_handle(dp); } else { diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 946eb20b8f836..28ab8cbb5ef9e 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -373,11 +373,15 @@ static void pmic_glink_altmode_worker(struct work_struct *work) else conn_status = connector_status_disconnected; - drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, - conn_status, - alt_port->hpd_irq ? - DRM_CONNECTOR_DP_IRQ_HPD : - DRM_CONNECTOR_NO_EXTRA_STATUS); + if (alt_port->hpd_irq) { + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, + connector_status_unknown, + DRM_CONNECTOR_DP_IRQ_HPD); + } else { + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, + conn_status, + DRM_CONNECTOR_NO_EXTRA_STATUS); + } } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { if (alt_port->svid == USB_TYPEC_TBT_SID) pmic_glink_altmode_enable_tbt(altmode, alt_port); From 3f48b0c65b47ad101e2caa65d351af2cc2e23a7b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:07 +0800 Subject: [PATCH 167/188] FROMLIST: drm/msm/dp: mark the SST connector disconnected when MST is enabled When MST becomes active, the initial HPD plug notification updates the SST connector state to connected. However, the subsequent SST connector detect path reports disconnected while MST is enabled. This connected -> disconnected transition is then observed by the polling logic and may result in an unnecessary hotplug event. Set the SST connector state to disconnected immediately after MST is initialized so that the detect path does not introduce a transient state change. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260629-msm-dp-msttypec-v1-5-646a10256233@oss.qualcomm.com/ --- drivers/gpu/drm/msm/dp/dp_display.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index d28369d1a5fa9..6f21a60f251d7 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -352,8 +352,10 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) if (dp->max_stream > 1 && drm_dp_read_mst_cap(dp->aux, dp->panel->dpcd)) msm_dp_display_mst_init(dp); - if (dp->msm_dp_display.mst_active) + if (dp->msm_dp_display.mst_active) { + connector->status = connector_status_disconnected; msm_dp_mst_display_set_mgr_state(&dp->msm_dp_display, true); + } msm_dp_link_reset_phy_params_vx_px(dp->link); From aacfc2dc811339bf7383e2e5cbe75d96ad60aa67 Mon Sep 17 00:00:00 2001 From: Kumar Anurag Date: Tue, 23 Jun 2026 23:54:55 -0700 Subject: [PATCH 168/188] FROMLIST: drm/msm/dp: return 0 from audio_prepare when cable is disconnected PipeWire treats a non-zero return from prepare as fatal, marking the DP audio device as a dummy sink when the cable is unplugged. The active_stream_cnt guard already prevents any unclocked hardware access, so return success instead of -EINVAL when the link is not active. Upstream uses variable of power_on while 0.0 kernel uses active_stream_cnt. Hence we use the active_stream_cnt in the current pull request. Link: https://lore.kernel.org/all/20260616151252.3599089-2-kumar.singh@oss.qualcomm.com/ Signed-off-by: Kumar Anurag --- drivers/gpu/drm/msm/dp/dp_audio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index 035e230201fd9..8118130a3220f 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -284,10 +284,8 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge, * such cases check for connection status and bail out if not * connected. */ - if (!msm_dp_display->active_stream_cnt) { - rc = -EINVAL; + if (!msm_dp_display->active_stream_cnt) goto end; - } audio = msm_dp_audio_get_data(msm_dp_display); if (IS_ERR(audio)) { From 0741b4d731d143ccc30446ac4fb5b89df509d688 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 19 Jun 2026 14:24:11 +0200 Subject: [PATCH 169/188] BACKPORT: drm/bridge: Add new atomic_create_state callback Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj") introduced a new pattern for allocating drm object states: atomic_create_state, a dedicated hook that allocates and initializes a pristine state without any side effect. The bridge atomic_reset callback is already fallible and in practice only allocates and initializes state without touching hardware. However, the reset name does not make this contract clear: callers and implementers cannot tell from the name alone whether the hardware will be affected or when the hook is safe to call. Add an atomic_create_state callback to drm_bridge_funcs to make the contract explicit: allocate a pristine state, initialize it, no side effects. The core calls it when available, falling back to atomic_reset otherwise. Reviewed-by: Thomas Zimmermann Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli # imx8mp + sn65dsi84 + bridge hotplug Link: https://patch.msgid.link/20260619-drm-no-more-bridge-reset-v3-6-ff399263111b@kernel.org Signed-off-by: Maxime Ripard Signed-off-by: Mohit Dsor --- drivers/gpu/drm/drm_atomic_state_helper.c | 5 +++-- drivers/gpu/drm/drm_bridge.c | 8 ++++++-- include/drm/drm_bridge.h | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index cc70508d4fdba..687fa2bf1fa89 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -818,8 +818,9 @@ EXPORT_SYMBOL(drm_atomic_helper_bridge_destroy_state); * @state: bridge state to initialize * * Initializes the bridge state to default values. This is meant to be called - * by the bridge &drm_bridge_funcs.atomic_reset hook for bridges that subclass - * the bridge state. + * by the bridge &drm_bridge_funcs.atomic_create_state or + * &drm_bridge_funcs.atomic_reset hook for bridges that subclass the bridge + * state. */ void __drm_atomic_helper_bridge_reset(struct drm_bridge *bridge, struct drm_bridge_state *state) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 3fd3e7bac588c..f905b1317b5fd 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -500,7 +500,10 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj) struct drm_bridge *bridge = drm_priv_to_bridge(obj); struct drm_bridge_state *state; - state = bridge->funcs->atomic_reset(bridge); + if (bridge->funcs->atomic_create_state) + state = bridge->funcs->atomic_create_state(bridge); + else + state = bridge->funcs->atomic_reset(bridge); if (IS_ERR(state)) return ERR_CAST(state); @@ -515,7 +518,8 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = { static bool drm_bridge_is_atomic(struct drm_bridge *bridge) { - return bridge->funcs->atomic_reset != NULL; + return (bridge->funcs->atomic_create_state || + bridge->funcs->atomic_reset); } /** diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index e6de665ce8f65..9663032cc0b8e 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -530,6 +530,22 @@ struct drm_bridge_funcs { */ struct drm_bridge_state *(*atomic_reset)(struct drm_bridge *bridge); + /** + * @atomic_create_state: + * + * Allocate a pristine, initialized, state for the bridge + * object and return it. This callback must have no side + * effects: in particular, the returned state must not be + * assigned to the object's state pointer and it must not affect + * the hardware state. + * + * RETURNS: + * + * A new, pristine, bridge state instance or an error pointer + * on failure. + */ + struct drm_bridge_state *(*atomic_create_state)(struct drm_bridge *bridge); + /** * @detect: * @@ -1374,7 +1390,8 @@ drm_bridge_get_current_state(struct drm_bridge *bridge) * drm_atomic_private_obj_init(), so we need to make sure we're * working with one before we try to use the lock. */ - if (!bridge->funcs || !bridge->funcs->atomic_reset) + if (!bridge->funcs || + !(bridge->funcs->atomic_reset || bridge->funcs->atomic_create_state)) return NULL; drm_modeset_lock_assert_held(&bridge->base.lock); From 4f4a3d4ac2d3cf71390b95f1d67a34487df1ec1e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 19 Jun 2026 14:24:12 +0200 Subject: [PATCH 170/188] BACKPORT: drm/atomic-state-helper: Add drm_atomic_helper_bridge_create_state() The drm_atomic_helper_bridge_reset() helper is deprecated in favour of the new atomic_create_state callback. Add drm_atomic_helper_bridge_create_state() as the counterpart helper for this new callback, and make drm_atomic_helper_bridge_reset() call this new helper. Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli # imx8mp + sn65dsi84 + bridge hotplug Link: https://patch.msgid.link/20260619-drm-no-more-bridge-reset-v3-7-ff399263111b@kernel.org Signed-off-by: Maxime Ripard Signed-off-by: Mohit Dsor --- drivers/gpu/drm/drm_atomic_state_helper.c | 22 +++++++++++++++++++++- include/drm/drm_atomic_state_helper.h | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index 687fa2bf1fa89..8b117922871b2 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -799,6 +799,7 @@ EXPORT_SYMBOL(drm_atomic_helper_bridge_duplicate_state); * @state: bridge state to destroy * * Destroys a bridge state previously created by + * &drm_atomic_helper_bridge_create_state(), * &drm_atomic_helper_bridge_reset() or * &drm_atomic_helper_bridge_duplicate_state(). This helper is meant to be * used as a bridge &drm_bridge_funcs.atomic_destroy_state hook for bridges @@ -842,6 +843,25 @@ EXPORT_SYMBOL(__drm_atomic_helper_bridge_reset); */ struct drm_bridge_state * drm_atomic_helper_bridge_reset(struct drm_bridge *bridge) +{ + return drm_atomic_helper_bridge_create_state(bridge); +} +EXPORT_SYMBOL(drm_atomic_helper_bridge_reset); + +/** + * drm_atomic_helper_bridge_create_state - default + * &drm_bridge_funcs.atomic_create_state hook for bridges + * @bridge: bridge object + * + * Allocates and initializes pristine @drm_bridge_state. + * + * This is useful for drivers that don't subclass @drm_bridge_state. + * + * RETURNS: + * Pointer to new bridge state, or ERR_PTR on failure. + */ +struct drm_bridge_state * +drm_atomic_helper_bridge_create_state(struct drm_bridge *bridge) { struct drm_bridge_state *bridge_state; @@ -852,4 +872,4 @@ drm_atomic_helper_bridge_reset(struct drm_bridge *bridge) __drm_atomic_helper_bridge_reset(bridge, bridge_state); return bridge_state; } -EXPORT_SYMBOL(drm_atomic_helper_bridge_reset); +EXPORT_SYMBOL(drm_atomic_helper_bridge_create_state); diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 61a3b38ad49fd..45e3d9db2b96b 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -99,4 +99,6 @@ void drm_atomic_helper_bridge_destroy_state(struct drm_bridge *bridge, void __drm_atomic_helper_bridge_reset(struct drm_bridge *bridge, struct drm_bridge_state *state); struct drm_bridge_state * +drm_atomic_helper_bridge_create_state(struct drm_bridge *bridge); +struct drm_bridge_state * drm_atomic_helper_bridge_reset(struct drm_bridge *bridge); From cebc1e6a1ff32d5e867cc4ffa3469a14830a9f3f Mon Sep 17 00:00:00 2001 From: Ravi Agola Date: Wed, 22 Jul 2026 18:12:31 +0530 Subject: [PATCH 171/188] FROMLIST: drm/bridge: lt9611uxc: support displays with up to 4 EDID blocks The LT9611UXC bridge can fetch only 2 EDID blocks at a time, which previously limited EDID reading to 2 blocks and prevented support for displays exposing more than 2 EDID blocks. Add driver support to fetch up to 4 EDID blocks by re-triggering EDID access after the first 2 blocks are read. For block 0 and 2, set the EDID ready flag in 0xb028 so the bridge can expose the corresponding EDID blocks, then retry the read until the expected EDID is fetched. Reset the edid_read flag on HPD disconnect so that the next connect event triggers a fresh EDID fetch. Increase EDID wait time from 500ms to 1000ms, On Qualcomm rb3gen2 platform sometimes edid read interrupt is coming 600-650 ms after HPD interrupt resulting in edid read failure. Signed-off-by: Ravi Agola Link: https://lore.kernel.org/all/20260722-lt9611usc_edid34_misc_next-v3-1-7ec2bba6ff8e@oss.qualcomm.com/ Signed-off-by: Vishnu Saini --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 80 ++++++++++++++++++---- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 30fe757e49723..2f96f596ce5a0 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -28,7 +28,7 @@ #include #define EDID_BLOCK_SIZE 128 -#define EDID_NUM_BLOCKS 2 +#define EDID_NUM_BLOCKS 4 #define FW_FILE "lt9611uxc_fw.bin" @@ -170,6 +170,8 @@ static void lt9611uxc_hpd_work(struct work_struct *work) mutex_lock(<9611uxc->ocm_lock); connected = lt9611uxc->hdmi_connected; + if (!connected) + lt9611uxc->edid_read = false; mutex_unlock(<9611uxc->ocm_lock); if (!connected) @@ -388,13 +390,39 @@ lt9611uxc_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connect static int lt9611uxc_wait_for_edid(struct lt9611uxc *lt9611uxc) { return wait_event_interruptible_timeout(lt9611uxc->wq, lt9611uxc->edid_read, - msecs_to_jiffies(500)); + msecs_to_jiffies(1000)); +} + +static int lt9611uxc_read_edid_block(struct lt9611uxc *lt9611uxc, unsigned int block, + u8 *buf, size_t len) +{ + int ret; + + lt9611uxc_lock(lt9611uxc); + + regmap_write(lt9611uxc->regmap, 0xb00a, (block % 2) * EDID_BLOCK_SIZE); + + ret = regmap_noinc_read(lt9611uxc->regmap, 0xb0b0, buf, len); + if (ret) { + dev_err(lt9611uxc->dev, "edid block %d read failed: %d\n", block, ret); + lt9611uxc_unlock(lt9611uxc); + return -EINVAL; + } + lt9611uxc_unlock(lt9611uxc); + + return ret; } static int lt9611uxc_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) { struct lt9611uxc *lt9611uxc = data; - int ret; + int ret = 0; + int retry_cnt = 10; + unsigned int edid_ready_flag = 0; + bool header_matched; + bool edid_valid = false; + const u8 edid_header[8] = { 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00 }; if (len > EDID_BLOCK_SIZE) return -EINVAL; @@ -402,20 +430,48 @@ static int lt9611uxc_get_edid_block(void *data, u8 *buf, unsigned int block, siz if (block >= EDID_NUM_BLOCKS) return -EINVAL; - lt9611uxc_lock(lt9611uxc); + if (block == 0 || block == 2) { - regmap_write(lt9611uxc->regmap, 0xb00b, 0x10); + lt9611uxc_lock(lt9611uxc); - regmap_write(lt9611uxc->regmap, 0xb00a, block * EDID_BLOCK_SIZE); + edid_ready_flag = (block == 0) ? BIT(0) : BIT(1); - ret = regmap_noinc_read(lt9611uxc->regmap, 0xb0b0, buf, len); - if (ret) - dev_err(lt9611uxc->dev, "edid read failed: %d\n", ret); + /* + * Set the EDID ready flag so that lt9611uxc can fetch correct EDID block + */ + regmap_write(lt9611uxc->regmap, 0xb028, edid_ready_flag); - lt9611uxc_unlock(lt9611uxc); + lt9611uxc_unlock(lt9611uxc); - return 0; -}; + do { + msleep(100); + ret = lt9611uxc_read_edid_block(lt9611uxc, block, buf, len); + if (ret) + break; + /* + * Compare first 8 bytes of EDID header for block 0 and block 2 + * to confirm EDID read successfully + */ + header_matched = (memcmp(edid_header, buf, 8) == 0); + edid_valid = (block == 0 && header_matched) || + (block == 2 && !header_matched); + if (edid_valid) + break; + + } while (retry_cnt-- > 0); + + if (!ret && !edid_valid) + ret = -ETIMEDOUT; + } else { + ret = lt9611uxc_read_edid_block(lt9611uxc, block, buf, len); + + lt9611uxc_lock(lt9611uxc); + regmap_write(lt9611uxc->regmap, 0xb028, 0x00); + lt9611uxc_unlock(lt9611uxc); + } + + return ret; +} static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) From a332851028d72fc0119561f9ac9946eee86b0c2f Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Fri, 17 Jul 2026 16:56:13 +0530 Subject: [PATCH 172/188] FROMLIST: drm/msm/dp: add .shutdown to mask HPD IRQ before system shutdown On reboot, a late DP hot-plug-detect (HPD) IRQ can fire after apps_smmu has already disabled translation for the display subsystem, causing the HPD thread to kick off a new modeset that drives DPU/DP hardware and DMA through a stale IOMMU mapping. DPU's own .shutdown disables all CRTCs first, but a pending HPD IRQ thread wakes up afterwards, reads the DPCD, and fires an unsolicited hotplug event that triggers a second atomic commit turning the display back on -- right as the IOMMU is disabling translation: systemd-shutdown[1]: Rebooting. msm_dpu: drm_atomic_commit: committing (shutdown disabling CRTCs) arm-smmu 3da0000.iommu: disabling translation msm_dpu: drm_dp_read_dpcd_caps (late HPD IRQ thread wakes up) msm_dpu: drm_sysfs_connector_hotplug_event: DP-1 hotplug event msm_dpu: drm_client_modeset_probe: DP-1 found preferred mode msm_dpu: drm_atomic_commit: committing (unsolicited, re-enables display) dpu_crtc_commit_kickoff: crtc94 first commit arm-smmu 15200000.iommu: disabling translation Mask and flush the IRQ in .shutdown so no HPD event can retrigger a modeset once shutdown has started. Reported on lemans-evk and monaco-evk during reboot stress testing. Link: https://lore.kernel.org/all/20260717-dpshutdown-v1-1-b062c2f7dfb1@oss.qualcomm.com/ Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Mahadevan P Signed-off-by: Yash Gupta --- drivers/gpu/drm/msm/dp/dp_display.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 6f21a60f251d7..8a75990d1055a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1414,6 +1414,14 @@ static void msm_dp_display_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); } +static void msm_dp_display_shutdown(struct platform_device *pdev) +{ + struct msm_dp_display_private *dp = dev_get_dp_display_private(&pdev->dev); + + disable_irq(dp->irq); + synchronize_irq(dp->irq); +} + static int msm_dp_pm_runtime_suspend(struct device *dev) { struct msm_dp_display_private *dp = dev_get_dp_display_private(dev); @@ -1460,6 +1468,7 @@ static const struct dev_pm_ops msm_dp_pm_ops = { static struct platform_driver msm_dp_display_driver = { .probe = msm_dp_display_probe, .remove = msm_dp_display_remove, + .shutdown = msm_dp_display_shutdown, .driver = { .name = "msm-dp-display", .of_match_table = msm_dp_dt_match, From 40f9796bb6bfcc1f1e1df50693694c5bc5b7418a Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 20 Jul 2026 15:56:08 +0800 Subject: [PATCH 173/188] FROMLIST: drm/msm/dpu: Fix DMA SSPP REC block offsets on DPU v13 On DPU v13, the DMA SSPP REC0 and REC1 blocks are located at offsets 0x1000 and 0x3000 from the SSPP common base. The existing DMA SSPP sub-block descriptor does not initialize sspp_rec0_blk and sspp_rec1_blk, causing REC register accesses to be performed at offset 0 instead of the corresponding REC block. As a result, DMA SSPP pipes are not programmed correctly and fail to produce output. Introduce a DPU v13 specific DMA SSPP descriptor with the correct REC block offsets and use it for all DMA SSPPs in the Kaanapali catalog. Signed-off-by: Yongxing Mou Fixes: 83fe2cd56b1d ("drm/msm/dpu: Add support for Kaanapali DPU") Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260720-dpu-v13-dma-sspp-rec-fix-v1-1-10d69b4875e7@oss.qualcomm.com/ --- .../drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h | 12 ++++++------ drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h index 06da1583fb1eb..85f455a9f29c8 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h +++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h @@ -86,42 +86,42 @@ static const struct dpu_sspp_cfg kaanapali_sspp[] = { .name = "sspp_8", .id = SSPP_DMA0, .base = 0x97000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 1, .type = SSPP_TYPE_DMA, }, { .name = "sspp_9", .id = SSPP_DMA1, .base = 0xa0000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 5, .type = SSPP_TYPE_DMA, }, { .name = "sspp_10", .id = SSPP_DMA2, .base = 0xa9000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 9, .type = SSPP_TYPE_DMA, }, { .name = "sspp_11", .id = SSPP_DMA3, .base = 0xb2000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 13, .type = SSPP_TYPE_DMA, }, { .name = "sspp_12", .id = SSPP_DMA4, .base = 0xbb000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 14, .type = SSPP_TYPE_DMA, }, { .name = "sspp_13", .id = SSPP_DMA5, .base = 0xc4000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 15, .type = SSPP_TYPE_DMA, }, diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c index 2e10add84fd71..9a993cdfab856 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c @@ -303,6 +303,16 @@ static const u32 wb2_formats_rgb_yuv[] = { .num_formats = ARRAY_SIZE(plane_formats), \ } +#define _DMA_SBLK_V13() \ + { \ + .sspp_rec0_blk = {.name = "sspp_rec0", \ + .base = 0x1000, .len = 0x180,}, \ + .sspp_rec1_blk = {.name = "sspp_rec1", \ + .base = 0x3000, .len = 0x180,}, \ + .format_list = plane_formats, \ + .num_formats = ARRAY_SIZE(plane_formats), \ + } + static const struct dpu_rotation_cfg dpu_rot_sc7280_cfg_v2 = { .rot_maxheight = 1088, .rot_num_formats = ARRAY_SIZE(rotation_v2_formats), @@ -353,6 +363,8 @@ static const struct dpu_sspp_sub_blks dpu_rgb_sblk = _RGB_SBLK(); static const struct dpu_sspp_sub_blks dpu_dma_sblk = _DMA_SBLK(); +static const struct dpu_sspp_sub_blks dpu_dma_sblk_v13 = _DMA_SBLK_V13(); + /************************************************************* * MIXER sub blocks config *************************************************************/ From 8dd6471d658738256f02e4baec175bea2179a98a Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 20 Jul 2026 16:43:54 +0800 Subject: [PATCH 174/188] FROMLIST: drm/msm/mdss: Enable INT2_GDSC alongside CORE_GDSC Kaanapali (SM8750) and Glymur use two display power domains. CORE_GDSC powers the main display hardware while INT2_GDSC powers a subset of SSPP blocks (VIG2/VIG3/DMA5/DMA6). The MDSS driver currently relies on the default runtime PM handling and does not enable the secondary INT2_GDSC. As a result, display pipes backed by INT2_GDSC remain inaccessible. Attach and manage both power domains explicitly and enable them during MDSS runtime PM activation. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260720-msm_gdsc2-v1-2-4687866d6cb0@oss.qualcomm.com/ --- drivers/gpu/drm/msm/msm_mdss.c | 53 +++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_mdss.c b/drivers/gpu/drm/msm/msm_mdss.c index 9087c4b290dba..63ece55c4f2ec 100644 --- a/drivers/gpu/drm/msm/msm_mdss.c +++ b/drivers/gpu/drm/msm/msm_mdss.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,7 @@ struct msm_mdss { struct icc_path *mdp_path[2]; u32 num_mdp_paths; struct icc_path *reg_bus_path; + struct dev_pm_domain_list *pd_list; }; static int msm_mdss_parse_data_bus_icc_path(struct device *dev, @@ -233,6 +235,21 @@ static int msm_mdss_enable(struct msm_mdss *msm_mdss) int ret, i; u32 hw_rev; + /* + * Platforms with two power domains (e.g. Kaanapali, Glymur) need both + * GDSCs enabled explicitly; the core framework only auto-attaches a + * single power domain. + */ + if (msm_mdss->pd_list) { + ret = pm_runtime_resume_and_get(msm_mdss->pd_list->pd_devs[0]); + if (ret < 0) + return ret; + + ret = pm_runtime_resume_and_get(msm_mdss->pd_list->pd_devs[1]); + if (ret < 0) + goto err_put_core; + } + /* * Several components have AXI clocks that can only be turned on if * the interconnect is enabled (non-zero bandwidth). Let's make sure @@ -255,7 +272,7 @@ static int msm_mdss_enable(struct msm_mdss *msm_mdss) ret = clk_bulk_prepare_enable(msm_mdss->num_clocks, msm_mdss->clocks); if (ret) { dev_err(msm_mdss->dev, "clock enable failed, ret:%d\n", ret); - return ret; + goto err_put_int2; } /* @@ -276,6 +293,14 @@ static int msm_mdss_enable(struct msm_mdss *msm_mdss) /* else UBWC 1.0 or none, no params to program */ return ret; + +err_put_int2: + if (msm_mdss->pd_list) + pm_runtime_put_sync(msm_mdss->pd_list->pd_devs[1]); +err_put_core: + if (msm_mdss->pd_list) + pm_runtime_put_sync(msm_mdss->pd_list->pd_devs[0]); + return ret; } static int msm_mdss_disable(struct msm_mdss *msm_mdss) @@ -290,6 +315,11 @@ static int msm_mdss_disable(struct msm_mdss *msm_mdss) if (msm_mdss->reg_bus_path) icc_set_bw(msm_mdss->reg_bus_path, 0, 0); + if (msm_mdss->pd_list) { + pm_runtime_put_sync(msm_mdss->pd_list->pd_devs[1]); + pm_runtime_put_sync(msm_mdss->pd_list->pd_devs[0]); + } + return 0; } @@ -421,6 +451,27 @@ static struct msm_mdss *msm_mdss_init(struct platform_device *pdev, bool is_mdp5 irq_set_chained_handler_and_data(irq, msm_mdss_irq, msm_mdss); + /* + * When two power domains are listed in DT, the core framework does not + * auto-attach either of them. Attach and manage both explicitly so + * that INT2_GDSC (needed for VIG2/VIG3) is enabled alongside the main + * CORE_GDSC. Single-domain platforms are unaffected. + */ + if (of_count_phandle_with_args(pdev->dev.of_node, "power-domains", + "#power-domain-cells") > 1) { + static const char * const mdss_pd_names[] = { "core", "int2" }; + static const struct dev_pm_domain_attach_data pd_data = { + .pd_names = mdss_pd_names, + .num_pd_names = ARRAY_SIZE(mdss_pd_names), + .pd_flags = PD_FLAG_NO_DEV_LINK, + }; + + ret = devm_pm_domain_attach_list(&pdev->dev, &pd_data, + &msm_mdss->pd_list); + if (ret < 0) + return ERR_PTR(ret); + } + pm_runtime_enable(&pdev->dev); return msm_mdss; From 860fe994a38001124b45616923c199cbc5cdc9dc Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Sun, 2 Aug 2026 16:57:34 +0800 Subject: [PATCH 175/188] FROMLIST: dt-bindings: display: msm: Allow two MDSS power domains Kaanapali (SM8750) and Glymur use two display power domains. CORE_GDSC powers the main display hardware while INT2_GDSC powers a subset of SSPP blocks. Allow the MDSS bindings to describe both power domains and their corresponding power-domain-names values. Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260720-msm_gdsc2-v1-1-4687866d6cb0@oss.qualcomm.com/ --- .../bindings/display/msm/mdss-common.yaml | 3 ++- .../bindings/display/msm/qcom,glymur-mdss.yaml | 13 ++++++++++++- .../bindings/display/msm/qcom,kaanapali-mdss.yaml | 13 ++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/mdss-common.yaml b/Documentation/devicetree/bindings/display/msm/mdss-common.yaml index c6305a6e03346..c0b650ffd9ef8 100644 --- a/Documentation/devicetree/bindings/display/msm/mdss-common.yaml +++ b/Documentation/devicetree/bindings/display/msm/mdss-common.yaml @@ -31,7 +31,8 @@ properties: const: mdss power-domains: - maxItems: 1 + minItems: 1 + maxItems: 2 clocks: minItems: 2 diff --git a/Documentation/devicetree/bindings/display/msm/qcom,glymur-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,glymur-mdss.yaml index 64dde43373ac7..685bedb26a124 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,glymur-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,glymur-mdss.yaml @@ -38,6 +38,16 @@ properties: - const: mdp0-mem - const: cpu-cfg + power-domains: + items: + - description: MDSS core GDSC power domain + - description: MDSS INT2 GDSC power domain + + power-domain-names: + items: + - const: core + - const: int2 + patternProperties: "^display-controller@[0-9a-f]+$": type: object @@ -95,7 +105,8 @@ examples: resets = <&disp_cc_mdss_core_bcr>; - power-domains = <&mdss_gdsc>; + power-domains = <&mdss_gdsc>, <&mdss_int2_gdsc>; + power-domain-names = "core", "int2"; iommus = <&apps_smmu 0x1c00 0x2>; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,kaanapali-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,kaanapali-mdss.yaml index 9f935defd6b12..088010f464c3d 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,kaanapali-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,kaanapali-mdss.yaml @@ -40,6 +40,16 @@ properties: - const: mdp0-mem - const: cpu-cfg + power-domains: + items: + - description: MDSS core GDSC power domain + - description: MDSS INT2 GDSC power domain + + power-domain-names: + items: + - const: core + - const: int2 + patternProperties: "^display-controller@[0-9a-f]+$": type: object @@ -89,7 +99,8 @@ examples: <&disp_cc_mdss_ahb_swi_clk>; resets = <&disp_cc_mdss_core_bcr>; - power-domains = <&mdss_gdsc>; + power-domains = <&mdss_gdsc>, <&mdss_int2_gdsc>; + power-domain-names = "core", "int2"; iommus = <&apps_smmu 0x800 0x2>; From 7346c3f9d8b2e674f9468c88a7661371217b9a6a Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 12 Jul 2026 14:56:55 +0300 Subject: [PATCH 176/188] BACKPORT: Revert "drm/msm: dsi: fix PLL init in bonded mode" Commit 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") fixed one of the issues with the DSI bonded mode, but broke non-bonded usecase for DSI as reported by Mohit Dsor. Clock divider is being programmed incorrectly, resultin in the wrong display mode being selected. Revert the offending commit, letting Neil to work on a better fix. Fixes: 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") Reported-by: Mohit Dsor Closes: https://lore.kernel.org/r/ae07cef84AmXK43H@hu-mdsor-hyd.qualcomm.com Cc: Neil Armstrong Cc: Thorsten Leemhuis Signed-off-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260712-msm-revert-dsi-pll-fix-v1-1-40122689ea25@oss.qualcomm.com Signed-off-by: Mohit Dsor --- drivers/gpu/drm/msm/dsi/phy/dsi_phy.h | 1 + drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h index 21a59d66e8dc5..f5d3e806f8fd5 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h @@ -111,6 +111,7 @@ struct msm_dsi_phy { struct msm_dsi_dphy_timing timing; const struct msm_dsi_phy_cfg *cfg; void *tuning_cfg; + void *pll_data; enum msm_dsi_phy_usecase usecase; bool regulator_ldo_mode; diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c index 984a66085dfbf..5d805a797abdb 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c @@ -426,8 +426,11 @@ static void dsi_pll_enable_pll_bias(struct dsi_pll_7nm *pll) u32 data; spin_lock_irqsave(&pll->pll_enable_lock, flags); - pll->pll_enable_cnt++; - WARN_ON(pll->pll_enable_cnt == INT_MAX); + if (pll->pll_enable_cnt++) { + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); + WARN_ON(pll->pll_enable_cnt == INT_MAX); + return; + } data = readl(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0); data |= DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; @@ -873,6 +876,7 @@ static int dsi_pll_7nm_init(struct msm_dsi_phy *phy) spin_lock_init(&pll_7nm->pll_enable_lock); pll_7nm->phy = phy; + phy->pll_data = pll_7nm; ret = pll_7nm_register(pll_7nm, phy->provided_clocks->hws); if (ret) { @@ -961,8 +965,10 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, u32 const delay_us = 5; u32 const timeout_us = 1000; struct msm_dsi_dphy_timing *timing = &phy->timing; + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; bool less_than_1500_mhz; + unsigned long flags; u32 vreg_ctrl_0, vreg_ctrl_1, lane_ctrl0; u32 glbl_pemph_ctrl_0; u32 glbl_str_swi_cal_sel_ctrl, glbl_hstx_str_ctrl_0; @@ -1084,10 +1090,13 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, glbl_rescode_bot_ctrl = 0x3c; } + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 1; /* de-assert digital and pll power down */ data = DSI_7nm_PHY_CMN_CTRL_0_DIGTOP_PWRDN_B | DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* Assert PLL core reset */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_PLL_CNTRL); @@ -1200,7 +1209,9 @@ static bool dsi_7nm_set_continuous_clock(struct msm_dsi_phy *phy, bool enable) static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) { + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; + unsigned long flags; u32 data; DBG(""); @@ -1227,8 +1238,11 @@ static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); writel(0, base + REG_DSI_7nm_PHY_CMN_LANE_CTRL0); + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 0; /* Turn off all PHY blocks */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* make sure phy is turned off */ wmb(); From 2fa3a1433e3b5d40bbdccb7f87a233489f071d26 Mon Sep 17 00:00:00 2001 From: Sunyun Yang Date: Sat, 8 Aug 2026 13:37:50 +0530 Subject: [PATCH 177/188] FROMLIST: dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port mipi dsi and output hdmi, differences in hardware features: - LT9611C: supports 1-port mipi dsi to hdmi 1.4 - LT9611EX: supports 2-port mipi dsi to hdmi 1.4 - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0 Link: https://lore.kernel.org/all/20260808-lt9611c-v7-v10-1-ee90a136d82a@oss.qualcomm.com/ Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sunyun Yang Signed-off-by: Mohit Dsor --- .../display/bridge/lontium,lt9611.yaml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml index 429a06057ae85..194aeaac601f3 100644 --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml @@ -4,19 +4,23 @@ $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge maintainers: - Vinod Koul description: | - The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI + The LT9611, LT9611UXC, LT9611C, LT9611EX, and LT9611UXD + are bridge devices which convert DSI to HDMI properties: compatible: enum: - lontium,lt9611 + - lontium,lt9611c + - lontium,lt9611ex - lontium,lt9611uxc + - lontium,lt9611uxd reg: maxItems: 1 @@ -78,6 +82,16 @@ required: allOf: - $ref: /schemas/sound/dai-common.yaml# + - if: + properties: + compatible: + contains: + const: lontium,lt9611c + then: + properties: + ports: + properties: + port@1: false unevaluatedProperties: false From 72725b8afed2abe36bea7c562d3ad65dfa8e0ff7 Mon Sep 17 00:00:00 2001 From: Sunyun Yang Date: Sat, 8 Aug 2026 13:37:51 +0530 Subject: [PATCH 178/188] FROMLIST: drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port mipi dsi and output hdmi, differences in hardware features: - LT9611C: supports 1-port mipi dsi to hdmi 1.4 - LT9611EX: supports 2-port mipi dsi to hdmi 1.4 - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0 Link: https://lore.kernel.org/all/20260808-lt9611c-v7-v10-2-ee90a136d82a@oss.qualcomm.com/ Signed-off-by: Sunyun Yang Co-developed-by: Mohit Dsor Signed-off-by: Mohit Dsor --- .../ABI/testing/sysfs-lontium-firmware | 10 + drivers/gpu/drm/bridge/Kconfig | 18 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt9611c.c | 1283 +++++++++++++++++ 4 files changed, 1312 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-lontium-firmware create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c diff --git a/Documentation/ABI/testing/sysfs-lontium-firmware b/Documentation/ABI/testing/sysfs-lontium-firmware new file mode 100644 index 0000000000000..8700933ba0f02 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-lontium-firmware @@ -0,0 +1,10 @@ +What: /sys/bus/i2c/drivers//.../firmware +Date: July 2026 +KernelVersion: 7.3 +Contact: Mohit Dsor +Description: + Read: Returns the current firmware version loaded on the + Lontium bridge chip in hex format (e.g. "0x0105"). + + Write: Triggers a firmware upgrade of the chip using the + firmware file loaded via the firmware loader. diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d3..d1597548e5787 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -177,6 +177,24 @@ config DRM_LONTIUM_LT9611 HDMI signals Please say Y if you have such hardware. +config DRM_LONTIUM_LT9611C + tristate "Lontium LT9611C DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF && I2C + select CRC8 + select FW_LOADER + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_HDMI_STATE_HELPER + select REGMAP_I2C + help + Driver for the Lontium LT9611C(EX/UXD) DSI to HDMI bridge chip. + It converts single or dual MIPI DSI and I2S signals to HDMI + output. Supports HDMI 1.4 (LT9611C/EX) and HDMI 2.0 (LT9611UXD). + Say Y if you have hardware using this chip. + config DRM_LONTIUM_LT9611UXC tristate "Lontium LT9611UXC DSI/HDMI bridge" select SND_SOC_HDMI_CODEC if SND_SOC diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7e..f322f3f89f6bf 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c new file mode 100644 index 0000000000000..e6887f2c89a6c --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c @@ -0,0 +1,1283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Lontium Semiconductor, Inc. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FW_SIZE (64 * 1024) +#define LT_PAGE_SIZE 256 +#define FW_FILE "lt9611c_fw.bin" +#define LT9611C_CRC_POLYNOMIAL 0x31 +#define LT9611C_PAGE_CONTROL 0xff +#define LT9611C_INFOFRAME_MAX_SIZE 32 +#define LT9611C_CMD_HDR_SIZE 4 +#define LT9611C_CMD_Y0_SIZE 1 /* Y0 echo byte in ACK response */ +#define LT9611C_EDID_BUF_SIZE 32 + +struct lt9611c_cmd_hdr { + u8 func; + u8 type; + u8 seq; + u8 sep; +}; + +/* lt9611c_cmd_hdr.func values */ +#define LT9611C_FUNC_WRITE 0x57 /* 'W' */ +#define LT9611C_FUNC_READ 0x52 /* 'R' */ +#define LT9611C_FUNC_ACK 0x41 /* 'A' */ + +/* lt9611c_cmd_hdr.type values */ +#define LT9611C_TYPE_MIPI 0x4d /* 'M' */ +#define LT9611C_TYPE_LVDS 0x4c /* 'L' */ +#define LT9611C_TYPE_HDMI 0x48 /* 'H' */ +#define LT9611C_TYPE_AUDIO 0x41 /* 'A' */ +#define LT9611C_TYPE_CUSTOM 0x43 /* 'C' */ + +/* lt9611c_cmd_hdr.sep is always ':' */ +#define LT9611C_CMD_SEP 0x3a /* ':' */ + +struct lt9611c_cmd { + struct lt9611c_cmd_hdr hdr; + const u8 *data; + size_t data_len; +}; + +struct lt9611c_rsp { + struct lt9611c_cmd_hdr hdr; + u8 *data; + unsigned int data_len; +}; + +enum lt9611_chip_type { + CHIP_LT9611C = 0, + CHIP_LT9611EX, + CHIP_LT9611UXD, +}; + +struct lt9611c_chip_data { + enum lt9611_chip_type chip_type; + unsigned long long max_tmds_rate; +}; + +static const struct lt9611c_chip_data lt9611c_chip_data[] = { + [CHIP_LT9611C] = { CHIP_LT9611C, 340000000 }, + [CHIP_LT9611EX] = { CHIP_LT9611EX, 340000000 }, + [CHIP_LT9611UXD] = { CHIP_LT9611UXD, 600000000 }, +}; + +struct lt9611c { + struct device *dev; + struct i2c_client *client; + struct drm_bridge bridge; + struct regmap *regmap; + struct mutex mcu_lock; + struct work_struct work; + struct device_node *dsi0_node; + struct device_node *dsi1_node; + struct mipi_dsi_device *dsi0; + struct mipi_dsi_device *dsi1; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + int fw_version; + /* Chip variant: C/EX/UXD */ + enum lt9611_chip_type chip_type; + unsigned long long max_tmds_rate; + bool hdmi_connected; +}; + +DECLARE_CRC8_TABLE(lt9611c_crc8_table); + +static const struct regmap_range_cfg lt9611c_ranges[] = { + { + .name = "register_range", + .range_min = 0, + .range_max = 0xfe9c, + .selector_reg = LT9611C_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config lt9611c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xfe9c, + .ranges = lt9611c_ranges, + .num_ranges = ARRAY_SIZE(lt9611c_ranges), +}; + +static int lt9611c_read_write_flow(struct lt9611c *lt9611c, + const struct lt9611c_cmd *cmd, + struct lt9611c_rsp *rsp) +{ + int ret; + unsigned int i; + unsigned int temp; + unsigned int max_params = 0xe0dd - 0xe0b0 + 1; + + regmap_write(lt9611c->regmap, 0xe0de, 0x01); + + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp == 0x01, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret = regmap_bulk_write(lt9611c->regmap, 0xe0b0, &cmd->hdr, + LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + for (i = 0; cmd->data && i < cmd->data_len && + (LT9611C_CMD_HDR_SIZE + i) < max_params; i++) + regmap_write(lt9611c->regmap, + 0xe0b0 + LT9611C_CMD_HDR_SIZE + i, cmd->data[i]); + + regmap_write(lt9611c->regmap, 0xe0de, 0x02); + + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp == 0x02, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe085, + &rsp->hdr, LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + + if (rsp->data && rsp->data_len) + ret = regmap_bulk_read(lt9611c->regmap, + 0xe085 + LT9611C_CMD_HDR_SIZE, + rsp->data, rsp->data_len); + + return ret; +} + +static void lt9611c_lock(struct lt9611c *lt9611c) +{ + mutex_lock(<9611c->mcu_lock); + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); +} + +static void lt9611c_unlock(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe0ee, 0x00); + mutex_unlock(<9611c->mcu_lock); +} + +static void lt9611c_config_parameters(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write_paras[] = { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe103, 0x3f), /*fifo rst*/ + REG_SEQ0(0xe103, 0xff), + REG_SEQ0(0xe05e, 0xc1), + REG_SEQ0(0xe058, 0x00), + REG_SEQ0(0xe059, 0x50), + REG_SEQ0(0xe05a, 0x10), + REG_SEQ0(0xe05a, 0x00), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras)); +} + +static void lt9611c_wren(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe05a, 0x04); + regmap_write(lt9611c->regmap, 0xe05a, 0x00); +} + +static void lt9611c_wrdi(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe05a, 0x08); + regmap_write(lt9611c->regmap, 0xe05a, 0x00); +} + +static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe05a, 0x04), + REG_SEQ0(0xe05a, 0x00), + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), + REG_SEQ0(0xe05d, addr & 0xff), + REG_SEQ0(0xe05a, 0x01), + REG_SEQ0(0xe05a, 0x00), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static unsigned int read_flash_reg_status(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe103, 0x3f), + REG_SEQ0(0xe103, 0xff), + REG_SEQ0(0xe05e, 0x40), + REG_SEQ0(0xe056, 0x05), + REG_SEQ0(0xe055, 0x25), + REG_SEQ0(0xe055, 0x01), + REG_SEQ0(0xe058, 0x21), + }; + unsigned int status; + int ret; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); + ret = regmap_read(lt9611c->regmap, 0xe05f, &status); + if (ret) + return 0xff; + + return status; +} + +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe051, 0x00), + REG_SEQ0(0xe055, 0xc0), + REG_SEQ0(0xe055, 0x80), + REG_SEQ0(0xe05e, 0xc0), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611c_data_to_sram(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe051, 0xff), + REG_SEQ0(0xe055, 0x80), + REG_SEQ0(0xe05e, 0xc0), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, size_t addr) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), + REG_SEQ0(0xe05d, addr & 0xff), + REG_SEQ0(0xe05a, 0x30), + REG_SEQ0(0xe05a, 0x00), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static int lt9611c_block_erase(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + unsigned int block_num; + unsigned int flash_status = 0; + u32 flash_addr = 0; + int ret; + + for (block_num = 0; block_num < 2; block_num++) { + flash_addr = block_num * 0x008000; + lt9611c_erase_op(lt9611c, flash_addr); + msleep(100); + ret = read_poll_timeout(read_flash_reg_status, flash_status, + !(flash_status & 0x01), + 50 * USEC_PER_MSEC, 2500 * USEC_PER_MSEC, + false, lt9611c); + if (ret) { + dev_err(dev, "flash erase timeout for block %u\n", block_num); + return ret; + } + } + + return 0; +} + +static int lt9611c_write_data(struct lt9611c *lt9611c, const struct firmware *fw, size_t addr) +{ + struct device *dev = lt9611c->dev; + int ret; + unsigned int page = 0, num = 0, i = 0; + size_t size, index; + const u8 *data; + u8 value; + + data = fw->data; + size = fw->size; + page = DIV_ROUND_UP(size, LT_PAGE_SIZE); + if (page * LT_PAGE_SIZE > FW_SIZE) { + dev_err(dev, "firmware size out of range\n"); + return -EINVAL; + } + + dev_dbg(dev, "%u pages, total size %zu byte\n", page, size); + + for (num = 0; num < page; num++) { + lt9611c_data_to_sram(lt9611c); + + for (i = 0; i < LT_PAGE_SIZE; i++) { + index = num * LT_PAGE_SIZE + i; + value = (index < size) ? data[index] : 0xff; + + ret = regmap_write(lt9611c->regmap, 0xe059, value); + if (ret < 0) { + dev_err(dev, "write error at page %u, index %u\n", num, i); + return ret; + } + } + + lt9611c_wren(lt9611c); + lt9611c_sram_to_flash(lt9611c, addr); + + addr += LT_PAGE_SIZE; + } + + lt9611c_wrdi(lt9611c); + + return 0; +} + +static int lt9611c_write_crc(struct lt9611c *lt9611c, u8 fw_crc, size_t addr) +{ + struct device *dev = lt9611c->dev; + int ret; + + lt9611c_crc_to_sram(lt9611c); + ret = regmap_write(lt9611c->regmap, 0xe059, fw_crc); + if (ret < 0) { + dev_err(dev, "failed to write crc\n"); + return ret; + } + + lt9611c_wren(lt9611c); + lt9611c_sram_to_flash(lt9611c, addr); + lt9611c_wrdi(lt9611c); + + dev_dbg(dev, "crc 0x%02x written to flash at addr 0x%zx\n", fw_crc, addr); + + return 0; +} + +static void lt9611c_reset(struct lt9611c *lt9611c) +{ + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); + usleep_range(10000, 12000); + + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); + msleep(400); +} + +static int lt9611c_upgrade_result(struct lt9611c *lt9611c, u8 fw_crc) +{ + struct device *dev = lt9611c->dev; + unsigned int crc_result; + int ret; + + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + ret = regmap_read(lt9611c->regmap, 0xe021, &crc_result); + if (ret) + return dev_err_probe(dev, ret, "failed to read firmware crc\n"); + + if (crc_result != fw_crc) { + dev_err(dev, "lt9611c fw upgrade failed, expected crc=0x%02x, read crc=0x%02x\n", + fw_crc, crc_result); + return -EIO; + } + + dev_info(dev, "lt9611c firmware upgrade success, crc=0x%02x\n", crc_result); + return 0; +} + +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + const struct firmware *fw; + u8 *buffer; + size_t total_size = FW_SIZE - 1; + u8 fw_crc; + int ret; + + /* load firmware — must happen outside the mcu_lock */ + ret = request_firmware(&fw, FW_FILE, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE); + + if (fw->size > total_size) { + dev_err(dev, "firmware too large (%zu > %zu)\n", fw->size, total_size); + ret = -EINVAL; + goto out_release_fw; + } + dev_dbg(dev, "firmware size: %zu bytes\n", fw->size); + + buffer = kzalloc(total_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out_release_fw; + } + + memcpy(buffer, fw->data, fw->size); + memset(buffer + fw->size, 0xff, total_size - fw->size); + + fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0); + kfree(buffer); + + dev_info(dev, "starting firmware upgrade, size: %zu bytes, crc: 0x%02x\n", + fw->size, fw_crc); + + /* hardware access requires mcu_lock */ + lt9611c_lock(lt9611c); + + lt9611c_config_parameters(lt9611c); + ret = lt9611c_block_erase(lt9611c); + if (ret < 0) + goto out_unlock; + + ret = lt9611c_write_data(lt9611c, fw, 0); + if (ret < 0) { + dev_err(dev, "failed to write firmware data\n"); + goto out_unlock; + } + + ret = lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1); + if (ret < 0) { + dev_err(dev, "failed to write firmware crc\n"); + goto out_unlock; + } + + lt9611c_reset(lt9611c); + ret = lt9611c_upgrade_result(lt9611c, fw_crc); + +out_unlock: + lt9611c_unlock(lt9611c); +out_release_fw: + release_firmware(fw); + return ret; +} + +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9611c, bridge); +} + +static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge) +{ + return container_of_const(bridge, struct lt9611c, bridge); +} + +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611c *lt9611c = dev_id; + struct device *dev = lt9611c->dev; + int ret; + unsigned int irq_status; + + guard(mutex)(<9611c->mcu_lock); + + ret = regmap_read(lt9611c->regmap, 0xe084, &irq_status); + if (ret) { + dev_err(dev, "failed to read irq status: %d\n", ret); + return IRQ_HANDLED; + } + + if (!(irq_status & BIT(0))) + return IRQ_NONE; + + /*Clear interrupt: hardware requires two writes with delay*/ + regmap_write(lt9611c->regmap, 0xe0df, irq_status & BIT(0)); + usleep_range(10000, 12000); + regmap_write(lt9611c->regmap, 0xe0df, irq_status & (~BIT(0))); + + schedule_work(<9611c->work); + + return IRQ_HANDLED; +} + +static void lt9611c_hpd_work(struct work_struct *work) +{ + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work); + struct device *dev = lt9611c->dev; + static const u8 hpd_data[] = { 0x00 }; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data = hpd_data, + .data_len = 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; + bool connected; + int ret; + + /* Added delay as need time to reflect hpd after interrupt*/ + msleep(200); + + mutex_lock(<9611c->mcu_lock); + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status\n"); + else + lt9611c->hdmi_connected = (hpd_status == 0x02); + + connected = lt9611c->hdmi_connected; + mutex_unlock(<9611c->mcu_lock); + + drm_bridge_hpd_notify(<9611c->bridge, + connected ? connector_status_connected : + connector_status_disconnected); +} + +static int lt9611c_regulator_init(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + int ret; + + lt9611c->supplies[0].supply = "vcc"; + lt9611c->supplies[1].supply = "vdd"; + + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies); + + return ret; +} + +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL }; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct device *dev = lt9611c->dev; + int ret; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + if (!host) + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n")); + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi device\n")); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) + return ERR_PTR(dev_err_probe(dev, ret, "failed to attach dsi to host\n")); + + return dsi; +} + +static int lt9611c_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + + return drm_bridge_attach(encoder, lt9611c->bridge.next_bridge, bridge, flags); +} + +static enum drm_mode_status +lt9611c_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + const struct lt9611c *lt9611c = bridge_to_lt9611c_const(bridge); + + if (tmds_rate > lt9611c->max_tmds_rate) + return MODE_CLOCK_HIGH; + + if (tmds_rate < 25000000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static void lt9611c_video_setup(struct lt9611c *lt9611c, + const struct drm_display_mode *mode) +{ + struct device *dev = lt9611c->dev; + int ret; + u32 h_total, hactive, hsync_len, hfront_porch, hback_porch; + u32 v_total, vactive, vsync_len, vfront_porch, vback_porch; + u8 timing_data[22]; + struct lt9611c_rsp rsp = {}; + u8 framerate; + u8 vic = 0x00; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_MIPI, 0x33, LT9611C_CMD_SEP }, + .data = timing_data, + .data_len = ARRAY_SIZE(timing_data), + }; + + guard(mutex)(<9611c->mcu_lock); + h_total = mode->htotal; + hactive = mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + hfront_porch = mode->hsync_start - mode->hdisplay; + hback_porch = mode->htotal - mode->hsync_end; + + v_total = mode->vtotal; + vactive = mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + vfront_porch = mode->vsync_start - mode->vdisplay; + vback_porch = mode->vtotal - mode->vsync_end; + framerate = drm_mode_vrefresh(mode); + vic = drm_match_cea_mode(mode); + + dev_dbg(dev, "hactive=%d, vactive=%d\n", hactive, vactive); + dev_dbg(dev, "framerate=%d\n", framerate); + dev_dbg(dev, "vic = 0x%02x\n", vic); + + put_unaligned_be16(h_total, &timing_data[0]); + put_unaligned_be16(hactive, &timing_data[2]); + put_unaligned_be16(hfront_porch, &timing_data[4]); + put_unaligned_be16(hsync_len, &timing_data[6]); + put_unaligned_be16(hback_porch, &timing_data[8]); + put_unaligned_be16(v_total, &timing_data[10]); + put_unaligned_be16(vactive, &timing_data[12]); + put_unaligned_be16(vfront_porch, &timing_data[14]); + put_unaligned_be16(vsync_len, &timing_data[16]); + put_unaligned_be16(vback_porch, &timing_data[18]); + timing_data[20] = framerate; + timing_data[21] = vic; + + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "video set failed\n"); +} + +static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *mode; + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (WARN_ON(!connector)) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + mode = &crtc_state->adjusted_mode; + + lt9611c_video_setup(lt9611c, mode); +} + +static enum drm_connector_status +lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + struct device *dev = lt9611c->dev; + int ret; + bool connected = false; + static const u8 hpd_data[] = { 0x00 }; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data = hpd_data, + .data_len = 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; + + guard(mutex)(<9611c->mcu_lock); + + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status (err=%d)\n", ret); + else + connected = (hpd_status == 0x02); + + lt9611c->hdmi_connected = connected; + + return connected ? connector_status_connected : + connector_status_disconnected; +} + +static int lt9611c_get_edid_block(void *data, u8 *buf, + unsigned int block, size_t len) +{ + struct lt9611c *lt9611c = data; + struct device *dev = lt9611c->dev; + u8 edid_raw[LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE]; + u8 y0; + int ret, i, offset = 0; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x33, LT9611C_CMD_SEP }, + }; + struct lt9611c_rsp rsp = { + .data = edid_raw, + .data_len = LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE, + }; + + if (len != 128) + return -EINVAL; + guard(mutex)(<9611c->mcu_lock); + + for (i = 0; i < 4; i++) { + y0 = block * 4 + i; + cmd.data = &y0; + cmd.data_len = 1; + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) { + dev_err(dev, "Failed to read EDID block %u packet %d\n", + block, i); + return ret; + } + memcpy(buf + offset, &edid_raw[LT9611C_CMD_Y0_SIZE], LT9611C_EDID_BUF_SIZE); + offset += LT9611C_EDID_BUF_SIZE; + } + + return 0; +} + +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c); +} + +static int lt9611c_write_infoframe(struct lt9611c *lt9611c, u8 type, + const u8 *buffer, size_t len) +{ + u8 extra[1 + LT9611C_INFOFRAME_MAX_SIZE]; + struct lt9611c_rsp rsp = {}; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x35, LT9611C_CMD_SEP }, + }; + + if (WARN_ON(len > LT9611C_INFOFRAME_MAX_SIZE)) + return -EINVAL; + + extra[0] = type; + memcpy(&extra[1], buffer, len); + cmd.data = extra; + cmd.data_len = 1 + len; + + guard(mutex)(<9611c->mcu_lock); + + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); +} + +static int lt9611c_clear_infoframe(struct lt9611c *lt9611c, u8 type) +{ + u8 clear_data = type; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x42, LT9611C_CMD_SEP }, + .data = &clear_data, + .data_len = 1, + }; + struct lt9611c_rsp rsp = {}; + + guard(mutex)(<9611c->mcu_lock); + + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); +} + +static int lt9611c_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x01, buffer, len); +} + +static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x01); +} + +static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x04, buffer, len); +} + +static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x04); +} + +static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x02, buffer, len); +} + +static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x02); +} + +static int lt9611c_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 audio_extra[2]; + struct lt9611c_rsp rsp = {}; + int ret; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x36, LT9611C_CMD_SEP }, + .data = audio_extra, + .data_len = ARRAY_SIZE(audio_extra) }; + + if (hparms->sample_width == 32) + return -EINVAL; + + switch (fmt->fmt) { + case HDMI_I2S: + audio_extra[0] = 0x01; + break; + case HDMI_SPDIF: + audio_extra[0] = 0x02; + break; + default: + return -EINVAL; + } + + audio_extra[1] = hparms->channels; + + scoped_guard(mutex, <9611c->mcu_lock) { + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret < 0) { + dev_err(lt9611c->dev, "set audio info failed!\n"); + return ret; + } + } + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + &hparms->cea); +} + +static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); +} + +static const struct drm_bridge_funcs lt9611c_bridge_funcs = { + .attach = lt9611c_bridge_attach, + .detect = lt9611c_bridge_detect, + .edid_read = lt9611c_bridge_edid_read, + .atomic_enable = lt9611c_bridge_atomic_enable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + + .hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid, + .hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe, + .hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe, + .hdmi_write_hdmi_infoframe = lt9611c_hdmi_write_hdmi_infoframe, + .hdmi_clear_hdmi_infoframe = lt9611c_hdmi_clear_hdmi_infoframe, + .hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe, + .hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe, + + .hdmi_audio_prepare = lt9611c_hdmi_audio_prepare, + .hdmi_audio_shutdown = lt9611c_hdmi_audio_shutdown, +}; + +static int lt9611c_parse_dt(struct device *dev, + struct lt9611c *lt9611c) +{ + int ret; + + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1); + if (!lt9611c->dsi0_node) + return dev_err_probe(dev, -ENODEV, "failed to get remote node for primary dsi\n"); + + lt9611c->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); + + if (lt9611c->dsi1_node && lt9611c->chip_type == CHIP_LT9611C) { + ret = dev_err_probe(dev, -EINVAL, + "LT9611C does not support dual DSI\n"); + goto err_put_dsi1; + } + + lt9611c->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 2, -1); + if (IS_ERR(lt9611c->bridge.next_bridge)) { + ret = PTR_ERR(lt9611c->bridge.next_bridge); + goto err_put_dsi1; + } + + return 0; + +err_put_dsi1: + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); + return ret; +} + +static int lt9611c_read_version(struct lt9611c *lt9611c) +{ + u8 buf[2]; + int ret; + + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe080, buf, ARRAY_SIZE(buf)); + if (ret) + return ret; + + return (buf[0] << 8) | buf[1]; +} + +static int lt9611c_read_chipid(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + u8 chipid[2]; + int ret; + + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2); + if (ret) + return ret; + + if (chipid[0] != 0x23 || chipid[1] != 0x06) { + dev_err(dev, "ChipID: 0x%02x 0x%02x\n", chipid[0], chipid[1]); + return -ENODEV; + } + + return 0; +} + +static ssize_t firmware_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + dev_warn(dev, "starting firmware upgrade — display will be disrupted\n"); + + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) { + dev_err(dev, "upgrade failure\n"); + return ret; + } + + lt9611c_lock(lt9611c); + lt9611c->fw_version = lt9611c_read_version(lt9611c); + lt9611c_unlock(lt9611c); + + if (lt9611c->fw_version < 0) + dev_warn(dev, "upgrade succeeded but failed to read new fw version\n"); + else + dev_info(dev, "firmware upgrade succeeded, version: 0x%04x\n", + lt9611c->fw_version); + + return len; +} + +static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%04x\n", lt9611c->fw_version); +} + +static DEVICE_ATTR_RW(firmware); + +static struct attribute *lt9611c_attrs[] = { + &dev_attr_firmware.attr, + NULL, +}; + +static const struct attribute_group lt9611c_attr_group = { + .attrs = lt9611c_attrs, +}; + +static const struct attribute_group *lt9611c_attr_groups[] = { + <9611c_attr_group, + NULL, +}; + +static int lt9611c_probe(struct i2c_client *client) +{ + struct lt9611c *lt9611c; + struct device *dev = &client->dev; + bool fw_updated = false; + int ret; + + crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n"); + + lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_bridge_funcs); + if (IS_ERR(lt9611c)) + return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n"); + + lt9611c->dev = dev; + lt9611c->client = client; + + const struct lt9611c_chip_data *cdata = i2c_get_match_data(client); + + if (!cdata) + return dev_err_probe(dev, -EINVAL, "no match data for device\n"); + + lt9611c->chip_type = cdata->chip_type; + lt9611c->max_tmds_rate = cdata->max_tmds_rate; + + ret = devm_mutex_init(dev, <9611c->mcu_lock); + if (ret) + return dev_err_probe(dev, ret, "failed to init mutex\n"); + + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config); + if (IS_ERR(lt9611c->regmap)) + return dev_err_probe(dev, PTR_ERR(lt9611c->regmap), "regmap i2c init failed\n"); + + ret = lt9611c_parse_dt(dev, lt9611c); + if (ret) + return dev_err_probe(dev, ret, "failed to parse device tree\n"); + + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611c->reset_gpio)) { + ret = PTR_ERR(lt9611c->reset_gpio); + goto err_of_put; + } + + ret = lt9611c_regulator_init(lt9611c); + if (ret < 0) + goto err_of_put; + + ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) + goto err_of_put; + + lt9611c_reset(lt9611c); + + lt9611c_lock(lt9611c); + + ret = lt9611c_read_chipid(lt9611c); + if (ret < 0) { + dev_err(dev, "failed to read chip id.\n"); + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + +retry: + lt9611c->fw_version = lt9611c_read_version(lt9611c); + if (lt9611c->fw_version < 0) { + dev_err(dev, "failed to read fw version\n"); + ret = -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + + } else if (lt9611c->fw_version == 0) { + if (!fw_updated) { + fw_updated = true; + lt9611c_unlock(lt9611c); + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) + goto err_disable_regulators; + lt9611c_lock(lt9611c); + goto retry; + + } else { + dev_err(dev, "fw version 0x%04x, update failed\n", lt9611c->fw_version); + ret = -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + } + + lt9611c_unlock(lt9611c); + dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version); + + INIT_WORK(<9611c->work, lt9611c_hpd_work); + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + lt9611c_irq_thread_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "lt9611c", lt9611c); + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto err_disable_regulators; + } + + lt9611c->bridge.of_node = client->dev.of_node; + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HDMI_AUDIO; + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + lt9611c->bridge.vendor = "Lontium"; + lt9611c->bridge.product = "LT9611C"; + + lt9611c->bridge.hdmi_audio_dev = dev; + lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8; + lt9611c->bridge.hdmi_audio_dai_port = 2; + + drm_bridge_add(<9611c->bridge); + + /* Attach primary DSI */ + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node); + if (IS_ERR(lt9611c->dsi0)) { + ret = PTR_ERR(lt9611c->dsi0); + goto err_remove_bridge; + } + + /* Attach secondary DSI, if specified */ + if (lt9611c->dsi1_node) { + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node); + if (IS_ERR(lt9611c->dsi1)) { + ret = PTR_ERR(lt9611c->dsi1); + goto err_remove_bridge; + } + } + + lt9611c->hdmi_connected = false; + i2c_set_clientdata(client, lt9611c); + enable_irq(client->irq); + + return 0; + +err_remove_bridge: + drm_bridge_remove(<9611c->bridge); + cancel_work_sync(<9611c->work); + +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + +err_of_put: + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); + + return ret; +} + +static void lt9611c_remove(struct i2c_client *client) +{ + struct lt9611c *lt9611c = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_work_sync(<9611c->work); + drm_bridge_remove(<9611c->bridge); + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); +} + +static int lt9611c_bridge_suspend(struct device *dev) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + disable_irq(lt9611c->client->irq); + cancel_work_sync(<9611c->work); + + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) { + dev_err(lt9611c->dev, "regulator bulk disable failed.\n"); + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); + enable_irq(lt9611c->client->irq); + return ret; + } + + return 0; +} + +static int lt9611c_bridge_resume(struct device *dev) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) { + dev_err(lt9611c->dev, "regulator bulk enable failed.\n"); + return ret; + } + lt9611c_reset(lt9611c); + enable_irq(lt9611c->client->irq); + + return ret; +} + +static const struct dev_pm_ops lt9611c_bridge_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend, + lt9611c_bridge_resume) +}; + +static struct i2c_device_id lt9611c_id[] = { + { "lontium,lt9611c", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611C] }, + { "lontium,lt9611ex", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611EX] }, + { "lontium,lt9611uxd", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611UXD] }, + { /* sentinel */ } +}; + +static const struct of_device_id lt9611c_match_table[] = { + { .compatible = "lontium,lt9611c", .data = <9611c_chip_data[CHIP_LT9611C] }, + { .compatible = "lontium,lt9611ex", .data = <9611c_chip_data[CHIP_LT9611EX] }, + { .compatible = "lontium,lt9611uxd", .data = <9611c_chip_data[CHIP_LT9611UXD] }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lt9611c_match_table); + +static struct i2c_driver lt9611c_driver = { + .driver = { + .name = "lt9611c", + .of_match_table = lt9611c_match_table, + .pm = <9611c_bridge_pm_ops, + .dev_groups = lt9611c_attr_groups, + }, + .probe = lt9611c_probe, + .remove = lt9611c_remove, + .id_table = lt9611c_id, +}; +module_i2c_driver(lt9611c_driver); + +MODULE_AUTHOR("SunYun Yang "); +MODULE_DESCRIPTION("Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FW_FILE); From aa8a839952017fd7016bf4ac2330442db01010f1 Mon Sep 17 00:00:00 2001 From: Nabige Aala Date: Mon, 8 Jun 2026 12:31:42 +0530 Subject: [PATCH 179/188] FROMLIST: dt-bindings: display: msm: qcm2290: Add Shikra MDSS Shikra reuses the same MDSS/DPU 6.5 hardware as QCM2290. Extend the existing qcm2290 bindings to cover Shikra by adding fallback compatible chains for MDSS, DPU and DSI controller nodes rather than introducing a separate binding file. Reviewed-by: Loic Poulain Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-shikra-display-v4-1-88a846afdd5d@oss.qualcomm.com/ Signed-off-by: Nabige Aala Signed-off-by: Mahadevan P --- .../display/msm/dsi-controller-main.yaml | 4 ++++ .../display/msm/qcom,qcm2290-dpu.yaml | 6 +++++- .../display/msm/qcom,qcm2290-mdss.yaml | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml index dbc0613e427ed..fd0834d09ad64 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml +++ b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml @@ -47,6 +47,10 @@ properties: - qcom,sm8650-dsi-ctrl - qcom,sm8750-dsi-ctrl - const: qcom,mdss-dsi-ctrl + - items: + - const: qcom,shikra-dsi-ctrl + - const: qcom,qcm2290-dsi-ctrl + - const: qcom,mdss-dsi-ctrl - items: - enum: - qcom,qcs8300-dsi-ctrl diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml index be6cd8adb3b67..034d3df8d2470 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml @@ -13,7 +13,11 @@ $ref: /schemas/display/msm/dpu-common.yaml# properties: compatible: - const: qcom,qcm2290-dpu + oneOf: + - const: qcom,qcm2290-dpu + - items: + - const: qcom,shikra-dpu + - const: qcom,qcm2290-dpu reg: items: diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml index bb09ecd1a5b4f..49a7b5c4c678e 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/display/msm/qcom,qcm2290-mdss.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Qualcomm QCM220 Display MDSS +title: Qualcomm QCM2290 and Shikra Display MDSS maintainers: - Loic Poulain @@ -12,13 +12,18 @@ maintainers: description: Device tree bindings for MSM Mobile Display Subsystem(MDSS) that encapsulates sub-blocks like DPU display controller and DSI. Device tree bindings of MDSS - are mentioned for QCM2290 target. + are mentioned for QCM2290 and Shikra targets. Shikra uses the same MDSS/DPU/DSI + hardware as QCM2290 (DPU 6.5) and shares the same register layout. $ref: /schemas/display/msm/mdss-common.yaml# properties: compatible: - const: qcom,qcm2290-mdss + oneOf: + - const: qcom,qcm2290-mdss + - items: + - const: qcom,shikra-mdss + - const: qcom,qcm2290-mdss clocks: items: @@ -52,7 +57,8 @@ patternProperties: properties: compatible: - const: qcom,qcm2290-dpu + contains: + const: qcom,qcm2290-dpu "^dsi@[0-9a-f]+$": type: object @@ -60,9 +66,8 @@ patternProperties: properties: compatible: - items: - - const: qcom,qcm2290-dsi-ctrl - - const: qcom,mdss-dsi-ctrl + contains: + const: qcom,qcm2290-dsi-ctrl "^phy@[0-9a-f]+$": type: object From df717e53803bfdced01e20b9f7fbbad5c82ec08e Mon Sep 17 00:00:00 2001 From: Nabige Aala Date: Mon, 8 Jun 2026 12:31:43 +0530 Subject: [PATCH 180/188] FROMLIST: soc: qcom: ubwc: Add Shikra UBWC config Add UBWC configuration for the Shikra platform. Shikra shares the same hardware as QCM2290 (Agatti), so reuse qcm2290_data for the UBWC settings Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260608-shikra-display-v4-2-88a846afdd5d@oss.qualcomm.com/ Signed-off-by: Nabige Aala Signed-off-by: Mahadevan P --- drivers/soc/qcom/ubwc_config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/qcom/ubwc_config.c b/drivers/soc/qcom/ubwc_config.c index 3fe47d8f0f638..1a2e54c6480df 100644 --- a/drivers/soc/qcom/ubwc_config.c +++ b/drivers/soc/qcom/ubwc_config.c @@ -278,6 +278,7 @@ static const struct of_device_id qcom_ubwc_configs[] __maybe_unused = { { .compatible = "qcom,sdm660", .data = &msm8937_data }, { .compatible = "qcom,sdm670", .data = &sdm670_data, }, { .compatible = "qcom,sdm845", .data = &sdm845_data, }, + { .compatible = "qcom,shikra", .data = &qcm2290_data, }, { .compatible = "qcom,sm4250", .data = &sm6115_data, }, { .compatible = "qcom,sm6115", .data = &sm6115_data, }, { .compatible = "qcom,sm6125", .data = &sm6125_data, }, From 209715ecd929604269ce01cd21239e1ae8d13fea Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 3 Aug 2026 16:43:08 +0800 Subject: [PATCH 181/188] FROMLIST: drm/bridge: aux-hpd-bridge: replay last HPD status on hpd_enable If a downstream consumer (e.g. drm_bridge_connector attached by the msm/dp driver) registers its HPD callback after an upstream driver has already reported a HPD event through drm_aux_hpd_bridge_notify(), the notification is dropped because bridge->hpd_cb is still NULL. This can affect any user of drm_aux_hpd_bridge_notify() whose downstream consumer arms HPD only after upstream events have started. The race has been observed on Qualcomm X1E-based laptops during boot, when pmic_glink_altmode reports the initial USB-C DP connection state before the DP driver has finished probing and enabled HPD handling on the bridge. The consumer then never observes the initial connected state and the external display remains dark. Cache the last HPD status reported through drm_aux_hpd_bridge_notify() and replay it when HPD is enabled by the downstream consumer. The replay is deferred to a work item so that the replayed HPD notification is delivered outside drm_bridge_hpd_enable()'s call context. This follows the same pattern as display-connector, which also defers an initial HPD notification from .hpd_enable(), but reuses the cached status since aux-hpd-bridge cannot re-detect sink presence on its own. Fixes: e560518a6c2e ("drm/bridge: implement generic DP HPD bridge") Signed-off-by: Yongxing Mou Link: https://lore.kernel.org/all/20260803-drm-usbdp-preboot-v1-1-2539b362be00@oss.qualcomm.com/ --- drivers/gpu/drm/bridge/aux-hpd-bridge.c | 61 ++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c index 0e2f0b0461213..66afdfe039d36 100644 --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,17 @@ static DEFINE_IDA(drm_aux_hpd_bridge_ida); struct drm_aux_hpd_bridge_data { struct drm_bridge bridge; struct device *dev; + + /* + * Last HPD status pushed through drm_aux_hpd_bridge_notify(). + * Replayed from .hpd_enable so that consumers registering their + * callback after the initial notification are caught up. + * + * Accessed lockless from the notify path (writer) and hpd_work + * (reader) - use WRITE_ONCE()/READ_ONCE(). + */ + enum drm_connector_status last_status; + struct work_struct hpd_work; }; static void drm_aux_hpd_bridge_release(struct device *dev) @@ -156,6 +168,16 @@ void drm_aux_hpd_bridge_notify_extra(struct device *dev, if (!data) return; + /* + * Only cache real connection state changes. IRQ_HPD-only events + * (sink attention on an already-connected link) pass + * connector_status_unknown and must not clobber the last real + * connected/disconnected state, otherwise the replay from + * .hpd_enable would be skipped. + */ + if (status != connector_status_unknown) + WRITE_ONCE(data->last_status, status); + drm_bridge_hpd_notify_extra(&data->bridge, status, extra_status); } EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify_extra); @@ -167,8 +189,42 @@ static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge, return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL; } +static void drm_aux_hpd_bridge_hpd_work(struct work_struct *work) +{ + struct drm_aux_hpd_bridge_data *data = + container_of(work, struct drm_aux_hpd_bridge_data, hpd_work); + enum drm_connector_status status = READ_ONCE(data->last_status); + + if (status == connector_status_unknown) + return; + + drm_bridge_hpd_notify(&data->bridge, status); +} + +/* + * Deferred to a work because drm_bridge_hpd_notify() takes + * bridge->hpd_mutex which is already held by drm_bridge_hpd_enable(). + */ +static void drm_aux_hpd_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct drm_aux_hpd_bridge_data *data = + container_of(bridge, struct drm_aux_hpd_bridge_data, bridge); + + schedule_work(&data->hpd_work); +} + +static void drm_aux_hpd_bridge_hpd_disable(struct drm_bridge *bridge) +{ + struct drm_aux_hpd_bridge_data *data = + container_of(bridge, struct drm_aux_hpd_bridge_data, bridge); + + cancel_work_sync(&data->hpd_work); +} + static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = { - .attach = drm_aux_hpd_bridge_attach, + .attach = drm_aux_hpd_bridge_attach, + .hpd_enable = drm_aux_hpd_bridge_hpd_enable, + .hpd_disable = drm_aux_hpd_bridge_hpd_disable, }; static int drm_aux_hpd_bridge_probe(struct auxiliary_device *auxdev, @@ -191,6 +247,9 @@ static int drm_aux_hpd_bridge_probe(struct auxiliary_device *auxdev, data->bridge.interlace_allowed = true; data->bridge.ycbcr_420_allowed = true; + data->last_status = connector_status_unknown; + INIT_WORK(&data->hpd_work, drm_aux_hpd_bridge_hpd_work); + auxiliary_set_drvdata(auxdev, data); return devm_drm_bridge_add(data->dev, &data->bridge); From 266893defd752299b2840baa794e699e813c0ee0 Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 29 Jul 2026 10:03:26 +0530 Subject: [PATCH 182/188] Revert "FROMLIST: arm64: dts: qcom: monaco: enable audio ML offload memory and SMMU mappings" This change have introduced an issue on multimedia builds where AudioRecord is broken on monaco-evk board. [ 153.325884] q6apm-lpass-dais 3000000.remoteproc:glink-edge:gpr:service@1:bedais: Failed to start APM port 19 [ 153.335982] q6apm-lpass-dais 3000000.remoteproc:glink-edge:gpr:service@1:bedais: ASoC error (-110): at soc_dai_trigger() on SEC_MI2S_TX [ 153.348489] MultiMedia2 Capture: ASoC error (-110): at dpcm_be_dai_trigger() on MultiMedia2 Capture [ 153.357875] MultiMedia2 Capture: ASoC error (-110): trigger FE cmd: 1 failed Revert the change for now, to fix the issue. This reverts commit cd0f5c65c4d5fe21873336b4fe32536b88b12ded. Signed-off-by: Salendarsingh Gaud --- arch/arm64/boot/dts/qcom/monaco-el2.dtso | 4 +--- arch/arm64/boot/dts/qcom/monaco.dtsi | 9 --------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/monaco-el2.dtso b/arch/arm64/boot/dts/qcom/monaco-el2.dtso index b3d009c95b440..7cae65ae499c3 100644 --- a/arch/arm64/boot/dts/qcom/monaco-el2.dtso +++ b/arch/arm64/boot/dts/qcom/monaco-el2.dtso @@ -20,9 +20,7 @@ }; &remoteproc_adsp { - iommus = <&apps_smmu 0x2000 0x0>, - <&apps_smmu 0x2060 0x9>, - <&apps_smmu 0x2062 0x1>; + iommus = <&apps_smmu 0x2000 0x0>; }; &remoteproc_cdsp { diff --git a/arch/arm64/boot/dts/qcom/monaco.dtsi b/arch/arm64/boot/dts/qcom/monaco.dtsi index b38926195223d..41c3aa89b9d4b 100644 --- a/arch/arm64/boot/dts/qcom/monaco.dtsi +++ b/arch/arm64/boot/dts/qcom/monaco.dtsi @@ -795,14 +795,6 @@ #size-cells = <2>; ranges; - audio_cma_mem: qcom,audio-ml { - compatible = "shared-dma-pool"; - alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>; - reusable; - alignment = <0x0 0x400000>; - size = <0x0 0x1000000>; - }; - aop_image_mem: aop-image-region@90800000 { reg = <0x0 0x90800000 0x0 0x60000>; no-map; @@ -2929,7 +2921,6 @@ q6apmdai: dais { compatible = "qcom,q6apm-dais"; iommus = <&apps_smmu 0x2001 0x0>; - memory-region = <&audio_cma_mem>; }; }; From bf0a6dc604585a82ca0c22cdaa22a9e39da8918c Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 5 Aug 2026 10:52:25 +0530 Subject: [PATCH 183/188] Revert "FROMLIST: arm64: dts: qcom: lemans: enable audio ML offload memory and SMMU mappings" This change have introduced an issue on multimedia builds where AudioRecord is broken on lemans-evk board. [ 840.168044] q6apm-lpass-dais 30000000.remoteproc:glink-edge:gpr:service@1:bedais: Failed to start APM port 21 [ 840.178229] q6apm-lpass-dais 30000000.remoteproc:glink-edge:gpr:service@1:bedais: ASoC error (-110): at soc_dai_trigger() on TERT_MI2S_TX [ 840.190901] MultiMedia2 Capture: ASoC error (-110): at dpcm_be_dai_trigger() on MultiMedia2 Capture [ 840.200277] MultiMedia2 Capture: ASoC error (-110): trigger FE cmd: 1 failed Revert the change for now, to fix the issue. This reverts commit f75b90a9cc0f349dce8f51620df7ca257b3dcc55. --- arch/arm64/boot/dts/qcom/lemans-el2.dtso | 4 +--- arch/arm64/boot/dts/qcom/lemans.dtsi | 9 --------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/lemans-el2.dtso b/arch/arm64/boot/dts/qcom/lemans-el2.dtso index 4f7395bccfaef..3efbdda9348b3 100644 --- a/arch/arm64/boot/dts/qcom/lemans-el2.dtso +++ b/arch/arm64/boot/dts/qcom/lemans-el2.dtso @@ -21,9 +21,7 @@ }; &remoteproc_adsp { - iommus = <&apps_smmu 0x3000 0x0>, - <&apps_smmu 0x3060 0x9>, - <&apps_smmu 0x3062 0x1>; + iommus = <&apps_smmu 0x3000 0x0>; }; &remoteproc_cdsp0 { diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index faabcc5c678a9..4248a924be3c7 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -688,14 +688,6 @@ #size-cells = <2>; ranges; - audio_cma_mem: qcom,audio-ml { - compatible = "shared-dma-pool"; - alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>; - reusable; - alignment = <0x0 0x400000>; - size = <0x0 0x1000000>; - }; - sail_ss_mem: sail-ss@80000000 { reg = <0x0 0x80000000 0x0 0x10000000>; no-map; @@ -8279,7 +8271,6 @@ q6apmdai: dais { compatible = "qcom,q6apm-dais"; iommus = <&apps_smmu 0x3001 0x0>; - memory-region = <&audio_cma_mem>; }; }; From 003a00cbdc32231f9efa4c60246d93785fb9162b Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Mon, 10 Aug 2026 17:27:23 +0530 Subject: [PATCH 184/188] Revert "FROMLIST: arm64: dts: qcom: shikra: Add CPU idle states" This change have introduced a regression on Shikra board, wherein Rx on UART Shell is stuck, and no input is accepted. Issue: https://github.com/qualcomm-linux/kernel/issues/921 Revert the change for now, to fix the issue. This reverts commit 6a36c4bd77a860ae8e4d85421f63bd6360b8a3eb. Signed-off-by: Salendarsingh Gaud --- arch/arm64/boot/dts/qcom/shikra.dtsi | 94 +--------------------------- 1 file changed, 1 insertion(+), 93 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 4ed5710948f3b..d9c5e137b3f52 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -67,8 +67,6 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x0>; enable-method = "psci"; - power-domains = <&cpu_pd0>; - power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -87,8 +85,6 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x100>; enable-method = "psci"; - power-domains = <&cpu_pd1>; - power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -107,8 +103,6 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x200>; enable-method = "psci"; - power-domains = <&cpu_pd2>; - power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -127,8 +121,6 @@ compatible = "arm,cortex-a78c"; reg = <0x0 0x300>; enable-method = "psci"; - power-domains = <&cpu_pd3>; - power-domain-names = "psci"; next-level-cache = <&l2_3>; capacity-dmips-mhz = <1946>; dynamic-power-coefficient = <489>; @@ -172,61 +164,6 @@ }; }; - idle-states { - entry-method = "psci"; - - little_cpu_sleep_0: cpu-sleep-0-0 { - compatible = "arm,idle-state"; - idle-state-name = "little-power-down"; - arm,psci-suspend-param = <0x40000003>; - entry-latency-us = <549>; - exit-latency-us = <901>; - min-residency-us = <1774>; - local-timer-stop; - }; - - little_cpu_sleep_1: cpu-sleep-0-1 { - compatible = "arm,idle-state"; - idle-state-name = "little-pll-power-down"; - arm,psci-suspend-param = <0x40000004>; - entry-latency-us = <702>; - exit-latency-us = <915>; - min-residency-us = <4001>; - local-timer-stop; - }; - - big_cpu_sleep_0: cpu-sleep-1-0 { - compatible = "arm,idle-state"; - idle-state-name = "big-power-down"; - arm,psci-suspend-param = <0x40000003>; - entry-latency-us = <523>; - exit-latency-us = <1244>; - min-residency-us = <2207>; - local-timer-stop; - }; - - big_cpu_sleep_1: cpu-sleep-1-1 { - compatible = "arm,idle-state"; - idle-state-name = "big-pll-power-down"; - arm,psci-suspend-param = <0x40000004>; - entry-latency-us = <526>; - exit-latency-us = <1854>; - min-residency-us = <5555>; - local-timer-stop; - }; - }; - - domain_idle_states: domain-idle-states { - cluster_sleep_apss_off: cluster-sleep-0 { - compatible = "domain-idle-state"; - idle-state-name = "cluster-power-down"; - arm,psci-suspend-param = <0x41000044>; - entry-latency-us = <2752>; - exit-latency-us = <3038>; - min-residency-us = <6118>; - }; - }; - l3: l3-cache { compatible = "cache"; cache-level = <3>; @@ -375,35 +312,6 @@ psci: psci { compatible = "arm,psci-1.0"; method = "smc"; - - cpu_pd0: power-domain-cpu0 { - #power-domain-cells = <0>; - power-domains = <&cluster_pd>; - domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; - }; - - cpu_pd1: power-domain-cpu1 { - #power-domain-cells = <0>; - power-domains = <&cluster_pd>; - domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; - }; - - cpu_pd2: power-domain-cpu2 { - #power-domain-cells = <0>; - power-domains = <&cluster_pd>; - domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; - }; - - cpu_pd3: power-domain-cpu3 { - #power-domain-cells = <0>; - power-domains = <&cluster_pd>; - domain-idle-states = <&big_cpu_sleep_0 &big_cpu_sleep_1>; - }; - - cluster_pd: power-domain-cluster { - #power-domain-cells = <0>; - domain-idle-states = <&cluster_sleep_apss_off>; - }; }; rpm: remoteproc { @@ -477,8 +385,8 @@ mboxes = <&apcs_glb 1>; interrupt-controller; #interrupt-cells = <2>; + #power-domain-cells = <0>; interrupt-parent = <&intc>; - power-domains = <&cluster_pd>; qcom,mpm-pin-count = <96>; qcom,mpm-pin-map = <2 275>, /* TSENS0 uplow */ <12 422>, /* DWC3 ss_phy_irq */ From 4020a6eefb695312f51bab5b107624d3e5de70c8 Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 12 Aug 2026 20:15:01 +0530 Subject: [PATCH 185/188] Add qcom-next log files for 20260812 Adding merge log file and topic_SHA1 file Signed-off-by: Salendarsingh Gaud --- qcom-next/merge.log | 520 +++++++++++++++++++++++++++++++++++++++++++ qcom-next/topic_SHA1 | 49 ++++ 2 files changed, 569 insertions(+) create mode 100644 qcom-next/merge.log create mode 100644 qcom-next/topic_SHA1 diff --git a/qcom-next/merge.log b/qcom-next/merge.log new file mode 100644 index 0000000000000..9cf8ae5413f7a --- /dev/null +++ b/qcom-next/merge.log @@ -0,0 +1,520 @@ +Verified existence of local and remote repos: Success +/local/mnt/workspace/sgaud/Builds/Github/All_Runners/kernel-automation/actions-runner/_work/kernel-automation/kernel-automation/kernel-topics /local/mnt/workspace/sgaud/Builds/Github/All_Runners/kernel-automation/actions-runner/_work/kernel-automation/kernel-automation/kernel-topics +Reuse-Recorded-Resolution: Enabled +Downloaded shared rerere cache +Local tree is clean +Removing old remotes ... +The remote kernel https://github.com/qualcomm-linux/kernel.git is no longer tracked. +Delete it [Y/n]? The remote origin https://github.com/qualcomm-linux/kernel-topics.git is no longer tracked. +Delete it [Y/n]? The remote trovalds https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git is no longer tracked. +Delete it [Y/n]? Done, removed 3 old remote(s). +Adding new remotes... +Adding remote baseline https://github.com/qualcomm-linux/kernel.git qcom-next-staging +Updating baseline +Adding remote tech/bsp/clk https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/clk +Updating tech/bsp/clk +Adding remote tech/bsp/devfreq https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/devfreq +Updating tech/bsp/devfreq +Adding remote tech/bsp/ec https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/ec +Updating tech/bsp/ec +Adding remote tech/bsp/interconnect https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/interconnect +Updating tech/bsp/interconnect +Adding remote tech/mem/secure-buffer https://github.com/qualcomm-linux/kernel-topics.git tech/mem/secure-buffer +Updating tech/mem/secure-buffer +Adding remote tech/security/firmware-smc https://github.com/qualcomm-linux/kernel-topics.git tech/security/firmware-smc +Updating tech/security/firmware-smc +Adding remote tech/bsp/soc-infra https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/soc-infra +Updating tech/bsp/soc-infra +Adding remote tech/debug/soc https://github.com/qualcomm-linux/kernel-topics.git tech/debug/soc +Updating tech/debug/soc +Adding remote tech/bsp/pinctrl https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/pinctrl +Updating tech/bsp/pinctrl +Adding remote tech/bsp/remoteproc https://github.com/qualcomm-linux/kernel-topics.git tech/bsp/remoteproc +Updating tech/bsp/remoteproc +Adding remote tech/bus/peripherals https://github.com/qualcomm-linux/kernel-topics.git tech/bus/peripherals +Updating tech/bus/peripherals +Adding remote tech/bus/pci/all https://github.com/qualcomm-linux/kernel-topics.git tech/bus/pci/all +Updating tech/bus/pci/all +Adding remote tech/bus/pci/mhi https://github.com/qualcomm-linux/kernel-topics.git tech/bus/pci/mhi +Updating tech/bus/pci/mhi +Adding remote tech/bus/pci/phy https://github.com/qualcomm-linux/kernel-topics.git tech/bus/pci/phy +Updating tech/bus/pci/phy +Adding remote tech/bus/pci/pwrctl https://github.com/qualcomm-linux/kernel-topics.git tech/bus/pci/pwrctl +Updating tech/bus/pci/pwrctl +Adding remote tech/bus/usb/dwc https://github.com/qualcomm-linux/kernel-topics.git tech/bus/usb/dwc +Updating tech/bus/usb/dwc +Adding remote tech/bus/usb/gadget https://github.com/qualcomm-linux/kernel-topics.git tech/bus/usb/gadget +Updating tech/bus/usb/gadget +Adding remote tech/bus/usb/phy https://github.com/qualcomm-linux/kernel-topics.git tech/bus/usb/phy +Updating tech/bus/usb/phy +Adding remote tech/debug/eud https://github.com/qualcomm-linux/kernel-topics.git tech/debug/eud +Updating tech/debug/eud +Adding remote tech/debug/hwtracing https://github.com/qualcomm-linux/kernel-topics.git tech/debug/hwtracing +Updating tech/debug/hwtracing +Adding remote tech/debug/rdbg https://github.com/qualcomm-linux/kernel-topics.git tech/debug/rdbg +Updating tech/debug/rdbg +Adding remote tech/pmic/backlight https://github.com/qualcomm-linux/kernel-topics.git tech/pmic/backlight +Updating tech/pmic/backlight +Adding remote tech/pmic/mfd https://github.com/qualcomm-linux/kernel-topics.git tech/pmic/mfd +Updating tech/pmic/mfd +Adding remote tech/pmic/misc https://github.com/qualcomm-linux/kernel-topics.git tech/pmic/misc +Updating tech/pmic/misc +Adding remote tech/pmic/regulator https://github.com/qualcomm-linux/kernel-topics.git tech/pmic/regulator +Updating tech/pmic/regulator +Adding remote tech/pmic/supply https://github.com/qualcomm-linux/kernel-topics.git tech/pmic/supply +Updating tech/pmic/supply +Adding remote tech/mem/dma-buf https://github.com/qualcomm-linux/kernel-topics.git tech/mem/dma-buf +Updating tech/mem/dma-buf +Adding remote tech/mem/iommu https://github.com/qualcomm-linux/kernel-topics.git tech/mem/iommu +Updating tech/mem/iommu +Adding remote tech/mm/audio/all https://github.com/qualcomm-linux/kernel-topics.git tech/mm/audio/all +Updating tech/mm/audio/all +Adding remote tech/mm/audio/soundwire https://github.com/qualcomm-linux/kernel-topics.git tech/mm/audio/soundwire +Updating tech/mm/audio/soundwire +Adding remote tech/mm/camss https://github.com/qualcomm-linux/kernel-topics.git tech/mm/camss +Updating tech/mm/camss +Adding remote tech/mm/drm https://github.com/qualcomm-linux/kernel-topics.git tech/mm/drm +Updating tech/mm/drm +Adding remote tech/mm/fastrpc https://github.com/qualcomm-linux/kernel-topics.git tech/mm/fastrpc +Updating tech/mm/fastrpc +Adding remote tech/mm/phy https://github.com/qualcomm-linux/kernel-topics.git tech/mm/phy +Updating tech/mm/phy +Adding remote tech/mm/video https://github.com/qualcomm-linux/kernel-topics.git tech/mm/video +Updating tech/mm/video +Adding remote tech/mm/gpu https://github.com/qualcomm-linux/kernel-topics.git tech/mm/gpu +Updating tech/mm/gpu +Adding remote tech/mproc/rpmsg https://github.com/qualcomm-linux/kernel-topics.git tech/mproc/rpmsg +Updating tech/mproc/rpmsg +Adding remote tech/mproc/qmi https://github.com/qualcomm-linux/kernel-topics.git tech/mproc/qmi +Updating tech/mproc/qmi +Adding remote tech/net/ath https://github.com/qualcomm-linux/kernel-topics.git tech/net/ath +Updating tech/net/ath +Adding remote tech/net/eth https://github.com/qualcomm-linux/kernel-topics.git tech/net/eth +Updating tech/net/eth +Adding remote tech/net/rmnet https://github.com/qualcomm-linux/kernel-topics.git tech/net/rmnet +Updating tech/net/rmnet +Adding remote tech/net/qrtr https://github.com/qualcomm-linux/kernel-topics.git tech/net/qrtr +Updating tech/net/qrtr +Adding remote tech/net/phy https://github.com/qualcomm-linux/kernel-topics.git tech/net/phy +Updating tech/net/phy +Adding remote tech/net/bluetooth https://github.com/qualcomm-linux/kernel-topics.git tech/net/bluetooth +Updating tech/net/bluetooth +Adding remote tech/pm/opp https://github.com/qualcomm-linux/kernel-topics.git tech/pm/opp +Updating tech/pm/opp +Adding remote tech/pm/pmdomain https://github.com/qualcomm-linux/kernel-topics.git tech/pm/pmdomain +Updating tech/pm/pmdomain +Adding remote tech/pm/power https://github.com/qualcomm-linux/kernel-topics.git tech/pm/power +Updating tech/pm/power +Adding remote tech/pm/thermal https://github.com/qualcomm-linux/kernel-topics.git tech/pm/thermal +Updating tech/pm/thermal +Adding remote tech/security/crypto https://github.com/qualcomm-linux/kernel-topics.git tech/security/crypto +Updating tech/security/crypto +Adding remote tech/security/fscrypt https://github.com/qualcomm-linux/kernel-topics.git tech/security/fscrypt +Updating tech/security/fscrypt +Adding remote tech/security/ice https://github.com/qualcomm-linux/kernel-topics.git tech/security/ice +Updating tech/security/ice +Adding remote tech/storage/nvmem https://github.com/qualcomm-linux/kernel-topics.git tech/storage/nvmem +Updating tech/storage/nvmem +Adding remote tech/storage/phy https://github.com/qualcomm-linux/kernel-topics.git tech/storage/phy +Updating tech/storage/phy +Adding remote tech/storage/all https://github.com/qualcomm-linux/kernel-topics.git tech/storage/all +Updating tech/storage/all +Adding remote tech/virt/gunyah https://github.com/qualcomm-linux/kernel-topics.git tech/virt/gunyah +Updating tech/virt/gunyah +Adding remote tech/all/dt/qcs6490 https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/qcs6490 +Updating tech/all/dt/qcs6490 +Adding remote tech/all/dt/qcs9100 https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/qcs9100 +Updating tech/all/dt/qcs9100 +Adding remote tech/all/dt/qcs8300 https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/qcs8300 +Updating tech/all/dt/qcs8300 +Adding remote tech/all/dt/qcs615 https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/qcs615 +Updating tech/all/dt/qcs615 +Adding remote tech/all/dt/agatti https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/agatti +Updating tech/all/dt/agatti +Adding remote tech/all/dt/eliza https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/eliza +Updating tech/all/dt/eliza +Adding remote tech/all/dt/hamoa https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/hamoa +Updating tech/all/dt/hamoa +Adding remote tech/all/dt/glymur https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/glymur +Updating tech/all/dt/glymur +Adding remote tech/all/dt/kaanapali https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/kaanapali +Updating tech/all/dt/kaanapali +Adding remote tech/all/dt/pakala https://github.com/qualcomm-linux/kernel-topics.git tech/all/dt/pakala +Updating tech/all/dt/pakala +Adding remote tech/all/config https://github.com/qualcomm-linux/kernel-topics.git tech/all/config +Updating tech/all/config +Adding remote tech/overlay/dt https://github.com/qualcomm-linux/kernel-topics.git tech/overlay/dt +Updating tech/overlay/dt +Adding remote tech/all/workaround https://github.com/qualcomm-linux/kernel-topics.git tech/all/workaround +Updating tech/all/workaround +Adding remote tech/mproc/all https://github.com/qualcomm-linux/kernel-topics.git tech/mproc/all +Updating tech/mproc/all +Adding remote tech/noup/debug/all https://github.com/qualcomm-linux/kernel-topics.git tech/noup/debug/all +Updating tech/noup/debug/all +Adding remote tech/hwe/unoq https://github.com/qualcomm-linux/kernel-topics.git tech/hwe/unoq +Updating tech/hwe/unoq +Adding remote early/hwe/shikra/drivers https://github.com/qualcomm-linux/kernel-topics.git early/hwe/shikra/drivers +Updating early/hwe/shikra/drivers +Adding remote early/hwe/shikra/dt https://github.com/qualcomm-linux/kernel-topics.git early/hwe/shikra/dt +Updating early/hwe/shikra/dt +Adding remote early/hwe/lyra https://github.com/qualcomm-linux/kernel-topics.git early/hwe/lyra +Updating early/hwe/lyra +Done, added 75 new remote(s). +Updating the remotes ... +Updating tech/bsp/clk +Updating tech/bsp/devfreq +Updating tech/bsp/ec +Updating tech/bsp/interconnect +Updating tech/mem/secure-buffer +Updating tech/security/firmware-smc +Updating tech/bsp/soc-infra +Updating tech/debug/soc +Updating tech/bsp/pinctrl +Updating tech/bsp/remoteproc +Updating tech/bus/peripherals +Updating tech/bus/pci/all +Updating tech/bus/pci/mhi +Updating tech/bus/pci/phy +Updating tech/bus/pci/pwrctl +Updating tech/bus/usb/dwc +Updating tech/bus/usb/gadget +Updating tech/bus/usb/phy +Updating tech/debug/eud +Updating tech/debug/hwtracing +Updating tech/debug/rdbg +Updating tech/pmic/backlight +Updating tech/pmic/mfd +Updating tech/pmic/misc +Updating tech/pmic/regulator +Updating tech/pmic/supply +Updating tech/mem/dma-buf +Updating tech/mem/iommu +Updating tech/mm/audio/all +Updating tech/mm/audio/soundwire +Updating tech/mm/camss +Updating tech/mm/drm +Updating tech/mm/fastrpc +Updating tech/mm/phy +Updating tech/mm/video +Updating tech/mm/gpu +Updating tech/mproc/rpmsg +Updating tech/mproc/qmi +Updating tech/net/ath +Updating tech/net/eth +Updating tech/net/rmnet +Updating tech/net/qrtr +Updating tech/net/phy +Updating tech/net/bluetooth +Updating tech/pm/opp +Updating tech/pm/pmdomain +Updating tech/pm/power +Updating tech/pm/thermal +Updating tech/security/crypto +Updating tech/security/fscrypt +Updating tech/security/ice +Updating tech/storage/nvmem +Updating tech/storage/phy +Updating tech/storage/all +Updating tech/virt/gunyah +Updating tech/all/dt/qcs6490 +Updating tech/all/dt/qcs9100 +Updating tech/all/dt/qcs8300 +Updating tech/all/dt/qcs615 +Updating tech/all/dt/agatti +Updating tech/all/dt/eliza +Updating tech/all/dt/hamoa +Updating tech/all/dt/glymur +Updating tech/all/dt/kaanapali +Updating tech/all/dt/pakala +Updating tech/all/config +Updating tech/overlay/dt +Updating tech/all/workaround +Updating tech/mproc/all +Updating tech/noup/debug/all +Updating tech/hwe/unoq +Updating early/hwe/shikra/drivers +Updating early/hwe/shikra/dt +Updating early/hwe/lyra +Done, updated 0 remote(s). +Updating baseline ... +Fetching baseline +latest tag/id is db2ddb87143519e20a95aa36c60b36107b736a58 +Done, updated baseline. +Latest tag is db2ddb87143519e20a95aa36c60b36107b736a58 +Create a new integration branch based on db2ddb87143519e20a95aa36c60b36107b736a58 +Merging topic branches... +------------------------------------------ + ** Merging topic branch: tech/bsp/clk/tech/bsp/clk +Merge successful : tech/bsp/clk : 24b9b5e45f09d6d747e025eedd9a296f0b5bd1e9 : 32 +------------------------------------------ + ** Merging topic branch: tech/bsp/devfreq/tech/bsp/devfreq +Merge successful : tech/bsp/devfreq : 480953456f9e3ec33a295dcca3ae1ad3fc55ac6e : 7 +------------------------------------------ + ** Merging topic branch: tech/bsp/ec/tech/bsp/ec +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/bsp/interconnect/tech/bsp/interconnect +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/mem/secure-buffer/tech/mem/secure-buffer +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/security/firmware-smc/tech/security/firmware-smc +Merge successful : tech/security/firmware-smc : de7413c5885e6106c7da9650d992ff2c0293525c : 6 +------------------------------------------ + ** Merging topic branch: tech/bsp/soc-infra/tech/bsp/soc-infra +Merge successful : tech/bsp/soc-infra : ff6ff7b65a2f4ba4822a6a5343f949b43cd6e87f : 22 +------------------------------------------ + ** Merging topic branch: tech/debug/soc/tech/debug/soc +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/bsp/pinctrl/tech/bsp/pinctrl +Merge successful : tech/bsp/pinctrl : 79149ef6a826d6237346aa74eca65ccbe19d761f : 1 +------------------------------------------ + ** Merging topic branch: tech/bsp/remoteproc/tech/bsp/remoteproc +Merge successful : tech/bsp/remoteproc : 39a86aa1e9c03be4b388d65a70fac68e69476be0 : 13 +------------------------------------------ + ** Merging topic branch: tech/bus/peripherals/tech/bus/peripherals +Merge successful : tech/bus/peripherals : e6f67411cde0905ca6034286f47f993cebed386e : 8 +------------------------------------------ + ** Merging topic branch: tech/bus/pci/all/tech/bus/pci/all +Merge successful : tech/bus/pci/all : 99d5cf2f33770c1850038f2fcfac7f98ce375ac5 : 45 +------------------------------------------ + ** Merging topic branch: tech/bus/pci/mhi/tech/bus/pci/mhi +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/bus/pci/phy/tech/bus/pci/phy +Merge successful : tech/bus/pci/phy : 66e44c2741ecc5bc40d8e66ee62ea5c5c179cbc7 : 14 +------------------------------------------ + ** Merging topic branch: tech/bus/pci/pwrctl/tech/bus/pci/pwrctl +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/bus/usb/dwc/tech/bus/usb/dwc +Merge successful : tech/bus/usb/dwc : 9dd47ad7e5c1dea9cfbcdd3f5e84af8d647af95f : 3 +------------------------------------------ + ** Merging topic branch: tech/bus/usb/gadget/tech/bus/usb/gadget +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/bus/usb/phy/tech/bus/usb/phy +Merge successful : tech/bus/usb/phy : c3aa7d5c9b171a9fb9ae9d1a9adf4dab251508f9 : 35 +------------------------------------------ + ** Merging topic branch: tech/debug/eud/tech/debug/eud +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/debug/hwtracing/tech/debug/hwtracing +Merge successful : tech/debug/hwtracing : 27d6059813d700717fdaf6c144693ab6a236d6e4 : 23 +------------------------------------------ + ** Merging topic branch: tech/debug/rdbg/tech/debug/rdbg +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pmic/backlight/tech/pmic/backlight +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pmic/mfd/tech/pmic/mfd +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pmic/misc/tech/pmic/misc +Merge successful : tech/pmic/misc : 8d60b5132ccc7e3505569fd1c476f3b3ada16286 : 22 +------------------------------------------ + ** Merging topic branch: tech/pmic/regulator/tech/pmic/regulator +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pmic/supply/tech/pmic/supply +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/mem/dma-buf/tech/mem/dma-buf +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/mem/iommu/tech/mem/iommu +Merge successful : tech/mem/iommu : cdc9e8034c64ae939077d3780be625bea6b923e9 : 9 +------------------------------------------ + ** Merging topic branch: tech/mm/audio/all/tech/mm/audio/all +Merge successful : tech/mm/audio/all : 88b8f29570c5211e654cec357e6a1282f92737b7 : 8 +------------------------------------------ + ** Merging topic branch: tech/mm/audio/soundwire/tech/mm/audio/soundwire +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/mm/camss/tech/mm/camss +Merge successful : tech/mm/camss : dffc6e97f95f9c17ef21a558a29f96e54944017c : 49 +------------------------------------------ + ** Merging topic branch: tech/mm/drm/tech/mm/drm +Merge successful : tech/mm/drm : 209715ecd929604269ce01cd21239e1ae8d13fea : 75 +------------------------------------------ + ** Merging topic branch: tech/mm/fastrpc/tech/mm/fastrpc +Merge failed, manual merge +No files need merging +[qcom-next b34b1016b0c2] Merge remote-tracking branch tech/mm/fastrpc into qcom-next +Merge successful : tech/mm/fastrpc : bff2f473e68a1a70c6998cf6648d75cd4a9a6bde : 12 +------------------------------------------ + ** Merging topic branch: tech/mm/phy/tech/mm/phy +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/mm/video/tech/mm/video +Merge successful : tech/mm/video : bdcc5c43477bf32b98214de3963e2ffdba31fa1e : 127 +------------------------------------------ + ** Merging topic branch: tech/mm/gpu/tech/mm/gpu +Merge successful : tech/mm/gpu : 0b8d9f42795065bb8e25714b5a169b59ae7e23a2 : 7 +------------------------------------------ + ** Merging topic branch: tech/mproc/rpmsg/tech/mproc/rpmsg +Merge successful : tech/mproc/rpmsg : 55dc46433784180276e93105b359bdce601d492e : 1 +------------------------------------------ + ** Merging topic branch: tech/mproc/qmi/tech/mproc/qmi +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/net/ath/tech/net/ath +Merge successful : tech/net/ath : 149122bebfa60c5f393153f5215fb77eab077698 : 20 +------------------------------------------ + ** Merging topic branch: tech/net/eth/tech/net/eth +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/net/rmnet/tech/net/rmnet +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/net/qrtr/tech/net/qrtr +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/net/phy/tech/net/phy +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/net/bluetooth/tech/net/bluetooth +Merge failed, manual merge +No files need merging +[qcom-next cf032f24bea6] Merge remote-tracking branch tech/net/bluetooth into qcom-next +Merge successful : tech/net/bluetooth : 450dc5efa2636baf7a6ebb4c7b784d8529141245 : 7 +------------------------------------------ + ** Merging topic branch: tech/pm/opp/tech/pm/opp +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pm/pmdomain/tech/pm/pmdomain +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/pm/power/tech/pm/power +Merge successful : tech/pm/power : f712532bb684387c2681cc912859b9d03e81457b : 16 +------------------------------------------ + ** Merging topic branch: tech/pm/thermal/tech/pm/thermal +Merge failed, manual merge +No files need merging +[qcom-next 07e2d4bbdc18] Merge remote-tracking branch tech/pm/thermal into qcom-next +Merge successful : tech/pm/thermal : d525ff96debf4348da73f19056038b06864095b3 : 8 +------------------------------------------ + ** Merging topic branch: tech/security/crypto/tech/security/crypto +Merge successful : tech/security/crypto : 1f60c0a2ca909753004854053ebac4cd45719e92 : 23 +------------------------------------------ + ** Merging topic branch: tech/security/fscrypt/tech/security/fscrypt +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/security/ice/tech/security/ice +Merge successful : tech/security/ice : beabac03036a596434bb02c3f91b7c9672ea7c15 : 9 +------------------------------------------ + ** Merging topic branch: tech/storage/nvmem/tech/storage/nvmem +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/storage/phy/tech/storage/phy +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/storage/all/tech/storage/all +Merge successful : tech/storage/all : cef1b3c7fc3078e27ca66dd58e8f9fe86b0e077a : 5 +------------------------------------------ + ** Merging topic branch: tech/virt/gunyah/tech/virt/gunyah +Nothing to merge: Already up to date. +------------------------------------------ + ** Merging topic branch: tech/all/dt/qcs6490/tech/all/dt/qcs6490 +Merge failed, manual merge +No files need merging +[qcom-next 4943c3abbe25] Merge remote-tracking branch tech/all/dt/qcs6490 into qcom-next +Merge successful : tech/all/dt/qcs6490 : 740676bbcc99769adabe7b3037019b06167777c8 : 27 +------------------------------------------ + ** Merging topic branch: tech/all/dt/qcs9100/tech/all/dt/qcs9100 +Merge failed, manual merge +No files need merging +[qcom-next ae57e0dbd7ac] Merge remote-tracking branch tech/all/dt/qcs9100 into qcom-next +Merge successful : tech/all/dt/qcs9100 : 0a48d61247aa4306f7ff83ceb8fdcbb7c06e55cc : 92 +------------------------------------------ + ** Merging topic branch: tech/all/dt/qcs8300/tech/all/dt/qcs8300 +Merge failed, manual merge +No files need merging +[qcom-next 1cda810b8eb4] Merge remote-tracking branch tech/all/dt/qcs8300 into qcom-next +Merge successful : tech/all/dt/qcs8300 : a02cf612f13f5efe97b10e2e228a8d727297632c : 31 +------------------------------------------ + ** Merging topic branch: tech/all/dt/qcs615/tech/all/dt/qcs615 +Merge successful : tech/all/dt/qcs615 : 5cec04d74f3d04ab1dd2078a929752246d059d62 : 10 +------------------------------------------ + ** Merging topic branch: tech/all/dt/agatti/tech/all/dt/agatti +Merge successful : tech/all/dt/agatti : c828f10cd2c53b7ff2cc061e73b239973ee17bc6 : 1 +------------------------------------------ + ** Merging topic branch: tech/all/dt/eliza/tech/all/dt/eliza +Merge successful : tech/all/dt/eliza : 5338032c5f8d1b9ad79a33cc6fb24e62b13d2668 : 25 +------------------------------------------ + ** Merging topic branch: tech/all/dt/hamoa/tech/all/dt/hamoa +Merge successful : tech/all/dt/hamoa : 106f486e8dedb5371b83fdb0001f5e8ba70c8606 : 49 +------------------------------------------ + ** Merging topic branch: tech/all/dt/glymur/tech/all/dt/glymur +Merge successful : tech/all/dt/glymur : c5bf216cf0bb7c05d02a57021492d3d5452b09c0 : 60 +------------------------------------------ + ** Merging topic branch: tech/all/dt/kaanapali/tech/all/dt/kaanapali +Merge successful : tech/all/dt/kaanapali : d21ef73d814a2cf34e6c0850a8dfb9f796ba8ae9 : 26 +------------------------------------------ + ** Merging topic branch: tech/all/dt/pakala/tech/all/dt/pakala +Merge failed, manual merge +No files need merging +[qcom-next 0e67fc29b6dd] Merge remote-tracking branch tech/all/dt/pakala into qcom-next +Merge successful : tech/all/dt/pakala : 33ecf6f10af6a446cb234b92f5579e23d7d0b8e8 : 14 +------------------------------------------ + ** Merging topic branch: tech/all/config/tech/all/config +Merge successful : tech/all/config : 0e19c8689cccb93d91806b975dfc8ed2cf10a82c : 76 +------------------------------------------ + ** Merging topic branch: tech/overlay/dt/tech/overlay/dt +Merge failed, manual merge +No files need merging +[qcom-next caeae307cd33] Merge remote-tracking branch tech/overlay/dt into qcom-next +Merge successful : tech/overlay/dt : b895d0e7537337daeeb814cea5cf764664b1bc36 : 78 +------------------------------------------ + ** Merging topic branch: tech/all/workaround/tech/all/workaround +Merge failed, manual merge +No files need merging +[qcom-next f7feefb3a96c] Merge remote-tracking branch tech/all/workaround into qcom-next +Merge successful : tech/all/workaround : 219d869a96c8fb79ab6c6604eaf61b0656c135ec : 12 +------------------------------------------ + ** Merging topic branch: tech/mproc/all/tech/mproc/all +Merge successful : tech/mproc/all : 104969c5b070fdc7bf93c7109c625b8b655a93ee : 2 +------------------------------------------ + ** Merging topic branch: tech/noup/debug/all/tech/noup/debug/all +Merge failed, manual merge +No files need merging +[qcom-next e93f77e82f44] Merge remote-tracking branch tech/noup/debug/all into qcom-next +Merge successful : tech/noup/debug/all : e47307681abe61565162d394afd788b86d460ed4 : 29 +------------------------------------------ + ** Merging topic branch: tech/hwe/unoq/tech/hwe/unoq +Merge successful : tech/hwe/unoq : a2d85fe547167dbd8fc59618907c20a6f17d2e5a : 4 +------------------------------------------ + ** Merging topic branch: early/hwe/shikra/drivers/early/hwe/shikra/drivers +Merge failed, manual merge +No files need merging +[qcom-next ad751ecfbed1] Merge remote-tracking branch early/hwe/shikra/drivers into qcom-next +Merge successful : early/hwe/shikra/drivers : b19d2ec290c8ae6fa2d3e8c1955413c4f20d4c3e : 184 +------------------------------------------ + ** Merging topic branch: early/hwe/shikra/dt/early/hwe/shikra/dt +Merge failed, manual merge +No files need merging +[qcom-next 1589d6874121] Merge remote-tracking branch early/hwe/shikra/dt into qcom-next +Merge successful : early/hwe/shikra/dt : dd90d88cfaad62aec33e4278bdd69b95aff0480d : 130 +------------------------------------------ + ** Merging topic branch: early/hwe/lyra/early/hwe/lyra +Merge failed, manual merge +No files need merging +[qcom-next a1e3c5ee5c9e] Merge remote-tracking branch early/hwe/lyra into qcom-next +Merge successful : early/hwe/lyra : d92faf9ccfd1376910986c5e7fa0e343c2eb1349 : 3 +Done, merged 47 topic(s). +[main 4375660] New rr-cache entries from ci-merge + 11 files changed, 36509 insertions(+) + create mode 100644 rr-cache/0364a1e4d8c5b29af9f441858f8380c0b89d7246/thisimage + create mode 100644 rr-cache/05e82070c315b2be48cd33792127cf3a72b5a438/thisimage + create mode 100644 rr-cache/0e82148188f7884397d317e3bcc4c2b5fa43d338/thisimage + create mode 100644 rr-cache/1b571f8cb815965b5cf4107eed32d5ab45a78aa2/thisimage + create mode 100644 rr-cache/4973cedb6f02099e8e49f38dc7931b182065b033/thisimage + create mode 100644 rr-cache/77696862045046fd743e45105d8fb3be8b30f5bd/thisimage + create mode 100644 rr-cache/7f101354216d063c68eae6fe874fd942bdbada17/thisimage + create mode 100644 rr-cache/7f8fd6d87d79173f9288206b701acb774e27a59d/thisimage + create mode 100644 rr-cache/9d51a59a1af0dae20770edb7c712ccfda036ddb5/thisimage + create mode 100644 rr-cache/b06fcc4729d60ffe65fe867d6d9227eb19fae33e/thisimage + create mode 100644 rr-cache/d8ab4fabaa4b0ccf2bd42f56176096466e5a83c9/thisimage diff --git a/qcom-next/topic_SHA1 b/qcom-next/topic_SHA1 new file mode 100644 index 0000000000000..326292fc37798 --- /dev/null +++ b/qcom-next/topic_SHA1 @@ -0,0 +1,49 @@ +Name SHA Commits +------------------------------------------------------------------------------------ +tech/bsp/clk 24b9b5e45f09d6d747e025eedd9a296f0b5bd1e9 32 +tech/bsp/devfreq 480953456f9e3ec33a295dcca3ae1ad3fc55ac6e 7 +tech/security/firmware-smc de7413c5885e6106c7da9650d992ff2c0293525c 6 +tech/bsp/soc-infra ff6ff7b65a2f4ba4822a6a5343f949b43cd6e87f 22 +tech/bsp/pinctrl 79149ef6a826d6237346aa74eca65ccbe19d761f 1 +tech/bsp/remoteproc 39a86aa1e9c03be4b388d65a70fac68e69476be0 13 +tech/bus/peripherals e6f67411cde0905ca6034286f47f993cebed386e 8 +tech/bus/pci/all 99d5cf2f33770c1850038f2fcfac7f98ce375ac5 45 +tech/bus/pci/phy 66e44c2741ecc5bc40d8e66ee62ea5c5c179cbc7 14 +tech/bus/usb/dwc 9dd47ad7e5c1dea9cfbcdd3f5e84af8d647af95f 3 +tech/bus/usb/phy c3aa7d5c9b171a9fb9ae9d1a9adf4dab251508f9 35 +tech/debug/hwtracing 27d6059813d700717fdaf6c144693ab6a236d6e4 23 +tech/pmic/misc 8d60b5132ccc7e3505569fd1c476f3b3ada16286 22 +tech/mem/iommu cdc9e8034c64ae939077d3780be625bea6b923e9 9 +tech/mm/audio/all 88b8f29570c5211e654cec357e6a1282f92737b7 8 +tech/mm/camss dffc6e97f95f9c17ef21a558a29f96e54944017c 49 +tech/mm/drm 209715ecd929604269ce01cd21239e1ae8d13fea 75 +tech/mm/fastrpc bff2f473e68a1a70c6998cf6648d75cd4a9a6bde 12 +tech/mm/video bdcc5c43477bf32b98214de3963e2ffdba31fa1e 127 +tech/mm/gpu 0b8d9f42795065bb8e25714b5a169b59ae7e23a2 7 +tech/mproc/rpmsg 55dc46433784180276e93105b359bdce601d492e 1 +tech/net/ath 149122bebfa60c5f393153f5215fb77eab077698 20 +tech/net/bluetooth 450dc5efa2636baf7a6ebb4c7b784d8529141245 7 +tech/pm/power f712532bb684387c2681cc912859b9d03e81457b 16 +tech/pm/thermal d525ff96debf4348da73f19056038b06864095b3 8 +tech/security/crypto 1f60c0a2ca909753004854053ebac4cd45719e92 23 +tech/security/ice beabac03036a596434bb02c3f91b7c9672ea7c15 9 +tech/storage/all cef1b3c7fc3078e27ca66dd58e8f9fe86b0e077a 5 +tech/all/dt/qcs6490 740676bbcc99769adabe7b3037019b06167777c8 27 +tech/all/dt/qcs9100 0a48d61247aa4306f7ff83ceb8fdcbb7c06e55cc 92 +tech/all/dt/qcs8300 a02cf612f13f5efe97b10e2e228a8d727297632c 31 +tech/all/dt/qcs615 5cec04d74f3d04ab1dd2078a929752246d059d62 10 +tech/all/dt/agatti c828f10cd2c53b7ff2cc061e73b239973ee17bc6 1 +tech/all/dt/eliza 5338032c5f8d1b9ad79a33cc6fb24e62b13d2668 25 +tech/all/dt/hamoa 106f486e8dedb5371b83fdb0001f5e8ba70c8606 49 +tech/all/dt/glymur c5bf216cf0bb7c05d02a57021492d3d5452b09c0 60 +tech/all/dt/kaanapali d21ef73d814a2cf34e6c0850a8dfb9f796ba8ae9 26 +tech/all/dt/pakala 33ecf6f10af6a446cb234b92f5579e23d7d0b8e8 14 +tech/all/config 0e19c8689cccb93d91806b975dfc8ed2cf10a82c 76 +tech/overlay/dt b895d0e7537337daeeb814cea5cf764664b1bc36 78 +tech/all/workaround 219d869a96c8fb79ab6c6604eaf61b0656c135ec 12 +tech/mproc/all 104969c5b070fdc7bf93c7109c625b8b655a93ee 2 +tech/noup/debug/all e47307681abe61565162d394afd788b86d460ed4 29 +tech/hwe/unoq a2d85fe547167dbd8fc59618907c20a6f17d2e5a 4 +early/hwe/shikra/drivers b19d2ec290c8ae6fa2d3e8c1955413c4f20d4c3e 184 +early/hwe/shikra/dt dd90d88cfaad62aec33e4278bdd69b95aff0480d 130 +early/hwe/lyra d92faf9ccfd1376910986c5e7fa0e343c2eb1349 3 From dbdf896df2901878eed9a84a82ad32ee1d55ce84 Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Wed, 19 Aug 2026 10:45:30 +0530 Subject: [PATCH 186/188] FROMLIST: arm64: dts: qcom: monaco{,-evk}: Move graph port/endpoint anchors to board file of_graph_is_present() only checks for the presence of a 'port' child node, not whether remote-endpoint is actually connected. Adding empty port anchor nodes to monaco.dtsi caused hci_qca to enter the M.2 pwrseq probe path on all monaco-based boards, including qcs8300-ride which has a soldered WCN6855 and no M.2 Key E connector. This broke BT initialization on qcs8300-ride. Fix this by moving the port/endpoint nodes from monaco.dtsi into the monaco-evk.dts board file where the M.2 connector is actually present, so that of_graph_is_present() only returns true for boards that have an M.2 Key E connector described. Fixes: 2479c6b2096e ("FROMLIST: arm64: dts: qcom: monaco: Add graph port/endpoint anchors to pcieport0 and uart2") Link: https://lore.kernel.org/all/20260819-b4-monaco-evk-m2-v1-v3-4-988145ef65cd@oss.qualcomm.com/ Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/monaco-evk.dts | 16 +++++++++++----- arch/arm64/boot/dts/qcom/monaco.dtsi | 8 -------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/monaco-evk.dts b/arch/arm64/boot/dts/qcom/monaco-evk.dts index 86c0084a89007..b6c066b3c3461 100644 --- a/arch/arm64/boot/dts/qcom/monaco-evk.dts +++ b/arch/arm64/boot/dts/qcom/monaco-evk.dts @@ -80,8 +80,12 @@ }; }; -&pcieport0_ep { - remote-endpoint = <&m2_e_pcie_ep>; +&pcieport0 { + port { + pcieport0_ep: endpoint { + remote-endpoint = <&m2_e_pcie_ep>; + }; + }; }; &sdhc_1 { @@ -97,8 +101,10 @@ &uart2 { status = "okay"; -}; -&uart2_ep { - remote-endpoint = <&m2_e_uart_ep>; + port { + uart2_ep: endpoint { + remote-endpoint = <&m2_e_uart_ep>; + }; + }; }; diff --git a/arch/arm64/boot/dts/qcom/monaco.dtsi b/arch/arm64/boot/dts/qcom/monaco.dtsi index 41c3aa89b9d4b..7ef74f027607e 100644 --- a/arch/arm64/boot/dts/qcom/monaco.dtsi +++ b/arch/arm64/boot/dts/qcom/monaco.dtsi @@ -1254,10 +1254,6 @@ power-domains = <&rpmhpd RPMHPD_CX>; operating-points-v2 = <&qup_opp_table>; status = "disabled"; - - port { - uart2_ep: endpoint {}; - }; }; i2c3: i2c@98c000 { @@ -2476,10 +2472,6 @@ #size-cells = <2>; ranges; phys = <&pcie0_phy>; - - port { - pcieport0_ep: endpoint {}; - }; }; }; From 3c1e80ceb9fb6978aa94bc0624e7c0003f3b4f6e Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Thu, 20 Aug 2026 14:34:35 +0530 Subject: [PATCH 187/188] Revert "serial: qcom-geni: fix TX DMA buffer flush" Bluetooth is failing on sm8750-mtp [ 13.837833] Bluetooth: hci0: Frame reassembly failed (-84) [ 13.871642] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.055451] Console: switching to colour frame buffer device 180x200 [ 14.055475] fb0: sys_imageblit: framebuffer is not in virtual address space. [ 14.107920] msm_dpu ae01000.display-controller: [drm] fb0: msmdrmfb frame buffer device [ 14.235842] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.307450] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.369565] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.400428] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.406300] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.465233] Bluetooth: hci0: Frame reassembly failed (-84) [ 14.525293] Bluetooth: hci0: Frame reassembly failed (-84) For now reverting the change to unblock. This reverts commit e3c04834ae1ab5e9cfbe8ac54ec734aa4774249d. --- drivers/tty/serial/qcom_geni_serial.c | 43 +++++++++++++-------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 4fb3233b76df2..ad69976555b86 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -158,7 +158,6 @@ static const struct uart_ops qcom_geni_uart_pops; static struct uart_driver qcom_geni_console_driver; static struct uart_driver qcom_geni_uart_driver; -static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport); static void __qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); @@ -637,34 +636,35 @@ static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport) return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS); } -static void qcom_geni_serial_flush_buffer_dma(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - - qcom_geni_serial_stop_tx_dma(uport); - port->tx_remaining = 0; - port->tx_queued = 0; -} - static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport) { struct qcom_geni_serial_port *port = to_dev_port(uport); + bool done; - if (qcom_geni_serial_main_active(uport)) - __qcom_geni_serial_cancel_tx_cmd(uport); + if (!qcom_geni_serial_main_active(uport)) + return; if (port->tx_dma_addr) { - writel(1, uport->membase + SE_DMA_TX_FSM_RST); - if (!qcom_geni_serial_poll_bit(uport, SE_DMA_TX_IRQ_STAT, - TX_RESET_DONE, true)) - dev_err_ratelimited(uport->dev, "TX DMA reset failed"); - writel(TX_RESET_DONE | TX_DMA_DONE, - uport->membase + SE_DMA_TX_IRQ_CLR); - geni_se_tx_dma_unprep(&port->se, port->tx_dma_addr, port->tx_remaining); port->tx_dma_addr = 0; + port->tx_remaining = 0; } + + geni_se_cancel_m_cmd(&port->se); + + done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_CANCEL_EN, true); + if (!done) { + geni_se_abort_m_cmd(&port->se); + done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_ABORT_EN, true); + if (!done) + dev_err_ratelimited(uport->dev, "M_CMD_ABORT_EN not set"); + writel(M_CMD_ABORT_EN, uport->membase + SE_GENI_M_IRQ_CLEAR); + } + + writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR); } static void qcom_geni_serial_start_tx_dma(struct uart_port *uport) @@ -1180,7 +1180,7 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) uart_port_unlock_irq(uport); } -static void qcom_geni_serial_flush_buffer_fifo(struct uart_port *uport) +static void qcom_geni_serial_flush_buffer(struct uart_port *uport) { qcom_geni_serial_cancel_tx_cmd(uport); } @@ -1769,7 +1769,7 @@ static const struct uart_ops qcom_geni_console_pops = { .request_port = qcom_geni_serial_request_port, .config_port = qcom_geni_serial_config_port, .shutdown = qcom_geni_serial_shutdown, - .flush_buffer = qcom_geni_serial_flush_buffer_fifo, + .flush_buffer = qcom_geni_serial_flush_buffer, .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, @@ -1792,7 +1792,6 @@ static const struct uart_ops qcom_geni_uart_pops = { .request_port = qcom_geni_serial_request_port, .config_port = qcom_geni_serial_config_port, .shutdown = qcom_geni_serial_shutdown, - .flush_buffer = qcom_geni_serial_flush_buffer_dma, .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, From 3219dc94222cf0be47b7550394379145c900fd05 Mon Sep 17 00:00:00 2001 From: Tingguo Cheng Date: Tue, 11 Aug 2026 18:39:56 +0800 Subject: [PATCH 188/188] FROMGIT: power: supply: qcom_battmgr: fix battery chemistry strncmp length The battery_chemistry field is a 4-byte array without guaranteed null termination. Using BATTMGR_CHEMISTRY_LEN (4) as the strncmp length for 3-character string literals implicitly requires chemistry[3] == '\0', which may not hold. Use 3 instead to match only the significant bytes. Link: https://lore.kernel.org/all/20260812-fix-qcom-batt-chemistry-strn-v1-1-458545e02641@oss.qualcomm.com/ Signed-off-by: Tingguo Cheng --- drivers/power/supply/qcom_battmgr.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index dfcfb646ad87a..e177ef10edd55 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1514,11 +1514,11 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry) { - if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) || - (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN))) + if ((!strncmp(chemistry, "LIO", 3)) || + (!strncmp(chemistry, "OOI", 3))) return POWER_SUPPLY_TECHNOLOGY_LION; - if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN) || - !strncmp(chemistry, "LiP", BATTMGR_CHEMISTRY_LEN)) + if (!strncmp(chemistry, "LIP", 3) || + !strncmp(chemistry, "LiP", 3)) return POWER_SUPPLY_TECHNOLOGY_LIPO; pr_err("Unknown battery technology '%s'\n", chemistry);