From 13898fe180df200699c0145b60f37781a0d66915 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 19 May 2026 18:25:24 +0800 Subject: [PATCH 1/5] arm64: dts: qcom: radxa-cm-q64: disabled DP0 Playback Because not all base boards equipped with radxa-cm-q64 will use the DP interface. So the DisplayPort audio is turned off by default following the display settings. Avoid sound card probe failure when using WCD Codec alone. Signed-off-by: Jiali Chen --- .../boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi index a4d9082bd20d6..5b8932346b945 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi @@ -845,22 +845,6 @@ "AMIC2", "MIC BIAS2", "TX SWR_ADC1", "ADC2_OUTPUT"; - dp0-dai-link { - link-name = "DP0 Playback"; - - codec { - sound-dai = <&mdss_dp>; - }; - - cpu { - sound-dai = <&q6apmbedai DISPLAY_PORT_RX_0>; - }; - - platform { - sound-dai = <&q6apm>; - }; - }; - wcd-playback-dai-link { link-name = "WCD Playback"; From dbe391a0b97ce247a586699aa190bd5f583bdc23 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 18 May 2026 19:08:53 +0800 Subject: [PATCH 2/5] drm/msm/dp: avoid redundant HPD bridge notifications Only propagate HPD notifications when the connector state actually changes so transient plug interrupts during normal operation do not trigger unnecessary userspace reprobes and mode fallback. Signed-off-by: Jiali Chen --- drivers/gpu/drm/msm/dp/dp_display.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dc49f50c519ec..75d56a35c6978 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -986,17 +986,21 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id) unsigned long flags; u32 hpd_isr_status; int rc; + bool was_plugged; spin_lock_irqsave(&dp->irq_thread_lock, flags); hpd_isr_status = dp->hpd_isr_status; dp->hpd_isr_status = 0; spin_unlock_irqrestore(&dp->irq_thread_lock, flags); - if (hpd_isr_status & DP_DP_HPD_UNPLUG_INT_MASK) + was_plugged = dp->plugged; + + /* notify only on actual state transitions */ + if (hpd_isr_status & DP_DP_HPD_UNPLUG_INT_MASK && was_plugged) drm_bridge_hpd_notify(dp->msm_dp_display.bridge, connector_status_disconnected); - if (hpd_isr_status & DP_DP_HPD_PLUG_INT_MASK) + if (hpd_isr_status & DP_DP_HPD_PLUG_INT_MASK && !was_plugged) drm_bridge_hpd_notify(dp->msm_dp_display.bridge, connector_status_connected); From 8f103493565e83f2ed970e92f1ac5d88962b1e3f Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Mon, 18 May 2026 19:09:21 +0800 Subject: [PATCH 3/5] drm/msm/dpu: cap DSPP reservations by hardware count Skip per-CRTC DSPP reservations when color management would require more DSPP blocks than the platform actually exposes so high-resolution modesets continue instead of failing allocation on DSPP-limited hardware. Signed-off-by: Jiali Chen --- drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c index 0f4921b1a8922..89da3797d928e 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c @@ -1415,8 +1415,15 @@ static struct msm_display_topology dpu_crtc_get_topology( else topology.num_lm = 1; - if (crtc_state->ctm || crtc_state->gamma_lut) - topology.num_dspp = topology.num_lm; + if (crtc_state->ctm || crtc_state->gamma_lut) { + if (topology.num_lm <= (int)dpu_kms->catalog->dspp_count) { + topology.num_dspp = topology.num_lm; + } else { + DRM_WARN("CTM/gamma on %d LM(s) with %d DSPP(s), " + "color management skipped\n", + topology.num_lm, dpu_kms->catalog->dspp_count); + } + } return topology; } From 877f6ce52a3986a386e43e6ed13c8197dafa1d61 Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Tue, 19 May 2026 18:32:05 +0800 Subject: [PATCH 4/5] remoteproc: qcom_q6v5_pas: attach to preloaded firmware Some Qualcomm platforms may have PAS firmware authenticated and started by boot firmware before Linux probes the remoteproc device. In that state Linux cannot safely reload the image, but it still needs to attach to the already running subsystem. Detect an externally started remote from the SMP2P boot-state signals and mark it detached so the remoteproc core can attach instead of attempting a fresh firmware load. When the remote is externally managed, reject firmware loading and avoid issuing PAS shutdown for the main and DTB images. This allows systems such as SC7280 with UEFI-preloaded firmware to keep the subsystem available without transferring firmware lifecycle ownership to Linux. Signed-off-by: Jiali Chen --- drivers/remoteproc/qcom_q6v5_pas.c | 121 +++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 24 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index ae37c7a2682ba..62b30a7520c60 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -88,6 +88,7 @@ struct qcom_pas { int crash_reason_smem; unsigned int smem_host_id; bool decrypt_shutdown; + bool preloaded; const char *info_name; const struct firmware *firmware; @@ -225,6 +226,11 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) struct qcom_pas *pas = rproc->priv; int ret; + if (pas->preloaded) { + dev_err(pas->dev, "remoteproc is externally managed\n"); + return -EPERM; + } + /* Store firmware handle to be used in qcom_pas_start() */ pas->firmware = fw; @@ -407,17 +413,23 @@ static int qcom_pas_stop(struct rproc *rproc) if (ret == -ETIMEDOUT) dev_err(pas->dev, "timed out on wait\n"); - ret = qcom_scm_pas_shutdown(pas->pas_id); - if (ret && pas->decrypt_shutdown) - ret = qcom_pas_shutdown_poll_decrypt(pas); + if (pas->preloaded) { + ret = 0; + } else { + ret = qcom_scm_pas_shutdown(pas->pas_id); + if (ret && pas->decrypt_shutdown) + ret = qcom_pas_shutdown_poll_decrypt(pas); - if (ret) - dev_err(pas->dev, "failed to shutdown: %d\n", ret); + if (ret) + dev_err(pas->dev, "failed to shutdown: %d\n", ret); + } if (pas->dtb_pas_id) { - ret = qcom_scm_pas_shutdown(pas->dtb_pas_id); - if (ret) - dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + if (!pas->preloaded) { + ret = qcom_scm_pas_shutdown(pas->dtb_pas_id); + if (ret) + dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + } qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } @@ -517,16 +529,65 @@ static unsigned long qcom_pas_panic(struct rproc *rproc) return qcom_q6v5_panic(&pas->q6v5); } +static int qcom_pas_get_boot_state_from_smp2p(struct qcom_pas *pas, + bool *ready_state, + bool *crash_state) +{ + bool handover = false; + bool ready = false; + bool fatal = false; + bool stop = false; + int ret; + + ret = irq_get_irqchip_state(pas->q6v5.handover_irq, + IRQCHIP_STATE_LINE_LEVEL, &handover); + if (ret) + return ret; + + ret = irq_get_irqchip_state(pas->q6v5.ready_irq, + IRQCHIP_STATE_LINE_LEVEL, &ready); + if (ret) + return ret; + + ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, + IRQCHIP_STATE_LINE_LEVEL, &fatal); + if (ret) + return ret; + + ret = irq_get_irqchip_state(pas->q6v5.stop_irq, + IRQCHIP_STATE_LINE_LEVEL, &stop); + if (ret) + return ret; + + *crash_state = fatal; + *ready_state = ready && handover && !stop && !fatal; + + return 0; +} + static int qcom_pas_attach(struct rproc *rproc) { int ret; struct qcom_pas *pas = rproc->priv; - bool ready_state; - bool crash_state; + bool ready_state = false; + bool crash_state = false; pas->q6v5.running = true; - ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, - IRQCHIP_STATE_LINE_LEVEL, &crash_state); + + if (pas->preloaded) { + ret = qcom_pas_get_boot_state_from_smp2p(pas, + &ready_state, + &crash_state); + } else { + ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, + IRQCHIP_STATE_LINE_LEVEL, + &crash_state); + + if (!ret && !crash_state) + ret = irq_get_irqchip_state(pas->q6v5.ready_irq, + IRQCHIP_STATE_LINE_LEVEL, + &ready_state); + } if (ret) goto disable_running; @@ -538,30 +599,28 @@ static int qcom_pas_attach(struct rproc *rproc) goto disable_running; } - ret = irq_get_irqchip_state(pas->q6v5.ready_irq, - IRQCHIP_STATE_LINE_LEVEL, &ready_state); - - if (ret) - goto disable_running; - if (unlikely(!ready_state)) { /* * The bootloader may not support early boot, mark the state as * RPROC_OFFLINE so that the PAS driver can load the firmware and * start the remoteproc. */ - dev_err(pas->dev, "Failed to get subsystem ready interrupt\n"); + dev_err(pas->dev, pas->preloaded ? + "Failed to detect already running subsystem\n" : + "Failed to get subsystem ready interrupt\n"); pas->rproc->state = RPROC_OFFLINE; ret = -EINVAL; goto disable_running; } - ret = qcom_q6v5_ping_subsystem(&pas->q6v5); + if (!pas->preloaded) { + ret = qcom_q6v5_ping_subsystem(&pas->q6v5); - if (ret) { - dev_err(pas->dev, "Failed to ping subsystem, assuming device crashed\n"); - rproc_report_crash(rproc, RPROC_FATAL_ERROR); - goto disable_running; + if (ret) { + dev_err(pas->dev, "Failed to ping subsystem, assuming device crashed\n"); + rproc_report_crash(rproc, RPROC_FATAL_ERROR); + goto disable_running; + } } pas->q6v5.handover_issued = true; @@ -805,6 +864,8 @@ static int qcom_pas_probe(struct platform_device *pdev) struct rproc *rproc; const char *fw_name, *dtb_fw_name = NULL; const struct rproc_ops *ops = &qcom_pas_ops; + bool ready_state; + bool crash_state; int ret; desc = of_device_get_match_data(&pdev->dev); @@ -852,6 +913,7 @@ static int qcom_pas_probe(struct platform_device *pdev) pas->info_name = desc->sysmon_name; pas->smem_host_id = desc->smem_host_id; pas->decrypt_shutdown = desc->decrypt_shutdown; + pas->preloaded = false; pas->region_assign_idx = desc->region_assign_idx; pas->region_assign_count = min_t(int, MAX_ASSIGN_COUNT, desc->region_assign_count); pas->region_assign_vmid = desc->region_assign_vmid; @@ -927,6 +989,17 @@ static int qcom_pas_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "Falling back to firmware load\n"); else pas->rproc->state = RPROC_DETACHED; + } else { + ret = qcom_pas_get_boot_state_from_smp2p(pas, + &ready_state, + &crash_state); + if (ret) { + dev_warn(&pdev->dev, "Failed to read SMP2P state; falling back to firmware load\n"); + } else if (ready_state) { + pas->preloaded = true; + pas->rproc->state = RPROC_DETACHED; + rproc_set_feature(pas->rproc, RPROC_FEAT_ATTACH_ON_RECOVERY); + } } ret = rproc_add(rproc); From da8e0d4ef8d6113bfbceaf2083fc49f813ff001e Mon Sep 17 00:00:00 2001 From: Jiali Chen Date: Wed, 20 May 2026 10:18:55 +0800 Subject: [PATCH 5/5] arm64: dts: qcom: radxa-cm-q64: set uart5_tx output-high Set default values to avoid after the serial port enters the idle state. A sustained pulled-low state will cause the level shifter chip to continuously pull the signal low and be unable to pull it high. Leading to the inability to communicate subsequently. Signed-off-by: Jiali Chen --- arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi index 5b8932346b945..b97efb18afce8 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64.dtsi @@ -1126,6 +1126,7 @@ /* For uart_dbg */ &qup_uart5_tx { bias-disable; + output-high; drive-strength = <16>; };