From 61b8fe70e37dd0284eccc88885492fad0b244b07 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 01:05:15 -0700 Subject: [PATCH 001/243] FROMLIST: media: qcom: camss: csiphy: Add support for v2.4.0 two-phase CSIPHY Add more detailed resource information for CSIPHY devices in the camss driver along with the support for v2.4.0 in the 2 phase CSIPHY driver that is responsible for the PHY lane register configuration, module reset and interrupt handling. Link: https://lore.kernel.org/all/20260508-kaanapali-camss-v13-3-2541d8e55651@oss.qualcomm.com/ Reviewed-by: Bryan O'Donoghue Signed-off-by: Hangxiang Ma --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 124 +++++++++++++++++ drivers/media/platform/qcom/camss/camss.c | 125 ++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index dac8d2ecf7995..795b333185e54 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -804,6 +804,123 @@ csiphy_lane_regs lane_regs_sm8650[] = { {0x0c10, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, }; +/* 3nm 2PH v 2.4.0 2p5Gbps 4 lane DPHY mode */ +static const struct +csiphy_lane_regs lane_regs_2_4_0[] = { + /* LN 0 */ + {0x0094, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x00A0, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0090, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0098, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0094, 0x07, 0xd1, CSIPHY_DEFAULT_PARAMS}, + {0x0030, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0000, 0x8C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0038, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x002C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0034, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x001C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0014, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x003C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0004, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0020, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0008, 0x19, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0010, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0094, 0xD7, 0x00, CSIPHY_SKEW_CAL}, + {0x005C, 0x54, 0x00, CSIPHY_SKEW_CAL}, + {0x0060, 0xFD, 0x00, CSIPHY_SKEW_CAL}, + {0x0064, 0x7F, 0x00, CSIPHY_SKEW_CAL}, + + /* LN 2 */ + {0x0494, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x04A0, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0490, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0498, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0494, 0x07, 0xd1, CSIPHY_DEFAULT_PARAMS}, + {0x0430, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0400, 0x8C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0438, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x042C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0434, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x041C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0414, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x043C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0404, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0420, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0408, 0x19, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0410, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0494, 0xD7, 0x00, CSIPHY_SKEW_CAL}, + {0x045C, 0x54, 0x00, CSIPHY_SKEW_CAL}, + {0x0460, 0xFD, 0x00, CSIPHY_SKEW_CAL}, + {0x0464, 0x7F, 0x00, CSIPHY_SKEW_CAL}, + + /* LN 4 */ + {0x0894, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x08A0, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0890, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0898, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0894, 0x07, 0xd1, CSIPHY_DEFAULT_PARAMS}, + {0x0830, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0800, 0x8C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0838, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x082C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0834, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x081C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0814, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x083C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0804, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0820, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0808, 0x19, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0810, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0894, 0xD7, 0x00, CSIPHY_SKEW_CAL}, + {0x085C, 0x54, 0x00, CSIPHY_SKEW_CAL}, + {0x0860, 0xFD, 0x00, CSIPHY_SKEW_CAL}, + {0x0864, 0x7F, 0x00, CSIPHY_SKEW_CAL}, + + /* LN 6 */ + {0x0C94, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0CA0, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C90, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C98, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C94, 0x07, 0xd1, CSIPHY_DEFAULT_PARAMS}, + {0x0C30, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C00, 0x8C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C38, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C2C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C34, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C1C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C14, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C3C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C04, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C20, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C08, 0x19, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0C10, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C94, 0xD7, 0x00, CSIPHY_SKEW_CAL}, + {0x0C5C, 0x54, 0x00, CSIPHY_SKEW_CAL}, + {0x0C60, 0xFD, 0x00, CSIPHY_SKEW_CAL}, + {0x0C64, 0x7F, 0x00, CSIPHY_SKEW_CAL}, + + /* LN CLK */ + {0x0E94, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0EA0, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E90, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E98, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E94, 0x07, 0xd1, CSIPHY_DEFAULT_PARAMS}, + {0x0E30, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E28, 0x04, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E00, 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E0C, 0xFF, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E38, 0x1F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E2C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E34, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E1C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E14, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E3C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E04, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E20, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0E08, 0x19, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0E10, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + /* 4nm 2PH v 2.1.2 2p5Gbps 4 lane DPHY mode */ static const struct csiphy_lane_regs lane_regs_x1e80100[] = { @@ -1140,6 +1257,7 @@ static bool csiphy_is_gen2(u32 version) case CAMSS_8550: case CAMSS_8650: case CAMSS_8775P: + case CAMSS_KAANAPALI: case CAMSS_X1E80100: ret = true; break; @@ -1259,6 +1377,12 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_regs = &lane_regs_sa8775p[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); break; + case CAMSS_KAANAPALI: + regs->lane_regs = &lane_regs_2_4_0[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_2_4_0); + regs->offset = 0x1000; + regs->common_status_offset = 0x138; + break; default: break; } diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index c6754760dc45b..27e1ad7ed799a 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -35,6 +35,129 @@ static const struct parent_dev_ops vfe_parent_dev_ops; +static const struct camss_subdev_resources csiphy_res_kaanapali[] = { + /* CSIPHY0 */ + { + .regulators = { + { .supply = "vdd-csiphy0-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy0-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy0", "csiphy0_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" }, + .csiphy = { + .id = 0, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY1 */ + { + .regulators = { + { .supply = "vdd-csiphy1-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy1-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy1", "csiphy1_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" }, + .csiphy = { + .id = 1, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY2 */ + { + .regulators = { + { .supply = "vdd-csiphy2-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy2-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy2", "csiphy2_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy2" }, + .interrupt = { "csiphy2" }, + .csiphy = { + .id = 2, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY3 */ + { + .regulators = { + { .supply = "vdd-csiphy3-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy3-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy3", "csiphy3_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy3" }, + .interrupt = { "csiphy3" }, + .csiphy = { + .id = 3, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY4 */ + { + .regulators = { + { .supply = "vdd-csiphy4-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy4-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy4", "csiphy4_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy4" }, + .interrupt = { "csiphy4" }, + .csiphy = { + .id = 4, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY5 */ + { + .regulators = { + { .supply = "vdd-csiphy5-0p8", .init_load_uA = 151020 }, + { .supply = "vdd-csiphy5-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy5", "csiphy5_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy5" }, + .interrupt = { "csiphy5" }, + .csiphy = { + .id = 5, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, +}; + static const struct resources_icc icc_res_kaanapali[] = { { .name = "ahb", @@ -5533,8 +5656,10 @@ static void camss_remove(struct platform_device *pdev) static const struct camss_resources kaanapali_resources = { .version = CAMSS_KAANAPALI, .pd_name = "top", + .csiphy_res = csiphy_res_kaanapali, .icc_res = icc_res_kaanapali, .icc_path_num = ARRAY_SIZE(icc_res_kaanapali), + .csiphy_num = ARRAY_SIZE(csiphy_res_kaanapali), }; static const struct camss_resources msm8916_resources = { From dba6e2bd8117442c16b3dcf3454d0a26c3fcdbe8 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 01:05:16 -0700 Subject: [PATCH 002/243] FROMLIST: media: qcom: camss: csid: Add support for CSID gen4 Add more detailed resource information for CSID devices along with the driver for CSID gen4 that is responsible for CSID register configuration, module reset and IRQ handling for BUF_DONE events. And aggregate a common definition 'CSI2_RX_CFG0_PHY_SEL_BASE_IDX' into csid header file. In this CSID version, RUP and AUP update values are split into two registers along with a SET register. Accordingly, enhance the CSID interface to accommodate both the legacy combined reg_update and the split RUP and AUP updates. Link: https://lore.kernel.org/all/20260508-kaanapali-camss-v13-4-2541d8e55651@oss.qualcomm.com/ Co-developed-by: Atiya Kailany Signed-off-by: Atiya Kailany Signed-off-by: Hangxiang Ma --- drivers/media/platform/qcom/camss/Makefile | 1 + .../platform/qcom/camss/camss-csid-680.c | 1 - .../platform/qcom/camss/camss-csid-gen3.c | 1 - .../platform/qcom/camss/camss-csid-gen4.c | 376 ++++++++++++++++++ .../media/platform/qcom/camss/camss-csid.h | 11 +- drivers/media/platform/qcom/camss/camss.c | 75 ++++ 6 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 drivers/media/platform/qcom/camss/camss-csid-gen4.c diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index 27898b3cc7d3c..cebfd947f28ef 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -10,6 +10,7 @@ qcom-camss-objs += \ camss-csid-680.o \ camss-csid-gen2.o \ camss-csid-gen3.o \ + camss-csid-gen4.o \ camss-csiphy.o \ camss-csiphy-2ph-1-0.o \ camss-csiphy-3ph-1-0.o \ diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/drivers/media/platform/qcom/camss/camss-csid-680.c index 345a67c8fb947..bf7164085ddb4 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-680.c +++ b/drivers/media/platform/qcom/camss/camss-csid-680.c @@ -101,7 +101,6 @@ #define CSI2_RX_CFG0_DL2_INPUT_SEL 12 #define CSI2_RX_CFG0_DL3_INPUT_SEL 16 #define CSI2_RX_CFG0_PHY_NUM_SEL 20 -#define CSI2_RX_CFG0_PHY_SEL_BASE_IDX 1 #define CSI2_RX_CFG0_PHY_TYPE_SEL 24 #define CSI2_RX_CFG0_TPG_MUX_EN BIT(27) #define CSI2_RX_CFG0_TPG_MUX_SEL GENMASK(29, 28) diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c index 0fdbf75fb27db..da9458cd178bd 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c +++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c @@ -105,7 +105,6 @@ #define CSID_RDI_IRQ_SUBSAMPLE_PERIOD(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\ (0x34C + 0x100 * (rdi)) :\ (0x54C + 0x100 * (rdi))) -#define CSI2_RX_CFG0_PHY_SEL_BASE_IDX 1 static void __csid_configure_rx(struct csid_device *csid, struct csid_phy_config *phy, int vc) diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen4.c b/drivers/media/platform/qcom/camss/camss-csid-gen4.c new file mode 100644 index 0000000000000..41035352c4bb0 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-gen4.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid-gen4.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include +#include +#include +#include + +#include "camss.h" +#include "camss-csid.h" +#include "camss-csid-gen3.h" + +/* Reset and Command Registers */ +#define CSID_RST_CFG 0x108 +#define RST_MODE BIT(0) +#define RST_LOCATION BIT(4) + +/* Reset and Command Registers */ +#define CSID_RST_CMD 0x10C +#define SELECT_HW_RST BIT(0) +#define SELECT_IRQ_RST BIT(2) +#define CSID_IRQ_CMD 0x110 +#define IRQ_CMD_CLEAR BIT(0) + +/* Register Update Commands, RUP/AUP */ +#define CSID_RUP_CMD 0x114 +#define CSID_AUP_CMD 0x118 +#define CSID_RUP_AUP_RDI(rdi) (BIT(8) << (rdi)) +#define CSID_RUP_AUP_CMD 0x11C +#define RUP_SET BIT(0) +#define MUP BIT(4) + +/* Top level interrupt registers */ +#define CSID_TOP_IRQ_STATUS 0x180 +#define CSID_TOP_IRQ_MASK 0x184 +#define CSID_TOP_IRQ_CLEAR 0x188 +#define INFO_RST_DONE BIT(0) +#define CSI2_RX_IRQ_STATUS BIT(2) +#define BUF_DONE_IRQ_STATUS BIT(3) + +/* Buffer done interrupt registers */ +#define CSID_BUF_DONE_IRQ_STATUS 0x1A0 +#define BUF_DONE_IRQ_STATUS_RDI_OFFSET 16 +#define CSID_BUF_DONE_IRQ_MASK 0x1A4 +#define CSID_BUF_DONE_IRQ_CLEAR 0x1A8 +#define CSID_BUF_DONE_IRQ_SET 0x1AC + +/* CSI2 RX interrupt registers */ +#define CSID_CSI2_RX_IRQ_STATUS 0x1B0 +#define CSID_CSI2_RX_IRQ_MASK 0x1B4 +#define CSID_CSI2_RX_IRQ_CLEAR 0x1B8 +#define CSID_CSI2_RX_IRQ_SET 0x1BC + +/* CSI2 RX Configuration */ +#define CSID_CSI2_RX_CFG0 0x880 +#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0 +#define CSI2_RX_CFG0_DL0_INPUT_SEL 4 +#define CSI2_RX_CFG0_PHY_NUM_SEL 20 +#define CSID_CSI2_RX_CFG1 0x884 +#define CSI2_RX_CFG1_ECC_CORRECTION_EN BIT(0) +#define CSI2_RX_CFG1_VC_MODE BIT(2) + +#define MSM_CSID_MAX_SRC_STREAMS_GEN4 (csid_is_lite(csid) ? 4 : 5) + +/* RDI Configuration */ +#define CSID_RDI_CFG0(rdi) \ + ((csid_is_lite(csid) ? 0x3080 : 0x5480) + 0x200 * (rdi)) +#define RDI_CFG0_RETIME_BS BIT(5) +#define RDI_CFG0_TIMESTAMP_EN BIT(6) +#define RDI_CFG0_TIMESTAMP_STB_SEL BIT(8) +#define RDI_CFG0_DECODE_FORMAT 12 +#define RDI_CFG0_DT 16 +#define RDI_CFG0_VC 22 +#define RDI_CFG0_EN BIT(31) + +/* RDI Control and Configuration */ +#define CSID_RDI_CTRL(rdi) \ + ((csid_is_lite(csid) ? 0x3088 : 0x5488) + 0x200 * (rdi)) +#define RDI_CTRL_START_CMD BIT(0) + +#define CSID_RDI_CFG1(rdi) \ + ((csid_is_lite(csid) ? 0x3094 : 0x5494) + 0x200 * (rdi)) +#define RDI_CFG1_DROP_H_EN BIT(5) +#define RDI_CFG1_DROP_V_EN BIT(6) +#define RDI_CFG1_CROP_H_EN BIT(7) +#define RDI_CFG1_CROP_V_EN BIT(8) +#define RDI_CFG1_PACKING_FORMAT_MIPI BIT(15) + +/* RDI Pixel Store Configuration */ +#define CSID_RDI_PIX_STORE_CFG0(rdi) (0x5498 + 0x200 * (rdi)) +#define RDI_PIX_STORE_CFG0_EN BIT(0) +#define RDI_PIX_STORE_CFG0_MIN_HBI 1 + +/* RDI IRQ Status in wrapper */ +#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) (0x224 + (0x10 * (rdi))) +#define CSID_CSI2_RDIN_IRQ_MASK(rdi) (0x228 + (0x10 * (rdi))) +#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) (0x22C + (0x10 * (rdi))) +#define INFO_RUP_DONE BIT(23) + +static void __csid_aup_rup_trigger(struct csid_device *csid) +{ + /* trigger SET in combined register */ + writel(RUP_SET, csid->base + CSID_RUP_AUP_CMD); +} + +static void __csid_aup_rup_clear(struct csid_device *csid, int port_id) +{ + /* Hardware clears the registers upon consuming the settings */ + csid->aup_update &= ~CSID_RUP_AUP_RDI(port_id); + csid->rup_update &= ~CSID_RUP_AUP_RDI(port_id); +} + +static void __csid_aup_update(struct csid_device *csid, int port_id) +{ + csid->aup_update |= CSID_RUP_AUP_RDI(port_id); + writel(csid->aup_update, csid->base + CSID_AUP_CMD); + + __csid_aup_rup_trigger(csid); +} + +static void __csid_reg_update(struct csid_device *csid, int port_id) +{ + csid->rup_update |= CSID_RUP_AUP_RDI(port_id); + writel(csid->rup_update, csid->base + CSID_RUP_CMD); + + __csid_aup_rup_trigger(csid); +} + +static void __csid_configure_rx(struct csid_device *csid, + struct csid_phy_config *phy) +{ + int val; + + val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; + val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; + val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) + << CSI2_RX_CFG0_PHY_NUM_SEL; + writel(val, csid->base + CSID_CSI2_RX_CFG0); + + val = CSI2_RX_CFG1_ECC_CORRECTION_EN; + writel(val, csid->base + CSID_CSI2_RX_CFG1); +} + +static void __csid_configure_rx_vc(struct csid_device *csid, int vc) +{ + int val; + + if (vc > 3) { + val = readl(csid->base + CSID_CSI2_RX_CFG1); + val |= CSI2_RX_CFG1_VC_MODE; + writel(val, csid->base + CSID_CSI2_RX_CFG1); + } +} + +static void __csid_ctrl_rdi(struct csid_device *csid, int enable, u8 rdi) +{ + int val = 0; + + if (enable) + val = RDI_CTRL_START_CMD; + + writel(val, csid->base + CSID_RDI_CTRL(rdi)); +} + +static void __csid_configure_rdi_pix_store(struct csid_device *csid, u8 rdi) +{ + u32 val; + + /* + * Configure pixel store to allow absorption of hblanking or idle time. + * This helps with horizontal crop and prevents line buffer conflicts. + * Reset state is 0x8 which has MIN_HBI=4, we keep the default MIN_HBI + * and just enable the pixel store functionality. + */ + val = (4 << RDI_PIX_STORE_CFG0_MIN_HBI) | RDI_PIX_STORE_CFG0_EN; + writel(val, csid->base + CSID_RDI_PIX_STORE_CFG0(rdi)); +} + +static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc) +{ + u32 val; + u8 lane_cnt = csid->phy.lane_cnt; + + /* Source pads matching RDI channels on hardware. + * E.g. Pad 1 -> RDI0, Pad 2 -> RDI1, etc. + */ + struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port]; + const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats, + csid->res->formats->nformats, + input_format->code); + + if (!lane_cnt) + lane_cnt = 4; + + val = RDI_CFG0_TIMESTAMP_EN; + val |= RDI_CFG0_TIMESTAMP_STB_SEL; + val |= RDI_CFG0_RETIME_BS; + + /* note: for non-RDI path, this should be format->decode_format */ + val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT; + val |= vc << RDI_CFG0_VC; + val |= format->data_type << RDI_CFG0_DT; + writel(val, csid->base + CSID_RDI_CFG0(port)); + + val = RDI_CFG1_PACKING_FORMAT_MIPI; + writel(val, csid->base + CSID_RDI_CFG1(port)); + + /* Configure pixel store using dedicated register in gen4 */ + if (!csid_is_lite(csid)) + __csid_configure_rdi_pix_store(csid, port); + + val = 0; + writel(val, csid->base + CSID_RDI_CTRL(port)); + + val = readl(csid->base + CSID_RDI_CFG0(port)); + + if (enable) + val |= RDI_CFG0_EN; + + writel(val, csid->base + CSID_RDI_CFG0(port)); +} + +static void csid_configure_stream(struct csid_device *csid, u8 enable) +{ + u8 i, k; + + __csid_configure_rx(csid, &csid->phy); + + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_GEN4; i++) { + if (csid->phy.en_vc & BIT(i)) { + __csid_configure_rdi_stream(csid, enable, i, 0); + __csid_configure_rx_vc(csid, 0); + + for (k = 0; k < CAMSS_INIT_BUF_COUNT; k++) + __csid_aup_update(csid, i); + + __csid_reg_update(csid, i); + + __csid_ctrl_rdi(csid, enable, i); + } + } +} + +static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val) +{ + return 0; +} + +static void csid_subdev_reg_update(struct csid_device *csid, int port_id, + bool clear) +{ + if (clear) + __csid_aup_rup_clear(csid, port_id); + else + __csid_aup_update(csid, port_id); +} + +/** + * csid_isr - CSID module interrupt service routine + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 val, buf_done_val; + u8 reset_done; + int i; + + val = readl(csid->base + CSID_TOP_IRQ_STATUS); + writel(val, csid->base + CSID_TOP_IRQ_CLEAR); + + reset_done = val & INFO_RST_DONE; + + buf_done_val = readl(csid->base + CSID_BUF_DONE_IRQ_STATUS); + writel(buf_done_val, csid->base + CSID_BUF_DONE_IRQ_CLEAR); + + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_GEN4; i++) { + if (csid->phy.en_vc & BIT(i)) { + val = readl(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i)); + writel(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i)); + + if (val & INFO_RUP_DONE) + csid_subdev_reg_update(csid, i, true); + + if (buf_done_val & BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i)) + camss_buf_done(csid->camss, csid->id, i); + } + } + + val = IRQ_CMD_CLEAR; + writel(val, csid->base + CSID_IRQ_CMD); + + if (reset_done) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +/** + * csid_reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + u32 val; + int i; + + reinit_completion(&csid->reset_complete); + + val = INFO_RST_DONE | BUF_DONE_IRQ_STATUS; + writel(val, csid->base + CSID_TOP_IRQ_CLEAR); + writel(val, csid->base + CSID_TOP_IRQ_MASK); + + val = 0; + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_GEN4; i++) { + if (csid->phy.en_vc & BIT(i)) { + /* + * Only need to clear buf done IRQ status here, + * RUP done IRQ status will be cleared once isr + * strobe generated by CSID_RST_CMD + */ + val |= BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i); + } + } + writel(val, csid->base + CSID_BUF_DONE_IRQ_CLEAR); + writel(val, csid->base + CSID_BUF_DONE_IRQ_MASK); + + /* Clear all IRQ status with CLEAR bits set */ + val = IRQ_CMD_CLEAR; + writel(val, csid->base + CSID_IRQ_CMD); + + val = RST_LOCATION | RST_MODE; + writel(val, csid->base + CSID_RST_CFG); + + val = SELECT_HW_RST | SELECT_IRQ_RST; + writel(val, csid->base + CSID_RST_CMD); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + + if (!time) { + dev_err(csid->camss->dev, "CSID reset timeout\n"); + return -EIO; + } + + return 0; +} + +static void csid_subdev_init(struct csid_device *csid) +{ + csid->testgen.nmodes = CSID_PAYLOAD_MODE_DISABLED; +} + +const struct csid_hw_ops csid_ops_gen4 = { + .configure_stream = csid_configure_stream, + .configure_testgen_pattern = csid_configure_testgen_pattern, + .hw_version = csid_hw_version, + .isr = csid_isr, + .reset = csid_reset, + .src_pad_code = csid_src_pad_code, + .subdev_init = csid_subdev_init, + .reg_update = csid_subdev_reg_update, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h index 5296b10f6bac8..e6590b2bd8be5 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.h +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -27,6 +27,8 @@ /* CSID hardware can demultiplex up to 4 outputs */ #define MSM_CSID_MAX_SRC_STREAMS 4 +/* CSIPHY to hardware PHY selector mapping */ +#define CSI2_RX_CFG0_PHY_SEL_BASE_IDX 1 #define CSID_RESET_TIMEOUT_MS 500 enum csid_testgen_mode { @@ -154,7 +156,13 @@ struct csid_device { void __iomem *base; u32 irq; char irq_name[30]; - u32 reg_update; + union { + u32 reg_update; + struct { + u32 rup_update; + u32 aup_update; + }; + }; struct camss_clock *clock; int nclocks; struct regulator_bulk_data *supplies; @@ -218,6 +226,7 @@ extern const struct csid_hw_ops csid_ops_340; extern const struct csid_hw_ops csid_ops_680; extern const struct csid_hw_ops csid_ops_gen2; extern const struct csid_hw_ops csid_ops_gen3; +extern const struct csid_hw_ops csid_ops_gen4; /* * csid_is_lite - Check if CSID is CSID lite. diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 27e1ad7ed799a..56dce0dd21688 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -158,6 +158,79 @@ static const struct camss_subdev_resources csiphy_res_kaanapali[] = { }, }; +static const struct camss_subdev_resources csid_res_kaanapali[] = { + /* CSID0 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_gen4, + .formats = &csid_formats_gen2 + } + }, + /* CSID1 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_gen4, + .formats = &csid_formats_gen2 + } + }, + /* CSID2 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_gen4, + .formats = &csid_formats_gen2 + } + }, + /* CSID_LITE0 */ + { + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid_lite0" }, + .interrupt = { "csid_lite0" }, + .csid = { + .is_lite = true, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_gen4, + .formats = &csid_formats_gen2 + } + }, + /* CSID_LITE1 */ + { + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid_lite1" }, + .interrupt = { "csid_lite1" }, + .csid = { + .is_lite = true, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_gen4, + .formats = &csid_formats_gen2 + } + } +}; + static const struct resources_icc icc_res_kaanapali[] = { { .name = "ahb", @@ -5657,9 +5730,11 @@ static const struct camss_resources kaanapali_resources = { .version = CAMSS_KAANAPALI, .pd_name = "top", .csiphy_res = csiphy_res_kaanapali, + .csid_res = csid_res_kaanapali, .icc_res = icc_res_kaanapali, .icc_path_num = ARRAY_SIZE(icc_res_kaanapali), .csiphy_num = ARRAY_SIZE(csiphy_res_kaanapali), + .csid_num = ARRAY_SIZE(csid_res_kaanapali), }; static const struct camss_resources msm8916_resources = { From 24577e4f5a0e39736b3ce926d56f64b565a68b15 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 01:05:17 -0700 Subject: [PATCH 003/243] FROMLIST: media: qcom: camss: vfe: Add support for VFE gen4 Add Video Front End (VFE) version gen4 as found on the Kaanapali SoC. The FULL front end modules in Kaanapali camera subsystem are called TFEs (Thin Front End), however, retaining the name VFE at places to maintain consistency and avoid unnecessary code changes. This change limits the VFE output lines to 3 for now as constrained by the CAMSS driver framework. Kaanapali architecture requires for the REG_UPDATE and AUP_UPDATE to be issued after all of the CSID configuration has been done. Additionally, the number of AUP_UPDATEs should match the number of buffers enqueued to the write master while it's being enabled. Although the real time data from TFE goes through the RT_CAMNOC, we are required to enable both the camnoc_rt_axi and camnoc_nrt_axi clocks for the PDX_NOC, that follows both the RT and NRT NOCs in this architecture, to ensure that both of the latter are idle after reset. Link: https://lore.kernel.org/all/20260508-kaanapali-camss-v13-5-2541d8e55651@oss.qualcomm.com/ Co-developed-by: Atiya Kailany Signed-off-by: Atiya Kailany Signed-off-by: Hangxiang Ma --- drivers/media/platform/qcom/camss/Makefile | 1 + .../platform/qcom/camss/camss-vfe-gen4.c | 197 ++++++++++++++++++ drivers/media/platform/qcom/camss/camss-vfe.c | 9 +- drivers/media/platform/qcom/camss/camss-vfe.h | 2 + drivers/media/platform/qcom/camss/camss.c | 138 ++++++++++++ 5 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-gen4.c diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index cebfd947f28ef..b114ca37e36e2 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -28,6 +28,7 @@ qcom-camss-objs += \ camss-vfe-680.o \ camss-vfe-gen1.o \ camss-vfe-gen3.o \ + camss-vfe-gen4.o \ camss-vfe-vbif.o \ camss-video.o diff --git a/drivers/media/platform/qcom/camss/camss-vfe-gen4.c b/drivers/media/platform/qcom/camss/camss-vfe-gen4.c new file mode 100644 index 0000000000000..d73d70898710a --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-gen4.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-gen4.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module gen4 + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" + +/* VFE-gen4 Bus Register Base Addresses */ +#define BUS_REG_BASE (vfe_is_lite(vfe) ? 0x800 : 0x1000) + +#define VFE_BUS_WM_CGC_OVERRIDE (BUS_REG_BASE + 0x08) +#define WM_CGC_OVERRIDE_ALL (0x7FFFFFF) + +#define VFE_BUS_WM_TEST_BUS_CTRL (BUS_REG_BASE + 0x128) + +#define VFE_BUS_WM_CFG(n) (BUS_REG_BASE + 0x500 + (n) * 0x100) +#define WM_CFG_EN BIT(0) +#define WM_VIR_FRM_EN BIT(1) +#define WM_CFG_MODE BIT(16) +#define VFE_BUS_WM_IMAGE_ADDR(n) (BUS_REG_BASE + 0x504 + (n) * 0x100) +#define VFE_BUS_WM_FRAME_INCR(n) (BUS_REG_BASE + 0x508 + (n) * 0x100) +#define VFE_BUS_WM_IMAGE_CFG_0(n) (BUS_REG_BASE + 0x50C + (n) * 0x100) +#define WM_IMAGE_CFG_0_DEFAULT_WIDTH (0xFFFF) +#define VFE_BUS_WM_IMAGE_CFG_2(n) (BUS_REG_BASE + 0x514 + (n) * 0x100) +#define WM_IMAGE_CFG_2_DEFAULT_STRIDE (0xFFFF) +#define VFE_BUS_WM_PACKER_CFG(n) (BUS_REG_BASE + 0x518 + (n) * 0x100) + +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (BUS_REG_BASE + 0x530 + (n) * 0x100) +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (BUS_REG_BASE + 0x534 + (n) * 0x100) + +/* VFE lite has no such registers */ +#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (BUS_REG_BASE + 0x538 + (n) * 0x100) +#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (BUS_REG_BASE + 0x53C + (n) * 0x100) + +#define VFE_BUS_WM_MMU_PREFETCH_CFG(n) (BUS_REG_BASE + 0x560 + (n) * 0x100) +#define VFE_BUS_WM_MMU_PREFETCH_MAX_OFFSET(n) (BUS_REG_BASE + 0x564 + (n) * 0x100) + +/* + * IFE write master client IDs + * + * VIDEO_FULL 0 + * VIDEO_DC4_Y 1 + * VIDEO_DC4_C 2 + * VIDEO_DC16_Y 3 + * VIDEO_DC16_C 4 + * DISPLAY_DS2_Y 5 + * DISPLAY_DS2_C 6 + * FD_Y 7 + * FD_C 8 + * PIXEL_RAW 9 + * STATS_AEC_BG 10 + * STATS_AEC_BHIST 11 + * STATS_TINTLESS_BG 12 + * STATS_AWB_BG 13 + * STATS_AWB_BFW 14 + * STATS_AF_BHIST 15 + * STATS_ALSC_BG 16 + * STATS_FLICKER_BAYERRS 17 + * STATS_TMC_BHIST 18 + * PDAF_0 19 + * PDAF_1 20 + * PDAF_2 21 + * PDAF_3 22 + * RDI0 23 + * RDI1 24 + * RDI2 25 + * RDI3 26 + * RDI4 27 + * + * IFE Lite write master client IDs + * + * RDI0 0 + * RDI1 1 + * RDI2 2 + * RDI3 3 + * GAMMA 4 + * STATES_BE 5 + */ +#define RDI_WM(n) ((vfe_is_lite(vfe) ? 0x0 : 0x17) + (n)) + +static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line) +{ + struct v4l2_pix_format_mplane *pix = + &line->video_out.active_fmt.fmt.pix_mp; + + wm = RDI_WM(wm); + + /* no clock gating at bus input */ + writel(WM_CGC_OVERRIDE_ALL, vfe->base + VFE_BUS_WM_CGC_OVERRIDE); + + writel(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL); + + writel(ALIGN(pix->plane_fmt[0].bytesperline, 16) * pix->height >> 8, + vfe->base + VFE_BUS_WM_FRAME_INCR(wm)); + writel((WM_IMAGE_CFG_0_DEFAULT_WIDTH & 0xFFFF), + vfe->base + VFE_BUS_WM_IMAGE_CFG_0(wm)); + writel(WM_IMAGE_CFG_2_DEFAULT_STRIDE, + vfe->base + VFE_BUS_WM_IMAGE_CFG_2(wm)); + writel(0, vfe->base + VFE_BUS_WM_PACKER_CFG(wm)); + + /* no dropped frames, one irq per frame */ + if (!vfe_is_lite(vfe)) { + writel(0, vfe->base + VFE_BUS_WM_FRAMEDROP_PERIOD(wm)); + writel(1, vfe->base + VFE_BUS_WM_FRAMEDROP_PATTERN(wm)); + } + + writel(0, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(wm)); + writel(1, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(wm)); + + writel(1, vfe->base + VFE_BUS_WM_MMU_PREFETCH_CFG(wm)); + writel(0xFFFFFFFF, vfe->base + VFE_BUS_WM_MMU_PREFETCH_MAX_OFFSET(wm)); + + writel(WM_CFG_EN | WM_CFG_MODE, vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_stop(struct vfe_device *vfe, u8 wm) +{ + wm = RDI_WM(wm); + writel(0, vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr, + struct vfe_line *line) +{ + wm = RDI_WM(wm); + writel(addr >> 8, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm)); + + dev_dbg(vfe->camss->dev, "wm:%d, image buf addr:0x%x\n", wm, addr); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + int port_id = line_id; + + camss_reg_update(vfe->camss, vfe->id, port_id, false); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + int port_id = line_id; + + camss_reg_update(vfe->camss, vfe->id, port_id, true); +} + +static const struct camss_video_ops vfe_video_ops_gen4 = { + .queue_buffer = vfe_queue_buffer_v2, + .flush_buffers = vfe_flush_buffers, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->video_ops = vfe_video_ops_gen4; +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + vfe_isr_reset_ack(vfe); +} + +static irqreturn_t vfe_isr(int irq, void *dev) +{ + /* nop */ + return IRQ_HANDLED; +} + +static int vfe_halt(struct vfe_device *vfe) +{ + /* rely on vfe_disable_output() to stop the VFE */ + return 0; +} + +const struct vfe_hw_ops vfe_ops_gen4 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .reg_update = vfe_reg_update, + .reg_update_clear = vfe_reg_update_clear, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_disable, + .vfe_enable = vfe_enable_v2, + .vfe_halt = vfe_halt, + .vfe_wm_start = vfe_wm_start, + .vfe_wm_stop = vfe_wm_stop, + .vfe_buf_done = vfe_buf_done, + .vfe_wm_update = vfe_wm_update, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 319d191589884..826ab85a44a14 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -353,6 +353,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, case CAMSS_8550: case CAMSS_8650: case CAMSS_8775P: + case CAMSS_KAANAPALI: case CAMSS_X1E80100: switch (sink_code) { case MEDIA_BUS_FMT_YUYV8_1X16: @@ -525,7 +526,8 @@ int vfe_enable_output_v2(struct vfe_line *line) spin_lock_irqsave(&vfe->output_lock, flags); - ops->reg_update_clear(vfe, line->id); + if (ops->reg_update_clear) + ops->reg_update_clear(vfe, line->id); if (output->state > VFE_OUTPUT_RESERVED) { dev_err(vfe->camss->dev, @@ -552,7 +554,9 @@ int vfe_enable_output_v2(struct vfe_line *line) output->gen2.active_num++; ops->vfe_wm_update(vfe, output->wm_idx[0], output->buf[i]->addr[0], line); - ops->reg_update(vfe, line->id); + + if (!vfe->res->reg_update_after_csid_config) + ops->reg_update(vfe, line->id); } spin_unlock_irqrestore(&vfe->output_lock, flags); @@ -2013,6 +2017,7 @@ static int vfe_bpl_align_rdi(struct vfe_device *vfe) case CAMSS_8550: case CAMSS_8650: case CAMSS_8775P: + case CAMSS_KAANAPALI: case CAMSS_X1E80100: ret = 16; break; diff --git a/drivers/media/platform/qcom/camss/camss-vfe.h b/drivers/media/platform/qcom/camss/camss-vfe.h index ae9dad353a378..c402ef170c81b 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.h +++ b/drivers/media/platform/qcom/camss/camss-vfe.h @@ -133,6 +133,7 @@ struct vfe_isr_ops { struct vfe_subdev_resources { bool is_lite; + bool reg_update_after_csid_config; u8 line_num; bool has_pd; char *pd_name; @@ -249,6 +250,7 @@ extern const struct vfe_hw_ops vfe_ops_340; extern const struct vfe_hw_ops vfe_ops_480; extern const struct vfe_hw_ops vfe_ops_680; extern const struct vfe_hw_ops vfe_ops_gen3; +extern const struct vfe_hw_ops vfe_ops_gen4; int vfe_get(struct vfe_device *vfe); void vfe_put(struct vfe_device *vfe); diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 56dce0dd21688..9409b16a8ea49 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -231,6 +231,142 @@ static const struct camss_subdev_resources csid_res_kaanapali[] = { } }; +/* In Kaanapali, CAMNOC requires all CPAS_TFEX clocks + * to operate on any TFE Full. + */ +static const struct camss_subdev_resources vfe_res_kaanapali[] = { + /* VFE0 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe0_fast_ahb", "vfe0", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife0", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE1 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe1_fast_ahb", "vfe1", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife1", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE2 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe2_fast_ahb", "vfe2", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe2" }, + .interrupt = { "vfe2" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife2", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE3 - IFE Lite */ + { + .clock = { "gcc_axi_hf", "vfe_lite_ahb", "vfe_lite", + "cpas_vfe_lite", "camnoc_rt_axi", + "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe_lite0" }, + .interrupt = { "vfe_lite0" }, + .vfe = { + .line_num = 4, + .is_lite = true, + .reg_update_after_csid_config = true, + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE4 - IFE Lite */ + { + .clock = { "gcc_axi_hf", "vfe_lite_ahb", "vfe_lite", + "cpas_vfe_lite", "camnoc_rt_axi", + "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe_lite1" }, + .interrupt = { "vfe_lite1" }, + .vfe = { + .line_num = 4, + .is_lite = true, + .reg_update_after_csid_config = true, + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, +}; + static const struct resources_icc icc_res_kaanapali[] = { { .name = "ahb", @@ -5731,10 +5867,12 @@ static const struct camss_resources kaanapali_resources = { .pd_name = "top", .csiphy_res = csiphy_res_kaanapali, .csid_res = csid_res_kaanapali, + .vfe_res = vfe_res_kaanapali, .icc_res = icc_res_kaanapali, .icc_path_num = ARRAY_SIZE(icc_res_kaanapali), .csiphy_num = ARRAY_SIZE(csiphy_res_kaanapali), .csid_num = ARRAY_SIZE(csid_res_kaanapali), + .vfe_num = ARRAY_SIZE(vfe_res_kaanapali), }; static const struct camss_resources msm8916_resources = { From f872d6522059f15aefe4482ca64fb72674e56ed7 Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Fri, 10 Apr 2026 12:25:31 +0800 Subject: [PATCH 004/243] FROMLIST: dt-bindings: media: Add bindings for qcom,x1p42100-camss Add bindings for the Camera Subsystem for X1P42100. The X1P42100 platform provides: - 2 x CSIPHY - 3 x TPG - 3 x CSID - 2 x CSID Lite - 1 x IFE - 2 x IFE Lite Link: https://lore.kernel.org/all/20260410-purwa_camss-v1-1-eedcf6d9d8ee@oss.qualcomm.com/ Signed-off-by: Wenmeng Liu --- .../bindings/media/qcom,x1p42100-camss.yaml | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/qcom,x1p42100-camss.yaml diff --git a/Documentation/devicetree/bindings/media/qcom,x1p42100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1p42100-camss.yaml new file mode 100644 index 0000000000000..8bfa7e616c3b6 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,x1p42100-camss.yaml @@ -0,0 +1,424 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,x1p42100-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm X1P42100 Camera Subsystem (CAMSS) + +maintainers: + - Wenmeng Liu + +description: + The CAMSS IP is a CSI decoder and ISP present on Qualcomm platforms. + +properties: + compatible: + const: qcom,x1p42100-camss + + reg: + maxItems: 14 + + reg-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csid_wrapper + - const: csiphy0 + - const: csiphy4 + - const: csitpg0 + - const: csitpg1 + - const: csitpg2 + - const: vfe0 + - const: vfe_lite0 + - const: vfe_lite1 + + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + + ranges: true + + clocks: + maxItems: 22 + + clock-names: + items: + - const: camnoc_nrt_axi + - const: camnoc_rt_axi + - const: core_ahb + - const: cpas_ahb + - const: cpas_fast_ahb + - const: cpas_vfe0 + - const: cpas_vfe_lite + - const: cphy_rx_clk_src + - const: csid + - const: csid_csiphy_rx + - const: csiphy0 + - const: csiphy0_timer + - const: csiphy4 + - const: csiphy4_timer + - const: gcc_axi_hf + - const: gcc_axi_sf + - const: vfe0 + - const: vfe0_fast_ahb + - const: vfe_lite + - const: vfe_lite_ahb + - const: vfe_lite_cphy_rx + - const: vfe_lite_csid + + interrupts: + maxItems: 10 + + interrupt-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csiphy0 + - const: csiphy4 + - const: vfe0 + - const: vfe_lite0 + - const: vfe_lite1 + + interconnects: + maxItems: 4 + + interconnect-names: + items: + - const: ahb + - const: hf_mnoc + - const: sf_mnoc + - const: sf_icp_mnoc + + iommus: + oneOf: + - items: + - description: S1 HLOS IFE and IFE_LITE non-protected read + - description: S1 HLOS IFE and IFE_LITE non-protected write + - description: S1 HLOS SFE non-protected read + - description: S1 HLOS SFE non-protected write + - description: S1 HLOS CDM IFE non-protected + - description: Legacy slot 0 - do not use + - description: Legacy slot 1 - do not use + - description: Legacy slot 2 - do not use + - items: + - description: S1 HLOS IFE and IFE_LITE non-protected read + - description: S1 HLOS IFE and IFE_LITE non-protected write + - description: S1 HLOS SFE non-protected read + - description: S1 HLOS SFE non-protected write + - description: S1 HLOS CDM IFE non-protected + + power-domains: + items: + - description: IFE0 GDSC - Image Front End, Global Distributed Switch Controller. + - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller. + + power-domain-names: + items: + - const: ife0 + - const: top + + vdd-csiphy-0p8-supply: + description: + 0.8V supply to a PHY. + + vdd-csiphy-1p2-supply: + description: + 1.2V supply to a PHY. + + phys: + maxItems: 2 + + phy-names: + items: + - const: csiphy0 + - const: csiphy4 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + description: + CSI input ports. Supports either standard single sensor mode or + Qualcomm's combo mode with one sensor in 2x1 + 1x1 data-lane, clock-lane mode. + + patternProperties: + "^port@[0-3]$": + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + description: + Input port for receiving CSI data. + + properties: + endpoint@0: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + description: + Endpoint for receiving a single sensor input (or first leg of combo). + + properties: + data-lanes: + minItems: 1 + maxItems: 4 # Base max allows 4 (for D-PHY) + + clock-lanes: + maxItems: 1 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + endpoint@1: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + description: + Endpoint for receiving the second leg of a combo sensor input. + + properties: + data-lanes: + maxItems: 1 + + clock-lanes: + maxItems: 1 + + bus-type: + const: 4 # Combo is D-PHY specific + + required: + - data-lanes + + allOf: + # Case 1: Combo Mode (endpoint@1 is present) + # If endpoint@1 exists, we restrict endpoint@0 to 2 lanes (D-PHY split) + - if: + required: + - endpoint@1 + then: + properties: + endpoint@0: + properties: + data-lanes: + minItems: 2 + maxItems: 2 + bus-type: + const: 4 + endpoint@1: + properties: + data-lanes: + minItems: 1 + maxItems: 1 + bus-type: + const: 4 + + # Case 2: Single Mode (endpoint@1 is missing) + # We explicitly allow up to 4 lanes here to cover the D-PHY use case. + - if: + not: + required: + - endpoint@1 + then: + properties: + endpoint@0: + properties: + data-lanes: + minItems: 1 + maxItems: 4 + +patternProperties: + "^phy@[0-9a-f]+$": + $ref: /schemas/phy/qcom,x1e80100-csi2-phy.yaml + unevaluatedProperties: false + + "^opp-table(-.*)?$": + type: object + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interconnects + - interconnect-names + - iommus + - power-domains + - power-domain-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + camss: isp@acb7000 { + compatible = "qcom,x1p42100-camss"; + + reg = <0 0x0acb7000 0 0x2000>, + <0 0x0acb9000 0 0x2000>, + <0 0x0acbb000 0 0x2000>, + <0 0x0acc6000 0 0x1000>, + <0 0x0acca000 0 0x1000>, + <0 0x0acb6000 0 0x1000>, + <0 0x0ace4000 0 0x1000>, + <0 0x0acec000 0 0x4000>, + <0 0x0acf6000 0 0x1000>, + <0 0x0acf7000 0 0x1000>, + <0 0x0acf8000 0 0x1000>, + <0 0x0ac62000 0 0x4000>, + <0 0x0acc7000 0 0x2000>, + <0 0x0accb000 0 0x2000>; + + reg-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csid_wrapper", + "csiphy0", + "csiphy4", + "csitpg0", + "csitpg1", + "csitpg2", + "vfe0", + "vfe_lite0", + "vfe_lite1"; + + #address-cells = <2>; + #size-cells = <2>; + ranges; + + clocks = <&camcc CAM_CC_CAMNOC_AXI_NRT_CLK>, + <&camcc CAM_CC_CAMNOC_AXI_RT_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>, + <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CPAS_FAST_AHB_CLK>, + <&camcc CAM_CC_CPAS_IFE_0_CLK>, + <&camcc CAM_CC_CPAS_IFE_LITE_CLK>, + <&camcc CAM_CC_CPHY_RX_CLK_SRC>, + <&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_CSIPHY4_CLK>, + <&camcc CAM_CC_CSI4PHYTIMER_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&gcc GCC_CAMERA_SF_AXI_CLK>, + <&camcc CAM_CC_IFE_0_CLK>, + <&camcc CAM_CC_IFE_0_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>; + + clock-names = "camnoc_nrt_axi", + "camnoc_rt_axi", + "core_ahb", + "cpas_ahb", + "cpas_fast_ahb", + "cpas_vfe0", + "cpas_vfe_lite", + "cphy_rx_clk_src", + "csid", + "csid_csiphy_rx", + "csiphy0", + "csiphy0_timer", + "csiphy4", + "csiphy4_timer", + "gcc_axi_hf", + "gcc_axi_sf", + "vfe0", + "vfe0_fast_ahb", + "vfe_lite", + "vfe_lite_ahb", + "vfe_lite_cphy_rx", + "vfe_lite_csid"; + + interrupts = , + , + , + , + , + , + , + , + , + ; + + interrupt-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy4", + "vfe0", + "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_ICP 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 0x800 0x60>, + <&apps_smmu 0x820 0x60>, + <&apps_smmu 0x840 0x60>, + <&apps_smmu 0x860 0x60>, + <&apps_smmu 0x18a0 0x0>; + + power-domains = <&camcc CAM_CC_IFE_0_GDSC>, + <&camcc CAM_CC_TITAN_TOP_GDSC>; + + power-domain-names = "ife0", + "top"; + + vdd-csiphy-0p8-supply = <&csiphy_0p8_supply>; + vdd-csiphy-1p2-supply = <&csiphy_1p2_supply>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy_ep0: endpoint { + data-lanes = <0 1>; + remote-endpoint = <&sensor_ep>; + }; + }; + }; + }; + }; From e7f27d2f3758afd6c0b9216d3e0b3f73ac8718d0 Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Fri, 10 Apr 2026 12:25:32 +0800 Subject: [PATCH 005/243] FROMLIST: media: qcom: camss: add support for X1P42100 camss The Purwa camera subsystem is a cut-down variant of the Hamoa CAMSS. Compared to Hamoa, Purwa provides only two CSIPHY instances and does not include the VFE1. Link: https://lore.kernel.org/all/20260410-purwa_camss-v1-2-eedcf6d9d8ee@oss.qualcomm.com/ Signed-off-by: Wenmeng Liu --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 2 + drivers/media/platform/qcom/camss/camss-vfe.c | 2 + drivers/media/platform/qcom/camss/camss.c | 109 ++++++++++++++++++ drivers/media/platform/qcom/camss/camss.h | 1 + 4 files changed, 114 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 795b333185e54..4badd4c64e1ef 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1259,6 +1259,7 @@ static bool csiphy_is_gen2(u32 version) case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: + case CAMSS_X1P42100: ret = true; break; } @@ -1358,6 +1359,7 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_array_size = ARRAY_SIZE(lane_regs_sc8280xp); break; case CAMSS_X1E80100: + case CAMSS_X1P42100: regs->lane_regs = &lane_regs_x1e80100[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_x1e80100); regs->offset = 0x1000; diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 826ab85a44a14..dbe5a9af452bf 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -355,6 +355,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: + case CAMSS_X1P42100: switch (sink_code) { case MEDIA_BUS_FMT_YUYV8_1X16: { @@ -2019,6 +2020,7 @@ static int vfe_bpl_align_rdi(struct vfe_device *vfe) case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: + case CAMSS_X1P42100: ret = 16; break; default: diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 9409b16a8ea49..6bfaba48b4c9f 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4847,6 +4847,98 @@ static const struct resources_wrapper csid_wrapper_res_x1e80100 = { .reg = "csid_wrapper", }; +static const struct camss_subdev_resources csiphy_res_x1p42100[] = { + /* CSIPHY0 */ + { + .csiphy = { + .id = 0, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + }, + }, + /* CSIPHY4 */ + { + .csiphy = { + .id = 4, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + }, + }, +}; + +static const struct camss_subdev_resources vfe_res_x1p42100[] = { + /* IFE0 */ + { + .regulators = {}, + .clock = {"camnoc_rt_axi", "camnoc_nrt_axi", "cpas_ahb", + "cpas_fast_ahb", "cpas_vfe0", "vfe0_fast_ahb", + "vfe0" }, + .clock_rate = { { 400000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 345600000, 432000000, 594000000, 675000000, + 727000000 }, }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" }, + .vfe = { + .line_num = 4, + .pd_name = "ife0", + .hw_ops = &vfe_ops_680, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + }, + }, + /* IFE_LITE_0 */ + { + .regulators = {}, + .clock = { "camnoc_rt_axi", "camnoc_nrt_axi", "cpas_ahb", + "vfe_lite_ahb", "cpas_vfe_lite", "vfe_lite", + "vfe_lite_csid" }, + .clock_rate = { { 400000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 266666667, 400000000, 480000000 }, }, + .reg = { "vfe_lite0" }, + .interrupt = { "vfe_lite0" }, + .vfe = { + .is_lite = true, + .line_num = 4, + .hw_ops = &vfe_ops_680, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + }, + }, + /* IFE_LITE_1 */ + { + .regulators = {}, + .clock = { "camnoc_rt_axi", "camnoc_nrt_axi", "cpas_ahb", + "vfe_lite_ahb", "cpas_vfe_lite", "vfe_lite", + "vfe_lite_csid" }, + .clock_rate = { { 400000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 266666667, 400000000, 480000000 }, }, + .reg = { "vfe_lite1" }, + .interrupt = { "vfe_lite1" }, + .vfe = { + .is_lite = true, + .line_num = 4, + .hw_ops = &vfe_ops_680, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + }, + }, +}; + /* * camss_add_clock_margin - Add margin to clock frequency rate * @rate: Clock frequency rate @@ -6123,6 +6215,22 @@ static const struct camss_resources x1e80100_resources = { .vfe_num = ARRAY_SIZE(vfe_res_x1e80100), }; +static const struct camss_resources x1p42100_resources = { + .version = CAMSS_X1P42100, + .pd_name = "top", + .csiphy_res = csiphy_res_x1p42100, + .tpg_res = tpg_res_x1e80100, + .csid_res = csid_res_x1e80100, + .vfe_res = vfe_res_x1p42100, + .csid_wrapper_res = &csid_wrapper_res_x1e80100, + .icc_res = icc_res_x1e80100, + .icc_path_num = ARRAY_SIZE(icc_res_x1e80100), + .csiphy_num = ARRAY_SIZE(csiphy_res_x1p42100), + .tpg_num = ARRAY_SIZE(tpg_res_x1e80100), + .csid_num = ARRAY_SIZE(csid_res_x1e80100), + .vfe_num = ARRAY_SIZE(vfe_res_x1p42100), +}; + static const struct of_device_id camss_dt_match[] = { { .compatible = "qcom,kaanapali-camss", .data = &kaanapali_resources }, { .compatible = "qcom,msm8916-camss", .data = &msm8916_resources }, @@ -6143,6 +6251,7 @@ static const struct of_device_id camss_dt_match[] = { { .compatible = "qcom,sm8550-camss", .data = &sm8550_resources }, { .compatible = "qcom,sm8650-camss", .data = &sm8650_resources }, { .compatible = "qcom,x1e80100-camss", .data = &x1e80100_resources }, + { .compatible = "qcom,x1p42100-camss", .data = &x1p42100_resources }, { } }; diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index cea213dc36ebc..6695c26fa8885 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -98,6 +98,7 @@ enum camss_version { CAMSS_8775P, CAMSS_KAANAPALI, CAMSS_X1E80100, + CAMSS_X1P42100, }; enum icc_count { From b41ab871f6b022d52b6111c643e9c7885eb311fc Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 02:51:03 -0700 Subject: [PATCH 006/243] FROMLIST: media: dt-bindings: Add CAMSS device for SM8750 Add bindings for Camera Subsystem (CAMSS) on the Qualcomm SM8750 platform. The SM8750 platform provides: - 6 x CSIPHY (CSI Physical Layer) - 3 x TPG (Test Pattern Generator) - 3 x CSID (CSI Decoder) - 2 x CSID Lite - 3 x VFE (Video Front End), 5 RDI per VFE - 2 x VFE Lite, 4 RDI per VFE Lite Reviewed-by: Bryan O'Donoghue Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260508-add-support-for-camss-on-sm8750-v3-1-fc6861a65c67@oss.qualcomm.com/ --- .../bindings/media/qcom,sm8750-camss.yaml | 433 ++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/qcom,sm8750-camss.yaml diff --git a/Documentation/devicetree/bindings/media/qcom,sm8750-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sm8750-camss.yaml new file mode 100644 index 0000000000000..fc0550899c074 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,sm8750-camss.yaml @@ -0,0 +1,433 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,sm8750-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SM8750 Camera Subsystem (CAMSS) + +maintainers: + - Hangxiang Ma + +description: + The CAMSS IP is a CSI decoder and ISP present on Qualcomm platforms. + +properties: + compatible: + const: qcom,sm8750-camss + + reg: + maxItems: 19 + + reg-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csiphy0 + - const: csiphy1 + - const: csiphy2 + - const: csiphy3 + - const: csiphy4 + - const: csiphy5 + - const: csitpg0 + - const: csitpg1 + - const: csitpg2 + - const: vfe0 + - const: vfe1 + - const: vfe2 + - const: vfe_lite0 + - const: vfe_lite1 + + clocks: + maxItems: 35 + + clock-names: + items: + - const: camnoc_nrt_axi + - const: camnoc_rt_axi + - const: cpas_ahb + - const: cpas_fast_ahb + - const: cpas_vfe0 + - const: cpas_vfe1 + - const: cpas_vfe2 + - const: cpas_vfe_lite + - const: csid + - const: csid_csiphy_rx + - const: csiphy0 + - const: csiphy0_timer + - const: csiphy1 + - const: csiphy1_timer + - const: csiphy2 + - const: csiphy2_timer + - const: csiphy3 + - const: csiphy3_timer + - const: csiphy4 + - const: csiphy4_timer + - const: csiphy5 + - const: csiphy5_timer + - const: gcc_axi_hf + - const: gcc_axi_sf + - const: vfe0 + - const: vfe0_fast_ahb + - const: vfe1 + - const: vfe1_fast_ahb + - const: vfe2 + - const: vfe2_fast_ahb + - const: vfe_lite + - const: vfe_lite_ahb + - const: vfe_lite_cphy_rx + - const: vfe_lite_csid + - const: qdss_debug_xo + + interrupts: + maxItems: 16 + + interrupt-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csiphy0 + - const: csiphy1 + - const: csiphy2 + - const: csiphy3 + - const: csiphy4 + - const: csiphy5 + - const: vfe0 + - const: vfe1 + - const: vfe2 + - const: vfe_lite0 + - const: vfe_lite1 + + interconnects: + maxItems: 4 + + interconnect-names: + items: + - const: ahb + - const: hf_mnoc + - const: sf_mnoc + - const: sf_icp_mnoc + + iommus: + maxItems: 1 + + power-domains: + items: + - description: + IFE0 GDSC - Global Distributed Switch Controller for IFE0. + - description: + IFE1 GDSC - Global Distributed Switch Controller for IFE1. + - description: + IFE2 GDSC - Global Distributed Switch Controller for IFE2. + - description: + Titan GDSC - Global Distributed Switch Controller for the entire camss. + + power-domain-names: + items: + - const: ife0 + - const: ife1 + - const: ife2 + - const: top + + vdd-csiphy0-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY0 core block. + + vdd-csiphy0-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY0 pll block. + + vdd-csiphy1-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY1 core block. + + vdd-csiphy1-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY1 pll block. + + vdd-csiphy2-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY2 core block. + + vdd-csiphy2-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY2 pll block. + + vdd-csiphy3-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY3 core block. + + vdd-csiphy3-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY3 pll block. + + vdd-csiphy4-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY4 core block. + + vdd-csiphy4-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY4 pll block. + + vdd-csiphy5-0p9-supply: + description: + Phandle to a 0.9V regulator supply to CSIPHY5 core block. + + vdd-csiphy5-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSIPHY5 pll block. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + description: + CSI input ports. + + patternProperties: + "^port@[0-5]$": + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Input ports for receiving CSI data on CSIPHY 0-5. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interconnects + - interconnect-names + - iommus + - power-domains + - power-domain-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + 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"; + + vdd-csiphy0-0p9-supply = <&vreg_0p9_supply>; + vdd-csiphy0-1p2-supply = <&vreg_1p2_supply>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + csiphy_ep0: endpoint { + data-lanes = <0 1>; + remote-endpoint = <&sensor_ep>; + }; + }; + }; + }; + }; From 4433da56740a90dadeba8dce932aa93276ffd9a7 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Tue, 12 May 2026 21:49:27 -0700 Subject: [PATCH 007/243] FROMLIST: media: qcom: camss: Add SM8750 compatible camss driver Add SM8750 platform support to the CAMSS driver with ICC bandwidth resources and device tree match entry. Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260508-add-support-for-camss-on-sm8750-v3-2-fc6861a65c67@oss.qualcomm.com/ --- drivers/media/platform/qcom/camss/camss.c | 22 ++++++++++++++++++++++ drivers/media/platform/qcom/camss/camss.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 6bfaba48b4c9f..ea4627b22b10d 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4534,6 +4534,20 @@ static const struct resources_icc icc_res_sa8775p[] = { }, }; +static const struct resources_icc icc_res_sm8750[] = { + { + .name = "ahb", + .icc_bw_tbl.avg = 150000, + .icc_bw_tbl.peak = 300000, + }, + /* Based on 4096 x 3072 30 FPS 2496 Mbps mode */ + { + .name = "hf_mnoc", + .icc_bw_tbl.avg = 471860, + .icc_bw_tbl.peak = 925857, + }, +}; + static const struct camss_subdev_resources csiphy_res_x1e80100[] = { /* CSIPHY0 */ { @@ -6199,6 +6213,13 @@ static const struct camss_resources sm8650_resources = { .vfe_num = ARRAY_SIZE(vfe_res_sm8650), }; +static const struct camss_resources sm8750_resources = { + .version = CAMSS_8750, + .pd_name = "top", + .icc_res = icc_res_sm8750, + .icc_path_num = ARRAY_SIZE(icc_res_sm8750), +}; + static const struct camss_resources x1e80100_resources = { .version = CAMSS_X1E80100, .pd_name = "top", @@ -6250,6 +6271,7 @@ static const struct of_device_id camss_dt_match[] = { { .compatible = "qcom,sm8250-camss", .data = &sm8250_resources }, { .compatible = "qcom,sm8550-camss", .data = &sm8550_resources }, { .compatible = "qcom,sm8650-camss", .data = &sm8650_resources }, + { .compatible = "qcom,sm8750-camss", .data = &sm8750_resources }, { .compatible = "qcom,x1e80100-camss", .data = &x1e80100_resources }, { .compatible = "qcom,x1p42100-camss", .data = &x1p42100_resources }, { } diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 6695c26fa8885..d0f60931cd1fe 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -95,6 +95,7 @@ enum camss_version { CAMSS_845, CAMSS_8550, CAMSS_8650, + CAMSS_8750, CAMSS_8775P, CAMSS_KAANAPALI, CAMSS_X1E80100, From 84e524bf1d2fbf09767442c10f922d6741a53279 Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 02:51:05 -0700 Subject: [PATCH 008/243] FROMLIST: media: qcom: camss: csiphy: Add support for v2.3.0 two-phase CSIPHY Add more detailed resource information for CSIPHY devices in the camss driver along with the support for v2.3.0 in the 2 phase CSIPHY driver that is responsible for the PHY lane register configuration, module reset and interrupt handling. Additionally, generalize the struct name for the lane configuration that had been added for Kaanapali and use it for SM8750 as well as they share the settings. Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260508-add-support-for-camss-on-sm8750-v3-3-fc6861a65c67@oss.qualcomm.com/ --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 5 +- drivers/media/platform/qcom/camss/camss.c | 125 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 4badd4c64e1ef..4c9e2d86e1a90 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -804,7 +804,7 @@ csiphy_lane_regs lane_regs_sm8650[] = { {0x0c10, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, }; -/* 3nm 2PH v 2.4.0 2p5Gbps 4 lane DPHY mode */ +/* 3nm 2PH v 2.3.0/2.4.0 2p5Gbps 4 lane DPHY mode */ static const struct csiphy_lane_regs lane_regs_2_4_0[] = { /* LN 0 */ @@ -1256,6 +1256,7 @@ static bool csiphy_is_gen2(u32 version) case CAMSS_845: case CAMSS_8550: case CAMSS_8650: + case CAMSS_8750: case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: @@ -1379,7 +1380,9 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_regs = &lane_regs_sa8775p[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); break; + case CAMSS_8750: case CAMSS_KAANAPALI: + /* CSPHY v2.4.0 is backward compatible with v2.3.0 settings */ regs->lane_regs = &lane_regs_2_4_0[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_2_4_0); regs->offset = 0x1000; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index ea4627b22b10d..baa40360885d5 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4534,6 +4534,129 @@ static const struct resources_icc icc_res_sa8775p[] = { }, }; +static const struct camss_subdev_resources csiphy_res_8750[] = { + /* CSIPHY0 */ + { + .regulators = { + { .supply = "vdd-csiphy0-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy0-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy0", "csiphy0_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" }, + .csiphy = { + .id = 0, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY1 */ + { + .regulators = { + { .supply = "vdd-csiphy1-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy1-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy1", "csiphy1_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" }, + .csiphy = { + .id = 1, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY2 */ + { + .regulators = { + { .supply = "vdd-csiphy2-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy2-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy2", "csiphy2_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy2" }, + .interrupt = { "csiphy2" }, + .csiphy = { + .id = 2, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY3 */ + { + .regulators = { + { .supply = "vdd-csiphy3-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy3-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy3", "csiphy3_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy3" }, + .interrupt = { "csiphy3" }, + .csiphy = { + .id = 3, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY4 */ + { + .regulators = { + { .supply = "vdd-csiphy4-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy4-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy4", "csiphy4_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy4" }, + .interrupt = { "csiphy4" }, + .csiphy = { + .id = 4, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY5 */ + { + .regulators = { + { .supply = "vdd-csiphy5-0p9", .init_load_uA = 148000 }, + { .supply = "vdd-csiphy5-1p2", .init_load_uA = 14660 } + }, + .clock = { "csiphy5", "csiphy5_timer", + "cpas_ahb", "cpas_fast_ahb" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000 }, + { 0 }, + { 0 } }, + .reg = { "csiphy5" }, + .interrupt = { "csiphy5" }, + .csiphy = { + .id = 5, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, +}; + static const struct resources_icc icc_res_sm8750[] = { { .name = "ahb", @@ -6216,7 +6339,9 @@ static const struct camss_resources sm8650_resources = { static const struct camss_resources sm8750_resources = { .version = CAMSS_8750, .pd_name = "top", + .csiphy_res = csiphy_res_8750, .icc_res = icc_res_sm8750, + .csiphy_num = ARRAY_SIZE(csiphy_res_8750), .icc_path_num = ARRAY_SIZE(icc_res_sm8750), }; From 8d642ef1caf26e2daeef64112ba3423cd32acddd Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 02:51:06 -0700 Subject: [PATCH 009/243] FROMLIST: media: qcom: camss: csid: Add support for CSID 980 Add more detailed resource information for CSID devices along with the driver for CSID 980 that is responsible for CSID register configuration, module reset and IRQ handling for BUF_DONE events. In SM8750, RUP and AUP updates for the CSID Full modules are split into two registers along with a SET register. However, CSID Lite modules still use a single register to update RUP and AUP without the additional SET register. Handled such differences in the driver. Co-developed-by: Atiya Kailany Signed-off-by: Atiya Kailany Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260508-add-support-for-camss-on-sm8750-v3-4-fc6861a65c67@oss.qualcomm.com/ --- drivers/media/platform/qcom/camss/Makefile | 1 + .../platform/qcom/camss/camss-csid-980.c | 442 ++++++++++++++++++ .../media/platform/qcom/camss/camss-csid.h | 1 + drivers/media/platform/qcom/camss/camss.c | 75 +++ 4 files changed, 519 insertions(+) create mode 100644 drivers/media/platform/qcom/camss/camss-csid-980.c diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index b114ca37e36e2..c19d3183882a9 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -8,6 +8,7 @@ qcom-camss-objs += \ camss-csid-4-7.o \ camss-csid-340.o \ camss-csid-680.o \ + camss-csid-980.o \ camss-csid-gen2.o \ camss-csid-gen3.o \ camss-csid-gen4.o \ diff --git a/drivers/media/platform/qcom/camss/camss-csid-980.c b/drivers/media/platform/qcom/camss/camss-csid-980.c new file mode 100644 index 0000000000000..79eb063000b8d --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-980.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid-980.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include +#include +#include +#include +#include "camss.h" +#include "camss-csid.h" +#include "camss-csid-gen3.h" + +/* Reset and Command Registers */ +#define CSID_RST_CFG 0xC +#define RST_MODE BIT(0) +#define RST_LOCATION BIT(4) + +/* Reset and Command Registers */ +#define CSID_RST_CMD 0x10 +#define SELECT_HW_RST BIT(0) +#define SELECT_IRQ_RST BIT(2) +#define CSID_IRQ_CMD 0x14 +#define IRQ_CMD_CLEAR BIT(0) + +/* Register Update Commands, RUP/AUP */ +#define CSID_RUP_CMD 0x18 +#define CSID_AUP_CMD 0x1C +#define CSID_RUP_AUP_RDI(rdi) (BIT(8) << (rdi)) +#define CSID_RUP_AUP_CMD 0x20 +#define RUP_SET BIT(0) +#define MUP BIT(4) + +#define CSID_LITE_RUP_AUP_CMD 0x18 +#define CSID_LITE_RUP_RDI(rdi) (BIT(4) << (rdi)) +#define CSID_LITE_AUP_RDI(rdi) (BIT(20) << (rdi)) + +/* Top level interrupt registers */ +#define CSID_TOP_IRQ_STATUS (csid_is_lite(csid) ? 0x7C : 0x84) +#define CSID_TOP_IRQ_MASK (csid_is_lite(csid) ? 0x80 : 0x88) +#define CSID_TOP_IRQ_CLEAR (csid_is_lite(csid) ? 0x84 : 0x8C) +#define CSID_TOP_IRQ_SET (csid_is_lite(csid) ? 0x88 : 0x90) +#define INFO_RST_DONE BIT(0) +#define CSI2_RX_IRQ_STATUS BIT(2) +#define BUF_DONE_IRQ_STATUS BIT(csid_is_lite(csid) ? 13 : 3) + +/* Buffer done interrupt registers */ +#define CSID_BUF_DONE_IRQ_STATUS (csid_is_lite(csid) ? 0x8C : 0xA4) +#define BUF_DONE_IRQ_STATUS_RDI_OFFSET (csid_is_lite(csid) ? 1 : 16) +#define CSID_BUF_DONE_IRQ_MASK (csid_is_lite(csid) ? 0x90 : 0xA8) +#define CSID_BUF_DONE_IRQ_CLEAR (csid_is_lite(csid) ? 0x94 : 0xAC) +#define CSID_BUF_DONE_IRQ_SET (csid_is_lite(csid) ? 0x98 : 0xB0) + +/* CSI2 RX interrupt registers */ +#define CSID_CSI2_RX_IRQ_STATUS (csid_is_lite(csid) ? 0x9C : 0xB4) +#define CSID_CSI2_RX_IRQ_MASK (csid_is_lite(csid) ? 0xA0 : 0xB8) +#define CSID_CSI2_RX_IRQ_CLEAR (csid_is_lite(csid) ? 0xA4 : 0xBC) +#define CSID_CSI2_RX_IRQ_SET (csid_is_lite(csid) ? 0xA8 : 0xC0) + +/* CSI2 RX Configuration */ +#define CSID_CSI2_RX_CFG0 (csid_is_lite(csid) ? 0x200 : 0x400) +#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0 +#define CSI2_RX_CFG0_DL0_INPUT_SEL 4 +#define CSI2_RX_CFG0_PHY_NUM_SEL 20 +#define CSID_CSI2_RX_CFG1 (csid_is_lite(csid) ? 0x204 : 0x404) +#define CSI2_RX_CFG1_ECC_CORRECTION_EN BIT(0) +#define CSI2_RX_CFG1_VC_MODE BIT(2) + +#define MSM_CSID_MAX_SRC_STREAMS_980 (csid_is_lite(csid) ? 4 : 5) + +#define CSID_RDI_CFG0(rdi) \ + ({ \ + __typeof__(rdi) _rdi = (rdi); \ + csid_is_lite(csid) ? 0x500 + 0x100 * _rdi : \ + 0xE00 + 0x200 * _rdi; \ + }) +#define RDI_CFG0_RETIME_BS BIT(5) +#define RDI_CFG0_TIMESTAMP_EN BIT(6) +#define RDI_CFG0_TIMESTAMP_STB_SEL BIT(8) +#define RDI_CFG0_DECODE_FORMAT 12 +#define RDI_CFG0_DT 16 +#define RDI_CFG0_VC 22 +#define RDI_CFG0_DT_ID 27 +#define RDI_CFG0_EN BIT(31) + +/* RDI Control and Configuration */ +#define CSID_RDI_CTRL(rdi) \ + ({ \ + __typeof__(rdi) _rdi = (rdi); \ + csid_is_lite(csid) ? 0x504 + 0x100 * _rdi : \ + 0xE04 + 0x200 * _rdi; \ + }) +#define RDI_CTRL_START_CMD BIT(0) + +#define CSID_RDI_CFG1(rdi) \ + ({ \ + __typeof__(rdi) _rdi = (rdi); \ + csid_is_lite(csid) ? 0x510 + 0x100 * _rdi : \ + 0xE10 + 0x200 * _rdi; \ + }) +#define RDI_CFG1_DROP_H_EN BIT(5) +#define RDI_CFG1_DROP_V_EN BIT(6) +#define RDI_CFG1_CROP_H_EN BIT(7) +#define RDI_CFG1_CROP_V_EN BIT(8) +#define RDI_CFG1_PACKING_FORMAT_MIPI BIT(15) + +/* RDI Pixel Store Configuration */ +#define CSID_RDI_PIX_STORE_CFG0(rdi) (0xE14 + 0x200 * (rdi)) +#define RDI_PIX_STORE_CFG0_EN BIT(0) +#define RDI_PIX_STORE_CFG0_MIN_HBI 1 + +/* RDI IRQ Status in wrapper */ +#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) \ + (csid_is_lite(csid) ? 0xEC : 0x114 + 0x10 * (rdi)) +#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) \ + (csid_is_lite(csid) ? 0xF4 : 0x11C + 0x10 * (rdi)) +#define INFO_RUP_DONE BIT(23) + +static void __csid_full_aup_rup_trigger(struct csid_device *csid) +{ + /* trigger SET in combined register */ + writel(RUP_SET, csid->base + CSID_RUP_AUP_CMD); +} + +static void __csid_aup_update(struct csid_device *csid, int port_id) +{ + if (csid_is_lite(csid)) { + /* CSID Lites in v980 follow the legacy way of a combined RUP + * and AUP commands without an explicit SET register. + */ + csid->reg_update |= CSID_LITE_AUP_RDI(port_id); + writel(csid->reg_update, csid->base + CSID_LITE_RUP_AUP_CMD); + } else { + csid->aup_update |= CSID_RUP_AUP_RDI(port_id); + writel(csid->aup_update, csid->base + CSID_AUP_CMD); + + /* CSID Fulls in v980 split AUP and RUP commands, which requires + * additional SET operation to make registers modification take + * effect. + */ + __csid_full_aup_rup_trigger(csid); + } +} + +static void __csid_rup_update(struct csid_device *csid, int port_id) +{ + if (csid_is_lite(csid)) { + /* CSID Lites in v980 follow the legacy way of a combined RUP + * and AUP commands without an explicit SET register. + */ + csid->reg_update |= CSID_LITE_RUP_RDI(port_id); + writel(csid->reg_update, csid->base + CSID_LITE_RUP_AUP_CMD); + } else { + csid->rup_update |= CSID_RUP_AUP_RDI(port_id); + writel(csid->rup_update, csid->base + CSID_RUP_CMD); + + /* CSID Fulls in v980 split AUP and RUP commands, which requires + * additional SET operation to make registers modification take + * effect. + */ + __csid_full_aup_rup_trigger(csid); + } +} + +static void __csid_aup_rup_clear(struct csid_device *csid, int port_id) +{ + /* Hardware clears the registers upon consuming the settings */ + if (csid_is_lite(csid)) { + csid->reg_update &= ~CSID_LITE_RUP_RDI(port_id); + csid->reg_update &= ~CSID_LITE_AUP_RDI(port_id); + } else { + csid->aup_update &= ~CSID_RUP_AUP_RDI(port_id); + csid->rup_update &= ~CSID_RUP_AUP_RDI(port_id); + } +} + +static void __csid_configure_rx(struct csid_device *csid, + struct csid_phy_config *phy) +{ + int val; + + val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; + val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; + val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) + << CSI2_RX_CFG0_PHY_NUM_SEL; + writel(val, csid->base + CSID_CSI2_RX_CFG0); + + val = CSI2_RX_CFG1_ECC_CORRECTION_EN; + writel(val, csid->base + CSID_CSI2_RX_CFG1); +} + +static void __csid_configure_rx_vc(struct csid_device *csid, int vc) +{ + int val; + + if (vc > 3) { + val = readl(csid->base + CSID_CSI2_RX_CFG1); + val |= CSI2_RX_CFG1_VC_MODE; + writel(val, csid->base + CSID_CSI2_RX_CFG1); + } +} + +static void __csid_ctrl_rdi(struct csid_device *csid, int enable, u8 rdi) +{ + int val = 0; + u32 rdi_ctrl_offset = CSID_RDI_CTRL(rdi); + + if (enable) + val = RDI_CTRL_START_CMD; + + writel(val, csid->base + rdi_ctrl_offset); +} + +static void __csid_configure_rdi_pix_store(struct csid_device *csid, u8 rdi) +{ + u32 val; + + /* + * Configure pixel store to allow absorption of hblanking or idle time. + * This helps with horizontal crop and prevents line buffer conflicts. + * Reset state is 0x8 which has MIN_HBI=4, we keep the default MIN_HBI + * and just enable the pixel store functionality. + */ + val = (4 << RDI_PIX_STORE_CFG0_MIN_HBI) | RDI_PIX_STORE_CFG0_EN; + writel(val, csid->base + CSID_RDI_PIX_STORE_CFG0(rdi)); +} + +static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc) +{ + u32 val; + u8 lane_cnt = csid->phy.lane_cnt; + + /* Source pads matching RDI channels on hardware. + * E.g. Pad 1 -> RDI0, Pad 2 -> RDI1, etc. + */ + struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port]; + const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats, + csid->res->formats->nformats, + input_format->code); + + if (!lane_cnt) + lane_cnt = 4; + + /* + * DT_ID is a two bit bitfield that is concatenated with + * the four least significant bits of the five bit VC + * bitfield to generate an internal CID value. + * + * CSID_RDI_CFG0(vc) + * DT_ID : 28:27 + * VC : 26:22 + * DT : 21:16 + * + * CID : VC 3:0 << 2 | DT_ID 1:0 + */ + u8 dt_id = vc & 0x03; + u32 rdi_cfg0_offset = CSID_RDI_CFG0(port); + u32 rdi_cfg1_offset = CSID_RDI_CFG1(port); + u32 rdi_ctrl_offset = CSID_RDI_CTRL(port); + + val = RDI_CFG0_TIMESTAMP_EN; + val |= RDI_CFG0_TIMESTAMP_STB_SEL; + val |= RDI_CFG0_RETIME_BS; + + /* note: for non-RDI path, this should be format->decode_format */ + val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT; + val |= vc << RDI_CFG0_VC; + val |= format->data_type << RDI_CFG0_DT; + val |= dt_id << RDI_CFG0_DT_ID; + writel(val, csid->base + rdi_cfg0_offset); + + val = RDI_CFG1_PACKING_FORMAT_MIPI; + writel(val, csid->base + rdi_cfg1_offset); + + /* Configure pixel store using dedicated register in 980 */ + if (!csid_is_lite(csid)) + __csid_configure_rdi_pix_store(csid, port); + + val = 0; + writel(val, csid->base + rdi_ctrl_offset); + + val = readl(csid->base + rdi_cfg0_offset); + + if (enable) + val |= RDI_CFG0_EN; + + writel(val, csid->base + rdi_cfg0_offset); +} + +static void csid_configure_stream(struct csid_device *csid, u8 enable) +{ + u8 i, k; + + __csid_configure_rx(csid, &csid->phy); + + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_980; i++) { + if (csid->phy.en_vc & BIT(i)) { + __csid_configure_rdi_stream(csid, enable, i, 0); + __csid_configure_rx_vc(csid, 0); + + for (k = 0; k < CAMSS_INIT_BUF_COUNT; k++) { + __csid_aup_update(csid, i); + __csid_rup_update(csid, i); + } + + __csid_ctrl_rdi(csid, enable, i); + } + } +} + +static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val) +{ + return 0; +} + +static void csid_subdev_reg_update(struct csid_device *csid, int port_id, + bool clear) +{ + if (clear) + __csid_aup_rup_clear(csid, port_id); + else + __csid_aup_update(csid, port_id); +} + +/** + * csid_isr - CSID module interrupt service routine + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 val, buf_done_val; + u8 reset_done; + int i; + + val = readl(csid->base + CSID_TOP_IRQ_STATUS); + writel(val, csid->base + CSID_TOP_IRQ_CLEAR); + + reset_done = val & INFO_RST_DONE; + + buf_done_val = readl(csid->base + CSID_BUF_DONE_IRQ_STATUS); + writel(buf_done_val, csid->base + CSID_BUF_DONE_IRQ_CLEAR); + + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_980; i++) { + if (csid->phy.en_vc & BIT(i)) { + val = readl(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i)); + writel(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i)); + + if (val & INFO_RUP_DONE) + csid_subdev_reg_update(csid, i, true); + + if (buf_done_val & BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i)) + camss_buf_done(csid->camss, csid->id, i); + } + } + + val = IRQ_CMD_CLEAR; + writel(val, csid->base + CSID_IRQ_CMD); + + if (reset_done) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +/** + * csid_reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + u32 val; + int i; + + reinit_completion(&csid->reset_complete); + + val = INFO_RST_DONE | BUF_DONE_IRQ_STATUS; + writel(val, csid->base + CSID_TOP_IRQ_CLEAR); + writel(val, csid->base + CSID_TOP_IRQ_MASK); + + val = 0; + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS_980; i++) { + if (csid->phy.en_vc & BIT(i)) { + /* + * Only need to clear buf done IRQ status here, + * RUP done IRQ status will be cleared once isr + * strobe generated by CSID_RST_CMD + */ + val |= BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i); + } + } + writel(val, csid->base + CSID_BUF_DONE_IRQ_CLEAR); + writel(val, csid->base + CSID_BUF_DONE_IRQ_MASK); + + /* Clear all IRQ status with CLEAR bits set */ + val = IRQ_CMD_CLEAR; + writel(val, csid->base + CSID_IRQ_CMD); + + val = RST_LOCATION | RST_MODE; + writel(val, csid->base + CSID_RST_CFG); + + val = SELECT_HW_RST | SELECT_IRQ_RST; + writel(val, csid->base + CSID_RST_CMD); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + + if (!time) { + dev_err(csid->camss->dev, "CSID reset timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void csid_subdev_init(struct csid_device *csid) +{ + csid->testgen.nmodes = CSID_PAYLOAD_MODE_DISABLED; +} + +const struct csid_hw_ops csid_ops_980 = { + .configure_stream = csid_configure_stream, + .configure_testgen_pattern = csid_configure_testgen_pattern, + .hw_version = csid_hw_version, + .isr = csid_isr, + .reset = csid_reset, + .src_pad_code = csid_src_pad_code, + .subdev_init = csid_subdev_init, + .reg_update = csid_subdev_reg_update, +}; + diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h index e6590b2bd8be5..dd9c0868e9c73 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.h +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -224,6 +224,7 @@ extern const struct csid_hw_ops csid_ops_4_1; extern const struct csid_hw_ops csid_ops_4_7; extern const struct csid_hw_ops csid_ops_340; extern const struct csid_hw_ops csid_ops_680; +extern const struct csid_hw_ops csid_ops_980; extern const struct csid_hw_ops csid_ops_gen2; extern const struct csid_hw_ops csid_ops_gen3; extern const struct csid_hw_ops csid_ops_gen4; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index baa40360885d5..ffbad28801b5a 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4657,6 +4657,79 @@ static const struct camss_subdev_resources csiphy_res_8750[] = { }, }; +static const struct camss_subdev_resources csid_res_8750[] = { + /* CSID0 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_980, + .formats = &csid_formats_gen2 + } + }, + /* CSID1 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_980, + .formats = &csid_formats_gen2 + } + }, + /* CSID2 */ + { + .clock = { "csid", "csid_csiphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" }, + .csid = { + .is_lite = false, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_980, + .formats = &csid_formats_gen2 + } + }, + /* CSID_LITE0 */ + { + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid_lite0" }, + .interrupt = { "csid_lite0" }, + .csid = { + .is_lite = true, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_980, + .formats = &csid_formats_gen2 + } + }, + /* CSID_LITE1 */ + { + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx" }, + .clock_rate = { { 400000000, 480000000 }, + { 400000000, 480000000 } }, + .reg = { "csid_lite1" }, + .interrupt = { "csid_lite1" }, + .csid = { + .is_lite = true, + .parent_dev_ops = &vfe_parent_dev_ops, + .hw_ops = &csid_ops_980, + .formats = &csid_formats_gen2 + } + } +}; + static const struct resources_icc icc_res_sm8750[] = { { .name = "ahb", @@ -6340,8 +6413,10 @@ static const struct camss_resources sm8750_resources = { .version = CAMSS_8750, .pd_name = "top", .csiphy_res = csiphy_res_8750, + .csid_res = csid_res_8750, .icc_res = icc_res_sm8750, .csiphy_num = ARRAY_SIZE(csiphy_res_8750), + .csid_num = ARRAY_SIZE(csid_res_8750), .icc_path_num = ARRAY_SIZE(icc_res_sm8750), }; From e0c979ae8090228804d0e3adcda7605fcff1ebdb Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Fri, 8 May 2026 02:51:07 -0700 Subject: [PATCH 010/243] FROMLIST: media: qcom: camss: vfe: Add support for VFE 980 Add support for Video Front End (VFE) that is on the SM8750 SoCs. VFE gen4 has support for VFE 980. This change limits SM8750 VFE output lines to 3 for now as constrained by the CAMSS driver framework. Reviewed-by: Bryan O'Donoghue Co-developed-by: Atiya Kailany Signed-off-by: Atiya Kailany Signed-off-by: Hangxiang Ma Link: https://lore.kernel.org/all/20260508-add-support-for-camss-on-sm8750-v3-5-fc6861a65c67@oss.qualcomm.com/ --- .../platform/qcom/camss/camss-vfe-gen4.c | 10 +- drivers/media/platform/qcom/camss/camss-vfe.c | 2 + drivers/media/platform/qcom/camss/camss.c | 135 ++++++++++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-vfe-gen4.c b/drivers/media/platform/qcom/camss/camss-vfe-gen4.c index d73d70898710a..46d8e61b9bac0 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-gen4.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-gen4.c @@ -13,8 +13,12 @@ #include "camss.h" #include "camss-vfe.h" -/* VFE-gen4 Bus Register Base Addresses */ -#define BUS_REG_BASE (vfe_is_lite(vfe) ? 0x800 : 0x1000) +#define IS_VFE_980(vfe) ((vfe)->camss->res->version == CAMSS_8750) + +#define BUS_REG_BASE_980 (vfe_is_lite(vfe) ? 0x200 : 0x800) +#define BUS_REG_BASE_1080 (vfe_is_lite(vfe) ? 0x800 : 0x1000) +#define BUS_REG_BASE \ + (IS_VFE_980(vfe) ? BUS_REG_BASE_980 : BUS_REG_BASE_1080) #define VFE_BUS_WM_CGC_OVERRIDE (BUS_REG_BASE + 0x08) #define WM_CGC_OVERRIDE_ALL (0x7FFFFFF) @@ -55,7 +59,7 @@ * DISPLAY_DS2_C 6 * FD_Y 7 * FD_C 8 - * PIXEL_RAW 9 + * RAW_OUT(1080)/IR_OUT(980) 9 * STATS_AEC_BG 10 * STATS_AEC_BHIST 11 * STATS_TINTLESS_BG 12 diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index dbe5a9af452bf..57c5be461c3b4 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -352,6 +352,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, case CAMSS_845: case CAMSS_8550: case CAMSS_8650: + case CAMSS_8750: case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: @@ -2017,6 +2018,7 @@ static int vfe_bpl_align_rdi(struct vfe_device *vfe) case CAMSS_845: case CAMSS_8550: case CAMSS_8650: + case CAMSS_8750: case CAMSS_8775P: case CAMSS_KAANAPALI: case CAMSS_X1E80100: diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index ffbad28801b5a..da687d54be9c6 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4730,6 +4730,139 @@ static const struct camss_subdev_resources csid_res_8750[] = { } }; +static const struct camss_subdev_resources vfe_res_8750[] = { + /* VFE0 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe0_fast_ahb", "vfe0", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife0", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE1 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe1_fast_ahb", "vfe1", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife1", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE2 - TFE Full */ + { + .clock = { "gcc_axi_hf", "vfe2_fast_ahb", "vfe2", + "cpas_vfe0", "cpas_vfe1", "cpas_vfe2", + "camnoc_rt_axi", "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 360280000, 480000000, 630000000, 716000000, + 833000000 }, + { 0 }, + { 0 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe2" }, + .interrupt = { "vfe2" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .reg_update_after_csid_config = true, + .has_pd = true, + .pd_name = "ife2", + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE_LITE0 */ + { + .clock = { "gcc_axi_hf", "vfe_lite_ahb", "vfe_lite", + "cpas_vfe_lite", "camnoc_rt_axi", + "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe_lite0" }, + .interrupt = { "vfe_lite0" }, + .vfe = { + .line_num = 4, + .is_lite = true, + .reg_update_after_csid_config = true, + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE_LITE1 */ + { + .clock = { "gcc_axi_hf", "vfe_lite_ahb", "vfe_lite", + "cpas_vfe_lite", "camnoc_rt_axi", + "camnoc_nrt_axi", "qdss_debug_xo" }, + .clock_rate = { { 0 }, + { 0 }, + { 266666667, 400000000, 480000000 }, + { 0 }, + { 200000000, 300000000, 400000000, 480000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe_lite1" }, + .interrupt = { "vfe_lite1" }, + .vfe = { + .line_num = 4, + .is_lite = true, + .reg_update_after_csid_config = true, + .hw_ops = &vfe_ops_gen4, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + } +}; + static const struct resources_icc icc_res_sm8750[] = { { .name = "ahb", @@ -6414,9 +6547,11 @@ static const struct camss_resources sm8750_resources = { .pd_name = "top", .csiphy_res = csiphy_res_8750, .csid_res = csid_res_8750, + .vfe_res = vfe_res_8750, .icc_res = icc_res_sm8750, .csiphy_num = ARRAY_SIZE(csiphy_res_8750), .csid_num = ARRAY_SIZE(csid_res_8750), + .vfe_num = ARRAY_SIZE(vfe_res_8750), .icc_path_num = ARRAY_SIZE(icc_res_sm8750), }; From 22cf55dbb7b700514c69246685944c1bbfaa0e19 Mon Sep 17 00:00:00 2001 From: Nihal Kumar Gupta Date: Tue, 19 May 2026 14:29:37 +0530 Subject: [PATCH 011/243] FROMLIST: dt-bindings: media: Add bindings for qcom,glymur-camss Add device tree bindings for the Camera Subsystem (CAMSS) on the Qualcomm Glymur platform. The Glymur platform provides: - 3 x CSIPHY (CSI Physical Layer) - 3 x CSID (CSI Decoder), 2 x CSID Lite - 3 x TPG (Test Pattern Generator) - 2 x VFE (Video Front End), 2 x VFE Lite Signed-off-by: Nihal Kumar Gupta Link: https://lore.kernel.org/all/20260529-glymur_camss-v1-1-bee535396d22@oss.qualcomm.com/ --- .../bindings/media/qcom,glymur-camss.yaml | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml diff --git a/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml b/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml new file mode 100644 index 0000000000000..5596f3fb99003 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,glymur-camss.yaml @@ -0,0 +1,343 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,glymur-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Glymur Camera Subsystem (CAMSS) + +maintainers: + - Nihal Kumar Gupta + +description: + The CAMSS IP is a CSI decoder and ISP present on Qualcomm platforms. + +properties: + compatible: + const: qcom,glymur-camss + + reg: + maxItems: 16 + + reg-names: + items: + - const: csid_wrapper + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csiphy0 + - const: csiphy1 + - const: csiphy4 + - const: csitpg0 + - const: csitpg1 + - const: csitpg2 + - const: vfe0 + - const: vfe1 + - const: vfe_lite0 + - const: vfe_lite1 + + clocks: + maxItems: 26 + + clock-names: + items: + - const: camnoc_nrt_axi + - const: camnoc_rt_axi + - const: core_ahb + - const: cpas_ahb + - const: cpas_fast_ahb + - const: cpas_vfe0 + - const: cpas_vfe1 + - const: cpas_vfe_lite + - const: csid + - const: csid_csiphy_rx + - const: csiphy0 + - const: csiphy0_timer + - const: csiphy1 + - const: csiphy1_timer + - const: csiphy4 + - const: csiphy4_timer + - const: gcc_axi_hf + - const: gcc_axi_sf + - const: vfe0 + - const: vfe0_fast_ahb + - const: vfe1 + - const: vfe1_fast_ahb + - const: vfe_lite + - const: vfe_lite_ahb + - const: vfe_lite_cphy_rx + - const: vfe_lite_csid + + interrupts: + maxItems: 12 + + interrupt-names: + items: + - const: csid0 + - const: csid1 + - const: csid2 + - const: csid_lite0 + - const: csid_lite1 + - const: csiphy0 + - const: csiphy1 + - const: csiphy4 + - const: vfe0 + - const: vfe1 + - const: vfe_lite0 + - const: vfe_lite1 + + interconnects: + maxItems: 4 + + interconnect-names: + items: + - const: ahb + - const: hf_mnoc + - const: sf_mnoc + - const: sf_icp_mnoc + + iommus: + maxItems: 1 + + power-domains: + items: + - description: IFE0 GDSC - Image Front End, Global Distributed Switch Controller. + - description: IFE1 GDSC - Image Front End, Global Distributed Switch Controller. + - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller. + + power-domain-names: + items: + - const: ife0 + - const: ife1 + - const: top + + vdd-csiphy-0p8-supply: + description: + Phandle to 0.8V regulator supply to CSI PHYs. + + vdd-csiphy-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSI PHYs pll block. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + description: + CSI input ports. + + patternProperties: + "^port@[0-2]$": + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + description: + Input port for receiving CSI data. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interconnects + - interconnect-names + - iommus + - power-domains + - power-domain-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + camss: isp@acb6000 { + compatible = "qcom,glymur-camss"; + + reg = <0x0 0x0acb6000 0x0 0x1000>, + <0x0 0x0acb7000 0x0 0x2000>, + <0x0 0x0acb9000 0x0 0x2000>, + <0x0 0x0acbb000 0x0 0x2000>, + <0x0 0x0acc6000 0x0 0x1000>, + <0x0 0x0acca000 0x0 0x1000>, + <0x0 0x0ace4000 0x0 0x2000>, + <0x0 0x0ace6000 0x0 0x2000>, + <0x0 0x0acec000 0x0 0x2000>, + <0x0 0x0acf6000 0x0 0x1000>, + <0x0 0x0acf7000 0x0 0x1000>, + <0x0 0x0acf8000 0x0 0x1000>, + <0x0 0x0ac62000 0x0 0xf000>, + <0x0 0x0ac71000 0x0 0xf000>, + <0x0 0x0acc7000 0x0 0x2000>, + <0x0 0x0accb000 0x0 0x2000>; + + reg-names = "csid_wrapper", + "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy4", + "csitpg0", + "csitpg1", + "csitpg2", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + clocks = <&camcc CAM_CC_CAMNOC_AXI_NRT_CLK>, + <&camcc CAM_CC_CAMNOC_AXI_RT_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>, + <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CPAS_FAST_AHB_CLK>, + <&camcc CAM_CC_CPAS_IFE_0_CLK>, + <&camcc CAM_CC_CPAS_IFE_1_CLK>, + <&camcc CAM_CC_CPAS_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_CSIPHY4_CLK>, + <&camcc CAM_CC_CSI4PHYTIMER_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&gcc GCC_CAMERA_SF_AXI_CLK>, + <&camcc CAM_CC_IFE_0_CLK>, + <&camcc CAM_CC_IFE_0_FAST_AHB_CLK>, + <&camcc CAM_CC_IFE_1_CLK>, + <&camcc CAM_CC_IFE_1_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>; + + clock-names = "camnoc_nrt_axi", + "camnoc_rt_axi", + "core_ahb", + "cpas_ahb", + "cpas_fast_ahb", + "cpas_vfe0", + "cpas_vfe1", + "cpas_vfe_lite", + "csid", + "csid_csiphy_rx", + "csiphy0", + "csiphy0_timer", + "csiphy1", + "csiphy1_timer", + "csiphy4", + "csiphy4_timer", + "gcc_axi_hf", + "gcc_axi_sf", + "vfe0", + "vfe0_fast_ahb", + "vfe1", + "vfe1_fast_ahb", + "vfe_lite", + "vfe_lite_ahb", + "vfe_lite_cphy_rx", + "vfe_lite_csid"; + + interrupts = , + , + , + , + , + , + , + , + , + , + , + ; + + interrupt-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy4", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + interconnects = <&hsc_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_ICP 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 0x800 0x00>; + + power-domains = <&camcc CAM_CC_IFE_0_GDSC>, + <&camcc CAM_CC_IFE_1_GDSC>, + <&camcc CAM_CC_TITAN_TOP_GDSC>; + + power-domain-names = "ife0", + "ife1", + "top"; + + vdd-csiphy-0p8-supply = <&csiphy_0p8_supply>; + vdd-csiphy-1p2-supply = <&csiphy_1p2_supply>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy_ep0: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&sensor_ep>; + }; + }; + }; + }; + }; From ac7a3ed92ef6d89ede2957b3f6f6ba19b4ea9844 Mon Sep 17 00:00:00 2001 From: Prashant Shrotriya Date: Mon, 25 May 2026 10:44:33 +0530 Subject: [PATCH 012/243] FROMLIST: media: qcom: camss: Add Glymur compatible Add CAMSS_GLYMUR enum, Glymur compatible and Glymur camss driver private data, the private data just include some basic information for now, later changes will enumerate with csiphy, tpg, csid and vfe resources. Signed-off-by: Prashant Shrotriya Link: https://lore.kernel.org/all/20260529-glymur_camss-v1-3-bee535396d22@oss.qualcomm.com/ --- drivers/media/platform/qcom/camss/camss.c | 7 +++++++ drivers/media/platform/qcom/camss/camss.h | 1 + 2 files changed, 8 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index da687d54be9c6..c5f9688ee2966 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -6587,7 +6587,14 @@ static const struct camss_resources x1p42100_resources = { .vfe_num = ARRAY_SIZE(vfe_res_x1p42100), }; +static const struct camss_resources glymur_resources = { + .version = CAMSS_GLYMUR, + .pd_name = "top", + .legacy_phy = true, +}; + static const struct of_device_id camss_dt_match[] = { + { .compatible = "qcom,glymur-camss", .data = &glymur_resources }, { .compatible = "qcom,kaanapali-camss", .data = &kaanapali_resources }, { .compatible = "qcom,msm8916-camss", .data = &msm8916_resources }, { .compatible = "qcom,msm8939-camss", .data = &msm8939_resources }, diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index d0f60931cd1fe..7cd35c838d74a 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -97,6 +97,7 @@ enum camss_version { CAMSS_8650, CAMSS_8750, CAMSS_8775P, + CAMSS_GLYMUR, CAMSS_KAANAPALI, CAMSS_X1E80100, CAMSS_X1P42100, From c2afdd9e02691f82b901f0c58f20749ccdca0b01 Mon Sep 17 00:00:00 2001 From: Prashant Shrotriya Date: Mon, 25 May 2026 10:57:02 +0530 Subject: [PATCH 013/243] FROMLIST: media: qcom: camss: Add CSIPHY support for Glymur Glymur uses the same CSIPHY hardware version as x1e80100. The only difference between the two platforms is the number of CSIPHY instances. x1e80100 has four, while Glymur has three. Signed-off-by: Prashant Shrotriya Link: https://lore.kernel.org/all/20260529-glymur_camss-v1-4-bee535396d22@oss.qualcomm.com/ --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 2 + drivers/media/platform/qcom/camss/camss.c | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 4c9e2d86e1a90..6de38deb63b97 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1258,6 +1258,7 @@ static bool csiphy_is_gen2(u32 version) case CAMSS_8650: case CAMSS_8750: case CAMSS_8775P: + case CAMSS_GLYMUR: case CAMSS_KAANAPALI: case CAMSS_X1E80100: case CAMSS_X1P42100: @@ -1359,6 +1360,7 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_regs = &lane_regs_sc8280xp[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sc8280xp); break; + case CAMSS_GLYMUR: case CAMSS_X1E80100: case CAMSS_X1P42100: regs->lane_regs = &lane_regs_x1e80100[0]; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index c5f9688ee2966..e4b5b5634ffeb 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -5282,6 +5282,60 @@ static const struct camss_subdev_resources vfe_res_x1p42100[] = { }, }; +static const struct camss_subdev_resources csiphy_res_glymur[] = { + /* CSIPHY0 */ + { + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, + .clock = { "csiphy0", "csiphy0_timer" }, + .clock_rate = { { 300000000, 400000000, 480000000 }, + { 266666667, 400000000 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" }, + .csiphy = { + .id = 0, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + }, + }, + /* CSIPHY1 */ + { + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, + .clock = { "csiphy1", "csiphy1_timer" }, + .clock_rate = { { 300000000, 400000000, 480000000 }, + { 266666667, 400000000 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" }, + .csiphy = { + .id = 1, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + }, + }, + /* CSIPHY4 */ + { + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, + .clock = { "csiphy4", "csiphy4_timer" }, + .clock_rate = { { 300000000, 400000000, 480000000 }, + { 266666667, 400000000 } }, + .reg = { "csiphy4" }, + .interrupt = { "csiphy4" }, + .csiphy = { + .id = 4, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + }, + }, +}; + /* * camss_add_clock_margin - Add margin to clock frequency rate * @rate: Clock frequency rate From c62106d768f82a42aae0071cbada6252c5055a78 Mon Sep 17 00:00:00 2001 From: Prashant Shrotriya Date: Mon, 25 May 2026 11:03:09 +0530 Subject: [PATCH 014/243] FROMLIST: media: qcom: camss: enable vfe for Glymur Extend vfe_src_pad_code() and vfe_bpl_align() for Glymur. Signed-off-by: Prashant Shrotriya Link: https://lore.kernel.org/all/20260529-glymur_camss-v1-5-bee535396d22@oss.qualcomm.com/ --- drivers/media/platform/qcom/camss/camss-vfe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 57c5be461c3b4..36e39d55a4a8c 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -354,6 +354,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, case CAMSS_8650: case CAMSS_8750: case CAMSS_8775P: + case CAMSS_GLYMUR: case CAMSS_KAANAPALI: case CAMSS_X1E80100: case CAMSS_X1P42100: @@ -2020,6 +2021,7 @@ static int vfe_bpl_align_rdi(struct vfe_device *vfe) case CAMSS_8650: case CAMSS_8750: case CAMSS_8775P: + case CAMSS_GLYMUR: case CAMSS_KAANAPALI: case CAMSS_X1E80100: case CAMSS_X1P42100: From 1d585a2988b77af4a4cb3a23321d82cf8ede3df0 Mon Sep 17 00:00:00 2001 From: Prashant Shrotriya Date: Mon, 25 May 2026 11:07:06 +0530 Subject: [PATCH 015/243] FROMLIST: media: qcom: camss: Enumerate resources for Glymur Enumerate csiphy, csid and vfe resources for Glymur. Signed-off-by: Prashant Shrotriya Link: https://lore.kernel.org/all/20260529-glymur_camss-v1-6-bee535396d22@oss.qualcomm.com/ --- drivers/media/platform/qcom/camss/camss.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index e4b5b5634ffeb..c98430a05a960 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -6645,6 +6645,17 @@ static const struct camss_resources glymur_resources = { .version = CAMSS_GLYMUR, .pd_name = "top", .legacy_phy = true, + .csiphy_res = csiphy_res_glymur, + .tpg_res = tpg_res_x1e80100, + .csid_res = csid_res_x1e80100, + .vfe_res = vfe_res_x1e80100, + .csid_wrapper_res = &csid_wrapper_res_x1e80100, + .icc_res = icc_res_x1e80100, + .icc_path_num = ARRAY_SIZE(icc_res_x1e80100), + .csiphy_num = ARRAY_SIZE(csiphy_res_glymur), + .tpg_num = ARRAY_SIZE(tpg_res_x1e80100), + .csid_num = ARRAY_SIZE(csid_res_x1e80100), + .vfe_num = ARRAY_SIZE(vfe_res_x1e80100), }; static const struct of_device_id camss_dt_match[] = { From 321ea9fd6c14a3636cc0e8bed24d1bee0ed09c29 Mon Sep 17 00:00:00 2001 From: Prashant Shrotriya Date: Fri, 15 May 2026 19:16:22 +0530 Subject: [PATCH 016/243] FROMLIST: media: qcom: camss: add support for QCM2390 camss Add CAMSS driver support for Shikra SoC. Add high level resource definitions for 2 CSIPHY, 2 CSID and 2 VFE instances along with the interconnect bandwidth votes for AHB, HF and SF MNOC paths. Signed-off-by: Prashant Shrotriya Signed-off-by: Nihal Kumar Gupta Link: https://lore.kernel.org/all/20260526-shikra-camss-review-v1-3-645d2c8c75a7@qti.qualcomm.com/ --- .../platform/qcom/camss/camss-csiphy-3ph-1-0.c | 2 ++ drivers/media/platform/qcom/camss/camss-vfe.c | 1 + drivers/media/platform/qcom/camss/camss.c | 14 ++++++++++++++ drivers/media/platform/qcom/camss/camss.h | 1 + 4 files changed, 18 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 6de38deb63b97..75c0aef3725e2 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1247,6 +1247,7 @@ static bool csiphy_is_gen2(u32 version) switch (version) { case CAMSS_2290: + case CAMSS_2390: case CAMSS_6150: case CAMSS_6350: case CAMSS_7280: @@ -1343,6 +1344,7 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845); break; case CAMSS_2290: + case CAMSS_2390: case CAMSS_6150: regs->lane_regs = &lane_regs_qcm2290[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_qcm2290); diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 36e39d55a4a8c..c12814085b077 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -342,6 +342,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, break; case CAMSS_660: case CAMSS_2290: + case CAMSS_2390: case CAMSS_6150: case CAMSS_6350: case CAMSS_7280: diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index c98430a05a960..a00af0c718dcd 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -6427,6 +6427,19 @@ static const struct camss_resources qcm2290_resources = { .vfe_num = ARRAY_SIZE(vfe_res_2290), }; +static const struct camss_resources qcm2390_resources = { + .version = CAMSS_2390, + .legacy_phy = true, + .csiphy_res = csiphy_res_2290, + .csid_res = csid_res_2290, + .vfe_res = vfe_res_2290, + .icc_res = icc_res_2290, + .icc_path_num = ARRAY_SIZE(icc_res_2290), + .csiphy_num = ARRAY_SIZE(csiphy_res_2290), + .csid_num = ARRAY_SIZE(csid_res_2290), + .vfe_num = ARRAY_SIZE(vfe_res_2290), +}; + static const struct camss_resources qcs8300_resources = { .version = CAMSS_8300, .pd_name = "top", @@ -6673,6 +6686,7 @@ static const struct of_device_id camss_dt_match[] = { { .compatible = "qcom,sdm660-camss", .data = &sdm660_resources }, { .compatible = "qcom,sdm670-camss", .data = &sdm670_resources }, { .compatible = "qcom,sdm845-camss", .data = &sdm845_resources }, + { .compatible = "qcom,shikra-camss", .data = &qcm2390_resources }, { .compatible = "qcom,sm6150-camss", .data = &sm6150_resources }, { .compatible = "qcom,sm6350-camss", .data = &sm6350_resources }, { .compatible = "qcom,sm8250-camss", .data = &sm8250_resources }, diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 7cd35c838d74a..91438769419e5 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -82,6 +82,7 @@ enum pm_domain { enum camss_version { CAMSS_660, CAMSS_2290, + CAMSS_2390, CAMSS_6150, CAMSS_6350, CAMSS_7280, From c394dfb4b3768df4d41b9b32bf310c307451fa68 Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Thu, 25 Jun 2026 17:42:45 +0800 Subject: [PATCH 017/243] FROMLIST: i2c: qcom-cci: drop custom suspend/resume and rely on runtime PM helpers cci_resume() unconditionally calls cci_resume_runtime() regardless of the runtime PM state. If the device is already runtime-suspended before system suspend, the clock is re-enabled while runtime_status remains RPM_SUSPENDED. As a result, pm_request_autosuspend() does not arm the timer, leaving the clock permanently enabled. Fixes: e517526195de ("i2c: Add Qualcomm CCI I2C driver") Cc: stable@vger.kernel.org Reviewed-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Reviewed-by: Loic Poulain Signed-off-by: Wenmeng Liu Link: https://lore.kernel.org/all/20260625-cci-v1-1-a100cda673ce@oss.qualcomm.com/ --- drivers/i2c/busses/i2c-qcom-cci.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index 4d64895a9e9e4..bdeda3979c481 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -492,24 +492,8 @@ static int __maybe_unused cci_resume_runtime(struct device *dev) return 0; } -static int __maybe_unused cci_suspend(struct device *dev) -{ - if (!pm_runtime_suspended(dev)) - return cci_suspend_runtime(dev); - - return 0; -} - -static int __maybe_unused cci_resume(struct device *dev) -{ - cci_resume_runtime(dev); - pm_request_autosuspend(dev); - - return 0; -} - static const struct dev_pm_ops qcom_cci_pm = { - SET_SYSTEM_SLEEP_PM_OPS(cci_suspend, cci_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(cci_suspend_runtime, cci_resume_runtime, NULL) }; From 821972c82835f28188f55d05b77e90117ecbaf82 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 26 May 2026 16:26:09 +0530 Subject: [PATCH 018/243] FROMLIST: iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver The SDAM0 IRQ can be triggered for both EOC (end of conversion) events for immediate ADC reads done in this driver and for threshold violation events, based on ADC_TM thresholds configured from the auxiliary ADC_TM driver on TM channels on the first SDAM. At present, this interrupt is handled only in the ISR in the main ADC driver. When the ISR is triggered for an ADC_TM event, this driver notifies the ADC_TM driver by calling a notifier callback exposed from it for this purpose. To simplify the interrupt handling in both drivers, share the interrupt between the drivers. With this, ADC_TM interrupts on SDAM0 will be handled directly in the ADC_TM driver, so remove the notifier callback and all TM interrupt handling in the main ADC ISR. Link: https://lore.kernel.org/all/20260526-gen3_adc_tm-v2-1-702fbac919ac@oss.qualcomm.com/ Reviewed-by: Jonathan Cameron Signed-off-by: Jishnu Prakash --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 66 ++++++------------- include/linux/iio/adc/qcom-adc5-gen3-common.h | 2 - 2 files changed, 19 insertions(+), 49 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 48c793b18d11e..f354532a17f0c 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -56,9 +56,6 @@ struct adc5_channel_prop { * requests from multiple clients. * @data: software configuration data. * @n_tm_channels: number of ADC channels used for TM measurements. - * @handler: TM callback to be called for threshold violation interrupt - * on first SDAM. - * @tm_aux: pointer to auxiliary TM device. */ struct adc5_chip { struct device *dev; @@ -70,8 +67,6 @@ struct adc5_chip { struct mutex lock; const struct adc5_data *data; unsigned int n_tm_channels; - void (*handler)(struct auxiliary_device *tm_aux); - struct auxiliary_device *tm_aux; }; int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index, @@ -287,23 +282,21 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) { struct adc5_chip *adc = dev_id; struct device *dev = adc->dev; - struct auxiliary_device *adev; u8 status, eoc_status, val; - u8 tm_status[2]; int ret; ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_STATUS1, &status, sizeof(status)); if (ret) { dev_err(dev, "adc read status1 failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status)); if (ret) { dev_err(dev, "adc read eoc status failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { @@ -316,30 +309,13 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) return IRQ_HANDLED; } - /* CHAN0 is the preconfigured channel for immediate conversion */ - if (eoc_status & ADC5_GEN3_EOC_CHAN_0) - complete(&adc->complete); - - ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, - ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status)); - if (ret) { - dev_err(dev, "adc read TM status failed with %d\n", ret); - return IRQ_HANDLED; - } - - dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n", - status, eoc_status, tm_status[0], tm_status[1]); + dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x\n", status, eoc_status); - if (tm_status[0] || tm_status[1]) { - adev = adc->tm_aux; - if (!adev || !adev->dev.driver) { - dev_err(dev, "adc_tm auxiliary device not initialized\n"); - return IRQ_HANDLED; - } - - adc->handler(adev); - } + /* CHAN0 is the preconfigured channel for immediate conversion */ + if (!(eoc_status & ADC5_GEN3_EOC_CHAN_0)) + return IRQ_NONE; + complete(&adc->complete); return IRQ_HANDLED; } @@ -684,8 +660,6 @@ static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc) if (ret) return ret; - adc->tm_aux = &aux_device->aux_dev; - return 0; } @@ -741,16 +715,6 @@ int adc5_gen3_therm_code_to_temp(struct device *dev, } EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3"); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)) -{ - struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); - struct adc5_chip *adc = iio_priv(indio_dev); - - adc->handler = handler; -} -EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3"); - static int adc5_gen3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -817,10 +781,18 @@ static int adc5_gen3_probe(struct platform_device *pdev) return -ENOMEM; } - ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, - adc5_gen3_isr, 0, - adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, - adc); + /* + * This interrupt is shared with the ADC_TM auxiliary driver, which + * is threaded and uses IRQF_ONESHOT. Since shared interrupts need + * to agree on IRQF_ONESHOT configuration and there is a kernel + * warning for using IRQF_ONESHOT with non-threaded interrupts, + * make this also a threaded IRQ. + */ + + ret = devm_request_threaded_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, + NULL, adc5_gen3_isr, IRQF_ONESHOT | IRQF_SHARED, + adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, + adc); if (ret) return dev_err_probe(dev, ret, "Failed to request SDAM%d irq\n", diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h index 6303eaa6640be..39cbfcbdb101c 100644 --- a/include/linux/iio/adc/qcom-adc5-gen3-common.h +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h @@ -205,7 +205,5 @@ int adc5_gen3_get_scaled_reading(struct device *dev, int adc5_gen3_therm_code_to_temp(struct device *dev, struct adc5_channel_common_prop *common_props, u16 code, int *val); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)); #endif /* QCOM_ADC5_GEN3_COMMON_H */ From 49e9e5097e0f01b91ab60ed65ebbf534eabdadf1 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 26 May 2026 16:26:10 +0530 Subject: [PATCH 019/243] FROMLIST: thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Add support for ADC_TM part of PMIC5 Gen3. This is an auxiliary driver under the Gen3 ADC driver, which implements the threshold setting and interrupt generating functionalities of QCOM ADC_TM drivers, used to support thermal trip points. Link: https://lore.kernel.org/all/20260526-gen3_adc_tm-v2-2-702fbac919ac@oss.qualcomm.com/ Signed-off-by: Jishnu Prakash --- drivers/thermal/qcom/Kconfig | 9 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 437 ++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec69..1acb11e4ac800 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,15 @@ config QCOM_SPMI_ADC_TM5 Thermal client sets threshold temperature for both warm and cool and gets updated when a threshold is reached. +config QCOM_SPMI_ADC_TM5_GEN3 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3" + depends on QCOM_SPMI_ADC5_GEN3 + help + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal + monitoring device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 0fa2512042e78..828d9e7bc7970 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 0000000000000..633008f173a8a --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 "../thermal_hwmon.h" + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used (ranging from 1-7). + * @sdam_index: SDAM on which this TM channel lies. + * @common_props: structure with common ADC channel properties. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + */ +struct adc_tm5_gen3_channel_props { + unsigned int timer; + unsigned int tm_chan_index; + unsigned int sdam_index; + struct adc5_channel_common_prop common_props; + bool high_thr_en; + bool low_thr_en; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; +}; + +/** + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure + * @dev_data: Top-level ADC device data. + * @chan_props: Array of ADC_TM channel structures. + * @nchannels: number of TM channels allocated + * @dev: SPMI ADC5 Gen3 device. + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + unsigned int nchannels; + struct device *dev; +}; + +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, adc5_gen3_mutex_lock(_T->dev), + adc5_gen3_mutex_unlock(_T->dev)) + +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq) +{ + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + if (adc_tm5->dev_data->base[i].irq == irq) + return i; + } + return -ENOENT; +} + +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int ret, sdam_num; + u8 tm_status[2]; + u8 status, val; + + sdam_num = get_sdam_from_irq(adc_tm5, irq); + if (sdam_num < 0) + return IRQ_NONE; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) + return IRQ_NONE; + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + if (tm_status[0] || tm_status[1]) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static int adc5_gen3_tm_status_check(struct adc_tm5_gen3_chip *adc_tm5, + int sdam_index, u8 *tm_status, u8 *buf) +{ + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, 2); + if (ret) + return ret; + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS_CLR, + tm_status, 2); + if (ret) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_CH_DATA0(0), + buf, 16); + return ret; +} + +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int sdam_index = -1; + u8 tm_status[2] = { }; + u8 buf[16] = { }; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i]; + int offset = chan_prop->tm_chan_index; + bool upper_set, lower_set; + int ret; + + scoped_guard(adc5_gen3, adc_tm5) { + if (chan_prop->sdam_index != sdam_index) { + sdam_index = chan_prop->sdam_index; + ret = adc5_gen3_tm_status_check(adc_tm5, sdam_index, + tm_status, buf); + if (ret) + return IRQ_NONE; + } + + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en); + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en); + } + + if (!(upper_set || lower_set)) + continue; + + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, + temp); +} + +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + int ret; + u8 val; + + prop->high_thr_en = false; + prop->low_thr_en = false; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret) + return ret; + + val = BIT(prop->tm_chan_index); + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + val = MEAS_INT_DISABLE; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TIMER_SEL, &val, sizeof(val)); + if (ret) + return ret; + + /* To indicate there is an actual conversion request */ + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_PERPH_CH, &val, sizeof(val)); + if (ret) + return ret; + + val = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &val, sizeof(val)); +} + +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + u8 buf[ADC_TM5_GEN3_CONFIG_REGS]; + u8 conv_req; + u16 adc_code; + int ret; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret < 0) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid); + + /* Select TM channel and indicate there is an actual conversion request */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]); + + /* Update fast average sample value */ + buf[4] &= ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= prop->common_props.avg_samples | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->common_props.channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->common_props.hw_settle_time_us); + + /* High temperature corresponds to low voltage threshold */ + prop->low_thr_en = (high_temp != INT_MAX); + if (prop->low_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp); + put_unaligned_le16(adc_code, &buf[8]); + } + + /* Low temperature corresponds to high voltage threshold */ + prop->high_thr_en = (low_temp != -INT_MAX); + if (prop->high_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp); + put_unaligned_le16(adc_code, &buf[10]); + } + + buf[7] = 0; + if (prop->high_thr_en) + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + if (prop->low_thr_en) + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->common_props.label, low_temp, high_temp); + + guard(adc5_gen3)(adc_tm5); + + return adc_tm5_gen3_configure(prop, low_temp, high_temp); +} + +static const struct thermal_zone_device_ops adc_tm_ops = { + .get_temp = adc_tm5_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5) +{ + struct thermal_zone_device *tzd; + unsigned int channel; + int ret; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props); + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel, + &adc_tm5->chan_props[i], + &adc_tm_ops); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) == -ENODEV) { + dev_info(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return dev_err_probe(adc_tm5->dev, PTR_ERR(tzd), + "Error registering TZ zone:%ld for channel:%d\n", + PTR_ERR(tzd), channel); + } + adc_tm5->chan_props[i].tzd = tzd; + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd); + if (ret) + return ret; + } + return 0; +} + +static void adc5_gen3_disable(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + guard(adc5_gen3)(adc_tm5); + /* Disable all available TM channels */ + for (int i = 0; i < adc_tm5->nchannels; i++) + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]); +} + +static int adc_tm5_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *id) +{ + struct adc_tm5_gen3_chip *adc_tm5; + struct tm5_aux_dev_wrapper *aux_dev_wrapper; + struct device *dev = &aux_dev->dev; + int ret; + + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL); + if (!adc_tm5) + return -ENOMEM; + + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, + aux_dev); + + adc_tm5->dev = dev; + adc_tm5->dev_data = aux_dev_wrapper->dev_data; + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels; + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels, + sizeof(*adc_tm5->chan_props), GFP_KERNEL); + if (!adc_tm5->chan_props) + return -ENOMEM; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i]; + adc_tm5->chan_props[i].timer = MEAS_INT_1S; + adc_tm5->chan_props[i].sdam_index = (i + 1) / 8; + adc_tm5->chan_props[i].tm_chan_index = (i + 1) % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + /* This is to disable all ADC_TM channels in case of probe failure. */ + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5); + if (ret) + return ret; + + /* + * First SDAM's interrupt is shared between main ADC driver + * and auxiliary TM driver, so its flags must include + * IRQF_SHARED. This is not needed for other SDAMs as they + * will be used only for TM functionality. + */ + + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[0].irq, + adctm5_gen3_isr, adctm5_gen3_isr_thread, + IRQF_ONESHOT | IRQF_SHARED, + adc_tm5->dev_data->base[0].irq_name, + adc_tm5); + if (ret < 0) + return ret; + + for (int i = 1; i < adc_tm5->dev_data->num_sdams; i++) { + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + adctm5_gen3_isr, adctm5_gen3_isr_thread, + IRQF_ONESHOT, adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + return adc_tm5_register_tzd(adc_tm5); +} + +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = { + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3", }, + { } +}; + +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table); + +static struct auxiliary_driver adctm5gen3_auxiliary_driver = { + .id_table = adctm5_auxiliary_id_table, + .probe = adc_tm5_probe, +}; + +module_auxiliary_driver(adctm5gen3_auxiliary_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); From e3d050b8af3edee6e995c32d24fefa00c4309f52 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Thu, 30 Apr 2026 14:28:56 +0530 Subject: [PATCH 020/243] FROMLIST: arm64: dts: qcom: Add header file for ADC5 Gen3 channel macros Add macro definitions for virtual channels (combination of ADC channel number and PMIC SID number), to be used in devicetree by clients of ADC5 GEN3 device and in the "reg" property of ADC channels. Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-1-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h diff --git a/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h b/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h new file mode 100644 index 0000000000000..aa8e54d7e786a --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __DTS_ARM64_QCOM_ADC5_GEN3_H__ +#define __DTS_ARM64_QCOM_ADC5_GEN3_H__ + +/* ADC channels for PMIC5 Gen3 */ + +#define VIRT_CHAN(sid, chan) ((sid) << 8 | (chan)) + +#define ADC5_GEN3_REF_GND(sid) VIRT_CHAN(sid, 0x00) +#define ADC5_GEN3_1P25VREF(sid) VIRT_CHAN(sid, 0x01) +#define ADC5_GEN3_VREF_VADC(sid) VIRT_CHAN(sid, 0x02) +#define ADC5_GEN3_DIE_TEMP(sid) VIRT_CHAN(sid, 0x03) + +#define ADC5_GEN3_AMUX1_THM(sid) VIRT_CHAN(sid, 0x04) +#define ADC5_GEN3_AMUX2_THM(sid) VIRT_CHAN(sid, 0x05) +#define ADC5_GEN3_AMUX3_THM(sid) VIRT_CHAN(sid, 0x06) +#define ADC5_GEN3_AMUX4_THM(sid) VIRT_CHAN(sid, 0x07) +#define ADC5_GEN3_AMUX5_THM(sid) VIRT_CHAN(sid, 0x08) +#define ADC5_GEN3_AMUX6_THM(sid) VIRT_CHAN(sid, 0x09) +#define ADC5_GEN3_AMUX1_GPIO(sid) VIRT_CHAN(sid, 0x0a) +#define ADC5_GEN3_AMUX2_GPIO(sid) VIRT_CHAN(sid, 0x0b) +#define ADC5_GEN3_AMUX3_GPIO(sid) VIRT_CHAN(sid, 0x0c) +#define ADC5_GEN3_AMUX4_GPIO(sid) VIRT_CHAN(sid, 0x0d) + +#define ADC5_GEN3_CHG_TEMP(sid) VIRT_CHAN(sid, 0x10) +#define ADC5_GEN3_USB_SNS_V_16(sid) VIRT_CHAN(sid, 0x11) +#define ADC5_GEN3_VIN_DIV16_MUX(sid) VIRT_CHAN(sid, 0x12) +#define ADC5_GEN3_VREF_BAT_THERM(sid) VIRT_CHAN(sid, 0x15) +#define ADC5_GEN3_IIN_FB(sid) VIRT_CHAN(sid, 0x17) +#define ADC5_GEN3_TEMP_ALARM_LITE(sid) VIRT_CHAN(sid, 0x18) +#define ADC5_GEN3_IIN_SMB(sid) VIRT_CHAN(sid, 0x19) +#define ADC5_GEN3_ICHG_SMB(sid) VIRT_CHAN(sid, 0x1b) +#define ADC5_GEN3_ICHG_FB(sid) VIRT_CHAN(sid, 0xa1) + +/* 30k pull-up */ +#define ADC5_GEN3_AMUX1_THM_30K_PU(sid) VIRT_CHAN(sid, 0x24) +#define ADC5_GEN3_AMUX2_THM_30K_PU(sid) VIRT_CHAN(sid, 0x25) +#define ADC5_GEN3_AMUX3_THM_30K_PU(sid) VIRT_CHAN(sid, 0x26) +#define ADC5_GEN3_AMUX4_THM_30K_PU(sid) VIRT_CHAN(sid, 0x27) +#define ADC5_GEN3_AMUX5_THM_30K_PU(sid) VIRT_CHAN(sid, 0x28) +#define ADC5_GEN3_AMUX6_THM_30K_PU(sid) VIRT_CHAN(sid, 0x29) +#define ADC5_GEN3_AMUX1_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2a) +#define ADC5_GEN3_AMUX2_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2b) +#define ADC5_GEN3_AMUX3_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2c) +#define ADC5_GEN3_AMUX4_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2d) + +/* 100k pull-up */ +#define ADC5_GEN3_AMUX1_THM_100K_PU(sid) VIRT_CHAN(sid, 0x44) +#define ADC5_GEN3_AMUX2_THM_100K_PU(sid) VIRT_CHAN(sid, 0x45) +#define ADC5_GEN3_AMUX3_THM_100K_PU(sid) VIRT_CHAN(sid, 0x46) +#define ADC5_GEN3_AMUX4_THM_100K_PU(sid) VIRT_CHAN(sid, 0x47) +#define ADC5_GEN3_AMUX5_THM_100K_PU(sid) VIRT_CHAN(sid, 0x48) +#define ADC5_GEN3_AMUX6_THM_100K_PU(sid) VIRT_CHAN(sid, 0x49) +#define ADC5_GEN3_AMUX1_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4a) +#define ADC5_GEN3_AMUX2_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4b) +#define ADC5_GEN3_AMUX3_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4c) +#define ADC5_GEN3_AMUX4_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4d) + +/* 400k pull-up */ +#define ADC5_GEN3_AMUX1_THM_400K_PU(sid) VIRT_CHAN(sid, 0x64) +#define ADC5_GEN3_AMUX2_THM_400K_PU(sid) VIRT_CHAN(sid, 0x65) +#define ADC5_GEN3_AMUX3_THM_400K_PU(sid) VIRT_CHAN(sid, 0x66) +#define ADC5_GEN3_AMUX4_THM_400K_PU(sid) VIRT_CHAN(sid, 0x67) +#define ADC5_GEN3_AMUX5_THM_400K_PU(sid) VIRT_CHAN(sid, 0x68) +#define ADC5_GEN3_AMUX6_THM_400K_PU(sid) VIRT_CHAN(sid, 0x69) +#define ADC5_GEN3_AMUX1_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6a) +#define ADC5_GEN3_AMUX2_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6b) +#define ADC5_GEN3_AMUX3_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6c) +#define ADC5_GEN3_AMUX4_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6d) + +/* 1/3 Divider */ +#define ADC5_GEN3_AMUX1_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8a) +#define ADC5_GEN3_AMUX2_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8b) +#define ADC5_GEN3_AMUX3_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8c) +#define ADC5_GEN3_AMUX4_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8d) + +#define ADC5_GEN3_VPH_PWR(sid) VIRT_CHAN(sid, 0x8e) +#define ADC5_GEN3_VBAT_SNS_QBG(sid) VIRT_CHAN(sid, 0x8f) + +#define ADC5_GEN3_VBAT_SNS_CHGR(sid) VIRT_CHAN(sid, 0x94) +#define ADC5_GEN3_VBAT_2S_MID_QBG(sid) VIRT_CHAN(sid, 0x96) +#define ADC5_GEN3_VBAT_2S_MID_CHGR(sid) VIRT_CHAN(sid, 0x9d) + +#endif /* __DTS_ARM64_QCOM_ADC5_GEN3_H__ */ From 6ff7ac7a98e82b66064f201fcab092a8ac11e7f2 Mon Sep 17 00:00:00 2001 From: Ayyagari Ushasreevalli Date: Thu, 30 Apr 2026 14:28:57 +0530 Subject: [PATCH 021/243] FROMLIST: arm64: dts: qcom: lemans-pmics: Add ADC support for PMM8654au Add ADC nodes for the four PMM8654au PMICs (pmm8654au_0 through pmm8654au_3) on the Lemans platform. Each ADC node exposes the following ADC channels: - DIE_TEMP: PMIC die temperature channel - VPH_PWR: Battery/supply voltage channel Also add the io-channels and io-channel-names properties under the temp-alarm nodes so that they can get temperature reading from the ADC die_temp channels. Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-2-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Ayyagari Ushasreevalli Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/lemans-pmics.dtsi | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi b/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi index 341119fc82440..6caec3e4df4bb 100644 --- a/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi @@ -5,6 +5,7 @@ #include #include +#include "qcom-adc5-gen3.h" / { thermal-zones { @@ -110,6 +111,8 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x0 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_0_adc ADC5_GEN3_DIE_TEMP(0)>; + io-channel-names = "thermal"; }; pmm8654au_0_pon: pon@1200 { @@ -141,6 +144,27 @@ interrupts = <0x0 0x62 0x1 IRQ_TYPE_EDGE_RISING>; }; + pmm8654au_0_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x0 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@3 { + reg = ; + label = "pmm8654au_0_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@8e { + reg = ; + label = "pmm8654au_0_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8654au_0_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -176,6 +200,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x2 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_1_adc ADC5_GEN3_DIE_TEMP(2)>; + io-channel-names = "thermal"; + }; + + pmm8654au_1_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x2 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@203 { + reg = ; + label = "pmm8654au_1_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@28e { + reg = ; + label = "pmm8654au_1_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_1_gpios: gpio@8800 { @@ -200,6 +247,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x4 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_2_adc ADC5_GEN3_DIE_TEMP(4)>; + io-channel-names = "thermal"; + }; + + pmm8654au_2_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x4 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@403 { + reg = ; + label = "pmm8654au_2_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@48e { + reg = ; + label = "pmm8654au_2_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_2_gpios: gpio@8800 { @@ -224,6 +294,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x6 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_3_adc ADC5_GEN3_DIE_TEMP(6)>; + io-channel-names = "thermal"; + }; + + pmm8654au_3_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x6 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@603 { + reg = ; + label = "pmm8654au_3_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@68e { + reg = ; + label = "pmm8654au_3_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_3_gpios: gpio@8800 { From 86a1828edec07f98a80d498477dda4101c3e0bee Mon Sep 17 00:00:00 2001 From: Ayyagari Ushasreevalli Date: Thu, 30 Apr 2026 14:28:58 +0530 Subject: [PATCH 022/243] FROMLIST: arm64: dts: qcom: monaco-pmics: Add ADC support for PMM8620AU Add ADC nodes for PMM8620AU PMIC instances (SID 0 and SID 2) present on the Monaco platform. Each ADC node exposes the following ADC channels: - DIE_TEMP: PMIC die temperature channel - VPH_PWR: Battery/supply voltage channel Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-3-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Ayyagari Ushasreevalli Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/monaco-pmics.dtsi | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi index e990d7367719b..232bcb942b54c 100644 --- a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi @@ -5,6 +5,7 @@ #include #include +#include "qcom-adc5-gen3.h" &spmi_bus { pmm8620au_0: pmic@0 { @@ -20,6 +21,27 @@ interrupts = <0x0 0x62 0x1 IRQ_TYPE_EDGE_RISING>; }; + pmm8620au_0_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x0 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@3 { + reg = ; + label = "pmm8620au_0_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@8e { + reg = ; + label = "pmm8620au_0_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8620au_0_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -37,6 +59,27 @@ #address-cells = <1>; #size-cells = <0>; + pmm8650au_1_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x2 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@203 { + reg = ; + label = "pmm8650au_1_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@28e { + reg = ; + label = "pmm8650au_1_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8650au_1_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; From 37c66db9917203d966b2853c8f63f46b2b9edcf4 Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Mon, 1 Jun 2026 16:31:18 +0530 Subject: [PATCH 023/243] FROMLIST: dt-bindings: thermal: Add Qualcomm MBG thermal monitor support Add bindings for the Qualcomm MBG (Master Bandgap) temperature alarm peripheral found on the PM8775 PMIC. Unlike the existing SPMI temp alarm peripheral, the MBG peripheral supports both hot and cold thresholdi monitoring across two programmable levels (LVL1 and LVL2), with interrupt status reported via a fault status register over SPMI. Link: https://lore.kernel.org/all/20260601-spmi-mbg-driver-v1-1-b4892b55a17f@oss.qualcomm.com/ Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta --- .../bindings/mfd/qcom,spmi-pmic.yaml | 4 ++ .../bindings/thermal/qcom-spmi-mbg-tm.yaml | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/qcom-spmi-mbg-tm.yaml diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e57..5f409fe700b22 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -193,6 +193,10 @@ patternProperties: type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# + "^temperature-sensor@[0-9a-f]+$": + type: object + $ref: /schemas/thermal/qcom-spmi-mbg-tm.yaml# + "^typec@[0-9a-f]+$": type: object $ref: /schemas/usb/qcom,pmic-typec.yaml# diff --git a/Documentation/devicetree/bindings/thermal/qcom-spmi-mbg-tm.yaml b/Documentation/devicetree/bindings/thermal/qcom-spmi-mbg-tm.yaml new file mode 100644 index 0000000000000..a0ecc9f35cf6e --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom-spmi-mbg-tm.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/qcom-spmi-mbg-tm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm's SPMI PMIC MBG Thermal Monitoring + +maintainers: + - Jishnu Prakash + - Kamal Wadhwa + +description: + Qualcomm's MBG(Master Bandgap) temperature alarm monitors the die + temperature and generates an interrupt if the PMIC die temperature is + over a set of programmable temperature thresholds. It allows monitoring + for both hot and cold, LVL1 and LVL2 thresholds, which makes it different + from the existing temp alarm peripheral. The interrupt comes over SPMI + and the MBG's fault status register gives details to understand whether + it is a hot/cold and LVL1/LVL2 violation. + +properties: + compatible: + const: qcom,pm8775-mbg-tm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + io-channels: + items: + - description: ADC channel, which reports chip die temperature. + + io-channel-names: + items: + - const: thermal + + '#thermal-sensor-cells': + const: 0 + +required: + - compatible + - reg + - interrupts + - io-channels + - io-channel-names + +allOf: + - $ref: thermal-sensor.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@d700 { + compatible = "qcom,pm8775-mbg-tm"; + reg = <0xd700>; + interrupts = <0x1 0xd7 0x0 IRQ_TYPE_EDGE_RISING>; + io-channels = <&pm8775_adc 0x3>; + io-channel-names = "thermal"; + #thermal-sensor-cells = <0>; + }; + }; +... From 6f59fadaf0f63b094a3084ddd19b01056bfb7d03 Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Mon, 1 Jun 2026 16:31:19 +0530 Subject: [PATCH 024/243] FROMLIST: thermal: qcom: Add support for Qualcomm MBG thermal monitoring Add driver for the Qualcomm MBG thermal monitoring device. It monitors the die temperature, and when there is a level 1 upper threshold violation, it receives an interrupt over spmi. The driver reads the fault status register and notifies thermal accordingly. Link: https://lore.kernel.org/all/20260601-spmi-mbg-driver-v1-2-b4892b55a17f@oss.qualcomm.com/ Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta --- drivers/thermal/qcom/Kconfig | 11 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 254 ++++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-mbg-tm.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 1acb11e4ac800..2d6df24e676ba 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -30,6 +30,17 @@ config QCOM_SPMI_ADC_TM5_GEN3 Thermal client sets threshold temperature for both warm and cool and gets updated when a threshold is reached. +config QCOM_SPMI_MBG_TM + tristate "Qualcomm SPMI PMIC MBG Temperature monitor" + depends on QCOM_SPMI_ADC5_GEN3 + select REGMAP_SPMI + help + This enables a thermal driver for the MBG thermal monitoring device. + It shows up in sysfs as a thermal sensor with single trip point. + It notifies the thermal framework when this trip is violated. The + temperature reported by the thermal sensor reflects the real + time die temperature through ADC channel. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 828d9e7bc7970..937ba0fe28013 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -5,5 +5,6 @@ qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o +obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c new file mode 100644 index 0000000000000..60190b341fc7c --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MBG_TEMP_MON2_FAULT_STATUS 0x50 + +#define MON_FAULT_STATUS_MASK GENMASK(7, 4) +#define MON_FAULT_LVL1_UPR 0x5 + +#define MON2_LVL1_UP_THRESH 0x59 + +#define MBG_TEMP_MON2_MISC_CFG 0x5f +#define MON2_UP_THRESH_EN BIT(1) + +#define MBG_TEMP_STEP_MV 8 +#define MBG_TEMP_DEFAULT_TEMP_MV 600 +#define MBG_TEMP_CONSTANT 1000 +#define MBG_MIN_TRIP_TEMP 25000 +#define MBG_MAX_SUPPORTED_TEMP 160000 + +/** + * struct mbg_tm_chip - MBG thermal monitor device data. + * @map: regmap for accessing MBG thermal registers. + * @dev: mbg_tm_chip device. + * @tz_dev: thermal zone device registered with the thermal framework. + * @lock: mbg_tm_chip lock for set trip temperature. + * @base: base register offset for this MBG instance + * @irq: interrupt line used to signal threshold events + * @last_temp: last measured temperature. + * @last_thres_crossed: indicates whether the last interrupt crossed a threshold + * @adc: IIO ADC channel used for temperature sensing + */ +struct mbg_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + struct mutex lock; + unsigned int base; + int irq; + int last_temp; + bool last_thres_crossed; + struct iio_channel *adc; +}; + +/** + * struct mbg_map_table - temperature to voltage mapping entry + * @min_temp: minimum temperature supported by this mapping entry + * @vtemp0: reference voltage or ADC code corresponding to the temperature + * @tc: temperature coefficient used for conversion calculations + */ +struct mbg_map_table { + int min_temp; + int vtemp0; + int tc; +}; + +static const struct mbg_map_table map_table[] = { + /* minT vtemp0 tc */ + { -60000, 4337, 1967 }, + { -40000, 4731, 1964 }, + { -20000, 5124, 1957 }, + { 0, 5515, 1949 }, + { 20000, 5905, 1940 }, + { 40000, 6293, 1930 }, + { 60000, 6679, 1921 }, + { 80000, 7064, 1910 }, + { 100000, 7446, 1896 }, + { 120000, 7825, 1878 }, + { 140000, 8201, 1859 }, +}; + +static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret, milli_celsius; + + if (chip->last_thres_crossed) { + dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp); + chip->last_thres_crossed = false; + *temp = chip->last_temp; + return 0; + } + + ret = iio_read_channel_processed(chip->adc, &milli_celsius); + if (ret < 0) { + dev_err(chip->dev, "Failed to read iio channel with %d\n", ret); + return ret; + } + + *temp = milli_celsius; + + return 0; +} + +static int temp_to_vtemp_mv(int temp) +{ + int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0; + + for (idx = 0; idx < ARRAY_SIZE(map_table); idx++) + if (temp >= map_table[idx].min_temp && + temp < (map_table[idx].min_temp + 20000)) { + tc = map_table[idx].tc; + t0 = map_table[idx].min_temp; + vtemp0 = map_table[idx].vtemp0; + break; + } + + /* + * Formula to calculate vtemp(mV) from a given temp + * vtemp = (temp - minT) * tc + vtemp0 + * tc, t0 and vtemp0 values are mentioned in the map_table array. + */ + vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000; + + /* step size is 8mV */ + return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV; +} + +static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int low_temp, + int temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret = 0; + + guard(mutex)(&chip->lock); + + /* The HW has a limitation that the trip set must be above 25C */ + if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) { + ret = regmap_set_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + if (ret < 0) + return ret; + + ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH, + temp_to_vtemp_mv(temp)); + if (ret < 0) + return ret; + } else { + dev_dbg(chip->dev, "Set trip b/w 25C and 160C\n"); + ret = regmap_clear_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + return ret; + } + + /* + * Configure the last_temp one degree higher, to ensure the + * violated temp is returned to thermal framework when it reads + * temperature for the first time after the violation happens. + * This is needed to account for the inaccuracy in the conversion + * formula used which leads to the thermal framework setting back + * the same thresholds in case the temperature it reads does not + * show violation. + */ + chip->last_temp = temp + MBG_TEMP_CONSTANT; + + return ret; +} + +static const struct thermal_zone_device_ops mbg_tm_ops = { + .get_temp = mbg_tm_get_temp, + .set_trips = mbg_tm_set_trip_temp, +}; + +static irqreturn_t mbg_tm_isr(int irq, void *data) +{ + struct mbg_tm_chip *chip = data; + int ret, val; + + scoped_guard(mutex, &chip->lock) { + ret = regmap_read(chip->map, chip->base + MBG_TEMP_MON2_FAULT_STATUS, &val); + if (ret < 0) + return IRQ_HANDLED; + } + + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) & MON_FAULT_LVL1_UPR) { + chip->last_thres_crossed = true; + dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val); + thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED); + } else { + dev_dbg(chip->dev, "Lvl1 upper threshold not violated, ignoring interrupt\n"); + } + + return IRQ_HANDLED; +} + +static int mbg_tm_probe(struct platform_device *pdev) +{ + struct mbg_tm_chip *chip; + struct device_node *node = pdev->dev.of_node; + u32 res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + mutex_init(&chip->lock); + + chip->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->map) + return -ENXIO; + + ret = device_property_read_u32(chip->dev, "reg", &res); + if (ret < 0) + return dev_err_probe(chip->dev, ret, "Couldn't read reg property\n"); + + chip->base = res; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return dev_err_probe(chip->dev, chip->irq, "Failed to get irq\n"); + + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); + if (IS_ERR(chip->adc)) + return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed to get adc channel\n"); + + chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip, &mbg_tm_ops); + if (IS_ERR(chip->tz_dev)) + return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev), + "Failed to register sensor\n"); + + return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL, mbg_tm_isr, IRQF_ONESHOT, + node->name, chip); +} + +static const struct of_device_id mbg_tm_match_table[] = { + { .compatible = "qcom,pm8775-mbg-tm" }, + { } +}; +MODULE_DEVICE_TABLE(of, mbg_tm_match_table); + +static struct platform_driver mbg_tm_driver = { + .driver = { + .name = "qcom-spmi-mbg-tm", + .of_match_table = mbg_tm_match_table, + }, + .probe = mbg_tm_probe, +}; +module_platform_driver(mbg_tm_driver); + +MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); +MODULE_LICENSE("GPL"); From 6fb618c321fd27ccf873f5b4bc6737baee8fd8bb Mon Sep 17 00:00:00 2001 From: Ayyagari Ushasreevalli Date: Sun, 14 Jun 2026 13:35:11 +0530 Subject: [PATCH 025/243] FROMLIST: arm64: dts: qcom: hamoa-pmics: Add ADC support Add ADC node and define channels for: - Die temperature for PMK8550, PM8550VE* and PMC8380* PMICs. - PM8550: Die temperature, VPH power, and system thermistors. Define thermal zones 'sys-0-thermal' through 'sys-6-thermal' which correspond to the off-PMIC system thermistors connected via PM8550 AMUX/GPIO lines. Also,add io-channels and io-channel-names properties to the temp_alarm nodes so that they can get temperature reading from the ADC die_temp channels. Link: https://lore.kernel.org/all/20260614-adc5_gen3_dt-v2-4-32ec576c5865@oss.qualcomm.com/ Signed-off-by: Ayyagari Ushasreevalli Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi | 250 ++++++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi index 6a31a0adf8be4..2e746ede850f8 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include "qcom-adc5-gen3.h" / { thermal-zones { @@ -189,6 +191,90 @@ }; }; }; + + sys-0-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX1_GPIO_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-1-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX2_GPIO_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-2-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX1_THM_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-3-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX2_THM_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-4-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX3_THM_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-5-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX4_THM_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-6-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8550_vadc ADC5_GEN3_AMUX5_THM_100K_PU(1)>; + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; }; }; @@ -277,6 +363,142 @@ status = "disabled"; }; + + pmk8550_vadc: adc@9000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x9000>, <0x9100>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x0 0x90 0x1 IRQ_TYPE_EDGE_RISING>, + <0x0 0x91 0x1 IRQ_TYPE_EDGE_RISING>; + #thermal-sensor-cells = <1>; + #io-channel-cells = <1>; + pinctrl-0 = <&sys_therm_0_gpio3>, <&sys_therm_1_gpio4>; + pinctrl-names = "default"; + + channel@3 { + reg = ; + label = "pmk8550_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@103 { + reg = ; + label = "pm8550_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@18e { + reg = ; + label = "pm8550_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + + channel@14a { + reg = ; + label = "pm8550_gpio_01"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@14b { + reg = ; + label = "pm8550_gpio_02"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@144 { + reg = ; + label = "pm8550_therm_2"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@145 { + reg = ; + label = "pm8550_therm_3"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@146 { + reg = ; + label = "pm8550_therm_4"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@147 { + reg = ; + label = "pm8550_therm_5"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@148 { + reg = ; + label = "pm8550_therm_6"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,adc-tm; + }; + + channel@203 { + reg = ; + label = "pm8550ve_2_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@303 { + reg = ; + label = "pmc8380_3_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@403 { + reg = ; + label = "pmc8380_4_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@503 { + reg = ; + label = "pmc8380_5_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@603 { + reg = ; + label = "pmc8380_6_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@803 { + reg = ; + label = "pm8550ve_8_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@903 { + reg = ; + label = "pm8550ve_9_die_temp"; + qcom,pre-scaling = <1 1>; + }; + }; }; /* PMC8380C */ @@ -291,6 +513,8 @@ reg = <0xa00>; interrupts = <0x1 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(1)>; + io-channel-names = "thermal"; }; pm8550_gpios: gpio@8800 { @@ -301,6 +525,18 @@ #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; + + sys_therm_0_gpio3: sys-therm_0-gpio3-state { + pins = "gpio3"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; + + sys_therm_1_gpio4: sys-therm-1-gpio4-state { + pins = "gpio4"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; }; pm8550_flash: led-controller@ee00 { @@ -329,6 +565,8 @@ reg = <0xa00>; interrupts = <0x2 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(2)>; + io-channel-names = "thermal"; }; pm8550ve_2_gpios: gpio@8800 { @@ -354,6 +592,8 @@ reg = <0xa00>; interrupts = <0x3 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(3)>; + io-channel-names = "thermal"; }; pmc8380_3_gpios: gpio@8800 { @@ -378,6 +618,8 @@ reg = <0xa00>; interrupts = <0x4 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(4)>; + io-channel-names = "thermal"; }; pmc8380_4_gpios: gpio@8800 { @@ -402,6 +644,8 @@ reg = <0xa00>; interrupts = <0x5 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(5)>; + io-channel-names = "thermal"; }; pmc8380_5_gpios: gpio@8800 { @@ -426,6 +670,8 @@ reg = <0xa00>; interrupts = <0x6 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(6)>; + io-channel-names = "thermal"; }; pmc8380_6_gpios: gpio@8800 { @@ -451,6 +697,8 @@ reg = <0xa00>; interrupts = <0x8 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(8)>; + io-channel-names = "thermal"; }; pm8550ve_8_gpios: gpio@8800 { @@ -476,6 +724,8 @@ reg = <0xa00>; interrupts = <0x9 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmk8550_vadc ADC5_GEN3_DIE_TEMP(9)>; + io-channel-names = "thermal"; }; pm8550ve_9_gpios: gpio@8800 { From dc1eec8a1679c205efa034f8110d2e0ec981154e Mon Sep 17 00:00:00 2001 From: Gaurav Kohli Date: Tue, 9 Jun 2026 15:52:56 +0530 Subject: [PATCH 026/243] dt-bindings: remoteproc: qcom,pas: add thermal mitigation properties Document Qualcomm PAS remoteproc thermal mitigation properties used for QMI-based throttling. Add: - #cooling-cells (2 or 3) - tmd-names (thermal mitigation device names) Link: https://lore.kernel.org/r/20260609-qmi-tmd-v3-1-291a2ff4c634@oss.qualcomm.com Signed-off-by: Gaurav Kohli --- .../bindings/remoteproc/qcom,pas-common.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml index 4607b459131b4..0d07a07507620 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml @@ -79,6 +79,20 @@ properties: channels and devices related to the ADSP. unevaluatedProperties: false + '#cooling-cells': + description: + Number of cooling cells; 2 for min/max cooling state and 3 when + selecting a thermal mitigation device index. + enum: [2, 3] + + tmd-names: + $ref: /schemas/types.yaml#/definitions/string-array + description: + Names of the thermal mitigation devices available in remote processor + subsystem. + minItems: 1 + maxItems: 5 + glink-edge: $ref: /schemas/remoteproc/qcom,glink-edge.yaml# description: From 6141250b6a49ffc876daf35bcb81856d37306d43 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Tue, 9 Jun 2026 15:52:57 +0530 Subject: [PATCH 027/243] soc: qcom: Add support for QMI TMD cooling devices Add a Qualcomm QMI Thermal Mitigation Device (TMD) to support thermal cooling devices backed by remote subsystems. On several Qualcomm platforms, remote processors (for example modem and CDSP) expose thermal mitigation controls through the TMD QMI service. Client drivers need a way to discover that service, map DT thermal mitigation endpoints to cooling devices, and forward cooling state updates to the remote subsystem. Co-developed-by: Gaurav Kohli Signed-off-by: Gaurav Kohli Signed-off-by: Casey Connolly Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20260609-qmi-tmd-v3-2-291a2ff4c634@oss.qualcomm.com --- MAINTAINERS | 6 + drivers/soc/qcom/Kconfig | 10 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qmi_tmd.c | 602 +++++++++++++++++++++++++++++++ include/linux/soc/qcom/qmi.h | 1 + include/linux/soc/qcom/qmi_tmd.h | 23 ++ 6 files changed, 643 insertions(+) create mode 100644 drivers/soc/qcom/qmi_tmd.c create mode 100644 include/linux/soc/qcom/qmi_tmd.h diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a99..829ffcb528f47 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22330,6 +22330,12 @@ F: Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml F: Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst F: drivers/net/ethernet/qualcomm/ppe/ +QUALCOMM QMI (REMOTEPROC THERMAL MITIGATION) TMD +M: Gaurav Kohli +L: linux-arm-msm@vger.kernel.org +L: linux-pm@vger.kernel.org +F: drivers/soc/qcom/qmi_tmd.c + QUALCOMM QSEECOM DRIVER M: Maximilian Luz L: linux-arm-msm@vger.kernel.org diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 2caadbbcf8307..a292ce57fd4a4 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -128,6 +128,16 @@ config QCOM_QMI_HELPERS tristate depends on NET +config QCOM_QMI_TMD + bool "Qualcomm QMI TMD library" if COMPILE_TEST + depends on ARCH_QCOM + select QCOM_QMI_HELPERS + help + This enables the QMI-based Thermal Mitigation Device (TMD) library + for Qualcomm remote subsystems. The library manages TMD messaging and + handles QMI communication with remote processors (modem, CDSP) to + exchange mitigation state and apply thermal mitigation requests. + config QCOM_RAMP_CTRL tristate "Qualcomm Ramp Controller driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index b7f1d2a573674..4544e61c74e73 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink.o obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink_altmode.o obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG) += pmic_pdcharger_ulog.o CFLAGS_pmic_pdcharger_ulog.o := -I$(src) +obj-$(CONFIG_QCOM_QMI_TMD) += qmi_tmd.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o diff --git a/drivers/soc/qcom/qmi_tmd.c b/drivers/soc/qcom/qmi_tmd.c new file mode 100644 index 0000000000000..e09131e8ef0f6 --- /dev/null +++ b/drivers/soc/qcom/qmi_tmd.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * QMI Thermal Mitigation Device (TMD) library. + * This library provides cooling device support for remote subsystems + * (modem and CDSP) running the TMD service via QMI. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QMI_TMD_INSTANCE_MODEM 0x0 +#define QMI_TMD_INSTANCE_CDSP 0x43 +#define QMI_TMD_INSTANCE_CDSP1 0x44 + +#define QMI_TMD_SERVICE_VERS_V01 0x01 + +#define QMI_TMD_SET_LEVEL_REQ 0x0021 +#define QMI_TMD_GET_DEV_LIST_REQ 0x0020 + +#define QMI_TMD_DEV_ID_LEN_MAX 32 +#define QMI_TMD_DEV_LIST_MAX 32 +#define QMI_TMD_RESP_TIMEOUT msecs_to_jiffies(100) +#define TMD_GET_LEVEL_REQ_MAX_LEN 36 +#define TMD_SET_LEVEL_REQ_MAX_LEN 40 + +#define TMD_GET_DEV_LIST_REQ_MAX_LEN 0 +#define TMD_GET_DEV_LIST_RESP_MAX_LEN 1099 + +struct tmd_dev_id { + char mitigation_dev_id[QMI_TMD_DEV_ID_LEN_MAX + 1]; +}; + +static const struct qmi_elem_info tmd_dev_id_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_TMD_DEV_ID_LEN_MAX + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct tmd_dev_id, + mitigation_dev_id), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct tmd_dev_list { + struct tmd_dev_id mitigation_dev_id; + u8 max_mitigation_level; +}; + +static const struct qmi_elem_info tmd_dev_list_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_dev_id), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct tmd_dev_list, + mitigation_dev_id), + .ei_array = tmd_dev_id_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct tmd_dev_list, + max_mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct tmd_get_dev_list_req { + char placeholder; +}; + +static const struct qmi_elem_info tmd_get_dev_list_req_ei[] = { + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct tmd_get_dev_list_resp { + struct qmi_response_type_v01 resp; + u8 mitigation_device_list_valid; + u32 mitigation_device_list_len; + struct tmd_dev_list + mitigation_device_list[QMI_TMD_DEV_LIST_MAX]; +}; + +static const struct qmi_elem_info tmd_get_dev_list_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct tmd_get_dev_list_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct tmd_get_dev_list_resp, + mitigation_device_list_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct tmd_get_dev_list_resp, + mitigation_device_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_TMD_DEV_LIST_MAX, + .elem_size = sizeof(struct tmd_dev_list), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct tmd_get_dev_list_resp, + mitigation_device_list), + .ei_array = tmd_dev_list_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct tmd_set_level_req { + struct tmd_dev_id mitigation_dev_id; + u8 mitigation_level; +}; + +static const struct qmi_elem_info tmd_set_level_req_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_dev_id), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct tmd_set_level_req, + mitigation_dev_id), + .ei_array = tmd_dev_id_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct tmd_set_level_req, + mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct tmd_set_level_resp { + struct qmi_response_type_v01 resp; +}; + +static const struct qmi_elem_info tmd_set_level_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct tmd_set_level_resp, resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +/** + * struct qmi_tmd - A TMD cooling device + * @name: The name of this tmd shared by the remote subsystem + * @cdev: Thermal cooling device handle + * @cur_state: The current mitigation state + * @max_state: The maximum state + * @qmi_tmd_cli: Parent QMI TMD client + */ +struct qmi_tmd { + const char *name; + struct thermal_cooling_device *cdev; + unsigned int cur_state; + unsigned int max_state; + struct qmi_tmd_client *qmi_tmd_cli; +}; + +/** + * struct qmi_tmd_client - QMI TMD client state + * @dev: Device associated with this instance + * @handle: QMI connection handle + * @mutex: Lock to synchronise QMI communication + * @connection_active: Whether or not we're connected to the QMI TMD service + * @svc_arrive_work: Work item for initialising when the TMD service starts + * @num_tmds: Number of tmds described in the device tree + * @tmds: An array of tmd structures + */ +struct qmi_tmd_client { + struct device *dev; + struct qmi_handle handle; + /* protects QMI transactions and connection_active */ + struct mutex mutex; + bool connection_active; + struct work_struct svc_arrive_work; + int num_tmds; + struct qmi_tmd tmds[] __counted_by(num_tmds); +}; + +/* Notify the remote subsystem of the requested cooling state */ +static int qmi_tmd_send_state_request(struct qmi_tmd *tmd, int state) +{ + struct tmd_set_level_resp resp = { 0 }; + struct tmd_set_level_req req = { 0 }; + struct qmi_tmd_client *qmi_tmd_cli = tmd->qmi_tmd_cli; + struct qmi_txn txn; + int ret = 0; + + guard(mutex)(&qmi_tmd_cli->mutex); + + if (!qmi_tmd_cli->connection_active) + return 0; + + strscpy(req.mitigation_dev_id.mitigation_dev_id, tmd->name, + QMI_TMD_DEV_ID_LEN_MAX + 1); + req.mitigation_level = state; + + ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn, + tmd_set_level_resp_ei, &resp); + if (ret < 0) { + dev_err(qmi_tmd_cli->dev, "qmi set state %d txn init failed for %s ret %d\n", + state, tmd->name, ret); + return ret; + } + + ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn, + QMI_TMD_SET_LEVEL_REQ, + TMD_SET_LEVEL_REQ_MAX_LEN, + tmd_set_level_req_ei, &req); + if (ret < 0) { + dev_err(qmi_tmd_cli->dev, "qmi set state %d txn send failed for %s ret %d\n", + state, tmd->name, ret); + qmi_txn_cancel(&txn); + return ret; + } + + ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT); + if (ret < 0) { + dev_err(qmi_tmd_cli->dev, "qmi set state %d txn wait failed for %s ret %d\n", + state, tmd->name, ret); + return ret; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + dev_err(qmi_tmd_cli->dev, + "qmi set state %d failed for %s result %#x error %#x\n", + state, tmd->name, + resp.resp.result, resp.resp.error); + return -EREMOTEIO; + } + + dev_dbg(qmi_tmd_cli->dev, "Requested state %d/%d for %s\n", state, + tmd->max_state, tmd->name); + + return 0; +} + +static int qmi_tmd_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qmi_tmd *tmd = cdev->devdata; + + *state = tmd->max_state; + + return 0; +} + +static int qmi_tmd_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qmi_tmd *tmd = cdev->devdata; + + *state = tmd->cur_state; + + return 0; +} + +static int qmi_tmd_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct qmi_tmd *tmd = cdev->devdata; + int ret; + + if (state > tmd->max_state) + return -EINVAL; + + if (tmd->cur_state == state) + return 0; + + ret = qmi_tmd_send_state_request(tmd, state); + if (!ret) + tmd->cur_state = state; + + return ret; +} + +static const struct thermal_cooling_device_ops qmi_tmd_cooling_ops = { + .get_max_state = qmi_tmd_get_max_state, + .get_cur_state = qmi_tmd_get_cur_state, + .set_cur_state = qmi_tmd_set_cur_state, +}; + +static int qmi_tmd_register(struct qmi_tmd_client *qmi_tmd_cli, + const char *label, u8 max_state) +{ + struct device *dev = qmi_tmd_cli->dev; + struct qmi_tmd *tmd; + int index; + + for (index = 0; index < qmi_tmd_cli->num_tmds; index++) { + tmd = &qmi_tmd_cli->tmds[index]; + + if (!strncasecmp(tmd->name, label, + QMI_TMD_DEV_ID_LEN_MAX + 1)) + goto found; + } + + dev_dbg(qmi_tmd_cli->dev, + "TMD '%s' available in firmware but not specified in DT\n", + label); + return 0; + +found: + tmd->max_state = max_state; + + /* + * If the cooling device already exists then the QMI service went away and + * came back. So just make sure the current cooling device state is + * reflected on the remote side and then return. + */ + if (tmd->cdev) + return qmi_tmd_send_state_request(tmd, tmd->cur_state); + + tmd->cdev = thermal_of_cooling_device_register(dev->of_node, index, + label, tmd, &qmi_tmd_cooling_ops); + if (IS_ERR(tmd->cdev)) + return PTR_ERR(tmd->cdev); + + return 0; +} + +static void qmi_tmd_unregister(struct qmi_tmd_client *qmi_tmd_cli) +{ + struct qmi_tmd *tmd; + int index; + + for (index = 0; index < qmi_tmd_cli->num_tmds; index++) { + tmd = &qmi_tmd_cli->tmds[index]; + + if (!tmd->cdev) + continue; + + thermal_cooling_device_unregister(tmd->cdev); + tmd->cdev = NULL; + } +} + +static void qmi_tmd_svc_arrive(struct work_struct *work) +{ + struct qmi_tmd_client *qmi_tmd_cli = + container_of(work, struct qmi_tmd_client, svc_arrive_work); + + struct tmd_get_dev_list_req req = { 0 }; + struct tmd_get_dev_list_resp *resp __free(kfree) = NULL; + int ret, i; + struct qmi_txn txn; + + resp = kzalloc_obj(*resp, GFP_KERNEL); + if (!resp) { + ret = -ENOMEM; + goto out; + } + + scoped_guard(mutex, &qmi_tmd_cli->mutex) { + ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn, + tmd_get_dev_list_resp_ei, resp); + if (ret < 0) + goto out; + + ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn, + QMI_TMD_GET_DEV_LIST_REQ, + TMD_GET_DEV_LIST_REQ_MAX_LEN, + tmd_get_dev_list_req_ei, &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + goto out; + } + + ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT); + if (ret < 0) + goto out; + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + ret = -EPROTO; + goto out; + } + + qmi_tmd_cli->connection_active = true; + } + + for (i = 0; i < resp->mitigation_device_list_len; i++) { + struct tmd_dev_list *device = + &resp->mitigation_device_list[i]; + + ret = qmi_tmd_register(qmi_tmd_cli, + device->mitigation_dev_id.mitigation_dev_id, + device->max_mitigation_level); + if (ret) + break; + } + +out: + if (ret) + dev_err(qmi_tmd_cli->dev, "Failed to initialize TMD service: %d\n", ret); +} + +static void qmi_tmd_del_server(struct qmi_handle *qmi, struct qmi_service *service) +{ + struct qmi_tmd_client *qmi_tmd_cli = + container_of(qmi, struct qmi_tmd_client, handle); + + scoped_guard(mutex, &qmi_tmd_cli->mutex) + qmi_tmd_cli->connection_active = false; +} + +static int qmi_tmd_new_server(struct qmi_handle *qmi, struct qmi_service *service) +{ + struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port }; + struct qmi_tmd_client *qmi_tmd_cli; + int ret; + + qmi_tmd_cli = container_of(qmi, struct qmi_tmd_client, handle); + + scoped_guard(mutex, &qmi_tmd_cli->mutex) { + ret = kernel_connect(qmi->sock, (struct sockaddr_unsized *)&sq, + sizeof(sq), 0); + } + + if (ret < 0) { + dev_err(qmi_tmd_cli->dev, "QMI connect failed for node %u port %u: %d\n", + service->node, service->port, ret); + return ret; + } + + queue_work(system_highpri_wq, &qmi_tmd_cli->svc_arrive_work); + + return 0; +} + +static const struct qmi_ops qmi_tmd_ops = { + .new_server = qmi_tmd_new_server, + .del_server = qmi_tmd_del_server, +}; + +static int qmi_tmd_get_instance_id(const char *remoteproc_name) +{ + if (!strcmp(remoteproc_name, "modem")) + return QMI_TMD_INSTANCE_MODEM; + + if (!strcmp(remoteproc_name, "cdsp")) + return QMI_TMD_INSTANCE_CDSP; + + if (!strcmp(remoteproc_name, "cdsp1")) + return QMI_TMD_INSTANCE_CDSP1; + + return -ENODEV; +} + +/** + * qmi_tmd_init() - Initialize QMI TMD instance + * @dev: Device pointer + * @remoteproc_name: Remoteproc name (for example modem, cdsp) + * @tmd_names: Array of TMD names + * @num_tmds: Number of TMD names + * + * Return: Pointer to qmi_tmd_client on success, ERR_PTR on failure + */ +struct qmi_tmd_client *qmi_tmd_init(struct device *dev, + const char *remoteproc_name, + const char * const *tmd_names, + int num_tmds) +{ + struct qmi_tmd_client *qmi_tmd_cli; + int ret, i, instance_id; + + if (!dev || !remoteproc_name || !tmd_names || num_tmds <= 0) + return ERR_PTR(-EINVAL); + + instance_id = qmi_tmd_get_instance_id(remoteproc_name); + if (instance_id < 0) { + dev_err(dev, "Unsupported remoteproc name '%s' for TMD lookup\n", + remoteproc_name); + return ERR_PTR(instance_id); + } + + qmi_tmd_cli = devm_kzalloc(dev, struct_size(qmi_tmd_cli, tmds, num_tmds), GFP_KERNEL); + if (!qmi_tmd_cli) + return ERR_PTR(-ENOMEM); + + qmi_tmd_cli->dev = dev; + qmi_tmd_cli->num_tmds = num_tmds; + mutex_init(&qmi_tmd_cli->mutex); + INIT_WORK(&qmi_tmd_cli->svc_arrive_work, qmi_tmd_svc_arrive); + + /* Initialize TMD structures */ + for (i = 0; i < num_tmds; i++) { + qmi_tmd_cli->tmds[i].name = tmd_names[i]; + qmi_tmd_cli->tmds[i].qmi_tmd_cli = qmi_tmd_cli; + } + + ret = qmi_handle_init(&qmi_tmd_cli->handle, + TMD_GET_DEV_LIST_RESP_MAX_LEN, + &qmi_tmd_ops, NULL); + if (ret < 0) { + dev_err(dev, "QMI handle init failed: %d\n", ret); + return ERR_PTR(ret); + } + + ret = qmi_add_lookup(&qmi_tmd_cli->handle, QMI_SERVICE_ID_TMD, + QMI_TMD_SERVICE_VERS_V01, instance_id); + if (ret < 0) { + dev_err(dev, "QMI add lookup failed: %d\n", ret); + goto err_release_handle; + } + + return qmi_tmd_cli; + +err_release_handle: + qmi_handle_release(&qmi_tmd_cli->handle); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(qmi_tmd_init); + +/** + * qmi_tmd_exit() - Deinitialize QMI TMD instance + * @qmi_tmd_cli: QMI TMD client to deinitialize + */ +void qmi_tmd_exit(struct qmi_tmd_client *qmi_tmd_cli) +{ + if (!qmi_tmd_cli) + return; + + cancel_work_sync(&qmi_tmd_cli->svc_arrive_work); + qmi_handle_release(&qmi_tmd_cli->handle); + qmi_tmd_unregister(qmi_tmd_cli); + + scoped_guard(mutex, &qmi_tmd_cli->mutex) + qmi_tmd_cli->connection_active = false; +} +EXPORT_SYMBOL_GPL(qmi_tmd_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm QMI Thermal Mitigation library"); diff --git a/include/linux/soc/qcom/qmi.h b/include/linux/soc/qcom/qmi.h index b9dcb437a0be5..683d27cee4132 100644 --- a/include/linux/soc/qcom/qmi.h +++ b/include/linux/soc/qcom/qmi.h @@ -96,6 +96,7 @@ struct qmi_elem_info { * Enumerate the IDs of the QMI services */ #define QMI_SERVICE_ID_TEST 0x0f /* 15 */ +#define QMI_SERVICE_ID_TMD 0x18 /* 24 */ #define QMI_SERVICE_ID_SSCTL 0x2b /* 43 */ #define QMI_SERVICE_ID_IPA 0x31 /* 49 */ #define QMI_SERVICE_ID_SERVREG_LOC 0x40 /* 64 */ diff --git a/include/linux/soc/qcom/qmi_tmd.h b/include/linux/soc/qcom/qmi_tmd.h new file mode 100644 index 0000000000000..75373eb09d618 --- /dev/null +++ b/include/linux/soc/qcom/qmi_tmd.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * QMI Thermal Mitigation Device (TMD) library header. + */ + +#ifndef __QMI_TMD_H__ +#define __QMI_TMD_H__ + +struct device; +struct qmi_tmd_client; + +struct qmi_tmd_client *qmi_tmd_init(struct device *dev, + const char *remoteproc_name, + const char * const *tmd_names, + int num_tmds); + +void qmi_tmd_exit(struct qmi_tmd_client *tmd_cli); + +#endif /* __QMI_TMD_H__ */ + From 5de58f932f1ab3ddf66805fdcf9e7cd0ed51026d Mon Sep 17 00:00:00 2001 From: Gaurav Kohli Date: Tue, 9 Jun 2026 15:52:58 +0530 Subject: [PATCH 028/243] remoteproc: qcom: pas: register TMD thermal cooling devices Add support for Thermal Mitigation Devices (TMDs) to enable thermal throttling of remote processors through QMI. This enables the thermal framework to request mitigation when remote subsystems (modem, CDSP) contribute to thermal issues. Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20260609-qmi-tmd-v3-3-291a2ff4c634@oss.qualcomm.com Signed-off-by: Gaurav Kohli --- drivers/remoteproc/Kconfig | 1 + drivers/remoteproc/qcom_q6v5_pas.c | 61 +++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index c521c744e7dbe..4fb2163c06556 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -228,6 +228,7 @@ config QCOM_Q6V5_PAS select QCOM_PIL_INFO select QCOM_MDT_LOADER select QCOM_Q6V5_COMMON + select QCOM_QMI_TMD select QCOM_RPROC_COMMON select QCOM_SCM help diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 808e9609988d3..25f17ed612017 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -5,6 +5,7 @@ * Copyright (C) 2016 Linaro Ltd * Copyright (C) 2014 Sony Mobile Communications AB * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include @@ -117,6 +119,8 @@ struct qcom_pas { struct qcom_scm_pas_context *pas_ctx; struct qcom_scm_pas_context *dtb_pas_ctx; + + struct qmi_tmd_client *tmd_inst; }; static void qcom_pas_segment_dump(struct rproc *rproc, @@ -730,6 +734,49 @@ static void qcom_pas_unassign_memory_region(struct qcom_pas *pas) } } +static int qcom_pas_setup_tmd(struct qcom_pas *pas) +{ + struct device *dev = pas->dev; + struct device_node *np = dev->of_node; + const char **tmd_names; + int num_tmds, ret, i; + + if (!of_find_property(np, "tmd-names", NULL)) + return 0; + + /* Get the TMD names array */ + num_tmds = of_property_count_strings(np, "tmd-names"); + if (num_tmds <= 0) + return 0; + + tmd_names = devm_kcalloc(dev, num_tmds, sizeof(*tmd_names), GFP_KERNEL); + if (!tmd_names) + return -ENOMEM; + + for (i = 0; i < num_tmds; i++) { + ret = of_property_read_string_index(np, "tmd-names", i, + &tmd_names[i]); + if (ret) { + dev_err(dev, "Failed to read tmd-names[%d]: %d\n", i, ret); + return ret; + } + } + + pas->tmd_inst = qmi_tmd_init(dev, pas->info_name, tmd_names, num_tmds); + if (IS_ERR(pas->tmd_inst)) { + dev_err(dev, "Failed to register '%s'\n", pas->info_name); + + ret = PTR_ERR(pas->tmd_inst); + if (ret == -ENODEV) { + pas->tmd_inst = NULL; + return 0; + } + return ret; + } + + return 0; +} + static int qcom_pas_probe(struct platform_device *pdev) { const struct qcom_pas_data *desc; @@ -852,12 +899,21 @@ static int qcom_pas_probe(struct platform_device *pdev) pas->pas_ctx->use_tzmem = rproc->has_iommu; pas->dtb_pas_ctx->use_tzmem = rproc->has_iommu; - ret = rproc_add(rproc); + + ret = qcom_pas_setup_tmd(pas); if (ret) goto remove_ssr_sysmon; + ret = rproc_add(rproc); + if (ret) + goto remove_setup_tmd; + return 0; +remove_setup_tmd: + if (pas->tmd_inst) + qmi_tmd_exit(pas->tmd_inst); + remove_ssr_sysmon: qcom_remove_ssr_subdev(rproc, &pas->ssr_subdev); qcom_remove_sysmon_subdev(pas->sysmon); @@ -880,6 +936,9 @@ static void qcom_pas_remove(struct platform_device *pdev) { struct qcom_pas *pas = platform_get_drvdata(pdev); + if (pas->tmd_inst) + qmi_tmd_exit(pas->tmd_inst); + rproc_del(pas->rproc); qcom_q6v5_deinit(&pas->q6v5); From 66025bf1befff2a56ee290497032e28438e16b19 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Fri, 6 Feb 2026 02:44:05 +0530 Subject: [PATCH 029/243] FROMLIST: dt-bindings: hwmon: Add qcom,bcl-hwmon yaml bindings Add devicetree binding documentation for Qualcomm PMIC Battery Current Limiting (BCL) hardware monitor. The BCL hardware provides monitoring and alarm functionality for battery overcurrent and battery/system under voltage conditions. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260206-qcom-bcl-hwmon-v1-1-7b426f0b77a1@oss.qualcomm.com --- .../bindings/hwmon/qcom,bcl-hwmon.yaml | 128 ++++++++++++++++++ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 + 2 files changed, 132 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml diff --git a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml new file mode 100644 index 0000000000000..6ffadc4d28bcf --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/qcom,bcl-hwmon.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor + +maintainers: + - Manaf Meethalavalappu Pallikunhi + +description: | + SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and + alarm functionality for battery overcurrent and battery or system under + voltage conditions. It monitors battery voltage and current, and + can trigger interrupts when configurable thresholds are exceeded. + +properties: + compatible: + oneOf: + - description: v1 based BCL + items: + - enum: + - qcom,pm7250b-bcl + - qcom,pm8250b-bcl + - const: qcom,bcl-v1 + + - description: v2 based BCL + items: + - enum: + - qcom,pm8350b-bcl + - qcom,pm8350c-bcl + - const: qcom,bcl-v2 + + - description: v3 bmx based BCL + items: + - enum: + - qcom,pm8550b-bcl + - qcom,pm7550ba-bcl + - const: qcom,bcl-v3-bmx + + - description: v3 core based BCL + items: + - enum: + - qcom,pm8550-bc0l + - qcom,pm7550-bcl + - const: qcom,bcl-v3-core + + - description: v3 wb based BCL + items: + - enum: + - qcom,pmw5100-bcl + - const: qcom,bcl-v3-wb + + - description: v4 bmx based BCL + items: + - enum: + - qcom,pmih010-bcl + - const: qcom,bcl-v4-bmx + + - description: v4 bmx with different scale based BCL + items: + - enum: + - qcom,pmv010-bcl + - const: qcom,bcl-v4-pmv010 + + - description: v4 core based BCL + items: + - enum: + - qcom,pmh010-bcl + - const: qcom,bcl-v4-core + + - description: v4 wb based BCL + items: + - enum: + - qcom,pmw6100-bcl + - const: qcom,bcl-v4-wb + + reg: + maxItems: 1 + description: BCL base address in the SPMI PMIC register map + + interrupts: + minItems: 2 + maxItems: 2 + description: + BCL alarm interrupts for different threshold levels + + interrupt-names: + items: + - const: bcl-max-min + - const: bcl-critical + + overcurrent-thresholds-milliamp: + description: + Current thresholds in milliamperes for the two configurable current + alarm levels (max and critical). These values are used to override + default thresholds if a platform has different battery ocp specification. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 2 + +required: + - compatible + - reg + - interrupts + - interrupt-names + +unevaluatedProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1d00 { + compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; + reg = <0x1d00>; + interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "bcl-max-min", + "bcl-critical"; + overcurrent-thresholds-milliamp = <5500 6000>; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e57..0d4b44bc88c51 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -189,6 +189,10 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# + "^sensor@[0-9a-f]+$": + type: object + $ref: /schemas/hwmon/qcom,bcl-hwmon.yaml# + "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# From 3122567e1af4ab6cf0748c2e49d3e1ab1c37a2cf Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Fri, 6 Feb 2026 02:44:06 +0530 Subject: [PATCH 030/243] FROMLIST: hwmon: Add Qualcomm PMIC BCL hardware monitor driver Add support for Qualcomm PMIC Battery Current Limiting (BCL) hardware monitor driver. The BCL peripheral is present in Qualcomm PMICs and provides real-time monitoring and protection against battery overcurrent and under voltage conditions. The driver monitors: - Battery voltage with configurable low voltage thresholds - Battery current with configurable high current thresholds - Two limit alarm interrupts (max/min, critical) The driver integrates with the Linux hwmon subsystem and provides standard hwmon attributes for monitoring battery conditions. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260206-qcom-bcl-hwmon-v1-2-7b426f0b77a1@oss.qualcomm.com --- MAINTAINERS | 9 + drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/qcom-bcl-hwmon.c | 982 +++++++++++++++++++++++++++++++++ drivers/hwmon/qcom-bcl-hwmon.h | 311 +++++++++++ 5 files changed, 1312 insertions(+) create mode 100644 drivers/hwmon/qcom-bcl-hwmon.c create mode 100644 drivers/hwmon/qcom-bcl-hwmon.h diff --git a/MAINTAINERS b/MAINTAINERS index 829ffcb528f47..ad7b6703c7d93 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22100,6 +22100,15 @@ S: Maintained F: Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml F: drivers/net/wwan/qcom_bam_dmux.c +QUALCOMM BCL HARDWARE MONITOR DRIVER +M: Manaf Meethalavalappu Pallikunhi +L: linux-hwmon@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml +F: drivers/hwmon/qcom-bcl-hwmon.c +F: drivers/hwmon/qcom-bcl-hwmon.h + QUALCOMM BLUETOOTH DRIVER M: Bartosz Golaszewski L: linux-arm-msm@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5c2d3ff5fce82..251f5cc900b3b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1939,6 +1939,15 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. +config SENSORS_QCOM_BCL + tristate "Qualcomm BCL hardware monitoring" + help + Say yes here to enable support for Qualcomm battery over current + and under voltage alarms monitor. + + This driver can also be built as a module. If so, the module + will be called qcom-bcl-hwmon. + config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63effc0ab8d11..e0293c14a7cba 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -202,6 +202,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o +obj-$(CONFIG_SENSORS_QCOM_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c new file mode 100644 index 0000000000000..9894b1d14d124 --- /dev/null +++ b/drivers/hwmon/qcom-bcl-hwmon.c @@ -0,0 +1,982 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm pmic BCL hardware driver for battery overcurrent and + * battery or system under voltage monitor + * + * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom-bcl-hwmon.h" + +ADD_BCL_HWMON_ALARM_MAPS(in, min, lcrit); +ADD_BCL_HWMON_ALARM_MAPS(curr, max, crit); + +/* Interrupt names for each alarm level */ +static const char * const bcl_int_names[ALARM_MAX] = { + [LVL0] = "bcl-max-min", + [LVL1] = "bcl-critical", +}; + +static const char * const bcl_channel_label[CHANNEL_MAX] = { + "BCL Voltage", + "BCL Current", +}; + +/* Common Reg Fields */ +static const struct reg_field common_reg_fields[COMMON_FIELD_MAX] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), +}; + +/* BCL Version/Modes specific fields */ +static const struct reg_field bcl_v1_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_v2_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 1), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_v3_bmx_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_v3_wb_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 3), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_v3_core_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 5), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +static const struct reg_field bcl_v4_bmx_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), +}; + +static const struct reg_field bcl_v4_wb_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 4), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), +}; + +static const struct reg_field bcl_v4_core_reg_fields[] = { + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +/* V1 BMX and core */ +static const struct bcl_desc pm7250b_data = { + .reg_fields = bcl_v1_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 8, + .thresh_field_bits_size = 7, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {ADC, ADC}, + }, +}; + +/* V2 BMX and core */ +static const struct bcl_desc pm8350_data = { + .reg_fields = bcl_v2_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {ADC, ADC}, + }, +}; + +/* V3 BMX */ +static const struct bcl_desc pm8550b_data = { + .reg_fields = bcl_v3_bmx_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .max = 12000, + .default_scale_nu = 366220, + .thresh_type = {ADC, ADC}, + }, +}; + +/* V3 WB */ +static const struct bcl_desc pmw5100_data = { + .reg_fields = bcl_v3_wb_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .base = 800, + .max = 2000, + .step = 100, + .default_scale_nu = 61035, + .thresh_type = {ADC, INDEX}, + }, +}; + +/* V3 CORE */ +static const struct bcl_desc pm8550_data = { + .reg_fields = bcl_v3_core_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .thresh_type = {INDEX, INDEX}, + }, +}; + +/* V4 BMX */ +static const struct bcl_desc pmih010_data = { + .reg_fields = bcl_v4_bmx_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .max = 20000, + .default_scale_nu = 610370, + .thresh_type = {ADC, ADC}, + }, +}; + +/* V4 WB */ +static const struct bcl_desc pmw6100_data = { + .reg_fields = bcl_v4_wb_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 1500, + .max = 4000, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .base = 900, + .max = 3300, + .step = 150, + .default_scale_nu = 152586, + .thresh_type = {ADC, INDEX}, + }, +}; + +/* V4 CORE */ +static const struct bcl_desc pmh010_data = { + .reg_fields = bcl_v4_core_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 1500, + .max = 4000, + .step = 25, + .thresh_type = {INDEX, INDEX}, + }, +}; + +/* V4 BMX with different scale */ +static const struct bcl_desc pmv010_data = { + .reg_fields = bcl_v4_bmx_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .channel[IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {ADC, INDEX}, + }, + .channel[CURR] = { + .max = 12000, + .default_scale_nu = 366220, + .thresh_type = {ADC, ADC}, + }, +}; + +/** + * bcl_convert_raw_to_milliunit - Convert raw value to milli unit + * @desc: BCL device descriptor + * @raw_val: Raw ADC value from hardware + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: value in milli unit + */ +static unsigned int bcl_convert_raw_to_milliunit(const struct bcl_desc *desc, int raw_val, + enum bcl_channel_type type, u8 field_width) +{ + u32 def_scale = desc->channel[type].default_scale_nu; + u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; + u32 scaling_factor = def_scale * lsb_weight; + + return div_s64((s64)raw_val * scaling_factor, 1000000); +} + +/** + * bcl_convert_milliunit_to_raw - Convert milli unit to raw value + * @desc: BCL device descriptor + * @ma_val: threshold value in milli unit + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: Raw ADC value for hardware + */ +static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_desc *desc, int mval, + enum bcl_channel_type type, u8 field_width) +{ + u32 def_scale = desc->channel[type].default_scale_nu; + u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; + u32 scaling_factor = def_scale * lsb_weight; + + return div_s64((s64)mval * 1000000, scaling_factor); +} + +/** + * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index + * @desc: BCL device descriptor + * @val: in or curr value in milli unit + * @type: type of the channel, in or curr + * + * Converts a value in milli unit to an index for BCL that use indexed thresholds. + * + * Return: Voltage index value + */ +static unsigned int bcl_convert_milliunit_to_index(const struct bcl_desc *desc, int val, + enum bcl_channel_type type) +{ + return div_s64((s64)val - desc->channel[type].base, desc->channel[type].step); +} + +/** + * bcl_convert_index_to_milliunit - Convert in or curr index to milli unit + * @desc: BCL device descriptor + * @val: index value + * @type: type of the channel, in or curr + * + * Converts an index value to milli unit for BCL that use indexed thresholds. + * + * Return: Voltage value in millivolts + */ +static unsigned int bcl_convert_index_to_milliunit(const struct bcl_desc *desc, int val, + enum bcl_channel_type type) +{ + return desc->channel[type].base + val * desc->channel[type].step; +} + +static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + int thresh = clamp_val(value, desc->channel[IN].base, desc->channel[IN].max); + + if (desc->channel[IN].thresh_type[lvl] == ADC) + raw_val = bcl_convert_milliunit_to_raw(desc, thresh, IN, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(desc, thresh, IN); + + return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); +} + +static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + /* Clamp only to curr max */ + int thresh = clamp_val(value, value, desc->channel[CURR].max); + + if (desc->channel[CURR].thresh_type[lvl] == ADC) + raw_val = bcl_convert_milliunit_to_raw(desc, thresh, CURR, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(desc, thresh, CURR); + + return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); +} + +static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = bcl_read_field_value(bcl, F_IN_L0_THR + lvl, &raw_val); + if (ret) + return ret; + + if (desc->channel[IN].thresh_type[lvl] == ADC) + thresh = bcl_convert_raw_to_milliunit(desc, raw_val, IN, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(desc, raw_val, IN); + + *out = thresh; + + return 0; +} + +static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = bcl_read_field_value(bcl, F_CURR_H0_THR + lvl, &raw_val); + if (ret) + return ret; + + if (desc->channel[CURR].thresh_type[lvl] == ADC) + thresh = bcl_convert_raw_to_milliunit(desc, raw_val, CURR, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(desc, raw_val, CURR); + + *out = thresh; + + return 0; +} + +static int bcl_curr_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = bcl_read_field_value(bcl, F_CURR_INPUT, &raw_val); + if (ret) + return ret; + + /* + * The sensor sometime can read a value 0 if there are + * consecutive reads + */ + if (raw_val != 0) + bcl->last_curr_input = + bcl_convert_raw_to_milliunit(desc, raw_val, CURR, + desc->data_field_bits_size); + + *out = bcl->last_curr_input; + + return 0; +} + +static int bcl_in_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = bcl_read_field_value(bcl, F_IN_INPUT, &raw_val); + if (ret) + return ret; + + if (raw_val < GENMASK(desc->data_field_bits_size - 1, 0)) + bcl->last_in_input = + bcl_convert_raw_to_milliunit(desc, raw_val, IN, + desc->data_field_bits_size); + + *out = bcl->last_in_input; + + return 0; +} + +static int bcl_read_alarm_status(struct bcl_device *bcl, + enum bcl_limit_alarm lvl, long *status) +{ + int ret; + u32 raw_val = 0; + + ret = bcl_read_field_value(bcl, F_LVL0_ALARM + lvl, &raw_val); + if (ret) + return ret; + + *status = raw_val; + + return 0; +} + +static unsigned int bcl_get_version_major(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + bcl_read_field_value(bcl, F_V_MAJOR, &raw_val); + + return raw_val; +} + +static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + bcl_read_field_value(bcl, F_V_MINOR, &raw_val); + + return raw_val; +} + +static void bcl_hwmon_notify_event(struct bcl_device *bcl, enum bcl_limit_alarm alarm) +{ + if (bcl->in_mon_enabled) + hwmon_notify_event(bcl->hwmon_dev, hwmon_in, + in_lvl_to_attr_map[alarm], 0); + if (bcl->curr_mon_enabled) + hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, + curr_lvl_to_attr_map[alarm], 0); +} + +static void bcl_alarm_enable_poll(struct work_struct *work) +{ + struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, + alarm_poll_work.work); + struct bcl_device *bcl = alarm->device; + long status; + + guard(mutex)(&bcl->lock); + + if (bcl_read_alarm_status(bcl, alarm->type, &status)) + goto re_schedule; + + if (!status & !alarm->irq_enabled) { + bcl_enable_irq(alarm); + bcl_hwmon_notify_event(bcl, alarm->type); + return; + } + +re_schedule: + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); +} + +static irqreturn_t bcl_handle_alarm(int irq, void *data) +{ + struct bcl_alarm_data *alarm = data; + struct bcl_device *bcl = alarm->device; + long status; + + guard(mutex)(&bcl->lock); + + if (bcl_read_alarm_status(bcl, alarm->type, &status) || !status) + return IRQ_HANDLED; + + if (!bcl->hwmon_dev) + return IRQ_HANDLED; + + bcl_hwmon_notify_event(bcl, alarm->type); + + bcl_disable_irq(alarm); + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + + dev_dbg(bcl->dev, "Irq:%d triggered for bcl type:%d\n", + irq, alarm->type); + + return IRQ_HANDLED; +} + +static umode_t bcl_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct bcl_device *bcl = data; + + switch (type) { + case hwmon_in: + if (!bcl->in_mon_enabled) + return 0; + switch (attr) { + case hwmon_in_input: + return bcl->in_input_enabled ? 0444 : 0; + case hwmon_in_label: + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + return 0444; + case hwmon_in_min: + case hwmon_in_lcrit: + return 0644; + default: + return 0; + } + case hwmon_curr: + if (!bcl->curr_mon_enabled) + return 0; + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_label: + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + return 0444; + case hwmon_curr_max: + case hwmon_curr_crit: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + int ret = -EOPNOTSUPP; + + guard(mutex)(&bcl->lock); + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_min: + case hwmon_in_lcrit: + ret = bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); + break; + default: + ret = -EOPNOTSUPP; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_max: + case hwmon_curr_crit: + ret = bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); + break; + default: + ret = -EOPNOTSUPP; + } + break; + default: + break; + } + + return ret; +} + +static int bcl_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + int ret; + + guard(mutex)(&bcl->lock); + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + ret = bcl_in_input_read(bcl, value); + break; + case hwmon_in_min: + case hwmon_in_lcrit: + ret = bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); + break; + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + ret = bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); + break; + default: + ret = -EOPNOTSUPP; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + ret = bcl_curr_input_read(bcl, value); + break; + case hwmon_curr_max: + case hwmon_curr_crit: + ret = bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); + break; + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + ret = bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); + break; + default: + ret = -EOPNOTSUPP; + } + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static int bcl_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_in: + if (attr != hwmon_in_label) + break; + *str = bcl_channel_label[IN]; + return 0; + case hwmon_curr: + if (attr != hwmon_curr_label) + break; + *str = bcl_channel_label[CURR]; + return 0; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops bcl_hwmon_ops = { + .is_visible = bcl_hwmon_is_visible, + .read = bcl_hwmon_read, + .read_string = bcl_hwmon_read_string, + .write = bcl_hwmon_write, +}; + +static const struct hwmon_channel_info *bcl_hwmon_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_MIN | + HWMON_I_LCRIT | HWMON_I_MIN_ALARM | + HWMON_I_LCRIT_ALARM), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | + HWMON_C_CRIT | HWMON_C_MAX_ALARM | + HWMON_C_CRIT_ALARM), + NULL, +}; + +static const struct hwmon_chip_info bcl_hwmon_chip_info = { + .ops = &bcl_hwmon_ops, + .info = bcl_hwmon_info, +}; + +static int bcl_curr_thresh_update(struct bcl_device *bcl) +{ + int ret, i; + + if (!bcl->curr_thresholds[0]) + return 0; + + for (i = 0; i < ALARM_MAX; i++) { + ret = bcl_curr_thresh_write(bcl, bcl->curr_thresholds[i], i); + if (ret < 0) + return ret; + } + + return 0; +} + +static void bcl_hw_channel_mon_init(struct bcl_device *bcl) +{ + bcl->in_mon_enabled = bcl_in_monitor_enabled(bcl); + bcl->in_input_enabled = bcl_in_input_enabled(bcl); + bcl->curr_mon_enabled = bcl_curr_monitor_enabled(bcl); +} + +static int bcl_alarm_irq_init(struct platform_device *pdev, + struct bcl_device *bcl) +{ + int ret = 0, irq_num = 0, i = 0; + struct bcl_alarm_data *alarm; + + for (i = LVL0; i < ALARM_MAX; i++) { + alarm = &bcl->bcl_alarms[i]; + alarm->type = i; + alarm->device = bcl; + + ret = devm_delayed_work_autocancel(bcl->dev, &alarm->alarm_poll_work, + bcl_alarm_enable_poll); + if (ret) + return ret; + + irq_num = platform_get_irq_byname(pdev, bcl_int_names[i]); + if (irq_num <= 0) + continue; + + ret = devm_request_threaded_irq(&pdev->dev, irq_num, NULL, + bcl_handle_alarm, IRQF_ONESHOT, + bcl_int_names[i], alarm); + if (ret) { + dev_err(&pdev->dev, "Error requesting irq(%s).err:%d\n", + bcl_int_names[i], ret); + return ret; + } + alarm->irq = irq_num; + enable_irq_wake(alarm->irq); + alarm->irq_enabled = true; + } + + return 0; +} + +static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, + const struct bcl_desc *data) +{ + int i; + struct reg_field fields[F_MAX_FIELDS]; + + BUILD_BUG_ON(ARRAY_SIZE(common_reg_fields) != COMMON_FIELD_MAX); + + for (i = 0; i < data->num_reg_fields; i++) { + if (i < COMMON_FIELD_MAX) + fields[i] = common_reg_fields[i]; + else + fields[i] = data->reg_fields[i]; + + /* Need to adjust BCL base from regmap dynamically */ + fields[i].reg += bcl->base; + } + + return devm_regmap_field_bulk_alloc(dev, bcl->regmap, bcl->fields, + fields, data->num_reg_fields); +} + +static int bcl_get_device_property_data(struct platform_device *pdev, + struct bcl_device *bcl) +{ + struct device *dev = &pdev->dev; + int ret; + u32 reg; + + ret = device_property_read_u32(dev, "reg", ®); + if (ret < 0) + return ret; + + bcl->base = reg; + + device_property_read_u32_array(dev, "overcurrent-thresholds-milliamp", + bcl->curr_thresholds, 2); + return 0; +} + +static int bcl_probe(struct platform_device *pdev) +{ + struct bcl_device *bcl; + int ret; + + bcl = devm_kzalloc(&pdev->dev, sizeof(*bcl), GFP_KERNEL); + if (!bcl) + return -ENOMEM; + + bcl->dev = &pdev->dev; + bcl->desc = device_get_match_data(&pdev->dev); + if (!bcl->desc) + return -EINVAL; + + ret = devm_mutex_init(bcl->dev, &bcl->lock); + if (ret) + return ret; + + bcl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bcl->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + ret = bcl_get_device_property_data(pdev, bcl); + if (ret < 0) + return ret; + + ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to allocate regmap fields, err:%d\n", ret); + return ret; + } + + if (!bcl_hw_is_enabled(bcl)) + return -ENODEV; + + ret = bcl_curr_thresh_update(bcl); + if (ret < 0) + return ret; + + ret = bcl_alarm_irq_init(pdev, bcl); + if (ret < 0) + return ret; + + bcl_hw_channel_mon_init(bcl); + + dev_set_drvdata(&pdev->dev, bcl); + + bcl->hwmon_name = devm_hwmon_sanitize_name(&pdev->dev, + dev_name(bcl->dev)); + if (IS_ERR(bcl->hwmon_name)) { + dev_err(&pdev->dev, "Failed to sanitize hwmon name\n"); + return PTR_ERR(bcl->hwmon_name); + } + + bcl->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + bcl->hwmon_name, + bcl, + &bcl_hwmon_chip_info, + NULL); + if (IS_ERR(bcl->hwmon_dev)) { + dev_err(&pdev->dev, "Failed to register hwmon device: %ld\n", + PTR_ERR(bcl->hwmon_dev)); + return PTR_ERR(bcl->hwmon_dev); + } + + dev_dbg(&pdev->dev, "BCL hwmon device with version: %u.%u registered\n", + bcl_get_version_major(bcl), bcl_get_version_minor(bcl)); + + return 0; +} + +static const struct of_device_id bcl_match[] = { + { + .compatible = "qcom,bcl-v1", + .data = &pm7250b_data, + }, { + .compatible = "qcom,bcl-v2", + .data = &pm8350_data, + }, { + .compatible = "qcom,bcl-v3-bmx", + .data = &pm8550b_data, + }, { + .compatible = "qcom,bcl-v3-wb", + .data = &pmw5100_data, + }, { + .compatible = "qcom,bcl-v3-core", + .data = &pm8550_data, + }, { + .compatible = "qcom,bcl-v4-bmx", + .data = &pmih010_data, + }, { + .compatible = "qcom,bcl-v4-wb", + .data = &pmw6100_data, + }, { + .compatible = "qcom,bcl-v4-core", + .data = &pmh010_data, + }, { + .compatible = "qcom,bcl-v4-pmv010", + .data = &pmv010_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, bcl_match); + +static struct platform_driver bcl_driver = { + .probe = bcl_probe, + .driver = { + .name = BCL_DRIVER_NAME, + .of_match_table = bcl_match, + }, +}; + +MODULE_AUTHOR("Manaf Meethalavalappu Pallikunhi "); +MODULE_DESCRIPTION("QCOM BCL HWMON driver"); +module_platform_driver(bcl_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/qcom-bcl-hwmon.h b/drivers/hwmon/qcom-bcl-hwmon.h new file mode 100644 index 0000000000000..28a7154d9486a --- /dev/null +++ b/drivers/hwmon/qcom-bcl-hwmon.h @@ -0,0 +1,311 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QCOM_BCL_HWMON_H__ +#define __QCOM_BCL_HWMON_H__ + +#define BCL_DRIVER_NAME "qcom-bcl-hwmon" + +/* BCL common regmap offset */ +#define REVISION1 0x0 +#define REVISION2 0x1 +#define STATUS 0x8 +#define INT_RT_STS 0x10 +#define EN_CTL1 0x46 + +/* BCL GEN1 regmap offsets */ +#define MODE_CTL1 0x41 +#define VADC_L0_THR 0x48 +#define VCMP_L1_THR 0x49 +#define IADC_H0_THR 0x4b +#define IADC_H1_THR 0x4c +#define VADC_CONV_REQ 0x72 +#define IADC_CONV_REQ 0x82 +#define VADC_DATA1 0x76 +#define IADC_DATA1 0x86 + +/* BCL GEN3 regmap offsets */ +#define VCMP_CTL 0x44 +#define VCMP_L0_THR 0x47 +#define PARAM_1 0x0e +#define IADC_H1_THR_GEN3 0x4d + +#define BCL_IN_INC_MV 25 +#define BCL_ALARM_POLLING_MS 50 + +/** + * enum bcl_limit_alarm - BCL alarm threshold levels + * @LVL0: Level 0 alarm threshold (mapped to in_min_alarm or curr_max_alarm) + * @LVL1: Level 1 alarm threshold (mapped to in_lcrit_alarm or curr_crit_alarm) + * @ALARM_MAX: sentinel value + * + * Defines the three threshold levels for BCL monitoring. Each level corresponds + * to different severity of in or curr conditions. + */ +enum bcl_limit_alarm { + LVL0, + LVL1, + + ALARM_MAX, +}; + +/** + * enum bcl_channel_type - BCL supported sensor channel type + * @IN: in (voltage) channel + * @CURR: curr (current) channel + * @CHANNEL_MAX: sentinel value + * + * Defines the supported channel types for bcl. + */ +enum bcl_channel_type { + IN, + CURR, + + CHANNEL_MAX, +}; + +/** + * enum bcl_thresh_type - voltage or current threshold representation type + * @ADC: Raw ADC value representation + * @INDEX: Index-based voltage or current representation + * + * Specifies how voltage or current thresholds are stored and interpreted in + * registers. Some PMICs use raw ADC values while others use indexed values. + */ +enum bcl_thresh_type { + ADC, + INDEX, +}; + +/** + * enum bcl_fields - BCL register field identifiers + * @F_V_MAJOR: Major revision info field + * @F_V_MINOR: Minor revision info field + * @F_CTL_EN: Monitor enable control field + * @F_LVL0_ALARM: Level 0 alarm status field + * @F_LVL1_ALARM: Level 1 alarm status field + * @COMMON_FIELD_MAX: sentinel value for common fields + * @F_IN_MON_EN: voltage monitor enable control field + * @F_IN_L0_THR: voltage level 0 threshold field + * @F_IN_L1_THR: voltage level 1 threshold field + * @F_IN_INPUT_EN: voltage input enable control field + * @F_IN_INPUT: voltage input data field + * @F_CURR_MON_EN: current monitor enable control field + * @F_CURR_H0_THR: current level 0 threshold field + * @F_CURR_H1_THR: current level 1 threshold field + * @F_CURR_INPUT: current input data field + * @F_MAX_FIELDS: sentinel value + * + * Enumeration of all register fields used by the BCL driver for accessing + * registers through regmap fields. + */ +enum bcl_fields { + F_V_MAJOR, + F_V_MINOR, + + F_CTL_EN, + + /* common alarm for in and curr channel */ + F_LVL0_ALARM, + F_LVL1_ALARM, + + COMMON_FIELD_MAX, + + F_IN_MON_EN = COMMON_FIELD_MAX, + F_IN_L0_THR, + F_IN_L1_THR, + + F_IN_INPUT_EN, + F_IN_INPUT, + + F_CURR_MON_EN, + F_CURR_H0_THR, + F_CURR_H1_THR, + + F_CURR_INPUT, + + F_MAX_FIELDS +}; + +#define ADD_BCL_HWMON_ALARM_MAPS(_type, lvl0_attr, lvl1_attr) \ + \ +static const u8 _type##_attr_to_lvl_map[] = { \ + [hwmon_##_type##_##lvl0_attr] = LVL0, \ + [hwmon_##_type##_##lvl1_attr] = LVL1, \ + [hwmon_##_type##_##lvl0_attr##_alarm] = LVL0, \ + [hwmon_##_type##_##lvl1_attr##_alarm] = LVL1, \ +}; \ + \ +static const u8 _type##_lvl_to_attr_map[ALARM_MAX] = { \ + [LVL0] = hwmon_##_type##_##lvl0_attr##_alarm, \ + [LVL1] = hwmon_##_type##_##lvl1_attr##_alarm, \ +} + +/** + * struct bcl_channel_cfg - BCL channel related configuration + * @default_scale_nu: Default scaling factor in nano unit + * @base: Base threshold value in milli unit + * @max: Maximum threshold value in milli unit + * @step: step increment value between two indexed threshold value + * @thresh_type: Array specifying threshold representation type for each alarm level + * + * Contains hardware-specific configuration and scaling parameters for different + * channel(voltage and current).. + */ + +struct bcl_channel_cfg { + u32 default_scale_nu; + u32 base; + u32 max; + u32 step; + u8 thresh_type[ALARM_MAX]; +}; + +/** + * struct bcl_desc - BCL device descriptor + * @reg_fields: Array of register field definitions for this device variant + * @channel: Each channel specific(voltage or current) configuration + * @num_reg_fields: Number of register field definitions for this device variant + * @data_field_bits_size: data read register bit size + * @thresh_field_bits_size: lsb bit size those are not included in threshold register + * + * Contains hardware-specific configuration and scaling parameters for different + * BCL variants. Each PMIC model may have different register layouts and + * conversion factors. + */ + +struct bcl_desc { + const struct reg_field *reg_fields; + struct bcl_channel_cfg channel[CHANNEL_MAX]; + u8 num_reg_fields; + u8 data_field_bits_size; + u8 thresh_field_bits_size; +}; + +struct bcl_device; + +/** + * struct bcl_alarm_data - BCL alarm interrupt data + * @irq: IRQ number assigned to this alarm + * @irq_enabled: Flag indicating if IRQ is enabled + * @type: Alarm level type (LVL0, or LVL1) + * @device: Pointer to parent BCL device structure + * @alarm_poll_work: delayed_work to poll alarm status + * + * Stores interrupt-related information for each alarm threshold level. + * Used by the IRQ handler to identify which alarm triggered. + */ +struct bcl_alarm_data { + int irq; + bool irq_enabled; + enum bcl_limit_alarm type; + struct bcl_device *device; + struct delayed_work alarm_poll_work; +}; + +/** + * struct bcl_device - Main BCL device structure + * @dev: Pointer to device structure + * @regmap: Regmap for accessing PMIC registers + * @fields: Array of regmap fields for register access + * @bcl_alarms: Array of alarm data structures for each threshold level + * @lock: Mutex for protecting concurrent hardware access + * @in_mon_enabled: Flag indicating if voltage monitoring is enabled + * @curr_mon_enabled: Flag indicating if current monitoring is enabled + * @curr_thresholds: Current threshold values in milliamps from dt-binding(LVL0 and LVL1) + * @base: the BCL regbase offset from regmap + * @in_input_enabled: Flag indicating if voltage input reading is enabled + * @last_in_input: Last valid voltage input reading in millivolts + * @last_curr_input: Last valid current input reading in milliamps + * @desc: Pointer to device descriptor with hardware-specific parameters + * @hwmon_dev: Pointer to registered hwmon device + * @hwmon_name: Sanitized name for hwmon device + * + * Main driver structure containing all state and configuration for a BCL + * monitoring instance. Manages voltage and current monitoring, thresholds, + * and alarm handling. + */ +struct bcl_device { + struct device *dev; + struct regmap *regmap; + u16 base; + struct regmap_field *fields[F_MAX_FIELDS]; + struct bcl_alarm_data bcl_alarms[ALARM_MAX]; + struct mutex lock; + u32 curr_thresholds[ALARM_MAX]; + u32 last_in_input; + u32 last_curr_input; + bool in_mon_enabled; + bool curr_mon_enabled; + bool in_input_enabled; + const struct bcl_desc *desc; + struct device *hwmon_dev; + char *hwmon_name; +}; + +/** + * bcl_read_field_value - Read alarm status for a given level + * @bcl: BCL device structure + * @id: Index in bcl->fields[] + * @val: Pointer to store val + * + * Return: 0 on success or regmap error code + */ +static inline int bcl_read_field_value(const struct bcl_device *bcl, enum bcl_fields id, u32 *val) +{ + return regmap_field_read(bcl->fields[id], val); +} + +/** + * bcl_field_enabled - Generic helper to check if a regmap field is enabled + * @bcl: BCL device structure + * @field: Index in bcl->fields[] + * + * Return: true if field is non-zero, false otherwise + */ +static inline bool bcl_field_enabled(const struct bcl_device *bcl, enum bcl_fields id) +{ + int ret; + u32 val = 0; + + ret = regmap_field_read(bcl->fields[id], &val); + if (ret) + return false; + + return !!val; +} + +#define bcl_in_input_enabled(bcl) bcl_field_enabled(bcl, F_IN_INPUT_EN) +#define bcl_curr_monitor_enabled(bcl) bcl_field_enabled(bcl, F_CURR_MON_EN) +#define bcl_in_monitor_enabled(bcl) bcl_field_enabled(bcl, F_IN_MON_EN) +#define bcl_hw_is_enabled(bcl) bcl_field_enabled(bcl, F_CTL_EN) + +/** + * bcl_enable_irq - Generic helper to enable alarm irq + * @alarm: BCL level alarm data + */ +static inline void bcl_enable_irq(struct bcl_alarm_data *alarm) +{ + if (alarm->irq_enabled) + return; + alarm->irq_enabled = true; + enable_irq(alarm->irq); + enable_irq_wake(alarm->irq); +} + +/** + * bcl_disable_irq - Generic helper to disable alarm irq + * @alarm: BCL level alarm data + */ +static inline void bcl_disable_irq(struct bcl_alarm_data *alarm) +{ + if (!alarm->irq_enabled) + return; + alarm->irq_enabled = false; + disable_irq_nosync(alarm->irq); + disable_irq_wake(alarm->irq); +} + +#endif /* __QCOM_BCL_HWMON_H__ */ From ffd0ae636da733dd3a19d12ac9f6209d071e7b16 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Sat, 20 Jun 2026 02:09:08 +0530 Subject: [PATCH 031/243] dt-bindings: power: limits: Describe Qualcomm SPEL hardware The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various domains. Add a DeviceTree binding to describe the SPEL block on Qualcomm's SoC. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260620-qcom_spel_driver_upstream-v2-1-a3ee6837c18f@oss.qualcomm.com --- .../bindings/power/limits/qcom,spel.yaml | 47 +++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 53 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/limits/qcom,spel.yaml diff --git a/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml b/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml new file mode 100644 index 0000000000000..4c6e6cbfbfe40 --- /dev/null +++ b/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/limits/qcom,spel.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SoC Power and Electrical Limits (SPEL) + +maintainers: + - Manaf Meethalavalappu Pallikunhi + +description: + The Qualcomm SPEL (SoC Power and Electrical Limits) provides hardware-based + power monitoring and limiting capabilities for various power domains in + Qualcomm SoCs. + +properties: + compatible: + const: qcom,glymur-spel + + reg: + maxItems: 3 + + reg-names: + items: + - const: config + - const: constraints + - const: nodes + +required: + - compatible + - reg + - reg-names + +additionalProperties: false + +examples: + - | + power-limits@ef3b000 { + compatible = "qcom,glymur-spel"; + reg = <0x0ef3b000 0x1000>, + <0x0ef3d000 0x1000>, + <0x0ef3e000 0x1000>; + reg-names = "config", + "constraints", + "nodes"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index ad7b6703c7d93..772d29ee46e47 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22331,6 +22331,12 @@ S: Maintained F: Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml F: drivers/power/supply/qcom_smbx.c +QUALCOMM SPEL POWERCAP DRIVER +M: Manaf Meethalavalappu Pallikunhi +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/power/limits/qcom,spel.yaml + QUALCOMM PPE DRIVER M: Luo Jie L: netdev@vger.kernel.org From d36b3ee3b5ce84165d974c7141b15c3b6a283691 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Sat, 20 Jun 2026 02:09:09 +0530 Subject: [PATCH 032/243] powercap: qcom: Add SPEL powercap driver The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various power domains including System, SoC, CPU clusters, GPU, and various other subsystems. The driver integrates with the Linux powercap framework, exposing SPEL capabilities through powercap sysfs interfaces. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260620-qcom_spel_driver_upstream-v2-2-a3ee6837c18f@oss.qualcomm.com --- MAINTAINERS | 1 + drivers/powercap/Kconfig | 13 + drivers/powercap/Makefile | 1 + drivers/powercap/qcom_spel.c | 776 +++++++++++++++++++++++++++++++++++ 4 files changed, 791 insertions(+) create mode 100644 drivers/powercap/qcom_spel.c diff --git a/MAINTAINERS b/MAINTAINERS index 772d29ee46e47..10b40310aef45 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22336,6 +22336,7 @@ M: Manaf Meethalavalappu Pallikunhi L: linux-arm-msm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/power/limits/qcom,spel.yaml +F: drivers/powercap/qcom_spel.c QUALCOMM PPE DRIVER M: Luo Jie diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig index 03c4c796d9931..e3a47c6534999 100644 --- a/drivers/powercap/Kconfig +++ b/drivers/powercap/Kconfig @@ -93,4 +93,17 @@ config DTPM_DEVFREQ help This enables support for device power limitation based on energy model. + +config QCOM_SPEL + tristate "Qualcomm SPEL Powercap driver" + depends on ARM64 || COMPILE_TEST + help + This enables support for the Qualcomm SoC Power and Electrical + Limits (SPEL) hardware, which allows power limits to be + enforced and monitored on Qualcomm SoCs. + + SPEL provides energy monitoring and power capping for multiple + domains including system, SoC, CPU clusters, GPU, and various + other subsystems. + endif diff --git a/drivers/powercap/Makefile b/drivers/powercap/Makefile index 5ab0dce565b9a..8235fb9d3df60 100644 --- a/drivers/powercap/Makefile +++ b/drivers/powercap/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o obj-$(CONFIG_INTEL_RAPL_TPMI) += intel_rapl_tpmi.o obj-$(CONFIG_IDLE_INJECT) += idle_inject.o obj-$(CONFIG_ARM_SCMI_POWERCAP) += arm_scmi_powercap.o +obj-$(CONFIG_QCOM_SPEL) += qcom_spel.o diff --git a/drivers/powercap/qcom_spel.c b/drivers/powercap/qcom_spel.c new file mode 100644 index 0000000000000..4dd91cf36ccca --- /dev/null +++ b/drivers/powercap/qcom_spel.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm SPEL (SoC Power and Electrical Limits) Driver + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPEL register bitmasks */ +#define ENERGY_STATUS_MASK GENMASK(31, 0) + +#define POWER_LIMIT_MASK GENMASK(14, 0) +#define POWER_LIMIT_ENABLE BIT(31) + +#define TIME_WINDOW_MASK_L GENMASK(14, 0) +#define TIME_WINDOW_MASK_H GENMASK(22, 16) +#define TIME_WINDOW_MAX ((FIELD_MAX(TIME_WINDOW_MASK_H) << 15) | \ + FIELD_MAX(TIME_WINDOW_MASK_L)) + +#define ENERGY_UNIT_MASK GENMASK(19, 16) +#define TIME_UNIT_MASK GENMASK(11, 8) +#define POWER_UNIT_MASK GENMASK(2, 0) + +#define LIMITS_CAPABILITY_OFFSET 0x20 +#define ENERGY_RPT_UNIT_OFFSET 0x04 + +#define ENERGY_UNIT_SCALE 1000 + +#define SPEL_DOMAIN_NAME_LENGTH 16 + +/* Domain types */ +enum spel_domain_type { + SPEL_DOMAIN_SYS, + SPEL_DOMAIN_SOC, + SPEL_DOMAIN_CL0, + SPEL_DOMAIN_CL1, + SPEL_DOMAIN_CL2, + SPEL_DOMAIN_IGPU, + SPEL_DOMAIN_DGPU, + SPEL_DOMAIN_NSP, + SPEL_DOMAIN_MMCX, + SPEL_DOMAIN_INFRA, + SPEL_DOMAIN_DRAM, + SPEL_DOMAIN_MDM, + SPEL_DOMAIN_WLAN, + SPEL_DOMAIN_USB1, + SPEL_DOMAIN_USB2, + SPEL_DOMAIN_USB3, + SPEL_DOMAIN_MAX, +}; + +/* Power limit IDs */ +enum spel_power_limit_id { + POWER_LIMIT1, + POWER_LIMIT2, + POWER_LIMIT3, + POWER_LIMIT4, + POWER_LIMITS_MAX, +}; + +/* Unit types for conversion */ +enum unit_type { + POWER_UNIT, + ENERGY_UNIT, + TIME_UNIT, +}; + +/* Power limit operation types */ +enum pl_ops_type { + PL_LIMIT, + PL_TIME_WINDOW, +}; + +static const char * const pl_names[] = { + [POWER_LIMIT1] = "pl1", + [POWER_LIMIT2] = "pl2", + [POWER_LIMIT3] = "pl3", + [POWER_LIMIT4] = "pl4", +}; + +/** + * struct spel_domain_info - Domain configuration + * @name: Domain name + * @offset: Register offset in node base + */ +struct spel_domain_info { + const char *name; + u32 offset; +}; + +/* Domain configuration */ +static const struct spel_domain_info domain_info[SPEL_DOMAIN_MAX] = { + [SPEL_DOMAIN_SYS] = { "sys", 0x40 }, + [SPEL_DOMAIN_SOC] = { "soc", 0x00 }, + [SPEL_DOMAIN_CL0] = { "cl0", 0x5c }, + [SPEL_DOMAIN_CL1] = { "cl1", 0x60 }, + [SPEL_DOMAIN_CL2] = { "cl2", 0x64 }, + [SPEL_DOMAIN_IGPU] = { "igpu", 0x08 }, + [SPEL_DOMAIN_DGPU] = { "dgpu", 0x44 }, + [SPEL_DOMAIN_NSP] = { "nsp", 0x0c }, + [SPEL_DOMAIN_MMCX] = { "mmcx", 0x10 }, + [SPEL_DOMAIN_INFRA] = { "infra", 0x18 }, + [SPEL_DOMAIN_DRAM] = { "dram", 0x1c }, + [SPEL_DOMAIN_MDM] = { "mdm", 0x48 }, + [SPEL_DOMAIN_WLAN] = { "wlan", 0x4c }, + [SPEL_DOMAIN_USB1] = { "usb1", 0x50 }, + [SPEL_DOMAIN_USB2] = { "usb2", 0x54 }, + [SPEL_DOMAIN_USB3] = { "usb3", 0x58 }, +}; + +/** + * struct spel_constraint_info - Power limit constraint information + * @limit_offset: Register offset for power limit value + * @time_window_offset: Register offset for time window + * @supported_mask: Bit mask in capability register + * @domain_id: Domain this constraint applies to + * @pl_id: Power limit ID (PL1, PL2, etc.) + */ +struct spel_constraint_info { + u32 limit_offset; + u32 time_window_offset; + u32 supported_mask; + enum spel_domain_type domain_id; + int pl_id; +}; + +/* Constraint configuration */ +static const struct spel_constraint_info constraints[] = { + /* SYS domain constraints */ + { 0x10, 0x70, BIT(0), SPEL_DOMAIN_SYS, POWER_LIMIT1 }, + { 0x14, 0x74, BIT(1), SPEL_DOMAIN_SYS, POWER_LIMIT2 }, + { 0x18, 0x78, BIT(2), SPEL_DOMAIN_SYS, POWER_LIMIT3 }, + { 0x1c, 0x7c, BIT(3), SPEL_DOMAIN_SYS, POWER_LIMIT4 }, + /* SoC domain constraints */ + { 0x00, 0x60, BIT(4), SPEL_DOMAIN_SOC, POWER_LIMIT1 }, + { 0x04, 0x64, BIT(5), SPEL_DOMAIN_SOC, POWER_LIMIT2 }, + { 0x08, 0x68, BIT(6), SPEL_DOMAIN_SOC, POWER_LIMIT3 }, + { 0x0c, 0x6c, BIT(7), SPEL_DOMAIN_SOC, POWER_LIMIT4 }, +}; + +/** + * struct spel_domain - SPEL power domain + * @power_zone: Powercap zone + * @lock: Mutex protecting register access + * @sp: Parent sys domain + * @status_reg: Energy counter register + * @name: Domain name + * @id: Domain type ID + */ +struct spel_domain { + struct powercap_zone power_zone; + struct mutex lock; /* Protects register read/write operations */ + void *sp; + void __iomem *status_reg; + char name[SPEL_DOMAIN_NAME_LENGTH]; + enum spel_domain_type id; +}; + +/** + * struct spel_system - SPEL system + * @domains: Array of domains + * @power_zone: Parent powercap zone + * @node_base: Base address for node registers + * @constraint_base: Base address for constraint registers + * @config_base: Base address for config registers + * @control_type: Powercap control type + * @dev: Device pointer for logging + * @limits: Supported power limits per domain + * @power_unit: Power unit in microWatts (common for all domains) + * @energy_unit: Energy unit in nanoJoules (common for all domains) + * @time_unit: Time unit in microseconds (common for all domains) + */ +struct spel_system { + struct spel_domain *domains; + struct powercap_zone *power_zone; + void __iomem *node_base; + void __iomem *constraint_base; + void __iomem *config_base; + struct powercap_control_type *control_type; + struct device *dev; + int limits[SPEL_DOMAIN_MAX]; + unsigned int power_unit; + unsigned int energy_unit; + unsigned int time_unit; +}; + +#define power_zone_to_spel_domain(_zone) \ + container_of(_zone, struct spel_domain, power_zone) + +static bool is_pl_valid(struct spel_domain *sd, int pl) +{ + struct spel_system *sp = sd->sp; + + return !!(sp->limits[sd->id] & BIT(pl)); +} + +static int get_pl_ops_offset(struct spel_domain *sd, int pl, enum pl_ops_type pl_op) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(constraints); i++) { + const struct spel_constraint_info *ci = &constraints[i]; + + if (ci->domain_id == sd->id && ci->pl_id == pl) { + switch (pl_op) { + case PL_LIMIT: + return ci->limit_offset; + case PL_TIME_WINDOW: + return ci->time_window_offset; + default: + return -EOPNOTSUPP; + } + } + } + + return -EOPNOTSUPP; +} + +static u64 spel_unit_xlate(struct spel_domain *sd, enum unit_type type, + u64 value, int to_raw) +{ + struct spel_system *sp = sd->sp; + u64 units, scale; + + switch (type) { + case POWER_UNIT: + units = sp->power_unit; + scale = 1; + break; + case ENERGY_UNIT: + units = sp->energy_unit; + scale = ENERGY_UNIT_SCALE; + break; + case TIME_UNIT: + units = sp->time_unit; + scale = 1; + break; + default: + return value; + } + + if (to_raw) + return DIV_ROUND_CLOSEST_ULL(value * scale, units); + + value *= units; + return div64_u64(value, scale); +} + +static int spel_read_pl_data(struct spel_domain *sd, int pl, + enum pl_ops_type pl_op, bool xlate, u64 *data) +{ + struct spel_system *sp = sd->sp; + void __iomem *reg_addr; + u64 value; + int offset; + + if (!is_pl_valid(sd, pl)) + return -EINVAL; + + offset = get_pl_ops_offset(sd, pl, pl_op); + if (offset < 0) + return offset; + + guard(mutex)(&sd->lock); + + reg_addr = sp->constraint_base + offset; + value = readl(reg_addr); + + switch (pl_op) { + case PL_LIMIT: + value = FIELD_GET(POWER_LIMIT_MASK, value); + if (xlate) + *data = spel_unit_xlate(sd, POWER_UNIT, value, 0); + else + *data = value; + break; + case PL_TIME_WINDOW: + /* Decode time window: bits [22:16] are upper 7 bits, [14:0] are lower 15 bits */ + value = (FIELD_GET(TIME_WINDOW_MASK_H, value) << 15) | + FIELD_GET(TIME_WINDOW_MASK_L, value); + if (xlate) + *data = spel_unit_xlate(sd, TIME_UNIT, value, 0); + else + *data = value; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int spel_write_pl_data(struct spel_domain *sd, int pl, + enum pl_ops_type pl_op, unsigned long long value) +{ + struct spel_system *sp = sd->sp; + void __iomem *reg_addr; + u64 reg_val, new_val; + int offset; + + if (!is_pl_valid(sd, pl)) + return -EINVAL; + + offset = get_pl_ops_offset(sd, pl, pl_op); + if (offset < 0) + return offset; + + guard(mutex)(&sd->lock); + + reg_addr = sp->constraint_base + offset; + reg_val = readl(reg_addr); + + switch (pl_op) { + case PL_LIMIT: + new_val = spel_unit_xlate(sd, POWER_UNIT, value, 1); + if (new_val > FIELD_MAX(POWER_LIMIT_MASK)) + return -EINVAL; + reg_val = (reg_val & ~POWER_LIMIT_MASK) | FIELD_PREP(POWER_LIMIT_MASK, new_val); + + /* + * Enable/Disable PL based on the value: + * - If value is 0, disable the PL (clear enable bit) + * - If value is non-zero, enable the PL (set enable bit) + */ + if (new_val == 0) + reg_val &= ~POWER_LIMIT_ENABLE; + else + reg_val |= POWER_LIMIT_ENABLE; + + writel(reg_val, reg_addr); + return 0; + + case PL_TIME_WINDOW: + /* + * Encode time window: upper 7 bits to [22:16], lower 15 bits to [14:0] + */ + new_val = spel_unit_xlate(sd, TIME_UNIT, value, 1); + if (new_val > TIME_WINDOW_MAX) + return -EINVAL; + /* Read-modify-write to preserve other bits */ + reg_val = (reg_val & ~(TIME_WINDOW_MASK_H | TIME_WINDOW_MASK_L)) | + FIELD_PREP(TIME_WINDOW_MASK_H, new_val >> 15) | + FIELD_PREP(TIME_WINDOW_MASK_L, new_val); + writel(reg_val, reg_addr); + + /* + * Time window register update doesn't trigger firmware interrupt. + * Write to the PL register with current value to trigger the interrupt. + */ + offset = get_pl_ops_offset(sd, pl, PL_LIMIT); + if (offset >= 0) { + reg_addr = sp->constraint_base + offset; + reg_val = readl(reg_addr); + writel(reg_val, reg_addr); + } + return 0; + + default: + return -EINVAL; + } +} + +static int spel_get_energy_counter(struct powercap_zone *power_zone, u64 *energy_raw) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 value; + + value = readl(sd->status_reg); + + *energy_raw = spel_unit_xlate(sd, ENERGY_UNIT, value, 0); + + return 0; +} + +static int spel_get_max_energy_counter(struct powercap_zone *pcd_dev, u64 *energy) +{ + struct spel_domain *sd = power_zone_to_spel_domain(pcd_dev); + + *energy = spel_unit_xlate(sd, ENERGY_UNIT, ENERGY_STATUS_MASK, 0); + + return 0; +} + +static int spel_release_zone(struct powercap_zone *power_zone) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + struct spel_system *sp = sd->sp; + + /* Free the domains array when the last zone (SYS domain) is released */ + if (sd->id == SPEL_DOMAIN_SYS) + kfree(sp->domains); + + return 0; +} + +static int spel_find_nr_power_limit(struct spel_domain *sd) +{ + int i, nr_pl = 0; + + for (i = 0; i < ARRAY_SIZE(pl_names); i++) { + if (is_pl_valid(sd, i)) + nr_pl++; + } + + return nr_pl; +} + +static const struct powercap_zone_ops zone_ops = { + .get_energy_uj = spel_get_energy_counter, + .get_max_energy_range_uj = spel_get_max_energy_counter, + .release = spel_release_zone, +}; + +static int spel_constraint_to_pl(struct spel_domain *sd, int cid) +{ + int i, id; + + for (i = 0, id = 0; i < ARRAY_SIZE(pl_names); i++) { + if (is_pl_valid(sd, i) && id++ == cid) + return i; + } + + return -EINVAL; +} + +static int spel_set_power_limit(struct powercap_zone *power_zone, int cid, + u64 power_limit) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + return spel_write_pl_data(sd, id, PL_LIMIT, power_limit); +} + +static int spel_get_power_limit(struct powercap_zone *power_zone, int cid, + u64 *data) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 val; + int ret, id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + ret = spel_read_pl_data(sd, id, PL_LIMIT, true, &val); + if (!ret) + *data = val; + + return ret; +} + +static int spel_set_time_window(struct powercap_zone *power_zone, int cid, + u64 window) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + return spel_write_pl_data(sd, id, PL_TIME_WINDOW, window); +} + +static int spel_get_time_window(struct powercap_zone *power_zone, int cid, + u64 *data) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + u64 val; + int ret, id; + + id = spel_constraint_to_pl(sd, cid); + if (id < 0) + return id; + + ret = spel_read_pl_data(sd, id, PL_TIME_WINDOW, true, &val); + if (!ret) + *data = val; + + return ret; +} + +static const char *spel_get_constraint_name(struct powercap_zone *power_zone, + int cid) +{ + struct spel_domain *sd = power_zone_to_spel_domain(power_zone); + int id; + + id = spel_constraint_to_pl(sd, cid); + if (id >= 0 && id < ARRAY_SIZE(pl_names)) + return pl_names[id]; + + return NULL; +} + +static const struct powercap_zone_constraint_ops constraint_ops = { + .set_power_limit_uw = spel_set_power_limit, + .get_power_limit_uw = spel_get_power_limit, + .set_time_window_us = spel_set_time_window, + .get_time_window_us = spel_get_time_window, + .get_name = spel_get_constraint_name, +}; + +static void spel_init_domains(struct spel_system *sp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(domain_info); i++) { + struct spel_domain *sd = &sp->domains[i]; + + sd->sp = sp; + snprintf(sd->name, SPEL_DOMAIN_NAME_LENGTH, "%s", + domain_info[i].name); + sd->id = i; + sd->status_reg = sp->node_base + domain_info[i].offset; + + /* PL1 is always supported (required for powercap registration) */ + sp->limits[i] = BIT(POWER_LIMIT1); + } +} + +static void spel_update_unit(struct spel_system *sp) +{ + u32 value, shift; + + /* Read power_unit and time_unit from offset 0x0 */ + value = readl(sp->config_base); + + /* + * Unit calculation: 1 / (2^shift) + * Masks limit: TIME_UNIT (4 bits, max 15), POWER_UNIT (3 bits, max 7). + */ + shift = FIELD_GET(POWER_UNIT_MASK, value); + sp->power_unit = 1000000 / (1 << shift); + + shift = FIELD_GET(TIME_UNIT_MASK, value); + /* + * Time window in register is in milliseconds. + */ + sp->time_unit = 1000 * (1 << shift); + + /* Read energy_unit from ENERGY_RPT_UNIT_OFFSET */ + value = readl(sp->config_base + ENERGY_RPT_UNIT_OFFSET); + + /* + * Unit calculation: 1 / (2^shift) + * Masks limit: ENERGY_UNIT (4 bits, max 15). + */ + shift = FIELD_GET(ENERGY_UNIT_MASK, value); + sp->energy_unit = ENERGY_UNIT_SCALE * 1000000 / (1 << shift); + + dev_dbg(sp->dev, "Units: energy=%dnJ, time=%dus, power=%duW\n", + sp->energy_unit, sp->time_unit, sp->power_unit); +} + +static void spel_detect_powerlimit(struct spel_domain *sd) +{ + struct spel_system *sp = sd->sp; + u32 capabilities; + int i, j; + + capabilities = readl(sp->config_base + LIMITS_CAPABILITY_OFFSET); + + /* + * Detect power limits from hardware capabilities. + * Start from index 1 (POWER_LIMIT2) since PL1 is always enabled in spel_init_domains(). + */ + for (i = 1; i < ARRAY_SIZE(pl_names); i++) { + for (j = 0; j < ARRAY_SIZE(constraints); j++) { + const struct spel_constraint_info *ci = &constraints[j]; + + if (ci->domain_id == sd->id && ci->pl_id == i) { + if (capabilities & ci->supported_mask) + sp->limits[sd->id] |= BIT(i); + break; + } + } + } +} + +static int spel_init_system(struct spel_system *sp, struct device *dev) +{ + int i, ret; + + /* Read unit configuration (common for all domains) */ + spel_update_unit(sp); + + sp->domains = kcalloc(ARRAY_SIZE(domain_info), + sizeof(struct spel_domain), GFP_KERNEL); + if (!sp->domains) + return -ENOMEM; + + spel_init_domains(sp); + + for (i = 0; i < ARRAY_SIZE(domain_info); i++) { + struct spel_domain *sd = &sp->domains[i]; + + ret = devm_mutex_init(dev, &sd->lock); + if (ret) { + dev_err(dev, "Failed to initialize mutex for domain %s\n", sd->name); + kfree(sp->domains); + return ret; + } + + spel_detect_powerlimit(sd); + } + + return 0; +} + +static int spel_register_powercap(struct spel_system *sp) +{ + struct spel_domain *sd; + struct powercap_zone *power_zone; + int nr_pl, ret, i; + + /* Register SYS domain as parent zone */ + sd = &sp->domains[SPEL_DOMAIN_SYS]; + nr_pl = spel_find_nr_power_limit(sd); + + power_zone = powercap_register_zone(&sd->power_zone, + sp->control_type, sd->name, + NULL, &zone_ops, nr_pl, + &constraint_ops); + if (IS_ERR(power_zone)) { + dev_err(sp->dev, "Failed to register power zone %s\n", + sd->name); + return PTR_ERR(power_zone); + } + sp->power_zone = power_zone; + + /* Register other domains as children */ + for (i = 0; i < ARRAY_SIZE(domain_info); i++) { + struct powercap_zone *parent; + + if (i == SPEL_DOMAIN_SYS) + continue; + + sd = &sp->domains[i]; + + /* SOC is child of SYS, others are children of SOC */ + if (i == SPEL_DOMAIN_SOC) + parent = sp->power_zone; + else + parent = &sp->domains[SPEL_DOMAIN_SOC].power_zone; + + nr_pl = spel_find_nr_power_limit(sd); + power_zone = powercap_register_zone(&sd->power_zone, + sp->control_type, + sd->name, parent, + &zone_ops, nr_pl, + &constraint_ops); + + if (IS_ERR(power_zone)) { + dev_err(sp->dev, "Failed to register power_zone %s\n", + sd->name); + ret = PTR_ERR(power_zone); + goto err_cleanup; + } + } + + return 0; + +err_cleanup: + /* Unregister in reverse order: children first, then SOC, then SYS */ + for (i = i - 1; i >= 0; i--) + powercap_unregister_zone(sp->control_type, &sp->domains[i].power_zone); + + return ret; +} + +static int spel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spel_system *sp; + int ret; + + sp = devm_kzalloc(dev, sizeof(*sp), GFP_KERNEL); + if (!sp) + return -ENOMEM; + + sp->dev = dev; + + /* Map config registers (units, capabilities) */ + sp->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); + if (IS_ERR(sp->config_base)) + return PTR_ERR(sp->config_base); + + /* Map constraint registers (power limits) */ + sp->constraint_base = devm_platform_ioremap_resource_byname(pdev, "constraints"); + if (IS_ERR(sp->constraint_base)) + return PTR_ERR(sp->constraint_base); + + /* Map spel domain registers (energy counters) */ + sp->node_base = devm_platform_ioremap_resource_byname(pdev, "nodes"); + if (IS_ERR(sp->node_base)) + return PTR_ERR(sp->node_base); + + sp->control_type = powercap_register_control_type(NULL, "qcom-spel", + NULL); + if (IS_ERR(sp->control_type)) { + dev_err(dev, "Failed to register control type\n"); + return PTR_ERR(sp->control_type); + } + + /* Initialize system and domains */ + ret = spel_init_system(sp, dev); + if (ret) { + dev_err(dev, "Failed to initialize system\n"); + goto err_unregister_control; + } + + ret = spel_register_powercap(sp); + if (ret) { + dev_err(dev, "Failed to register powercap zones\n"); + if (!sp->power_zone) + kfree(sp->domains); + goto err_unregister_control; + } + + platform_set_drvdata(pdev, sp); + + return 0; + +err_unregister_control: + powercap_unregister_control_type(sp->control_type); + return ret; +} + +static void spel_remove(struct platform_device *pdev) +{ + struct spel_system *sp = platform_get_drvdata(pdev); + int i; + + /* Unregister in reverse order: children first, then SOC, then SYS */ + for (i = ARRAY_SIZE(domain_info) - 1; i >= 0; i--) + powercap_unregister_zone(sp->control_type, &sp->domains[i].power_zone); + + powercap_unregister_control_type(sp->control_type); +} + +static const struct of_device_id spel_of_match[] = { + { .compatible = "qcom,glymur-spel" }, + { } +}; +MODULE_DEVICE_TABLE(of, spel_of_match); + +static struct platform_driver spel_driver = { + .probe = spel_probe, + .remove = spel_remove, + .driver = { + .name = "qcom_spel", + .of_match_table = spel_of_match, + }, +}; + +module_platform_driver(spel_driver); + +MODULE_DESCRIPTION("Qualcomm SPEL Powercap Driver"); +MODULE_LICENSE("GPL"); From f44a95f51a1b61c8830334334dc42cae09763da0 Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Tue, 5 May 2026 19:07:27 +0530 Subject: [PATCH 033/243] FROMLIST: arm64: dts: qcom: glymur: Add psci reboot-mode edl Add PSCI SYSTEM_RESET2 reboot-modes for glymur to be invoked by the psci reboot-mode driver. The following modes are defined: - edl: reboot into emergency download mode for image loading via the Firehose protocol. Support for these modes is dependent on the psci firmware Signed-off-by: Pankaj Patil Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 6 ++++++ arch/arm64/boot/dts/qcom/glymur.dtsi | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 51d7944af3a8e..4c56d7e5147fd 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -973,6 +973,12 @@ status = "okay"; }; +&psci { + reboot-mode { + mode-edl = <0x80000000 0x1>; + }; +}; + &tlmm { gpio-reserved-ranges = <4 4>, /* EC TZ Secure I3C */ <10 2>, /* OOB UART */ diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 8b54f99785c2f..96c9dcc4cce2a 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -418,7 +418,7 @@ interrupts = ; }; - psci { + psci: psci { compatible = "arm,psci-1.0"; method = "smc"; From b53984072b62cfaf3df869538ba7cec34d2da278 Mon Sep 17 00:00:00 2001 From: Harshal Dev Date: Thu, 16 Apr 2026 13:40:52 +0530 Subject: [PATCH 034/243] FROMLIST: arm64: dts: qcom: glymur: add TRNG node Glymur has a True Random Number Generator, add the node with the correct compatible set. Link: https://lore.kernel.org/all/20260424-glymur_trng_enablement-v2-2-0603cbe68440@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Signed-off-by: Harshal Dev Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 96c9dcc4cce2a..af98a6f4d27ef 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -3778,6 +3778,11 @@ interconnect-names = "memory"; }; + rng: rng@10c3000 { + compatible = "qcom,glymur-trng", "qcom,trng"; + reg = <0x0 0x010c3000 0x0 0x1000>; + }; + tcsr_mutex: hwlock@1f40000 { compatible = "qcom,tcsr-mutex"; reg = <0x0 0x01f40000 0x0 0x20000>; From fe8cfa7e83316c6ee119837b021ff5fb29af9aaf Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:36 +0530 Subject: [PATCH 035/243] FROMLIST: arm64: dts: qcom: glymur: Enable LLCC/DDR/DDR_QOS dvfs On Qualcomm Glymur SoCs, the memlat governor and the mechanism to control the LLCC and DDR/DDR_QOS is hosted on the CPU Control Processor (CPUCP). Enable the nodes required to get QCOM SCMI Generic Extension protocol to probe on Glymur and Mahua SoCs. Link: https://lore.kernel.org/lkml/20260507062237.78051-8-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index af98a6f4d27ef..b4b08efe4d116 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -385,6 +385,21 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; }; + cpucp_scmi { + compatible = "arm,scmi"; + mboxes = <&cpucp_mbox 0>, <&cpucp_mbox 2>; + + mbox-names = "tx", "rx"; + shmem = <&cpucp_scp_lpri0>, <&cpucp_scp_lpri1>; + + #address-cells = <1>; + #size-cells = <0>; + + scmi_vendor: protocol@80 { + reg = <0x80>; + }; + }; + scmi { compatible = "arm,scmi"; mboxes = <&pdp0_mbox 0>, <&pdp0_mbox 1>; @@ -7572,6 +7587,13 @@ #mbox-cells = <1>; }; + cpucp_mbox: mailbox@17620000 { + compatible = "qcom,glymur-cpucp-mbox", "qcom,x1e80100-cpucp-mbox"; + reg = <0 0x17620000 0 0x8000>, <0 0x18830000 0 0x8000>; + interrupts = ; + #mbox-cells = <1>; + }; + timer@17810000 { compatible = "arm,armv7-timer-mem"; reg = <0x0 0x17810000 0x0 0x1000>; @@ -7756,6 +7778,26 @@ }; }; + cpucp_sram: sram@18b4e000 { + compatible = "mmio-sram"; + reg = <0x0 0x18b4e000 0x0 0x400>; + + #address-cells = <1>; + #size-cells = <1>; + + ranges = <0x0 0x0 0x18b4e000 0x400>; + + cpucp_scp_lpri0: scp-sram-section@0 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0x200>; + }; + + cpucp_scp_lpri1: scp-sram-section@200 { + compatible = "arm,scmi-shmem"; + reg = <0x200 0x200>; + }; + }; + nsi_noc: interconnect@1d600000 { compatible = "qcom,glymur-nsinoc"; reg = <0x0 0x1d600000 0x0 0x14080>; From d812782b05d17bcc4b089e36b2789f4bd021b206 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Wed, 29 Apr 2026 19:44:58 +0530 Subject: [PATCH 036/243] FROMLIST: arm64: dts: qcom: glymur: Add camera clock controller support Add support for camera clock controller for camera clients to be able to request for camera clocks on Glymur SoC's. Link: https://lore.kernel.org/r/20260429-glymur_camcc-v2-3-0c3fd1977869@oss.qualcomm.com Signed-off-by: Jagadeesh Kona Reviewed-by: Bryan O'Donoghue Signed-off-by: Taniya Das Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index b4b08efe4d116..1e858904ba00d 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -3,6 +3,7 @@ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +#include #include #include #include @@ -5030,6 +5031,22 @@ }; }; + camcc: clock-controller@ade0000 { + compatible = "qcom,glymur-camcc"; + reg = <0x0 0x0ade0000 0x0 0x20000>; + clocks = <&gcc GCC_CAMERA_AHB_CLK>, + <&rpmhcc RPMH_CXO_CLK>, + <&rpmhcc RPMH_CXO_CLK_A>, + <&sleep_clk>; + power-domains = <&rpmhpd RPMHPD_MXC>, + <&rpmhpd RPMHPD_MMCX>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + mdss: display-subsystem@ae00000 { compatible = "qcom,glymur-mdss"; reg = <0x0 0x0ae00000 0x0 0x1000>; From b1549735ecae6623b8ab4f4fd19f7724322818c6 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Sat, 20 Jun 2026 02:09:10 +0530 Subject: [PATCH 037/243] FROMLIST: arm64: dts: qcom: glymur: Enable SPEL powercap driver The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware based power monitoring and limiting capabilities for various power domains including System, SoC, CPU clusters, GPU, and various other subsystems for glymur. Link: https://lore.kernel.org/r/20260620-qcom_spel_driver_upstream-v2-3-a3ee6837c18f@oss.qualcomm.com Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 1e858904ba00d..918ed3800ac89 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -5730,6 +5730,16 @@ }; }; + power-limits@ef3b000 { + compatible = "qcom,glymur-spel"; + reg = <0x0 0x0ef3b000 0x0 0x1000>, + <0x0 0x0ef3d000 0x0 0x1000>, + <0x0 0x0ef3e000 0x0 0x1000>; + reg-names = "config", + "constraints", + "nodes"; + }; + tlmm: pinctrl@f100000 { compatible = "qcom,glymur-tlmm"; reg = <0x0 0x0f100000 0x0 0xf00000>; From cd0376b41f0188456912f3728f3113264bd0e7f5 Mon Sep 17 00:00:00 2001 From: Pradyot Kumar Nayak Date: Wed, 27 May 2026 22:28:26 +0530 Subject: [PATCH 038/243] FROMLIST: arm64: dts: qcom: glymur-crd: Add FocalTech FT3D81 touchscreen support The Glymur CRD board includes a FocalTech FT3D81 touchscreen connected to I2C bus.FT3D81 driver is compatible to ft112. Link: https://lore.kernel.org/linux-arm-msm/20260603-arm64-dts-glymur-crd-add-reset-gpio-to-v3-2-3453ef577bcf@oss.qualcomm.com/ Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 4c56d7e5147fd..b16d4a299ee11 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -662,14 +662,15 @@ status = "okay"; touchscreen@38 { - compatible = "hid-over-i2c"; + compatible = "focaltech,ft3d81", "focaltech,ft8112"; reg = <0x38>; - hid-descr-addr = <0x1>; interrupts-extended = <&tlmm 51 IRQ_TYPE_LEVEL_LOW>; - vdd-supply = <&vreg_misc_3p3>; - vddl-supply = <&vreg_l15b_e0_1p8>; + vcc33-supply = <&vreg_misc_3p3>; + vccio-supply = <&vreg_l15b_e0_1p8>; + + reset-gpios = <&tlmm 48 GPIO_ACTIVE_LOW>; pinctrl-0 = <&ts0_default>; pinctrl-names = "default"; From e59d7613b1a78eec958cd2a4c36f4ce11538cc56 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 12 May 2026 17:47:24 +0530 Subject: [PATCH 039/243] PENDING: arm64: dts: qcom: glymur-crd: Add support for PM8010 Add PM8010 PMIC and peripherals under it. PM8010 regulators are needed to power camera sensors. PM8010 is mostly equivalent to PM8008 with some improvements like lower LDO noise and lower power mode support in the HW. Signed-off-by: Jishnu Prakash Signed-off-by: Nihal Kumar Gupta Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 107 +++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index b16d4a299ee11..4b7d7015e4a08 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -654,6 +654,76 @@ #phy-cells = <0>; }; + + pm8010: pmic@8 { + compatible = "qcom,pm8008"; + reg = <0x8>; + + interrupts-extended = <&tlmm 90 IRQ_TYPE_EDGE_RISING>; + reset-gpios = <&tlmm 106 GPIO_ACTIVE_LOW>; + + vdd-l1-l2-supply = <&vreg_l6p>; + vdd-l3-l4-supply = <&vreg_bob1_e0>; + vdd-l5-supply = <&vreg_bob1_e0>; + vdd-l6-supply = <&vreg_bob1_e0>; + vdd-l7-supply = <&vreg_bob1_e0>; + + pinctrl-names = "default"; + pinctrl-0 = <&pm8010_default>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pm8010 0 0 2>; + + interrupt-controller; + #interrupt-cells = <2>; + + #thermal-sensor-cells = <0>; + + regulators { + vreg_l1p: ldo1 { + regulator-name = "vreg_l1p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l2p: ldo2 { + regulator-name = "vreg_l2p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l3p: ldo3 { + regulator-name = "vreg_l3p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l4p: ldo4 { + regulator-name = "vreg_l4p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l5p: ldo5 { + regulator-name = "vreg_l5p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l6p: ldo6 { + regulator-name = "vreg_l6p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l7p: ldo7 { + regulator-name = "vreg_l7p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + }; + }; }; &i2c8 { @@ -980,6 +1050,27 @@ }; }; +&thermal_zones { + pm8010-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&pm8010>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; +}; + &tlmm { gpio-reserved-ranges = <4 4>, /* EC TZ Secure I3C */ <10 2>, /* OOB UART */ @@ -1109,6 +1200,22 @@ }; }; + pm8010_default: pm8010-default-state { + int-pins { + pins = "gpio90"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + }; + + reset-n-pins { + pins = "gpio106"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + }; + tpad_default: tpad-default-state { pins = "gpio3"; function = "gpio"; From 32a561a64571c2c5173e97d5ec0e1d5bd5d25725 Mon Sep 17 00:00:00 2001 From: Nihal Kumar Gupta Date: Tue, 19 May 2026 14:38:41 +0530 Subject: [PATCH 040/243] PENDING: arm64: dts: qcom: glymur: Add camss node Add node for the Glymur camera subsystem. Signed-off-by: Nihal Kumar Gupta Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 160 +++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 918ed3800ac89..a00ec2ee8acd8 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -1828,6 +1828,166 @@ }; }; + + camss: isp@acb6000 { + compatible = "qcom,glymur-camss"; + + reg = <0x0 0x0acb6000 0x0 0x1000>, + <0x0 0x0acb7000 0x0 0x2000>, + <0x0 0x0acb9000 0x0 0x2000>, + <0x0 0x0acbb000 0x0 0x2000>, + <0x0 0x0acc6000 0x0 0x1000>, + <0x0 0x0acca000 0x0 0x1000>, + <0x0 0x0ace4000 0x0 0x2000>, + <0x0 0x0ace6000 0x0 0x2000>, + <0x0 0x0acec000 0x0 0x2000>, + <0x0 0x0acf6000 0x0 0x1000>, + <0x0 0x0acf7000 0x0 0x1000>, + <0x0 0x0acf8000 0x0 0x1000>, + <0x0 0x0ac62000 0x0 0xf000>, + <0x0 0x0ac71000 0x0 0xf000>, + <0x0 0x0acc7000 0x0 0x2000>, + <0x0 0x0accb000 0x0 0x2000>; + + reg-names = "csid_wrapper", + "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy4", + "csitpg0", + "csitpg1", + "csitpg2", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + clocks = <&camcc CAM_CC_CAMNOC_AXI_NRT_CLK>, + <&camcc CAM_CC_CAMNOC_AXI_RT_CLK>, + <&camcc CAM_CC_CORE_AHB_CLK>, + <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CPAS_FAST_AHB_CLK>, + <&camcc CAM_CC_CPAS_IFE_0_CLK>, + <&camcc CAM_CC_CPAS_IFE_1_CLK>, + <&camcc CAM_CC_CPAS_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_CSIPHY4_CLK>, + <&camcc CAM_CC_CSI4PHYTIMER_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&gcc GCC_CAMERA_SF_AXI_CLK>, + <&camcc CAM_CC_IFE_0_CLK>, + <&camcc CAM_CC_IFE_0_FAST_AHB_CLK>, + <&camcc CAM_CC_IFE_1_CLK>, + <&camcc CAM_CC_IFE_1_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>; + + clock-names = "camnoc_nrt_axi", + "camnoc_rt_axi", + "core_ahb", + "cpas_ahb", + "cpas_fast_ahb", + "cpas_vfe0", + "cpas_vfe1", + "cpas_vfe_lite", + "csid", + "csid_csiphy_rx", + "csiphy0", + "csiphy0_timer", + "csiphy1", + "csiphy1_timer", + "csiphy4", + "csiphy4_timer", + "gcc_axi_hf", + "gcc_axi_sf", + "vfe0", + "vfe0_fast_ahb", + "vfe1", + "vfe1_fast_ahb", + "vfe_lite", + "vfe_lite_ahb", + "vfe_lite_cphy_rx", + "vfe_lite_csid"; + + interrupts = , + , + , + , + , + , + , + , + , + , + , + ; + + interrupt-names = "csid0", + "csid1", + "csid2", + "csid_lite0", + "csid_lite1", + "csiphy0", + "csiphy1", + "csiphy4", + "vfe0", + "vfe1", + "vfe_lite0", + "vfe_lite1"; + + interconnects = <&hsc_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_ICP 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 0x820 0x00>; + + power-domains = <&camcc CAM_CC_IFE_0_GDSC>, + <&camcc CAM_CC_IFE_1_GDSC>, + <&camcc CAM_CC_TITAN_TOP_GDSC>; + power-domain-names = "ife0", + "ife1", + "top"; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + }; + + port@1 { + reg = <1>; + }; + + port@2 { + reg = <2>; + }; + }; + }; + gpi_dma0: dma-controller@b00000 { compatible = "qcom,glymur-gpi-dma", "qcom,sm6350-gpi-dma"; reg = <0x0 0x00b00000 0x0 0x60000>; From a4b7eb58b4707bdec5b7203d6d440ac17877fcdc Mon Sep 17 00:00:00 2001 From: Nihal Kumar Gupta Date: Tue, 19 May 2026 14:48:27 +0530 Subject: [PATCH 041/243] PENDING: arm64: dts: qcom: glymur: Add CCI definitions Qualcomm Glymur SoC has two Camera Control Interface (CCI) controllers. Each controller contains two I2C hosts. Signed-off-by: Nihal Kumar Gupta Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 201 +++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index a00ec2ee8acd8..a7ae1ca7a3bcc 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -1828,6 +1828,79 @@ }; }; + cci0: cci@ac15000 { + compatible = "qcom,glymur-cci", "qcom,msm8996-cci"; + reg = <0x0 0x0ac15000 0x0 0x1000>; + + interrupts = ; + + clocks = <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CCI_0_CLK>; + clock-names = "ahb", + "cci"; + + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; + + pinctrl-0 = <&cci0_0_default &cci0_1_default>; + pinctrl-1 = <&cci0_0_sleep &cci0_1_sleep>; + pinctrl-names = "default", "sleep"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + + cci0_i2c0: i2c-bus@0 { + reg = <0>; + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + }; + + cci0_i2c1: i2c-bus@1 { + reg = <1>; + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + + cci1: cci@ac16000 { + compatible = "qcom,glymur-cci", "qcom,msm8996-cci"; + reg = <0x0 0x0ac16000 0x0 0x1000>; + + interrupts = ; + + clocks = <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CCI_1_CLK>; + clock-names = "ahb", + "cci"; + + power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; + + pinctrl-0 = <&cci1_0_default &cci1_1_default>; + pinctrl-1 = <&cci1_0_sleep &cci1_1_sleep>; + pinctrl-names = "default", "sleep"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + + cci1_i2c0: i2c-bus@0 { + reg = <0>; + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + }; + + cci1_i2c1: i2c-bus@1 { + reg = <1>; + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; camss: isp@acb6000 { compatible = "qcom,glymur-camss"; @@ -5911,6 +5984,134 @@ gpio-ranges = <&tlmm 0 0 249>; wakeup-parent = <&pdc>; + cci0_0_default: cci0-0-default-state { + sda-pins { + pins = "gpio101"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + + scl-pins { + pins = "gpio102"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + }; + + cci0_0_sleep: cci0-0-sleep-state { + sda-pins { + pins = "gpio101"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio102"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci0_1_default: cci0-1-default-state { + sda-pins { + pins = "gpio103"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + + scl-pins { + pins = "gpio104"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + }; + + cci0_1_sleep: cci0-1-sleep-state { + sda-pins { + pins = "gpio103"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio104"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci1_0_default: cci1-0-default-state { + sda-pins { + pins = "gpio105"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + + scl-pins { + pins = "gpio106"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + }; + + cci1_0_sleep: cci1-0-sleep-state { + sda-pins { + pins = "gpio105"; + function = "cci_i2c_sda"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio106"; + function = "cci_i2c_scl"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + cci1_1_default: cci1-1-default-state { + sda-pins { + pins = "gpio235"; + function = "asc_cci"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + + scl-pins { + pins = "gpio236"; + function = "asc_cci"; + drive-strength = <2>; + bias-pull-up = <2200>; + }; + }; + + cci1_1_sleep: cci1-1-sleep-state { + sda-pins { + pins = "gpio235"; + function = "asc_cci"; + drive-strength = <2>; + bias-pull-down; + }; + + scl-pins { + pins = "gpio236"; + function = "asc_cci"; + drive-strength = <2>; + bias-pull-down; + }; + }; + qup_i2c0_data_clk: qup-i2c0-data-clk-state { /* SDA, SCL */ pins = "gpio0", "gpio1"; From b980733055ef6188be7002d1a78b15674eef84da Mon Sep 17 00:00:00 2001 From: Nihal Kumar Gupta Date: Tue, 19 May 2026 18:34:01 +0530 Subject: [PATCH 042/243] PENDING: arm64: dts: qcom: glymur: Add camera MCLK pinctrl Define pinctrl definitions to enable camera master clocks on glymur. Signed-off-by: Nihal Kumar Gupta Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index a7ae1ca7a3bcc..7c6df96529217 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -5984,6 +5984,41 @@ gpio-ranges = <&tlmm 0 0 249>; wakeup-parent = <&pdc>; + cam_mclk0_default: cam-mclk0-default-state { + pins = "gpio96"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam_mclk1_default: cam-mclk1-default-state { + pins = "gpio97"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam_mclk2_default: cam-mclk2-default-state { + pins = "gpio98"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam_mclk3_default: cam-mclk3-default-state { + pins = "gpio99"; + function = "cam_mclk"; + drive-strength = <2>; + bias-disable; + }; + + cam_mclk4_default: cam-mclk4-default-state { + pins = "gpio100"; + function = "cam_asc_mclk4"; + drive-strength = <2>; + bias-disable; + }; + cci0_0_default: cci0-0-default-state { sda-pins { pins = "gpio101"; From 277befa84af7d41d3027b3608615844ec4d01d80 Mon Sep 17 00:00:00 2001 From: Nihal Kumar Gupta Date: Tue, 19 May 2026 17:08:29 +0530 Subject: [PATCH 043/243] PENDING: arm64: dts: qcom: glymur-crd: Add ov08x40 RGB sensor on CSIPHY4 Define ov08x40 on cci1_i2c1. The RGB sensor is connected to CSIPHY4 in four lane mode. Signed-off-by: Nihal Kumar Gupta Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 4b7d7015e4a08..dd53e6459cbbe 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -788,6 +788,67 @@ status = "okay"; }; +&camss { + vdd-csiphy-0p8-supply = <&vreg_l1f_e1_0p82>; + vdd-csiphy-1p2-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + /* + * port0 => csiphy0 + * port1 => csiphy1 + * port2 => csiphy4 + */ + port@2 { + reg = <2>; + + csiphy4_ep: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&ov08x40_ep>; + }; + }; + }; +}; + +&cci1 { + pinctrl-0 = <&cci1_1_default>; + pinctrl-1 = <&cci1_1_sleep>; + + status = "okay"; +}; + +&cci1_i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + camera@36 { + compatible = "ovti,ov08x40"; + reg = <0x36>; + + reset-gpios = <&tlmm 239 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&cam_mclk4_default &cam_reset4_default>; + pinctrl-names = "default"; + + clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_l7p>; + dovdd-supply = <&vreg_l4p>; + + port { + ov08x40_ep: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + remote-endpoint = <&csiphy4_ep>; + }; + }; + }; +}; + &lpass_vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>; pinctrl-names = "default"; @@ -1076,6 +1137,13 @@ <10 2>, /* OOB UART */ <44 4>; /* Security SPI (TPM) */ + cam_reset4_default: cam-reset4-default-state { + pins = "gpio239"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + edp_bl_en: edp-bl-en-state { pins = "gpio18"; function = "gpio"; From 220c00438f39ff14b02ec929c2c702b2632c5504 Mon Sep 17 00:00:00 2001 From: Ananthu C V Date: Thu, 4 Jun 2026 23:54:10 +0530 Subject: [PATCH 044/243] FROMLIST: arch: arm64: boot: dts: qcom: add IMEM and PIL regions for glymur Add an IMEM on glymur which falls back to mmio-sram and define the PIL relocation info region as its child, for post mortem tools to locate the loaded remoteprocs. Link: https://lore.kernel.org/lkml/20260424-glymur-imem-v5-2-18ede63cf063@oss.qualcomm.com/#r Signed-off-by: Ananthu C V Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 7c6df96529217..0fbc209cecd37 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -7836,6 +7836,22 @@ }; }; + sram@14680000 { + compatible = "qcom,glymur-imem", "mmio-sram"; + reg = <0x0 0x14680000 0x0 0x2c000>; + ranges = <0 0 0x14680000 0x2c000>; + + no-memory-wc; + + #address-cells = <1>; + #size-cells = <1>; + + pil-sram@94c { + compatible = "qcom,pil-reloc-info"; + reg = <0x94c 0xc8>; + }; + }; + apps_smmu: iommu@15000000 { compatible = "qcom,glymur-smmu-500", "qcom,smmu-500", From e356c4bb58107aaf998bb4a62930c64c82cf2c7c Mon Sep 17 00:00:00 2001 From: Pradyot Kumar Nayak Date: Fri, 3 Jul 2026 14:21:19 +0530 Subject: [PATCH 045/243] PENDING: arm64: dts: qcom: glymur: add CPU capacity-dmips-mhz Without this property the scheduler treats all cores as equal. Values derived from performance measurements at 825.6 MHz: - M-class cores (cpu0-cpu5, oryon-2-2): capacity-dmips-mhz = 1024 - L-class cores (cpu6-cpu17, oryon-2-1): capacity-dmips-mhz = 1372 Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 0fbc209cecd37..8c2b99cf19db2 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -43,6 +43,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x0>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd0>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -60,6 +61,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x100>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd1>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -71,6 +73,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x200>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd2>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -82,6 +85,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x300>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd3>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -93,6 +97,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x400>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd4>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -104,6 +109,7 @@ compatible = "qcom,oryon-2-2"; reg = <0x0 0x500>; enable-method = "psci"; + capacity-dmips-mhz = <1024>; power-domains = <&cpu_pd5>, <&scmi_perf 0>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_0>; @@ -115,6 +121,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10000>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd6>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -132,6 +139,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10100>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd7>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -143,6 +151,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10200>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd8>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -154,6 +163,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10300>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd9>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -165,6 +175,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10400>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd10>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -176,6 +187,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x10500>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd11>, <&scmi_perf 1>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_1>; @@ -187,6 +199,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20000>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd12>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; @@ -204,6 +217,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20100>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd13>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; @@ -215,6 +229,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20200>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd14>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; @@ -226,6 +241,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20300>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd15>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; @@ -237,6 +253,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20400>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd16>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; @@ -248,6 +265,7 @@ compatible = "qcom,oryon-2-1"; reg = <0x0 0x20500>; enable-method = "psci"; + capacity-dmips-mhz = <1372>; power-domains = <&cpu_pd17>, <&scmi_perf 2>; power-domain-names = "psci", "perf"; next-level-cache = <&l2_2>; From dfcf878abd92200e6cb15c34df054af95e8a784d Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Wed, 24 Jun 2026 15:57:31 +0800 Subject: [PATCH 046/243] FROMLIST: arm64: dts: qcom: glymur: Add label properties to CoreSight devices Add label properties to TPDM and CTI nodes in the glymur 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/all/20260624-add-label-node-for-glymur-v2-1-e8420fd7025f@oss.qualcomm.com/ Signed-off-by: Jie Gan Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 8c2b99cf19db2..8a4d0965eb43f 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -6879,6 +6879,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_spdm"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -6943,6 +6944,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_gcc"; qcom,dsb-msrs-num = <32>; @@ -6961,6 +6963,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_cdsp"; qcom,dsb-element-bits = <32>; qcom,dsb-msrs-num = <32>; @@ -6980,6 +6983,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_cdsp_dpm_1"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -6999,6 +7003,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_cdsp_dpm_2"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7119,6 +7124,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "cti_cdsp"; }; cti_wpss: cti@111ab000 { @@ -7127,6 +7133,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "cti_wpss"; }; tpdm@111d0000 { @@ -7135,6 +7142,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_qm"; qcom,dsb-msrs-num = <32>; @@ -7310,6 +7318,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_mm_dsb"; qcom,dsb-msrs-num = <32>; @@ -7328,6 +7337,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_east_dsb"; qcom,dsb-msrs-num = <32>; @@ -7346,6 +7356,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_west_dsb"; qcom,dsb-msrs-num = <32>; @@ -7364,6 +7375,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_center_dsb"; qcom,dsb-msrs-num = <32>; @@ -7382,6 +7394,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_ipcc"; qcom,cmb-msrs-num = <32>; @@ -7400,6 +7413,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_qrng"; qcom,cmb-msrs-num = <32>; @@ -7418,6 +7432,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_pmu"; qcom,dsb-msrs-num = <32>; @@ -7436,6 +7451,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_rdpm_cx"; qcom,cmb-msrs-num = <32>; @@ -7454,6 +7470,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_rdpm_mxc"; qcom,cmb-msrs-num = <32>; @@ -7472,6 +7489,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_rdpm_mxa"; qcom,cmb-msrs-num = <32>; @@ -7490,6 +7508,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_center_dsb_1"; qcom,dsb-msrs-num = <32>; @@ -7508,6 +7527,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_south2_dsb"; qcom,dsb-msrs-num = <32>; @@ -7526,6 +7546,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_south_dsb"; qcom,dsb-msrs-num = <32>; @@ -7544,6 +7565,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_pcie_rscc"; qcom,cmb-element-bits = <32>; qcom,cmb-msrs-num = <32>; @@ -7587,6 +7609,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_4"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7765,6 +7788,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_0"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7784,6 +7808,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_1"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7803,6 +7828,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_2"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7822,6 +7848,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao_prio_3"; qcom,cmb-element-bits = <64>; qcom,cmb-msrs-num = <32>; @@ -7841,6 +7868,7 @@ clocks = <&aoss_qmp>; clock-names = "apb_pclk"; + label = "tpdm_swao"; qcom,dsb-element-bits = <32>; qcom,dsb-msrs-num = <32>; From 19d99914ab5936d2c7df462afc7e84754c213781 Mon Sep 17 00:00:00 2001 From: Gopikrishna Garmidi Date: Thu, 25 Jun 2026 23:32:21 -0700 Subject: [PATCH 047/243] PENDING: dt-bindings: arm: qcom: Add Glymur QCB The QCB (Qualcomm Compute Board) is a board built on the Qualcomm Glymur SoC. Unlike the CRD, the QCB exposes the board in a form factor suitable for compute workloads, power measurements and oscilloscope-based hardware validation. Add a compatible for this board. Signed-off-by: Gopikrishna Garmidi Signed-off-by: Pradyot Kumar Nayak --- Documentation/devicetree/bindings/arm/qcom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index 50cc18a6ec5ed..5c4b516895f4b 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -69,6 +69,7 @@ properties: - items: - enum: - qcom,glymur-crd + - qcom,glymur-qcb - const: qcom,glymur - items: From 1fdc2136bec2b6d32b7ceacdc921943036a2d30f Mon Sep 17 00:00:00 2001 From: Gopikrishna Garmidi Date: Thu, 25 Jun 2026 23:32:35 -0700 Subject: [PATCH 048/243] PENDING: arm64: dts: qcom: glymur: Add Glymur QCB The QCB (Qualcomm Compute Board) is a board built on the Qualcomm Glymur SoC. Unlike the CRD, the QCB exposes the board in a form factor suitable for compute workloads, power measurements and oscilloscope-based hardware validation. Add initial device tree support for it. Signed-off-by: Gopikrishna Garmidi Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/Makefile | 1 + arch/arm64/boot/dts/qcom/glymur-qcb.dts | 1322 +++++++++++++++++++++++ 2 files changed, 1323 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/glymur-qcb.dts diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 6f33c4e2f09c3..b74caa0f74ea0 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -16,6 +16,7 @@ dtb-$(CONFIG_ARCH_QCOM) += apq8096sg-db820c.dtb dtb-$(CONFIG_ARCH_QCOM) += apq8096-ifc6640.dtb dtb-$(CONFIG_ARCH_QCOM) += eliza-mtp.dtb dtb-$(CONFIG_ARCH_QCOM) += glymur-crd.dtb +dtb-$(CONFIG_ARCH_QCOM) += glymur-qcb.dtb dtb-$(CONFIG_ARCH_QCOM) += hamoa-iot-evk.dtb hamoa-iot-evk-el2-dtbs := hamoa-iot-evk.dtb x1-el2.dtbo diff --git a/arch/arm64/boot/dts/qcom/glymur-qcb.dts b/arch/arm64/boot/dts/qcom/glymur-qcb.dts new file mode 100644 index 0000000000000..5b4e146753621 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/glymur-qcb.dts @@ -0,0 +1,1322 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; + +#include "glymur.dtsi" +#include "pmcx0102.dtsi" /* SPMI0: SID-2/3 SPMI1: SID-2/3 */ +#include "pmh0101.dtsi" /* SPMI0: SID-1 */ +#include "pmh0110-glymur.dtsi" /* SPMI0: SID-5/7 SPMI1: SID-5 */ +#include "pmh0104-glymur.dtsi" /* SPMI0: SID-8/9 SPMI1: SID-11 */ +#include "pmk8850.dtsi" /* SPMI0: SID-0 */ +#include "smb2370.dtsi" /* SPMI2: SID-9/10/11 */ + +#include + +/ { + model = "Qualcomm Technologies, Inc. Glymur QCB"; + compatible = "qcom,glymur-qcb", "qcom,glymur"; + + aliases { + serial0 = &uart21; + serial1 = &uart14; + i2c1 = &i2c4; + i2c2 = &i2c5; + spi0 = &spi18; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + xo_board: xo-board { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32000>; + #clock-cells = <0>; + }; + }; + + gpio-keys { + compatible = "gpio-keys"; + + pinctrl-0 = <&key_vol_up_default>; + pinctrl-names = "default"; + + key-volume-up { + label = "Volume Up"; + linux,code = ; + gpios = <&pmh0101_gpios 6 GPIO_ACTIVE_LOW>; + debounce-interval = <15>; + linux,can-disable; + wakeup-source; + }; + }; + + pmic-glink { + compatible = "qcom,glymur-pmic-glink", + "qcom,pmic-glink"; + #address-cells = <1>; + #size-cells = <0>; + + connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + pmic_glink_hs_in: endpoint { + remote-endpoint = <&usb_0_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + pmic_glink_ss_in: endpoint { + remote-endpoint = <&usb_0_qmpphy_out>; + }; + }; + }; + }; + + connector@1 { + compatible = "usb-c-connector"; + reg = <1>; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + pmic_glink_hs_in1: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + pmic_glink_ss_in1: endpoint { + remote-endpoint = <&usb_1_qmpphy_out>; + }; + }; + }; + }; + }; + + vreg_edp_3p3: regulator-edp-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_EDP_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 70 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&edp_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_misc_3p3: regulator-misc-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_MISC_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0110_f_e0_gpios 6 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&misc_3p3_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_nvme: regulator-nvme { + compatible = "regulator-fixed"; + + regulator-name = "VREG_NVME_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0101_gpios 14 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&nvme_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_nvmesec: regulator-nvmesec { + compatible = "regulator-fixed"; + + regulator-name = "VREG_NVME_SEC_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0110_f_e1_gpios 14 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&nvme_sec_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_wcn_0p95: regulator-wcn-0p95 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_0P95"; + regulator-min-microvolt = <950000>; + regulator-max-microvolt = <950000>; + + vin-supply = <&vreg_wcn_3p3>; + }; + + vreg_wcn_3p3: regulator-wcn-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 94 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wcn_sw_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_wwan: regulator-wwan { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WWAN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 246 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wwan_reg_en>; + pinctrl-names = "default"; + }; + + sound { + compatible = "qcom,glymur-sndcard"; + model = "GLYMUR-CRD"; + audio-routing = "WooferLeft IN", "WSA WSA_SPK1 OUT", + "TweeterLeft IN", "WSA WSA_SPK2 OUT", + "WooferRight IN", "WSA2 WSA_SPK2 OUT", + "TweeterRight IN", "WSA2 WSA_SPK2 OUT", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb"; + + wsa-dai-link { + link-name = "WSA Playback"; + + cpu { + sound-dai = <&q6apmbedai WSA_CODEC_DMA_RX_0>; + }; + + codec { + sound-dai = <&left_woofer>, <&left_tweeter>, + <&swr0 0>, <&lpass_wsamacro 0>, + <&right_woofer>, <&right_tweeter>, + <&swr3 0>, <&lpass_wsa2macro 0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + va-dai-link { + link-name = "VA Capture"; + + codec { + sound-dai = <&lpass_vamacro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + }; + + wcn7850-pmu { + compatible = "qcom,wcn7850-pmu"; + + vdd-supply = <&vreg_wcn_0p95>; + vddio-supply = <&vreg_l15b_e0_1p8>; + vddaon-supply = <&vreg_l15b_e0_1p8>; + vdddig-supply = <&vreg_l15b_e0_1p8>; + vddrfa1p2-supply = <&vreg_l15b_e0_1p8>; + vddrfa1p8-supply = <&vreg_l15b_e0_1p8>; + + wlan-enable-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; + bt-enable-gpios = <&tlmm 116 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&wcn_wlan_bt_en>; + pinctrl-names = "default"; + + 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 { + regulators-0 { + compatible = "qcom,pmh0101-rpmh-regulators"; + qcom,pmic-id = "B_E0"; + + vreg_bob1_e0: bob1 { + regulator-name = "vreg_bob1_e0"; + regulator-min-microvolt = <2200000>; + regulator-max-microvolt = <4224000>; + regulator-initial-mode = ; + }; + + vreg_bob2_e0: bob2 { + regulator-name = "vreg_bob2_e0"; + regulator-min-microvolt = <2540000>; + regulator-max-microvolt = <3600000>; + regulator-initial-mode = ; + }; + + vreg_l1b_e0_1p8: ldo1 { + regulator-name = "vreg_l1b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l2b_e0_2p9: ldo2 { + regulator-name = "vreg_l2b_e0_2p9"; + regulator-min-microvolt = <2904000>; + regulator-max-microvolt = <2904000>; + regulator-initial-mode = ; + }; + + vreg_l7b_e0_2p79: ldo7 { + regulator-name = "vreg_l7b_e0_2p79"; + regulator-min-microvolt = <2790000>; + regulator-max-microvolt = <2792000>; + regulator-initial-mode = ; + }; + + vreg_l8b_e0_1p50: ldo8 { + regulator-name = "vreg_l8b_e0_1p50"; + regulator-min-microvolt = <1504000>; + regulator-max-microvolt = <1504000>; + regulator-initial-mode = ; + }; + + vreg_l9b_e0_2p7: ldo9 { + regulator-name = "vreg_l9b_e0_2p7"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <2704000>; + regulator-initial-mode = ; + }; + + vreg_l10b_e0_1p8: ldo10 { + regulator-name = "vreg_l10b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l11b_e0_1p2: ldo11 { + regulator-name = "vreg_l11b_e0_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_l12b_e0_1p14: ldo12 { + regulator-name = "vreg_l12b_e0_1p14"; + regulator-min-microvolt = <1144000>; + regulator-max-microvolt = <1144000>; + regulator-initial-mode = ; + }; + + vreg_l15b_e0_1p8: ldo15 { + regulator-name = "vreg_l15b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l17b_e0_2p4: ldo17 { + regulator-name = "vreg_l17b_e0_2p4"; + regulator-min-microvolt = <2400000>; + regulator-max-microvolt = <2700000>; + regulator-initial-mode = ; + }; + + vreg_l18b_e0_1p2: ldo18 { + regulator-name = "vreg_l18b_e0_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + }; + + regulators-1 { + compatible = "qcom,pmcx0102-rpmh-regulators"; + qcom,pmic-id = "C_E1"; + + vreg_l1c_e1_0p82: ldo1 { + regulator-name = "vreg_l1c_e1_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2c_e1_1p14: ldo2 { + regulator-name = "vreg_l2c_e1_1p14"; + regulator-min-microvolt = <1144000>; + regulator-max-microvolt = <1144000>; + regulator-initial-mode = ; + }; + + vreg_l3c_e1_0p89: ldo3 { + regulator-name = "vreg_l3c_e1_0p89"; + regulator-min-microvolt = <890000>; + regulator-max-microvolt = <980000>; + regulator-initial-mode = ; + }; + + vreg_l4c_e1_0p72: ldo4 { + regulator-name = "vreg_l4c_e1_0p72"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <720000>; + regulator-initial-mode = ; + }; + }; + + regulators-2 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "F_E0"; + + vreg_s7f_e0_1p32: smps7 { + regulator-name = "vreg_s7f_e0_1p32"; + regulator-min-microvolt = <1320000>; + regulator-max-microvolt = <1352000>; + regulator-initial-mode = ; + }; + + vreg_s8f_e0_0p95: smps8 { + regulator-name = "vreg_s8f_e0_0p95"; + regulator-min-microvolt = <952000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_s9f_e0_1p9: smps9 { + regulator-name = "vreg_s9f_e0_1p9"; + regulator-min-microvolt = <1900000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + }; + + vreg_l2f_e0_0p82: ldo2 { + regulator-name = "vreg_l2f_e0_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l3f_e0_0p72: ldo3 { + regulator-name = "vreg_l3f_e0_0p72"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <720000>; + regulator-initial-mode = ; + }; + + vreg_l4f_e0_0p3: ldo4 { + regulator-name = "vreg_l4f_e0_0p3"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + }; + + regulators-3 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "F_E1"; + + vreg_s7f_e1_0p3: smps7 { + regulator-name = "vreg_s7f_e1_0p3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_l1f_e1_0p82: ldo1 { + regulator-name = "vreg_l1f_e1_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2f_e1_0p83: ldo2 { + regulator-name = "vreg_l2f_e1_0p83"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l4f_e1_1p08: ldo4 { + regulator-name = "vreg_l4f_e1_1p08"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1320000>; + regulator-initial-mode = ; + }; + }; + + regulators-4 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "H_E0"; + + vreg_l1h_e0_0p89: ldo1 { + regulator-name = "vreg_l1h_e0_0p89"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2h_e0_0p72: ldo2 { + regulator-name = "vreg_l2h_e0_0p72"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l3h_e0_0p32: ldo3 { + regulator-name = "vreg_l3h_e0_0p32"; + regulator-min-microvolt = <320000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + }; + + vreg_l4h_e0_1p2: ldo4 { + regulator-name = "vreg_l4h_e0_1p2"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1320000>; + regulator-initial-mode = ; + }; + }; +}; + +&camss { + vdd-csiphy-0p8-supply = <&vreg_l1f_e1_0p82>; + vdd-csiphy-1p2-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + /* + * port0 => csiphy0 + * port1 => csiphy1 + * port2 => csiphy4 + */ + port@2 { + reg = <2>; + + csiphy4_ep: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&ov08x40_ep>; + }; + }; + }; +}; + +&cci1 { + pinctrl-0 = <&cci1_1_default>; + pinctrl-1 = <&cci1_1_sleep>; + + status = "okay"; +}; + +&cci1_i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + camera@36 { + compatible = "ovti,ov08x40"; + reg = <0x36>; + + reset-gpios = <&tlmm 239 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&cam_mclk4_default &cam_reset4_default>; + pinctrl-names = "default"; + + clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_l7p>; + dovdd-supply = <&vreg_l4p>; + + port { + ov08x40_ep: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + remote-endpoint = <&csiphy4_ep>; + }; + }; + }; +}; + +&lpass_vamacro { + pinctrl-0 = <&dmic01_default>, <&dmic23_default>; + pinctrl-names = "default"; + qcom,dmic-sample-rate = <4800000>; +}; + +&i2c5 { + clock-frequency = <400000>; + + status = "okay"; + + ptn3222_0: redriver@43 { + compatible = "nxp,ptn3222"; + reg = <0x43>; + + reset-gpios = <&tlmm 8 GPIO_ACTIVE_LOW>; + + vdd3v3-supply = <&vreg_l8b_e0_1p50>; + vdd1v8-supply = <&vreg_l15b_e0_1p8>; + + #phy-cells = <0>; + }; + + ptn3222_1: redriver@47 { + compatible = "nxp,ptn3222"; + reg = <0x47>; + + reset-gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; + + vdd3v3-supply = <&vreg_l8b_e0_1p50>; + vdd1v8-supply = <&vreg_l15b_e0_1p8>; + + #phy-cells = <0>; + }; + + pm8010: pmic@8 { + compatible = "qcom,pm8008"; + reg = <0x8>; + + interrupts-extended = <&tlmm 90 IRQ_TYPE_EDGE_RISING>; + reset-gpios = <&tlmm 106 GPIO_ACTIVE_LOW>; + + vdd-l1-l2-supply = <&vreg_l6p>; + vdd-l3-l4-supply = <&vreg_bob1_e0>; + vdd-l5-supply = <&vreg_bob1_e0>; + vdd-l6-supply = <&vreg_bob1_e0>; + vdd-l7-supply = <&vreg_bob1_e0>; + + pinctrl-names = "default"; + pinctrl-0 = <&pm8010_default>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pm8010 0 0 2>; + + interrupt-controller; + #interrupt-cells = <2>; + + #thermal-sensor-cells = <0>; + + regulators { + vreg_l1p: ldo1 { + regulator-name = "vreg_l1p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l2p: ldo2 { + regulator-name = "vreg_l2p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l3p: ldo3 { + regulator-name = "vreg_l3p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l4p: ldo4 { + regulator-name = "vreg_l4p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l5p: ldo5 { + regulator-name = "vreg_l5p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l6p: ldo6 { + regulator-name = "vreg_l6p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l7p: ldo7 { + regulator-name = "vreg_l7p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + }; + }; +}; + +&i2c9 { + clock-frequency = <400000>; + + status = "okay"; + + embedded-controller@76 { + compatible = "qcom,glymur-crd-ec", "qcom,hamoa-crd-ec"; + reg = <0x76>; + + interrupts-extended = <&tlmm 66 IRQ_TYPE_EDGE_FALLING>; + + pinctrl-0 = <&ec_int_n_default>; + pinctrl-names = "default"; + }; +}; + +&pcie3b { + vddpe-3v3-supply = <&vreg_nvmesec>; + + pinctrl-0 = <&pcie3b_default>; + pinctrl-names = "default"; +}; + +&pcie3b_phy { + vdda-phy-supply = <&vreg_l3c_e1_0p89>; + vdda-pll-supply = <&vreg_l2c_e1_1p14>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; +}; + +&pcie3b_port0 { + reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; +}; + +&pcie4 { + pinctrl-0 = <&pcie4_default>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie4_phy { + vdda-phy-supply = <&vreg_l1c_e1_0p82>; + vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + + status = "okay"; +}; + +&pcie4_port0 { + reset-gpios = <&tlmm 146 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 148 GPIO_ACTIVE_LOW>; + + wifi@0 { + compatible = "pci17cb,1107"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>; + vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>; + }; +}; + +&pcie5 { + vddpe-3v3-supply = <&vreg_nvme>; + + pinctrl-0 = <&pcie5_default>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie5_phy { + vdda-phy-supply = <&vreg_l2f_e0_0p82>; + vdda-pll-supply = <&vreg_l4h_e0_1p2>; + vdda-qref-supply = <&vreg_l3f_e0_0p72>; + + status = "okay"; +}; + +&pcie5_port0 { + reset-gpios = <&tlmm 152 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 154 GPIO_ACTIVE_LOW>; +}; + +&pcie6 { + vddpe-3v3-supply = <&vreg_wwan>; + + pinctrl-0 = <&pcie6_default>; + pinctrl-names = "default"; + + status = "disabled"; +}; + +&pcie6_phy { + vdda-phy-supply = <&vreg_l1c_e1_0p82>; + vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + + status = "disabled"; +}; + +&pcie6_port0 { + reset-gpios = <&tlmm 149 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 151 GPIO_ACTIVE_LOW>; +}; + +&pmh0101_gpios { + nvme_reg_en: nvme-reg-en-state { + pins = "gpio14"; + function = "normal"; + bias-disable; + }; +}; + +&pmh0110_f_e1_gpios { + nvme_sec_reg_en: nvme-reg-en-state { + pins = "gpio14"; + function = "normal"; + bias-disable; + }; +}; + +&pmh0101_gpios { + key_vol_up_default: key-vol-up-default-state { + pins = "gpio6"; + function = "normal"; + output-disable; + bias-pull-up; + }; +}; + +&pmh0110_f_e0_gpios { + misc_3p3_reg_en: misc-3p3-reg-en-state { + pins = "gpio6"; + function = "normal"; + bias-disable; + input-disable; + output-enable; + drive-push-pull; + power-source = <1>; /* 1.8 V */ + qcom,drive-strength = ; + }; +}; + +&pmk8850_rtc { + qcom,no-alarm; +}; + +&pon_resin { + linux,code = ; + status = "okay"; +}; + +&smb2370_l_e2 { + /* SID 11 charger is not populated on the QCB */ + status = "disabled"; +}; + +&smb2370_j_e2_eusb2_repeater { + vdd18-supply = <&vreg_l15b_e0_1p8>; + vdd3-supply = <&vreg_l7b_e0_2p79>; +}; + +&smb2370_k_e2_eusb2_repeater { + vdd18-supply = <&vreg_l15b_e0_1p8>; + vdd3-supply = <&vreg_l7b_e0_2p79>; +}; + +&swr0 { + status = "okay"; + + /* WSA8845, Left Woofer */ + left_woofer: speaker@0,0 { + compatible = "sdw20217020400"; + reg = <0 0>; + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "WooferLeft"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <1 2 3 7 12 14>; + }; + + /* WSA8845, Left Tweeter */ + left_tweeter: speaker@0,1 { + compatible = "sdw20217020400"; + reg = <0 1>; + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "TweeterLeft"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <4 5 6 7 13 15>; + }; +}; + +&swr3 { + status = "okay"; + + /* WSA8845, Right Woofer */ + right_woofer: speaker@0,0 { + compatible = "sdw20217020400"; + reg = <0 0>; + reset-gpios = <&lpass_tlmm 13 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "WooferRight"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <1 2 3 7 12 14>; + }; + + /* WSA8845, Right Tweeter */ + right_tweeter: speaker@0,1 { + compatible = "sdw20217020400"; + reg = <0 1>; + reset-gpios = <&lpass_tlmm 13 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "TweeterRight"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <4 5 6 7 13 15>; + }; +}; + +&remoteproc_adsp { + firmware-name = "qcom/glymur/adsp.mbn", + "qcom/glymur/adsp_dtb.mbn"; + + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/glymur/cdsp.mbn", + "qcom/glymur/cdsp_dtb.mbn"; + + status = "okay"; +}; + +&remoteproc_soccp { + firmware-name = "qcom/glymur/soccp.mbn", + "qcom/glymur/soccp_dtb.mbn"; + + status = "okay"; +}; + +&psci { + reboot-mode { + mode-edl = <0x80000000 0x1>; + }; +}; + +&thermal_zones { + pm8010-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&pm8010>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; +}; + +&tlmm { + gpio-reserved-ranges = <4 4>, /* EC TZ Secure I3C */ + <10 2>, /* OOB UART */ + <44 4>; /* Security SPI (TPM) */ + + cam_reset4_default: cam-reset4-default-state { + pins = "gpio239"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + edp_bl_en: edp-bl-en-state { + pins = "gpio18"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + + edp_reg_en: edp-reg-en-state { + pins = "gpio70"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + + ec_int_n_default: ec-int-n-state { + pins = "gpio66"; + function = "gpio"; + bias-disable; + }; + + pcie4_default: pcie4-default-state { + clkreq-n-pins { + pins = "gpio147"; + function = "pcie4_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio146"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio148"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie5_default: pcie5-default-state { + clkreq-n-pins { + pins = "gpio153"; + function = "pcie5_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio152"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio154"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie6_default: pcie6-default-state { + clkreq-n-pins { + pins = "gpio150"; + function = "pcie6_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio149"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio151"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie3b_default: pcie3b-default-state { + clkreq-n-pins { + pins = "gpio156"; + function = "pcie3b_clk"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio155"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio157"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pm8010_default: pm8010-default-state { + int-pins { + pins = "gpio90"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + }; + + reset-n-pins { + pins = "gpio106"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + }; + + tpad_default: tpad-default-state { + pins = "gpio3"; + function = "gpio"; + bias-disable; + }; + + ts0_default: ts0-default-state { + int-n-pins { + pins = "gpio51"; + function = "gpio"; + bias-disable; + }; + + reset-n-pins { + pins = "gpio48"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + }; + + wcn_wlan_bt_en: wcn-wlan-bt-en-state { + pins = "gpio116", "gpio117"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wcn_sw_en: wcn-sw-en-state { + pins = "gpio94"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wwan_reg_en: wwan-reg-en-state { + pins = "gpio246"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; +}; + +&usb_0 { + status = "okay"; +}; + +&usb_0_dwc3_hs { + remote-endpoint = <&pmic_glink_hs_in>; +}; + +&usb_0_hsphy { + vdd-supply = <&vreg_l3f_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&smb2370_j_e2_eusb2_repeater>; + + status = "okay"; +}; + +&usb_0_qmpphy { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l3f_e0_0p72>; + refgen-supply = <&vreg_l2f_e0_0p82>; + + status = "okay"; +}; + +&usb_0_qmpphy_out { + remote-endpoint = <&pmic_glink_ss_in>; +}; + +&usb_1 { + status = "okay"; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&pmic_glink_hs_in1>; +}; + +&usb_1_hsphy { + vdd-supply = <&vreg_l3f_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&smb2370_k_e2_eusb2_repeater>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l1h_e0_0p89>; + refgen-supply = <&vreg_l2f_e0_0p82>; + + status = "okay"; +}; + +&usb_1_qmpphy_out { + remote-endpoint = <&pmic_glink_ss_in1>; +}; + +&usb_hs { + status = "okay"; +}; + +&usb_hs_phy { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&ptn3222_1>; + + status = "okay"; +}; + +&usb_mp { + status = "okay"; +}; + +&usb_mp_hsphy0 { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&ptn3222_0>; + + status = "okay"; +}; + +&usb_mp_hsphy1 { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + status = "okay"; +}; + +&usb_mp_qmpphy0 { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l2h_e0_0p72>; + refgen-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; +}; + +&usb_mp_qmpphy1 { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l2h_e0_0p72>; + refgen-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; +}; From 3bd372af88221a70522af0596cb7864f6cdefeb3 Mon Sep 17 00:00:00 2001 From: Gopikrishna Garmidi Date: Wed, 1 Jul 2026 05:48:16 -0700 Subject: [PATCH 049/243] PENDING: dt-bindings: arm: qcom: Add Mahua QCB The Mahua QCB (Qualcomm Compute Board) is a board built on the Qualcomm Mahua SoC, a 12-core variant of the Glymur QCB in an open form factor. Unlike the CRD, the QCB is suitable for compute workloads, power measurements and oscilloscope-based hardware validation. Add a compatible for this board. Signed-off-by: Gopikrishna Garmidi Signed-off-by: Pradyot Kumar Nayak --- Documentation/devicetree/bindings/arm/qcom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index 5c4b516895f4b..f6bb8defc5297 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -75,6 +75,7 @@ properties: - items: - enum: - qcom,mahua-crd + - qcom,mahua-qcb - const: qcom,mahua - items: From 57225cc9ee93a61e2b5a79cbfeabadf12fb5362c Mon Sep 17 00:00:00 2001 From: Gopikrishna Garmidi Date: Wed, 1 Jul 2026 05:52:20 -0700 Subject: [PATCH 050/243] PENDING: arm64: dts: qcom: mahua: Add Mahua QCB Add initial device tree support for the Mahua QCB. The QCB (Qualcomm Compute Board) is a board built on the Qualcomm Mahua SoC, a 12-core variant of the Glymur QCB in an open form factor. Unlike the CRD, the QCB is suitable for compute workloads, power measurements and oscilloscope-based hardware validation. Signed-off-by: Gopikrishna Garmidi Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/Makefile | 1 + arch/arm64/boot/dts/qcom/mahua-qcb.dts | 1329 ++++++++++++++++++++++++ 2 files changed, 1330 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/mahua-qcb.dts diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index b74caa0f74ea0..f649919e885d8 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -57,6 +57,7 @@ dtb-$(CONFIG_ARCH_QCOM) += lemans-evk-el2.dtb lemans-evk-ifp-mezzanine-dtbs := lemans-evk.dtb lemans-evk-ifp-mezzanine.dtbo dtb-$(CONFIG_ARCH_QCOM) += lemans-evk-ifp-mezzanine.dtb dtb-$(CONFIG_ARCH_QCOM) += mahua-crd.dtb +dtb-$(CONFIG_ARCH_QCOM) += mahua-qcb.dtb dtb-$(CONFIG_ARCH_QCOM) += milos-fairphone-fp6.dtb dtb-$(CONFIG_ARCH_QCOM) += milos-nothing-asteroids.dtb dtb-$(CONFIG_ARCH_QCOM) += monaco-arduino-monza.dtb diff --git a/arch/arm64/boot/dts/qcom/mahua-qcb.dts b/arch/arm64/boot/dts/qcom/mahua-qcb.dts new file mode 100644 index 0000000000000..9a447c76c8724 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/mahua-qcb.dts @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; + +#include "mahua.dtsi" +#include "pmcx0102.dtsi" /* SPMI0: SID-2/3 SPMI1: SID-2/3 */ +#include "pmh0101.dtsi" /* SPMI0: SID-1 */ +#include "pmh0110-glymur.dtsi" /* SPMI0: SID-5/7 SPMI1: SID-5 */ +#include "pmh0104-glymur.dtsi" /* SPMI0: SID-8/9 SPMI1: SID-11 */ +#include "pmk8850.dtsi" /* SPMI0: SID-0 */ +#include "smb2370.dtsi" /* SPMI2: SID-9/10/11 */ + +#include + +/delete-node/ &pmcx0102_d_e0; +/delete-node/ &pmcx0102_d0_thermal; +/delete-node/ &pmh0104_i_e0; +/delete-node/ &pmh0104_i0_thermal; +/delete-node/ &pmh0104_j_e0; +/delete-node/ &pmh0104_j0_thermal; + +/ { + model = "Qualcomm Technologies, Inc. Mahua QCB"; + compatible = "qcom,mahua-qcb", "qcom,mahua"; + + aliases { + serial0 = &uart21; + serial1 = &uart14; + i2c1 = &i2c4; + i2c2 = &i2c5; + spi0 = &spi18; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + xo_board: xo-board { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32000>; + #clock-cells = <0>; + }; + }; + + gpio-keys { + compatible = "gpio-keys"; + + pinctrl-0 = <&key_vol_up_default>; + pinctrl-names = "default"; + + key-volume-up { + label = "Volume Up"; + linux,code = ; + gpios = <&pmh0101_gpios 6 GPIO_ACTIVE_LOW>; + debounce-interval = <15>; + linux,can-disable; + wakeup-source; + }; + }; + + pmic-glink { + compatible = "qcom,glymur-pmic-glink", + "qcom,pmic-glink"; + #address-cells = <1>; + #size-cells = <0>; + + connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + pmic_glink_hs_in: endpoint { + remote-endpoint = <&usb_0_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + pmic_glink_ss_in: endpoint { + remote-endpoint = <&usb_0_qmpphy_out>; + }; + }; + }; + }; + + connector@1 { + compatible = "usb-c-connector"; + reg = <1>; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + pmic_glink_hs_in1: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + pmic_glink_ss_in1: endpoint { + remote-endpoint = <&usb_1_qmpphy_out>; + }; + }; + }; + }; + }; + + vreg_edp_3p3: regulator-edp-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_EDP_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 70 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&edp_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_misc_3p3: regulator-misc-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_MISC_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0110_f_e0_gpios 6 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&misc_3p3_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_nvme: regulator-nvme { + compatible = "regulator-fixed"; + + regulator-name = "VREG_NVME_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0101_gpios 14 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&nvme_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_nvmesec: regulator-nvmesec { + compatible = "regulator-fixed"; + + regulator-name = "VREG_NVME_SEC_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&pmh0110_f_e1_gpios 14 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&nvme_sec_reg_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_wcn_0p95: regulator-wcn-0p95 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_0P95"; + regulator-min-microvolt = <950000>; + regulator-max-microvolt = <950000>; + + vin-supply = <&vreg_wcn_3p3>; + }; + + vreg_wcn_3p3: regulator-wcn-3p3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 94 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wcn_sw_en>; + pinctrl-names = "default"; + + regulator-boot-on; + }; + + vreg_wwan: regulator-wwan { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WWAN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 246 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wwan_reg_en>; + pinctrl-names = "default"; + }; + + sound { + compatible = "qcom,glymur-sndcard"; + model = "GLYMUR-CRD"; + audio-routing = "WooferLeft IN", "WSA WSA_SPK1 OUT", + "TweeterLeft IN", "WSA WSA_SPK2 OUT", + "WooferRight IN", "WSA2 WSA_SPK2 OUT", + "TweeterRight IN", "WSA2 WSA_SPK2 OUT", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb"; + + wsa-dai-link { + link-name = "WSA Playback"; + + cpu { + sound-dai = <&q6apmbedai WSA_CODEC_DMA_RX_0>; + }; + + codec { + sound-dai = <&left_woofer>, <&left_tweeter>, + <&swr0 0>, <&lpass_wsamacro 0>, + <&right_woofer>, <&right_tweeter>, + <&swr3 0>, <&lpass_wsa2macro 0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + va-dai-link { + link-name = "VA Capture"; + + codec { + sound-dai = <&lpass_vamacro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + }; + + wcn7850-pmu { + compatible = "qcom,wcn7850-pmu"; + + vdd-supply = <&vreg_wcn_0p95>; + vddio-supply = <&vreg_l15b_e0_1p8>; + vddaon-supply = <&vreg_l15b_e0_1p8>; + vdddig-supply = <&vreg_l15b_e0_1p8>; + vddrfa1p2-supply = <&vreg_l15b_e0_1p8>; + vddrfa1p8-supply = <&vreg_l15b_e0_1p8>; + + wlan-enable-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; + bt-enable-gpios = <&tlmm 116 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&wcn_wlan_bt_en>; + pinctrl-names = "default"; + + 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 { + regulators-0 { + compatible = "qcom,pmh0101-rpmh-regulators"; + qcom,pmic-id = "B_E0"; + + vreg_bob1_e0: bob1 { + regulator-name = "vreg_bob1_e0"; + regulator-min-microvolt = <2200000>; + regulator-max-microvolt = <4224000>; + regulator-initial-mode = ; + }; + + vreg_bob2_e0: bob2 { + regulator-name = "vreg_bob2_e0"; + regulator-min-microvolt = <2540000>; + regulator-max-microvolt = <3600000>; + regulator-initial-mode = ; + }; + + vreg_l1b_e0_1p8: ldo1 { + regulator-name = "vreg_l1b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l2b_e0_2p9: ldo2 { + regulator-name = "vreg_l2b_e0_2p9"; + regulator-min-microvolt = <2904000>; + regulator-max-microvolt = <2904000>; + regulator-initial-mode = ; + }; + + vreg_l7b_e0_2p79: ldo7 { + regulator-name = "vreg_l7b_e0_2p79"; + regulator-min-microvolt = <2790000>; + regulator-max-microvolt = <2792000>; + regulator-initial-mode = ; + }; + + vreg_l8b_e0_1p50: ldo8 { + regulator-name = "vreg_l8b_e0_1p50"; + regulator-min-microvolt = <1504000>; + regulator-max-microvolt = <1504000>; + regulator-initial-mode = ; + }; + + vreg_l9b_e0_2p7: ldo9 { + regulator-name = "vreg_l9b_e0_2p7"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <2704000>; + regulator-initial-mode = ; + }; + + vreg_l10b_e0_1p8: ldo10 { + regulator-name = "vreg_l10b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l11b_e0_1p2: ldo11 { + regulator-name = "vreg_l11b_e0_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_l12b_e0_1p14: ldo12 { + regulator-name = "vreg_l12b_e0_1p14"; + regulator-min-microvolt = <1144000>; + regulator-max-microvolt = <1144000>; + regulator-initial-mode = ; + }; + + vreg_l15b_e0_1p8: ldo15 { + regulator-name = "vreg_l15b_e0_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l17b_e0_2p4: ldo17 { + regulator-name = "vreg_l17b_e0_2p4"; + regulator-min-microvolt = <2400000>; + regulator-max-microvolt = <2700000>; + regulator-initial-mode = ; + }; + + vreg_l18b_e0_1p2: ldo18 { + regulator-name = "vreg_l18b_e0_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + }; + + regulators-1 { + compatible = "qcom,pmcx0102-rpmh-regulators"; + qcom,pmic-id = "C_E1"; + + vreg_l1c_e1_0p82: ldo1 { + regulator-name = "vreg_l1c_e1_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2c_e1_1p14: ldo2 { + regulator-name = "vreg_l2c_e1_1p14"; + regulator-min-microvolt = <1144000>; + regulator-max-microvolt = <1144000>; + regulator-initial-mode = ; + }; + + vreg_l3c_e1_0p89: ldo3 { + regulator-name = "vreg_l3c_e1_0p89"; + regulator-min-microvolt = <890000>; + regulator-max-microvolt = <980000>; + regulator-initial-mode = ; + }; + + vreg_l4c_e1_0p72: ldo4 { + regulator-name = "vreg_l4c_e1_0p72"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <720000>; + regulator-initial-mode = ; + }; + }; + + regulators-2 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "F_E0"; + + vreg_s7f_e0_1p32: smps7 { + regulator-name = "vreg_s7f_e0_1p32"; + regulator-min-microvolt = <1320000>; + regulator-max-microvolt = <1352000>; + regulator-initial-mode = ; + }; + + vreg_s8f_e0_0p95: smps8 { + regulator-name = "vreg_s8f_e0_0p95"; + regulator-min-microvolt = <952000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_s9f_e0_1p9: smps9 { + regulator-name = "vreg_s9f_e0_1p9"; + regulator-min-microvolt = <1900000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + }; + + vreg_l2f_e0_0p82: ldo2 { + regulator-name = "vreg_l2f_e0_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l3f_e0_0p72: ldo3 { + regulator-name = "vreg_l3f_e0_0p72"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <720000>; + regulator-initial-mode = ; + }; + + vreg_l4f_e0_0p3: ldo4 { + regulator-name = "vreg_l4f_e0_0p3"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + }; + + regulators-3 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "F_E1"; + + vreg_s7f_e1_0p3: smps7 { + regulator-name = "vreg_s7f_e1_0p3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_l1f_e1_0p82: ldo1 { + regulator-name = "vreg_l1f_e1_0p82"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2f_e1_0p83: ldo2 { + regulator-name = "vreg_l2f_e1_0p83"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l4f_e1_1p08: ldo4 { + regulator-name = "vreg_l4f_e1_1p08"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1320000>; + regulator-initial-mode = ; + }; + }; + + regulators-4 { + compatible = "qcom,pmh0110-rpmh-regulators"; + qcom,pmic-id = "H_E0"; + + vreg_l1h_e0_0p89: ldo1 { + regulator-name = "vreg_l1h_e0_0p89"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l2h_e0_0p72: ldo2 { + regulator-name = "vreg_l2h_e0_0p72"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <832000>; + regulator-initial-mode = ; + }; + + vreg_l3h_e0_0p32: ldo3 { + regulator-name = "vreg_l3h_e0_0p32"; + regulator-min-microvolt = <320000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + }; + + vreg_l4h_e0_1p2: ldo4 { + regulator-name = "vreg_l4h_e0_1p2"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1320000>; + regulator-initial-mode = ; + }; + }; +}; + +&camss { + vdd-csiphy-0p8-supply = <&vreg_l1f_e1_0p82>; + vdd-csiphy-1p2-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + /* + * port0 => csiphy0 + * port1 => csiphy1 + * port2 => csiphy4 + */ + port@2 { + reg = <2>; + + csiphy4_ep: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&ov08x40_ep>; + }; + }; + }; +}; + +&cci1 { + pinctrl-0 = <&cci1_1_default>; + pinctrl-1 = <&cci1_1_sleep>; + + status = "okay"; +}; + +&cci1_i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + camera@36 { + compatible = "ovti,ov08x40"; + reg = <0x36>; + + reset-gpios = <&tlmm 239 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&cam_mclk4_default &cam_reset4_default>; + pinctrl-names = "default"; + + clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_l7p>; + dovdd-supply = <&vreg_l4p>; + + port { + ov08x40_ep: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + remote-endpoint = <&csiphy4_ep>; + }; + }; + }; +}; + +&lpass_vamacro { + pinctrl-0 = <&dmic01_default>, <&dmic23_default>; + pinctrl-names = "default"; + qcom,dmic-sample-rate = <4800000>; +}; + +&i2c5 { + clock-frequency = <400000>; + + status = "okay"; + + ptn3222_0: redriver@43 { + compatible = "nxp,ptn3222"; + reg = <0x43>; + + reset-gpios = <&tlmm 8 GPIO_ACTIVE_LOW>; + + vdd3v3-supply = <&vreg_l8b_e0_1p50>; + vdd1v8-supply = <&vreg_l15b_e0_1p8>; + + #phy-cells = <0>; + }; + + ptn3222_1: redriver@47 { + compatible = "nxp,ptn3222"; + reg = <0x47>; + + reset-gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; + + vdd3v3-supply = <&vreg_l8b_e0_1p50>; + vdd1v8-supply = <&vreg_l15b_e0_1p8>; + + #phy-cells = <0>; + }; + + pm8010: pmic@8 { + compatible = "qcom,pm8008"; + reg = <0x8>; + + interrupts-extended = <&tlmm 90 IRQ_TYPE_EDGE_RISING>; + reset-gpios = <&tlmm 106 GPIO_ACTIVE_LOW>; + + vdd-l1-l2-supply = <&vreg_l6p>; + vdd-l3-l4-supply = <&vreg_bob1_e0>; + vdd-l5-supply = <&vreg_bob1_e0>; + vdd-l6-supply = <&vreg_bob1_e0>; + vdd-l7-supply = <&vreg_bob1_e0>; + + pinctrl-names = "default"; + pinctrl-0 = <&pm8010_default>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pm8010 0 0 2>; + + interrupt-controller; + #interrupt-cells = <2>; + + #thermal-sensor-cells = <0>; + + regulators { + vreg_l1p: ldo1 { + regulator-name = "vreg_l1p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l2p: ldo2 { + regulator-name = "vreg_l2p"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + vreg_l3p: ldo3 { + regulator-name = "vreg_l3p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l4p: ldo4 { + regulator-name = "vreg_l4p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l5p: ldo5 { + regulator-name = "vreg_l5p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + vreg_l6p: ldo6 { + regulator-name = "vreg_l6p"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + vreg_l7p: ldo7 { + regulator-name = "vreg_l7p"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + }; + }; +}; + +&i2c9 { + clock-frequency = <400000>; + + status = "okay"; + + embedded-controller@76 { + compatible = "qcom,glymur-crd-ec", "qcom,hamoa-crd-ec"; + reg = <0x76>; + + interrupts-extended = <&tlmm 66 IRQ_TYPE_EDGE_FALLING>; + + pinctrl-0 = <&ec_int_n_default>; + pinctrl-names = "default"; + }; +}; + +&pcie3b { + vddpe-3v3-supply = <&vreg_nvmesec>; + + pinctrl-0 = <&pcie3b_default>; + pinctrl-names = "default"; +}; + +&pcie3b_phy { + vdda-phy-supply = <&vreg_l3c_e1_0p89>; + vdda-pll-supply = <&vreg_l2c_e1_1p14>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; +}; + +&pcie3b_port0 { + reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; +}; + +&pcie4 { + pinctrl-0 = <&pcie4_default>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie4_phy { + vdda-phy-supply = <&vreg_l1c_e1_0p82>; + vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + + status = "okay"; +}; + +&pcie4_port0 { + reset-gpios = <&tlmm 146 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 148 GPIO_ACTIVE_LOW>; + + wifi@0 { + compatible = "pci17cb,1107"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>; + vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>; + }; +}; + +&pcie5 { + vddpe-3v3-supply = <&vreg_nvme>; + + pinctrl-0 = <&pcie5_default>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie5_phy { + vdda-phy-supply = <&vreg_l2f_e0_0p82>; + vdda-pll-supply = <&vreg_l4h_e0_1p2>; + vdda-qref-supply = <&vreg_l3f_e0_0p72>; + + status = "okay"; +}; + +&pcie5_port0 { + reset-gpios = <&tlmm 152 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 154 GPIO_ACTIVE_LOW>; +}; + +&pcie6 { + vddpe-3v3-supply = <&vreg_wwan>; + + pinctrl-0 = <&pcie6_default>; + pinctrl-names = "default"; + + status = "disabled"; +}; + +&pcie6_phy { + vdda-phy-supply = <&vreg_l1c_e1_0p82>; + vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-qref-supply = <&vreg_l1f_e1_0p82>; + vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + + status = "disabled"; +}; + +&pcie6_port0 { + reset-gpios = <&tlmm 149 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 151 GPIO_ACTIVE_LOW>; +}; + +&pmh0101_gpios { + nvme_reg_en: nvme-reg-en-state { + pins = "gpio14"; + function = "normal"; + bias-disable; + }; +}; + +&pmh0110_f_e1_gpios { + nvme_sec_reg_en: nvme-reg-en-state { + pins = "gpio14"; + function = "normal"; + bias-disable; + }; +}; + +&pmh0101_gpios { + key_vol_up_default: key-vol-up-default-state { + pins = "gpio6"; + function = "normal"; + output-disable; + bias-pull-up; + }; +}; + +&pmh0110_f_e0_gpios { + misc_3p3_reg_en: misc-3p3-reg-en-state { + pins = "gpio6"; + function = "normal"; + bias-disable; + input-disable; + output-enable; + drive-push-pull; + power-source = <1>; /* 1.8 V */ + qcom,drive-strength = ; + }; +}; + +&pmk8850_rtc { + qcom,no-alarm; +}; + +&pon_resin { + linux,code = ; + status = "okay"; +}; + +&smb2370_l_e2 { + /* SID 11 charger is not populated on the QCB */ + status = "disabled"; +}; + +&smb2370_j_e2_eusb2_repeater { + vdd18-supply = <&vreg_l15b_e0_1p8>; + vdd3-supply = <&vreg_l7b_e0_2p79>; +}; + +&smb2370_k_e2_eusb2_repeater { + vdd18-supply = <&vreg_l15b_e0_1p8>; + vdd3-supply = <&vreg_l7b_e0_2p79>; +}; + +&swr0 { + status = "okay"; + + /* WSA8845, Left Woofer */ + left_woofer: speaker@0,0 { + compatible = "sdw20217020400"; + reg = <0 0>; + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "WooferLeft"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <1 2 3 7 12 14>; + }; + + /* WSA8845, Left Tweeter */ + left_tweeter: speaker@0,1 { + compatible = "sdw20217020400"; + reg = <0 1>; + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "TweeterLeft"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <4 5 6 7 13 15>; + }; +}; + +&swr3 { + status = "okay"; + + /* WSA8845, Right Woofer */ + right_woofer: speaker@0,0 { + compatible = "sdw20217020400"; + reg = <0 0>; + reset-gpios = <&lpass_tlmm 13 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "WooferRight"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <1 2 3 7 12 14>; + }; + + /* WSA8845, Right Tweeter */ + right_tweeter: speaker@0,1 { + compatible = "sdw20217020400"; + reg = <0 1>; + reset-gpios = <&lpass_tlmm 13 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <0>; + sound-name-prefix = "TweeterRight"; + vdd-1p8-supply = <&vreg_l15b_e0_1p8>; + vdd-io-supply = <&vreg_l18b_e0_1p2>; + qcom,port-mapping = <4 5 6 7 13 15>; + }; +}; + +&remoteproc_adsp { + firmware-name = "qcom/glymur/adsp.mbn", + "qcom/glymur/adsp_dtb.mbn"; + + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/glymur/cdsp.mbn", + "qcom/glymur/cdsp_dtb.mbn"; + + status = "okay"; +}; + +&remoteproc_soccp { + firmware-name = "qcom/glymur/soccp.mbn", + "qcom/glymur/soccp_dtb.mbn"; + + status = "okay"; +}; + +&psci { + reboot-mode { + mode-edl = <0x80000000 0x1>; + }; +}; + +&thermal_zones { + pm8010-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&pm8010>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; +}; + +&tlmm { + gpio-reserved-ranges = <4 4>, /* EC TZ Secure I3C */ + <10 2>, /* OOB UART */ + <44 4>; /* Security SPI (TPM) */ + + cam_reset4_default: cam-reset4-default-state { + pins = "gpio239"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + edp_bl_en: edp-bl-en-state { + pins = "gpio18"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + + edp_reg_en: edp-reg-en-state { + pins = "gpio70"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + + ec_int_n_default: ec-int-n-state { + pins = "gpio66"; + function = "gpio"; + bias-disable; + }; + + pcie4_default: pcie4-default-state { + clkreq-n-pins { + pins = "gpio147"; + function = "pcie4_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio146"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio148"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie5_default: pcie5-default-state { + clkreq-n-pins { + pins = "gpio153"; + function = "pcie5_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio152"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio154"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie6_default: pcie6-default-state { + clkreq-n-pins { + pins = "gpio150"; + function = "pcie6_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio149"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio151"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie3b_default: pcie3b-default-state { + clkreq-n-pins { + pins = "gpio156"; + function = "pcie3b_clk"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio155"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio157"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pm8010_default: pm8010-default-state { + int-pins { + pins = "gpio90"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + }; + + reset-n-pins { + pins = "gpio106"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + }; + + tpad_default: tpad-default-state { + pins = "gpio3"; + function = "gpio"; + bias-disable; + }; + + ts0_default: ts0-default-state { + int-n-pins { + pins = "gpio51"; + function = "gpio"; + bias-disable; + }; + + reset-n-pins { + pins = "gpio48"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + }; + + wcn_wlan_bt_en: wcn-wlan-bt-en-state { + pins = "gpio116", "gpio117"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wcn_sw_en: wcn-sw-en-state { + pins = "gpio94"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wwan_reg_en: wwan-reg-en-state { + pins = "gpio246"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; +}; + +&usb_0 { + status = "okay"; +}; + +&usb_0_dwc3_hs { + remote-endpoint = <&pmic_glink_hs_in>; +}; + +&usb_0_hsphy { + vdd-supply = <&vreg_l3f_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&smb2370_j_e2_eusb2_repeater>; + + status = "okay"; +}; + +&usb_0_qmpphy { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l3f_e0_0p72>; + refgen-supply = <&vreg_l2f_e0_0p82>; + + status = "okay"; +}; + +&usb_0_qmpphy_out { + remote-endpoint = <&pmic_glink_ss_in>; +}; + +&usb_1 { + status = "okay"; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&pmic_glink_hs_in1>; +}; + +&usb_1_hsphy { + vdd-supply = <&vreg_l3f_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&smb2370_k_e2_eusb2_repeater>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l1h_e0_0p89>; + refgen-supply = <&vreg_l2f_e0_0p82>; + + status = "okay"; +}; + +&usb_1_qmpphy_out { + remote-endpoint = <&pmic_glink_ss_in1>; +}; + +&usb_hs { + status = "okay"; +}; + +&usb_hs_phy { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&ptn3222_1>; + + status = "okay"; +}; + +&usb_mp { + status = "okay"; +}; + +&usb_mp_hsphy0 { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + phys = <&ptn3222_0>; + + status = "okay"; +}; + +&usb_mp_hsphy1 { + vdd-supply = <&vreg_l2h_e0_0p72>; + vdda12-supply = <&vreg_l4h_e0_1p2>; + + status = "okay"; +}; + +&usb_mp_qmpphy0 { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l2h_e0_0p72>; + refgen-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; +}; + +&usb_mp_qmpphy1 { + vdda-phy-supply = <&vreg_l4h_e0_1p2>; + vdda-pll-supply = <&vreg_l2h_e0_0p72>; + refgen-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; +}; From a4cd987afb830c234923a59d41a453805bd8c410 Mon Sep 17 00:00:00 2001 From: Vinayak Katoch Date: Wed, 1 Jul 2026 15:35:31 +0530 Subject: [PATCH 051/243] FROMLIST: arm64: dts: qcom: glymur: Add memory-region for audio PD Reserve memory region for audio PD dynamic loading and remote heap requirements. Add the required VMID list for memory ownership transfers. Link: https://lore.kernel.org/all/20260701-glymur-audio-v1-1-2c3862d95a09@oss.qualcomm.com/ Reviewed-by: Ekansh Gupta Signed-off-by: Vinayak Katoch --- arch/arm64/boot/dts/qcom/glymur.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 8a4d0965eb43f..87761cc44f8b2 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -700,6 +701,14 @@ hwlocks = <&tcsr_mutex 3>; no-map; }; + + adsp_rpc_remote_heap_mem: adsp-rpc-remote-heap { + compatible = "shared-dma-pool"; + alloc-ranges = <0x0 0x80000000 0x0 0x80000000>; + reusable; + alignment = <0x0 0x400000>; + size = <0x0 0x800000>; + }; }; smp2p-adsp { @@ -4388,6 +4397,9 @@ compatible = "qcom,glymur-fastrpc", "qcom,kaanapali-fastrpc"; qcom,glink-channels = "fastrpcglink-apps-dsp"; label = "adsp"; + memory-region = <&adsp_rpc_remote_heap_mem>; + qcom,vmids = ; #address-cells = <1>; #size-cells = <0>; From 8bd4cd2448577bb87d9bd9298ff0a5f31577d9a1 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 23 Jun 2026 06:05:19 -0700 Subject: [PATCH 052/243] FROMLIST: arm64: dts: qcom: glymur-crd: Add refgen supplies for PCIe PHY on Glymur The PCIe PHYs on Glymur require a reference voltage provided by REFGEN, which in turn is powered by two LDOs. Since there is no devicetree node for REFGEN, add the vdda-refgen0p9 and vdda-refgen1p2 supplies for each PCIe PHY node. Link: https://lore.kernel.org/all/20260623-phy_refgen-v2-0-4d15983bf91d@oss.qualcomm.com/ Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index dd53e6459cbbe..f9b840454898b 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -888,6 +888,8 @@ &pcie3b_phy { vdda-phy-supply = <&vreg_l3c_e1_0p89>; vdda-pll-supply = <&vreg_l2c_e1_1p14>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; }; &pcie3b_port0 { @@ -905,6 +907,8 @@ &pcie4_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "okay"; }; @@ -941,6 +945,8 @@ &pcie5_phy { vdda-phy-supply = <&vreg_l2f_e0_0p82>; vdda-pll-supply = <&vreg_l4h_e0_1p2>; + vdda-refgen0p9-supply = <&vreg_l2f_e0_0p82>; + vdda-refgen1p2-supply = <&vreg_l4h_e0_1p2>; status = "okay"; }; @@ -962,6 +968,8 @@ &pcie6_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "okay"; }; From 3bc3293493c57774c30658087fb4f485284278d5 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Thu, 2 Jul 2026 00:36:16 -0700 Subject: [PATCH 053/243] FROMLIST: arm64: dts: qcom: glymur: Add QREF regulator supplies to TCSR The TCSR clkref_en clocks gate the QREF block which provides reference clocks to the PCIe PHYs. Wire up the LDO supplies required by the QREF and refgen blocks on the CRD board. Link: https://lore.kernel.org/all/20260702-tcsr_qref_0702-v7-0-776f2811b7af@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-crd.dts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dts b/arch/arm64/boot/dts/qcom/glymur-crd.dts index 6125617de82a6..44766a73b0d1a 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dts +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dts @@ -28,3 +28,23 @@ &mdss_dp1_out { link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>; }; + +&tcsr { + vdda-qrefrpt0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt3-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrpt4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx5-0p9-supply = <&vreg_l3f_e0_0p72>; + vdda-qreftx0-0p9-supply = <&vreg_l3f_e0_0p72>; + vdda-qreftx0-1p2-supply = <&vreg_l4h_e0_1p2>; + vdda-qreftx1-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-0p9-supply = <&vreg_l2f_e0_0p82>; + vdda-refgen3-1p2-supply = <&vreg_l4h_e0_1p2>; + vdda-refgen4-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen4-1p2-supply = <&vreg_l4f_e1_1p08>; +}; From 30dc8829827d6fcc34a7df4c523d0e8e2c3fbb3d Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Thu, 2 Jul 2026 00:36:17 -0700 Subject: [PATCH 054/243] FROMLIST: arm64: dts: qcom: mahua: Add QREF regulator supplies to TCSR Mahua has a different QREF topology from Glymur. Override the TCSR compatible to qcom,mahua-tcsr in mahua.dtsi, and wire up the required LDO supplies on the CRD board. Unlike the other PCIe controllers, PCIe5 PHY on Mahua gets its refclk from the CXO0 pad directly and requires no QREF clkref_en voting. Hence, point its ref clock at RPMH_CXO_CLK. Link: https://lore.kernel.org/all/20260702-tcsr_qref_0702-v7-0-776f2811b7af@oss.qualcomm.com/ Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/mahua-crd.dts | 16 ++++++++++++++++ arch/arm64/boot/dts/qcom/mahua.dtsi | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/mahua-crd.dts b/arch/arm64/boot/dts/qcom/mahua-crd.dts index 9c8244e892ddd..fa5229064b10e 100644 --- a/arch/arm64/boot/dts/qcom/mahua-crd.dts +++ b/arch/arm64/boot/dts/qcom/mahua-crd.dts @@ -19,3 +19,19 @@ model = "Qualcomm Technologies, Inc. Mahua CRD"; compatible = "qcom,mahua-crd", "qcom,mahua"; }; + +&tcsr { + vdda-qrefrpt0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt3-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-qrefrpt4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrpt5-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx3-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qreftx1-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-1p2-supply = <&vreg_l4f_e1_1p08>; +}; diff --git a/arch/arm64/boot/dts/qcom/mahua.dtsi b/arch/arm64/boot/dts/qcom/mahua.dtsi index 22822b6b2e8b9..e6c0597089122 100644 --- a/arch/arm64/boot/dts/qcom/mahua.dtsi +++ b/arch/arm64/boot/dts/qcom/mahua.dtsi @@ -115,6 +115,15 @@ compatible = "qcom,mahua-oobm-ss-noc", "qcom,glymur-oobm-ss-noc"; }; +&pcie5_phy { + clocks = <&gcc GCC_PCIE_PHY_5_AUX_CLK>, + <&gcc GCC_PCIE_5_CFG_AHB_CLK>, + <&rpmhcc RPMH_CXO_CLK>, + <&gcc GCC_PCIE_5_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_5_PIPE_CLK>, + <&gcc GCC_PCIE_5_PIPE_DIV2_CLK>; +}; + &pcie_east_anoc { compatible = "qcom,mahua-pcie-east-anoc", "qcom,glymur-pcie-east-anoc"; }; @@ -286,6 +295,10 @@ }; }; +&tcsr { + compatible = "qcom,mahua-tcsr", "syscon"; +}; + &tlmm { compatible = "qcom,mahua-tlmm"; }; From 7e515f9b56cd6fb5e60b9f6e780961bf05997a93 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sat, 4 Jul 2026 09:41:26 +0800 Subject: [PATCH 055/243] PENDING: arm64: dts: qcom: glymur: Add QREF regulator supplies to TCSR for glymur-qcb Wire up the required LDO supplies on the QCB board. Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-qcb.dts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-qcb.dts b/arch/arm64/boot/dts/qcom/glymur-qcb.dts index 5b4e146753621..bf535603a5867 100644 --- a/arch/arm64/boot/dts/qcom/glymur-qcb.dts +++ b/arch/arm64/boot/dts/qcom/glymur-qcb.dts @@ -1214,6 +1214,26 @@ }; }; +&tcsr { + vdda-qrefrpt0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt3-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrpt4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx5-0p9-supply = <&vreg_l3f_e0_0p72>; + vdda-qreftx0-0p9-supply = <&vreg_l3f_e0_0p72>; + vdda-qreftx0-1p2-supply = <&vreg_l4h_e0_1p2>; + vdda-qreftx1-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-0p9-supply = <&vreg_l2f_e0_0p82>; + vdda-refgen3-1p2-supply = <&vreg_l4h_e0_1p2>; + vdda-refgen4-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen4-1p2-supply = <&vreg_l4f_e1_1p08>; +}; + &usb_0 { status = "okay"; }; From b54868d8a7f1bc8cb8fd9d2afa160aaeda01a7af Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sat, 4 Jul 2026 09:43:24 +0800 Subject: [PATCH 056/243] PENDING: arm64: dts: qcom: glymur: Add QREF regulator supplies to TCSR for mahua-qcb Wire up the required LDO supplies on the QCB board. Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/mahua-qcb.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/mahua-qcb.dts b/arch/arm64/boot/dts/qcom/mahua-qcb.dts index 9a447c76c8724..1c1066bf3a5ad 100644 --- a/arch/arm64/boot/dts/qcom/mahua-qcb.dts +++ b/arch/arm64/boot/dts/qcom/mahua-qcb.dts @@ -1221,6 +1221,22 @@ }; }; +&tcsr { + vdda-qrefrpt0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrpt3-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-qrefrpt4-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrpt5-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qrefrx0-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx1-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx2-0p9-supply = <&vreg_l2f_e1_0p83>; + vdda-qrefrx3-0p9-supply = <&vreg_l2h_e0_0p72>; + vdda-qreftx1-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-0p9-supply = <&vreg_l1f_e1_0p82>; + vdda-refgen3-1p2-supply = <&vreg_l4f_e1_1p08>; +}; + &usb_0 { status = "okay"; }; From 56e0fe0fc4de7af23dec658e0e24d8c088d943b9 Mon Sep 17 00:00:00 2001 From: Kamal Wadhwa Date: Thu, 2 Jul 2026 15:10:53 +0530 Subject: [PATCH 057/243] FROMLIST: arm64: dts: qcom: glymur-crd: Update VREG l2b_e0 and l9b_e0 voltage for SD-card SD cards may need 1.8v VDDIO also to be supported, to accommodate this requirement reduce the min voltage to 1.8v for `vreg_l2b_e0` which supplies to VDDIO pin of SD card. NOTE - Since this SD card is the only client on this regulator, this change should not have any side effect on any other clients. moreover, SD card driver takes care to explicitly vote for the regulator voltage based on the SD card detection sequence. Also for stable operation of the SD card increase VDD voltage supplied by `vreg_l9b_e0` to 2.96v. Signed-off-by: Kamal Wadhwa Signed-off-by: Monish Chunara Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/all/20260702094056.3755467-2-mchunara@oss.qualcomm.com/ Signed-off-by: Pradeep P V K --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index f9b840454898b..3dbe44463cf2f 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -372,7 +372,7 @@ vreg_l2b_e0_2p9: ldo2 { regulator-name = "vreg_l2b_e0_2p9"; - regulator-min-microvolt = <2904000>; + regulator-min-microvolt = <1804000>; regulator-max-microvolt = <2904000>; regulator-initial-mode = ; }; @@ -391,10 +391,10 @@ regulator-initial-mode = ; }; - vreg_l9b_e0_2p7: ldo9 { - regulator-name = "vreg_l9b_e0_2p7"; - regulator-min-microvolt = <2704000>; - regulator-max-microvolt = <2704000>; + vreg_l9b_e0_2p9: ldo9 { + regulator-name = "vreg_l9b_e0_2p9"; + regulator-min-microvolt = <2960000>; + regulator-max-microvolt = <2960000>; regulator-initial-mode = ; }; From 2e878af0c84bb8f0d16cc5bfa555663275481541 Mon Sep 17 00:00:00 2001 From: Monish Chunara Date: Thu, 2 Jul 2026 15:10:54 +0530 Subject: [PATCH 058/243] FROMLIST: dt-bindings: mmc: sdhci-msm: Document the Glymur compatible Document the Glymur-specific SDHCI compatible in the sdhci-msm binding. Use "qcom,sdhci-msm-v5" as the fallback compatible for the MSM SDHCI v5 controller used on Glymur. Signed-off-by: Monish Chunara Link: https://lore.kernel.org/all/20260703-loutish-stimulating-hummingbird-aada5e@quoll/ Signed-off-by: Pradeep P V K --- Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml index bd558a11b7929..6a8ef84617a93 100644 --- a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml +++ b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml @@ -38,6 +38,7 @@ properties: - items: - enum: - qcom,eliza-sdhci + - qcom,glymur-sdhci - qcom,hawi-sdhci - qcom,ipq5018-sdhci - qcom,ipq5210-sdhci From 55ec64f8053983751469569b3810d814d0afc9ad Mon Sep 17 00:00:00 2001 From: Monish Chunara Date: Thu, 2 Jul 2026 15:10:55 +0530 Subject: [PATCH 059/243] FROMLIST: arm64: dts: qcom: Add SD Card support for Glymur SoC Add support for SD card on Glymur SoC and enable the required pinctrl configurations. Co-developed-by: Sachin Rathore Signed-off-by: Sachin Rathore Signed-off-by: Monish Chunara Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260702094056.3755467-4-mchunara@oss.qualcomm.com/ Signed-off-by: Pradeep P V K --- arch/arm64/boot/dts/qcom/glymur.dtsi | 91 ++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 87761cc44f8b2..b6fbd802a51b9 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -4743,6 +4743,58 @@ #interconnect-cells = <2>; }; + sdhc_2: mmc@8804000 { + compatible = "qcom,glymur-sdhci", "qcom,sdhci-msm-v5"; + + reg = <0x0 0x08804000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "hc_irq", + "pwr_irq"; + + clocks = <&gcc GCC_SDCC2_AHB_CLK>, + <&gcc GCC_SDCC2_APPS_CLK>, + <&rpmhcc RPMH_CXO_CLK>; + clock-names = "iface", + "core", + "xo"; + + iommus = <&apps_smmu 0xd00 0x0>; + qcom,dll-config = <0x0007442c>; + qcom,ddr-config = <0x80040868>; + + power-domains = <&rpmhpd RPMHPD_CX>; + operating-points-v2 = <&sdhc2_opp_table>; + + interconnects = <&aggre3_noc MASTER_SDCC_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_SDCC_2 QCOM_ICC_TAG_ACTIVE_ONLY>; + interconnect-names = "sdhc-ddr", + "cpu-sdhc"; + + bus-width = <4>; + dma-coherent; + + resets = <&gcc GCC_SDCC2_BCR>; + + status = "disabled"; + + sdhc2_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-100000000 { + opp-hz = /bits/ 64 <100000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; + + opp-202000000 { + opp-hz = /bits/ 64 <202000000>; + required-opps = <&rpmhpd_opp_nom>; + }; + }; + }; + usb_2_hsphy: phy@88e0000 { compatible = "qcom,glymur-m31-eusb2-phy", "qcom,sm8750-m31-eusb2-phy"; @@ -6835,6 +6887,45 @@ bias-disable; }; }; + sdc2_default_state: sdc2-default-state { + clk-pins { + pins = "sdc2_clk"; + drive-strength = <16>; + bias-disable; + }; + + cmd-pins { + pins = "sdc2_cmd"; + drive-strength = <10>; + bias-pull-up; + }; + + data-pins { + pins = "sdc2_data"; + drive-strength = <10>; + bias-pull-up; + }; + }; + + sdc2_sleep_state: sdc2-sleep-state { + clk-pins { + pins = "sdc2_clk"; + drive-strength = <2>; + bias-disable; + }; + + cmd-pins { + pins = "sdc2_cmd"; + drive-strength = <2>; + bias-pull-up; + }; + + data-pins { + pins = "sdc2_data"; + drive-strength = <2>; + bias-pull-up; + }; + }; }; stm: stm@10002000 { From 19104c845611d53cedfed91352187eb2f3e78389 Mon Sep 17 00:00:00 2001 From: Monish Chunara Date: Thu, 2 Jul 2026 15:10:56 +0530 Subject: [PATCH 060/243] FROMLIST: arm64: dts: qcom: Enable SD card for Glymur CRD Enable SD card for Glymur CRD platform. Configure the vmmc/vqmmc regulators and gpio-based card detection for the platform. Co-developed-by: Sachin Rathore Signed-off-by: Sachin Rathore Signed-off-by: Monish Chunara Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/20260702094056.3755467-5-mchunara@oss.qualcomm.com/ Signed-off-by: Pradeep P V K --- arch/arm64/boot/dts/qcom/glymur-crd.dts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dts b/arch/arm64/boot/dts/qcom/glymur-crd.dts index 44766a73b0d1a..d7471c823e4da 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dts +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dts @@ -48,3 +48,28 @@ vdda-refgen4-0p9-supply = <&vreg_l1f_e1_0p82>; vdda-refgen4-1p2-supply = <&vreg_l4f_e1_1p08>; }; + +&sdhc_2 { + vmmc-supply = <&vreg_l9b_e0_2p9>; + vqmmc-supply = <&vreg_l2b_e0_2p9>; + + pinctrl-0 = <&sdc2_default_state &sdc2_card_det_n>; + pinctrl-1 = <&sdc2_sleep_state &sdc2_card_det_n>; + pinctrl-names = "default", "sleep"; + + cd-gpios = <&tlmm 221 GPIO_ACTIVE_LOW>; + + no-mmc; + no-sdio; + + status = "okay"; +}; + +&tlmm { + sdc2_card_det_n: sd-card-det-n-state { + pins = "gpio221"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; +}; From 9b9c0cf7d87e1b7efecb44fd7de7b4f55e14b851 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Fri, 10 Jul 2026 10:39:04 +0800 Subject: [PATCH 061/243] FROMLIST: arm64: dts: qcom: glymur: use Aggregator TNOC compatible The traceNoC node is the system-level Aggregator TNOC, so it must own a valid ATID that tags the whole aggregation path. It was marked compatible with "qcom,coresight-itnoc", an Interconnect TNOC, which is never assigned an ATID. As a result the aggregator had no trace ID and could not tag the merged trace. An Interconnect TNOC is a subsystem-level aggregator: it merges trace from the ATB sources within its subsystem (TPDMs and other ATB masters) and forwards the combined stream to the system-level Aggregator TNOC. It carries no ATID of its own, because the Aggregator TNOC downstream in the path already owns the ATID. Switch the node to "qcom,coresight-tnoc" so it is described as the Aggregator TNOC it is and is assigned a system trace ID. Rename the node to "tn" and use the "apb_pclk" clock name as required by the Aggregator TNOC binding. Link: https://lore.kernel.org/all/20260710-fix-tracenoc-probe-issue-v6-5-41eb36fef8d9@oss.qualcomm.com/ Fixes: 1f7d0c42a08d ("arm64: dts: qcom: glymur: add coresight nodes") Signed-off-by: Jie Gan --- arch/arm64/boot/dts/qcom/glymur.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index b6fbd802a51b9..3bcf1992c754e 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -7258,12 +7258,12 @@ }; }; - itnoc@11200000 { - compatible = "qcom,coresight-itnoc"; + tn@11200000 { + compatible = "qcom,coresight-tnoc"; reg = <0x0 0x11200000 0x0 0x3c00>; clocks = <&aoss_qmp>; - clock-names = "apb"; + clock-names = "apb_pclk"; in-ports { #address-cells = <1>; From aba48bfccf1b818e4b7cce356894f00f738a3102 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Tue, 14 Jul 2026 14:50:58 +0800 Subject: [PATCH 062/243] FROMLIST: arm64: dts: qcom: glymur: 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/20260714-add-ctcu-etr-for-glymur-v1-1-791de63c0713@oss.qualcomm.com/ Signed-off-by: Jie Gan --- arch/arm64/boot/dts/qcom/glymur.dtsi | 152 +++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 3bcf1992c754e..0b16f38e51f00 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -6928,6 +6928,35 @@ }; }; + ctcu@10001000 { + compatible = "qcom,glymur-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: stm@10002000 { compatible = "arm,coresight-stm", "arm,primecell"; reg = <0x0 0x10002000 0x0 0x1000>, @@ -6958,6 +6987,14 @@ #address-cells = <1>; #size-cells = <0>; + port@0 { + reg = <0>; + + swao_rep_out0: endpoint { + remote-endpoint = <&qdss_rep_in>; + }; + }; + port@1 { reg = <1>; @@ -7041,6 +7078,121 @@ }; }; + 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 0x00e0 0x0000>; + 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@1004f000 { + compatible = "arm,coresight-tmc", "arm,primecell"; + reg = <0x0 0x1004f000 0x0 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + iommus = <&apps_smmu 0x0100 0x0000>; + arm,scatter-gather; + + in-ports { + port { + etr1_in: endpoint { + remote-endpoint = <&etr_rep_out1>; + }; + }; + }; + + out-ports { + port { + etr1_out: endpoint { + remote-endpoint = <&ctcu_in1>; + }; + }; + }; + }; + tpdm@1102c000 { compatible = "qcom,coresight-tpdm", "arm,primecell"; reg = <0x0 0x1102c000 0x0 0x1000>; From f011fd8387d2a67e7bd8eccef36b635bf0e1a17a Mon Sep 17 00:00:00 2001 From: Haritha S K Date: Fri, 19 Jun 2026 15:50:59 +0530 Subject: [PATCH 063/243] FROMLIST: dt-bindings: thermal: qcom-tsens: Document the Maili Temperature Sensor Document the Temperature Sensor (TSENS) on the Qualcomm Maili SoC. Acked-by: Krzysztof Kozlowski Signed-off-by: Haritha S K Link: https://patch.msgid.link/20260619-b4-maili-upstream-3-v2-1-e54516c37022@oss.qualcomm.com --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index f0efaa8349ee5..5a8f7673e7303 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -58,6 +58,7 @@ properties: - qcom,glymur-tsens - qcom,hawi-tsens - qcom,kaanapali-tsens + - qcom,maili-tsens - qcom,milos-tsens - qcom,nord-tsens - qcom,msm8953-tsens From 2b677ca2cf30e6c61dc0dadf5df21881d0c6938e Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Wed, 27 May 2026 15:13:29 +0530 Subject: [PATCH 064/243] FROMLIST: arm64: dts: qcom: eliza: Reduce OS PDC DRV span to 0x10000 The OS PDC DRV register window on eliza spans 0x10000 bytes. Reduce the size of the first reg entry from 0x40000 to 0x10000. Signed-off-by: Mukesh Ojha Link: https://patch.msgid.link/20260527094333.2311731-25-mukesh.ojha@oss.qualcomm.com Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 977de44b816e4..2b73daea3592f 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -2696,7 +2696,7 @@ pdc: interrupt-controller@b220000 { compatible = "qcom,eliza-pdc", "qcom,pdc"; - reg = <0x0 0x0b220000 0x0 0x40000>, + reg = <0x0 0x0b220000 0x0 0x10000>, <0x0 0x174000f0 0x0 0x64>; qcom,pdc-ranges = <0 480 8>, <8 719 1>, <9 718 1>, From 1cc943980f19088e656a09623a8ba159bdd7cd64 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 30 Jun 2026 10:22:40 +0300 Subject: [PATCH 065/243] FROMLIST: dt-bindings: arm: qcom: Document Eliza CQS SoM and its EVK board Document the compatible strings for the Qualcomm Eliza CQS System-on-Module (SoM) and its EVK board. The SoM is populated with a CQ7790S Eliza variant, PMICs, LPDDR and eMMC. The SoM is then connected to the EVK base board, which provides a multitude of connectors for peripherals. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa --- Documentation/devicetree/bindings/arm/qcom.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index 50cc18a6ec5ed..bf7df33c1da8d 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -61,6 +61,11 @@ properties: - qcom,apq8084-sbc - const: qcom,apq8084 + - items: + - const: qcom,eliza-cqs-evk + - const: qcom,eliza-cqs-som + - const: qcom,eliza + - items: - enum: - qcom,eliza-mtp From 3cd5ef255ef768ef476ce21fa4c231cb25cb3bb9 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 30 Jun 2026 10:22:41 +0300 Subject: [PATCH 066/243] FROMLIST: arm64: dts: qcom: Add Eliza CQS SoM platform The Eliza CQS (CQ7790S) System-on-Module is designed to be connected to an Eliza EVK base board. The SoM provides the SoC, PMICs, LPDDR and eMMC, while the EVK base board provides connectors for a multitude of peripherals. Add the Eliza CQS SoM DTSI so it can be included by the EVK board DTS. Describe the regulators and board clocks, enable eMMC support through SDHC1, specify the ADSP firmware and enable the ADSP remoteproc. Reviewed-by: Konrad Dybcio Reviewed-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi | 394 ++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi diff --git a/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi new file mode 100644 index 0000000000000..33f4cd2822724 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include + +#include "eliza.dtsi" +#include "pm7550ba-eliza.dtsi" + +/ { + clocks { + xo_board: xo-board { + compatible = "fixed-clock"; + clock-frequency = <76800000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32764>; + #clock-cells = <0>; + }; + + bi_tcxo_div2: bi-tcxo-div2-clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-mult = <1>; + clock-div = <2>; + }; + + bi_tcxo_ao_div2: bi-tcxo-ao-div2-clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + + clocks = <&rpmhcc RPMH_CXO_CLK_A>; + clock-mult = <1>; + clock-div = <2>; + }; + }; + + vph_pwr: regulator-vph-pwr { + compatible = "regulator-fixed"; + + regulator-name = "vph_pwr"; + regulator-min-microvolt = <3700000>; + regulator-max-microvolt = <3700000>; + + regulator-always-on; + regulator-boot-on; + }; +}; + +&apps_rsc { + regulators-0 { + compatible = "qcom,pm7550-rpmh-regulators"; + + vdd-l1-supply = <&vreg_s3b>; + vdd-l2-l3-supply = <&vreg_s3b>; + vdd-l4-l5-supply = <&vreg_s2b>; + vdd-l6-supply = <&vreg_s2b>; + vdd-l7-supply = <&vreg_s1b>; + vdd-l8-supply = <&vreg_s1b>; + vdd-l9-l10-supply = <&vreg_s1b>; + vdd-l11-supply = <&vreg_s1b>; + vdd-l12-l14-supply = <&vreg_bob>; + vdd-l13-l16-supply = <&vreg_bob>; + vdd-l15-l17-l18-l19-l20-l21-l22-l23-supply = <&vreg_bob>; + vdd-s1-supply = <&vph_pwr>; + vdd-s2-supply = <&vph_pwr>; + vdd-s3-supply = <&vph_pwr>; + vdd-s4-supply = <&vph_pwr>; + vdd-s5-supply = <&vph_pwr>; + vdd-s6-supply = <&vph_pwr>; + + vdd-bob-supply = <&vph_pwr>; + + qcom,pmic-id = "b"; + + vreg_s1b: smps1 { + regulator-name = "vreg_s1b"; + regulator-min-microvolt = <1850000>; + regulator-max-microvolt = <2040000>; + regulator-initial-mode = ; + }; + + vreg_s2b: smps2 { + regulator-name = "vreg_s2b"; + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <2744000>; + regulator-initial-mode = ; + }; + + vreg_s3b: smps3 { + regulator-name = "vreg_s3b"; + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <2744000>; + regulator-initial-mode = ; + }; + + vreg_s4b: smps4 { + regulator-name = "vreg_s4b"; + regulator-min-microvolt = <2156000>; + regulator-max-microvolt = <2400000>; + regulator-initial-mode = ; + }; + + vreg_l2b: ldo2 { + regulator-name = "vreg_l2b"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <950000>; + regulator-initial-mode = ; + }; + + vreg_l3b: ldo3 { + regulator-name = "vreg_l3b"; + regulator-min-microvolt = <880000>; + regulator-max-microvolt = <912000>; + regulator-initial-mode = ; + }; + + vreg_l4b: ldo4 { + regulator-name = "vreg_l4b"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + }; + + vreg_l6b: ldo6 { + regulator-name = "vreg_l6b"; + regulator-min-microvolt = <866000>; + regulator-max-microvolt = <958000>; + regulator-initial-mode = ; + }; + + vreg_l7b: ldo7 { + regulator-name = "vreg_l7b"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l8b: ldo8 { + regulator-name = "vreg_l8b"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l9b: ldo9 { + regulator-name = "vreg_l9b"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l10b: ldo10 { + regulator-name = "vreg_l10b"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l11b: ldo11 { + regulator-name = "vreg_l11b"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + + vreg_l12b: ldo12 { + regulator-name = "vreg_l12b"; + regulator-min-microvolt = <2400000>; + regulator-max-microvolt = <3300000>; + regulator-initial-mode = ; + }; + + vreg_l13b: ldo13 { + regulator-name = "vreg_l13b"; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <3300000>; + regulator-initial-mode = ; + }; + + vreg_l14b: ldo14 { + regulator-name = "vreg_l14b"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3304000>; + regulator-initial-mode = ; + }; + + vreg_l15b: ldo15 { + regulator-name = "vreg_l15b"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3304000>; + regulator-initial-mode = ; + }; + + vreg_l16b: ldo16 { + regulator-name = "vreg_l16b"; + regulator-min-microvolt = <3008000>; + regulator-max-microvolt = <3008000>; + regulator-initial-mode = ; + }; + + vreg_l17b: ldo17 { + regulator-name = "vreg_l17b"; + regulator-min-microvolt = <3104000>; + regulator-max-microvolt = <3104000>; + regulator-initial-mode = ; + }; + + vreg_l18b: ldo18 { + regulator-name = "vreg_l18b"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-initial-mode = ; + }; + + vreg_l19b: ldo19 { + regulator-name = "vreg_l19b"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-initial-mode = ; + }; + + vreg_l20b: ldo20 { + regulator-name = "vreg_l20b"; + regulator-min-microvolt = <1620000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + }; + + vreg_l21b: ldo21 { + regulator-name = "vreg_l21b"; + regulator-min-microvolt = <1620000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + }; + + vreg_l22b: ldo22 { + regulator-name = "vreg_l22b"; + regulator-min-microvolt = <3200000>; + regulator-max-microvolt = <3200000>; + regulator-initial-mode = ; + }; + + vreg_l23b: ldo23 { + regulator-name = "vreg_l23b"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + }; + + vreg_bob: bob { + regulator-name = "vreg_bob"; + regulator-min-microvolt = <3008000>; + regulator-max-microvolt = <3960000>; + regulator-initial-mode = ; + }; + }; + + regulators-1 { + compatible = "qcom,pm8550vs-rpmh-regulators"; + + vdd-l1-supply = <&vreg_s1b>; + + qcom,pmic-id = "d"; + + vreg_l1d: ldo1 { + regulator-name = "vreg_l1d"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + }; + + regulators-2 { + compatible = "qcom,pm8550vs-rpmh-regulators"; + + vdd-l1-supply = <&vreg_s2b>; + vdd-l3-supply = <&vreg_s2b>; + + qcom,pmic-id = "g"; + + vreg_l1g: ldo1 { + regulator-name = "vreg_l1g"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1260000>; + regulator-initial-mode = ; + }; + + vreg_l3g: ldo3 { + regulator-name = "vreg_l3g"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1260000>; + regulator-initial-mode = ; + }; + + }; + + regulators-3 { + compatible = "qcom,pmr735d-rpmh-regulators"; + + vdd-l1-l2-l5-supply = <&vreg_s3b>; + vdd-l3-l4-supply = <&vreg_s2b>; + vdd-l6-supply = <&vreg_s1b>; + vdd-l7-supply = <&vreg_s3b>; + + qcom,pmic-id = "k"; + + vreg_l1k: ldo1 { + regulator-name = "vreg_l1k"; + regulator-min-microvolt = <488000>; + regulator-max-microvolt = <912000>; + regulator-initial-mode = ; + }; + + vreg_l2k: ldo2 { + regulator-name = "vreg_l2k"; + regulator-min-microvolt = <920000>; + regulator-max-microvolt = <969000>; + regulator-initial-mode = ; + }; + + vreg_l3k: ldo3 { + regulator-name = "vreg_l3k"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1350000>; + regulator-initial-mode = ; + }; + + vreg_l4k: ldo4 { + regulator-name = "vreg_l4k"; + regulator-min-microvolt = <960000>; + regulator-max-microvolt = <1980000>; + regulator-initial-mode = ; + }; + + vreg_l5k: ldo5 { + regulator-name = "vreg_l5k"; + regulator-min-microvolt = <866000>; + regulator-max-microvolt = <931000>; + regulator-initial-mode = ; + }; + + vreg_l6k: ldo6 { + regulator-name = "vreg_l6k"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + }; + + vreg_l7k: ldo7 { + regulator-name = "vreg_l7k"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <958000>; + regulator-initial-mode = ; + }; + }; +}; + +&remoteproc_adsp { + firmware-name = "qcom/eliza/adsp.mbn", + "qcom/eliza/adsp_dtb.mbn"; + + status = "okay"; +}; + +&sdhc_1 { + vmmc-supply = <&vreg_l12b>; + vqmmc-supply = <&vreg_l1d>; + pinctrl-0 = <&sdc1_default>; + pinctrl-1 = <&sdc1_sleep>; + pinctrl-names = "default", "sleep"; + mmc-hs400-1_8v; + mmc-hs200-1_8v; + non-removable; + supports-cqe; + no-sdio; + no-sd; + + status = "okay"; +}; + +&tlmm { + gpio-reserved-ranges = <20 4>, /* NFC SPI */ + <111 2>, /* WCN UART1 */ + <118 1>; /* NFC Secure I/O */ +}; From effa19153d85507ef3bfb71a565cd220d40fbafd Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 30 Jun 2026 10:22:42 +0300 Subject: [PATCH 067/243] FROMLIST: arm64: dts: qcom: Add Eliza CQS EVK board The Eliza CQS EVK board combines the CQ7790S-based (Eliza) SoM with the common Eliza EVK base board, which provides connectors for different peripherals. Add a common Eliza EVK dtsi for the base board bits that can be reused alongside other Eliza SoM variants. Then, add the final Eliza CQS EVK dts, including the CQS SoM and common EVK dtsi. Reviewed-by: Konrad Dybcio Reviewed-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/Makefile | 1 + arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts | 14 ++++++++++++++ arch/arm64/boot/dts/qcom/eliza-evk.dtsi | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts create mode 100644 arch/arm64/boot/dts/qcom/eliza-evk.dtsi diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 6f33c4e2f09c3..001e18cc4bc33 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -14,6 +14,7 @@ dtb-$(CONFIG_ARCH_QCOM) += apq8094-sony-xperia-kitakami-karin_windy.dtb dtb-$(CONFIG_ARCH_QCOM) += apq8096-db820c.dtb dtb-$(CONFIG_ARCH_QCOM) += apq8096sg-db820c.dtb dtb-$(CONFIG_ARCH_QCOM) += apq8096-ifc6640.dtb +dtb-$(CONFIG_ARCH_QCOM) += eliza-cqs-evk.dtb dtb-$(CONFIG_ARCH_QCOM) += eliza-mtp.dtb dtb-$(CONFIG_ARCH_QCOM) += glymur-crd.dtb dtb-$(CONFIG_ARCH_QCOM) += hamoa-iot-evk.dtb diff --git a/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts b/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts new file mode 100644 index 0000000000000..43d428a4ed2df --- /dev/null +++ b/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; + +#include "eliza-cqs-som.dtsi" +#include "eliza-evk.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. Eliza CQS EVK"; + compatible = "qcom,eliza-cqs-evk", "qcom,eliza-cqs-som", "qcom,eliza"; +}; diff --git a/arch/arm64/boot/dts/qcom/eliza-evk.dtsi b/arch/arm64/boot/dts/qcom/eliza-evk.dtsi new file mode 100644 index 0000000000000..e47b24f8b8277 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/eliza-evk.dtsi @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/ { + aliases { + serial0 = &uart13; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&uart13 { + compatible = "qcom,geni-debug-uart"; + + status = "okay"; +}; From 6042f64a5465758d8e952f09b9addc21c0046d50 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 30 Jun 2026 10:50:20 +0300 Subject: [PATCH 068/243] FROMLIST: arm64: dts: qcom: eliza: Add PMIC nodes for CQS SoM and MTP Both the CQS SoM and the MTP have the following array of PMICs: PMK8550, PM7550, 2x PM8550VS, PMIV0102 and PMR735D. Since on Eliza there is already support for SPMI multi-master, it is necessary to duplicate the devicetree description for each of these PMICs, due to the SPMI bus index and address. So add a new Elize specific dtsi for each of these PMICs and include all of them in both the CQS SoM and MTP board dts. Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi | 5 ++ arch/arm64/boot/dts/qcom/eliza-mtp.dts | 5 ++ arch/arm64/boot/dts/qcom/pm7550-eliza.dtsi | 61 +++++++++++++ .../arm64/boot/dts/qcom/pm8550vs-d-eliza.dtsi | 63 ++++++++++++++ .../arm64/boot/dts/qcom/pm8550vs-g-eliza.dtsi | 63 ++++++++++++++ arch/arm64/boot/dts/qcom/pmk8550-eliza.dtsi | 87 +++++++++++++++++++ arch/arm64/boot/dts/qcom/pmr735d-eliza.dtsi | 63 ++++++++++++++ 7 files changed, 347 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/pm7550-eliza.dtsi create mode 100644 arch/arm64/boot/dts/qcom/pm8550vs-d-eliza.dtsi create mode 100644 arch/arm64/boot/dts/qcom/pm8550vs-g-eliza.dtsi create mode 100644 arch/arm64/boot/dts/qcom/pmk8550-eliza.dtsi create mode 100644 arch/arm64/boot/dts/qcom/pmr735d-eliza.dtsi diff --git a/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi index 33f4cd2822724..318ae3c223515 100644 --- a/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi @@ -9,6 +9,11 @@ #include "eliza.dtsi" #include "pm7550ba-eliza.dtsi" +#include "pm7550-eliza.dtsi" +#include "pm8550vs-d-eliza.dtsi" +#include "pm8550vs-g-eliza.dtsi" +#include "pmr735d-eliza.dtsi" +#include "pmk8550-eliza.dtsi" / { clocks { diff --git a/arch/arm64/boot/dts/qcom/eliza-mtp.dts b/arch/arm64/boot/dts/qcom/eliza-mtp.dts index 1374afd9d14e3..a7d6f9d52ef31 100644 --- a/arch/arm64/boot/dts/qcom/eliza-mtp.dts +++ b/arch/arm64/boot/dts/qcom/eliza-mtp.dts @@ -11,6 +11,11 @@ #include "eliza.dtsi" #include "pm7550ba-eliza.dtsi" +#include "pm7550-eliza.dtsi" +#include "pm8550vs-d-eliza.dtsi" +#include "pm8550vs-g-eliza.dtsi" +#include "pmr735d-eliza.dtsi" +#include "pmk8550-eliza.dtsi" / { model = "Qualcomm Technologies, Inc. Eliza MTP"; diff --git a/arch/arm64/boot/dts/qcom/pm7550-eliza.dtsi b/arch/arm64/boot/dts/qcom/pm7550-eliza.dtsi new file mode 100644 index 0000000000000..9b907f69264b2 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/pm7550-eliza.dtsi @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +/ { + thermal-zones { + pm7550-thermal { + polling-delay-passive = <100>; + + thermal-sensors = <&pm7550_temp_alarm>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + /* + * Current Linux driver currently only supports up to + * 125°C, should be updated to 145°C once available. + */ + temperature = <125000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + +&spmi_bus0 { + pm7550: pmic@1 { + compatible = "qcom,pm7550", "qcom,spmi-pmic"; + reg = <1 SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + pm7550_temp_alarm: temp-alarm@a00 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0xa00>; + interrupts = <0x1 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + + pm7550_gpios: gpio@8800 { + compatible = "qcom,pm7550-gpio", "qcom,spmi-gpio"; + reg = <0x8800>; + gpio-controller; + gpio-ranges = <&pm7550_gpios 0 0 12>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/pm8550vs-d-eliza.dtsi b/arch/arm64/boot/dts/qcom/pm8550vs-d-eliza.dtsi new file mode 100644 index 0000000000000..548d2aa839623 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/pm8550vs-d-eliza.dtsi @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +/ { + thermal-zones { + pm8550vs-d-thermal { + polling-delay-passive = <100>; + + thermal-sensors = <&pm8550vs_d_temp_alarm>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "hot"; + }; + + trip2 { + temperature = <145000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + +&spmi_bus0 { + pm8550vs_d: pmic@3 { + compatible = "qcom,pm8550vs", "qcom,spmi-pmic"; + reg = <3 SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + pm8550vs_d_temp_alarm: temp-alarm@a00 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0xa00>; + interrupts = <0x3 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + + pm8550vs_d_gpios: gpio@8800 { + compatible = "qcom,pm8550vs-gpio", "qcom,spmi-gpio"; + reg = <0x8800>; + gpio-controller; + gpio-ranges = <&pm8550vs_d_gpios 0 0 6>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/pm8550vs-g-eliza.dtsi b/arch/arm64/boot/dts/qcom/pm8550vs-g-eliza.dtsi new file mode 100644 index 0000000000000..4ad2cd189a6c1 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/pm8550vs-g-eliza.dtsi @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +/ { + thermal-zones { + pm8550vs-g-thermal { + polling-delay-passive = <100>; + + thermal-sensors = <&pm8550vs_g_temp_alarm>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "hot"; + }; + + trip2 { + temperature = <145000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + +&spmi_bus0 { + pm8550vs_g: pmic@6 { + compatible = "qcom,pm8550vs", "qcom,spmi-pmic"; + reg = <6 SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + pm8550vs_g_temp_alarm: temp-alarm@a00 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0xa00>; + interrupts = <0x6 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + + pm8550vs_g_gpios: gpio@8800 { + compatible = "qcom,pm8550vs-gpio", "qcom,spmi-gpio"; + reg = <0x8800>; + gpio-controller; + gpio-ranges = <&pm8550vs_g_gpios 0 0 6>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/pmk8550-eliza.dtsi b/arch/arm64/boot/dts/qcom/pmk8550-eliza.dtsi new file mode 100644 index 0000000000000..e0ff4ff9c4693 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/pmk8550-eliza.dtsi @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include + +/ { + reboot-mode { + compatible = "nvmem-reboot-mode"; + nvmem-cells = <&reboot_reason>; + nvmem-cell-names = "reboot-mode"; + mode-recovery = <0x01>; + mode-bootloader = <0x02>; + }; +}; + +&spmi_bus0 { + pmk8550: pmic@0 { + compatible = "qcom,pm8550", "qcom,spmi-pmic"; + reg = <0x0 SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + pmk8550_pon: pon@1300 { + compatible = "qcom,pmk8350-pon"; + reg = <0x1300>, <0x800>; + reg-names = "hlos", "pbs"; + + pon_pwrkey: pwrkey { + compatible = "qcom,pmk8350-pwrkey"; + interrupts = <0x0 0x13 0x7 IRQ_TYPE_EDGE_BOTH>; + linux,code = ; + status = "disabled"; + }; + + pon_resin: resin { + compatible = "qcom,pmk8350-resin"; + interrupts = <0x0 0x13 0x6 IRQ_TYPE_EDGE_BOTH>; + status = "disabled"; + }; + }; + + pmk8550_rtc: rtc@6100 { + compatible = "qcom,pmk8350-rtc"; + reg = <0x6100>, <0x6200>; + reg-names = "rtc", "alarm"; + interrupts = <0x0 0x62 0x1 IRQ_TYPE_EDGE_RISING>; + }; + + pmk8550_sdam_2: nvram@7100 { + compatible = "qcom,spmi-sdam"; + reg = <0x7100>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x7100 0x100>; + + reboot_reason: reboot-reason@48 { + reg = <0x48 0x1>; + bits = <1 7>; + }; + }; + + pmk8550_gpios: gpio@b800 { + compatible = "qcom,pmk8550-gpio", "qcom,spmi-gpio"; + reg = <0xb800>; + gpio-controller; + gpio-ranges = <&pmk8550_gpios 0 0 6>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + + pmk8550_pwm: pwm { + compatible = "qcom,pmk8550-pwm"; + + #address-cells = <1>; + #size-cells = <0>; + #pwm-cells = <2>; + + status = "disabled"; + }; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/pmr735d-eliza.dtsi b/arch/arm64/boot/dts/qcom/pmr735d-eliza.dtsi new file mode 100644 index 0000000000000..1c29d8ddea65d --- /dev/null +++ b/arch/arm64/boot/dts/qcom/pmr735d-eliza.dtsi @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +/ { + thermal-zones { + pmr735d-thermal { + polling-delay-passive = <100>; + + thermal-sensors = <&pmr735d_temp_alarm>; + + trips { + trip0 { + temperature = <95000>; + hysteresis = <0>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <0>; + type = "hot"; + }; + + trip2 { + temperature = <145000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + +&spmi_bus0 { + pmr735d: pmic@a { + compatible = "qcom,pmr735d", "qcom,spmi-pmic"; + reg = <0xa SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + pmr735d_temp_alarm: temp-alarm@a00 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0xa00>; + interrupts = <0xa 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + + pmr735d_gpios: gpio@8800 { + compatible = "qcom,pmr735d-gpio", "qcom,spmi-gpio"; + reg = <0x8800>; + gpio-controller; + gpio-ranges = <&pmr735d_gpios 0 0 2>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; +}; From 3174cfeaa27131751d452648f2938710b9af7a99 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 25 May 2026 16:16:09 +0530 Subject: [PATCH 069/243] FROMLIST: arm64: dts: qcom: eliza: Add support for MM clock controllers Add the device nodes for the multimedia clock controllers (cambistmclkcc, camcc, videocc, gpucc) for Qualcomm Eliza SoC. Signed-off-by: Taniya Das Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260525-eliza_mm_cc_v2-v5-7-a1d125619a5a@oss.qualcomm.com Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 2b73daea3592f..2709bcf116099 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -4,9 +4,13 @@ */ #include +#include +#include #include #include +#include #include +#include #include #include #include @@ -1779,6 +1783,19 @@ #interconnect-cells = <2>; }; + cambistmclkcc: clock-controller@1760000 { + compatible = "qcom,eliza-cambistmclkcc"; + reg = <0x0 0x01760000 0x0 0x6000>; + + clocks = <&bi_tcxo_div2>, + <&sleep_clk>, + <&gcc GCC_CAM_BIST_MCLK_AHB_CLK>; + + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + mmss_noc: interconnect@1780000 { compatible = "qcom,eliza-mmss-noc"; reg = <0x0 0x01780000 0x0 0x7d800>; @@ -2017,6 +2034,23 @@ }; }; + gpucc: clock-controller@3d90000 { + compatible = "qcom,eliza-gpucc"; + reg = <0x0 0x03d90000 0x0 0xa000>; + + clocks = <&bi_tcxo_div2>, + <&gcc GCC_GPU_GPLL0_CPH_CLK_SRC>, + <&gcc GCC_GPU_GPLL0_DIV_CPH_CLK_SRC>; + + power-domains = <&rpmhpd RPMHPD_MX>, + <&rpmhpd RPMHPD_CX>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + lpass_lpiaon_noc: interconnect@7400000 { compatible = "qcom,eliza-lpass-lpiaon-noc"; reg = <0x0 0x07400000 0x0 0x19080>; @@ -2687,6 +2721,20 @@ #power-domain-cells = <1>; }; + videocc: clock-controller@aaf0000 { + compatible = "qcom,eliza-videocc"; + reg = <0x0 0xaaf0000 0x0 0x10000>; + + clocks = <&bi_tcxo_div2>, + <&bi_tcxo_ao_div2>, + <&sleep_clk>, + <&gcc GCC_VIDEO_AHB_CLK>; + + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + lpass_ag_noc: interconnect@7e40000 { compatible = "qcom,eliza-lpass-ag-noc"; reg = <0x0 0x07e40000 0x0 0xe080>; @@ -2694,6 +2742,19 @@ #interconnect-cells = <2>; }; + camcc: clock-controller@ade0000 { + compatible = "qcom,eliza-camcc"; + reg = <0x0 0x0ade0000 0x0 0x20000>; + + clocks = <&bi_tcxo_div2>, + <&sleep_clk>, + <&gcc GCC_CAMERA_AHB_CLK>; + + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + pdc: interrupt-controller@b220000 { compatible = "qcom,eliza-pdc", "qcom,pdc"; reg = <0x0 0x0b220000 0x0 0x10000>, From fb2831e4431b5ab260ad37eda3db5ebbf5e77693 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 8 Jul 2026 11:06:38 +0530 Subject: [PATCH 070/243] FROMLIST: arm64: dts: qcom: eliza: Add PCIe PHY and controller nodes Eliza supports two PCIe instances: one 8GT/s x1 (PCIe0) and one 8GT/s x2 (PCIe1). Add PCIe controller and PHY nodes for both instances, and update the GCC clock references to use the newly added PHY nodes instead of placeholder zeros. Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 411 +++++++++++++++++++++++++++- 1 file changed, 409 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 2709bcf116099..3cc115b87cdc2 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -644,8 +644,8 @@ clocks = <&bi_tcxo_div2>, <&sleep_clk>, - <0>, - <0>, + <&pcie0_phy>, + <&pcie1_phy>, <&ufs_mem_phy 0>, <&ufs_mem_phy 1>, <&ufs_mem_phy 2>, @@ -1803,6 +1803,367 @@ #interconnect-cells = <2>; }; + pcie0: pcie@1c00000 { + device_type = "pci"; + compatible = "qcom,eliza-pcie", "qcom,pcie-sm8550"; + reg = <0 0x01c00000 0 0x3000>, + <0 0x40000000 0 0xf1d>, + <0 0x40000f20 0 0xa8>, + <0 0x40001000 0 0x1000>, + <0 0x40100000 0 0x100000>, + <0 0x01c03000 0 0x1000>; + reg-names = "parf", + "dbi", + "elbi", + "atu", + "config", + "mhi"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0 0x00000000 0 0x40200000 0 0x100000>, + <0x02000000 0 0x40300000 0 0x40300000 0 0x3d00000>; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "global"; + + clocks = <&gcc GCC_PCIE_0_AUX_CLK>, + <&gcc GCC_PCIE_0_CFG_AHB_CLK>, + <&gcc GCC_PCIE_0_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_0_SLV_AXI_CLK>, + <&gcc GCC_PCIE_0_SLV_Q2A_AXI_CLK>, + <&gcc GCC_DDRSS_PCIE_SF_QTB_CLK>, + <&gcc GCC_AGGRE_NOC_PCIE_AXI_CLK>, + <&gcc GCC_CNOC_PCIE_SF_AXI_CLK>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "ddrss_sf_tbu", + "noc_aggr", + "cnoc_sf_axi"; + + resets = <&gcc GCC_PCIE_0_BCR>, + <&gcc GCC_PCIE_0_LINK_DOWN_BCR>; + reset-names = "pci", + "link_down"; + + interconnects = <&pcie_noc MASTER_PCIE_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &cnoc_main SLAVE_PCIE_0 QCOM_ICC_TAG_ACTIVE_ONLY>; + interconnect-names = "pcie-mem", + "cpu-pcie"; + + power-domains = <&gcc GCC_PCIE_0_GDSC>; + + operating-points-v2 = <&pcie0_opp_table>; + + iommu-map = <0 &apps_smmu 0x1480 0x1>, + <0x100 &apps_smmu 0x1481 0x1>; + + interrupt-map = <0 0 0 1 &intc 0 0 0 564 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc 0 0 0 565 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc 0 0 0 566 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc 0 0 0 567 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map-mask = <0 0 0 0x7>; + #interrupt-cells = <1>; + + linux,pci-domain = <0>; + num-lanes = <1>; + bus-range = <0 0xff>; + + dma-coherent; + + pinctrl-0 = <&pcie0_default_state>; + pinctrl-names = "default"; + + status = "disabled"; + + pcie0_opp_table: opp-table { + compatible = "operating-points-v2"; + + /* 2.5 GT/s x1 */ + opp-2500000-1 { + opp-hz = /bits/ 64 <2500000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <250000 1>; + opp-level = <1>; + }; + + /* 5 GT/s x1 */ + opp-5000000-2 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <500000 1>; + opp-level = <2>; + }; + + /* 8 GT/s x1 */ + opp-8000000-3 { + opp-hz = /bits/ 64 <8000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <984500 1>; + opp-level = <3>; + }; + + }; + + pcie0port0: pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + phys = <&pcie0_phy>; + }; + }; + + pcie0_phy: phy@1c06000 { + compatible = "qcom,eliza-qmp-gen3x1-pcie-phy"; + reg = <0 0x01c06000 0 0x2000>; + + clocks = <&gcc GCC_PCIE_0_AUX_CLK>, + <&gcc GCC_PCIE_0_CFG_AHB_CLK>, + <&tcsr TCSR_PCIE_0_CLKREF_EN>, + <&gcc GCC_PCIE_0_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_0_PIPE_CLK>, + <&gcc GCC_PCIE_0_PIPE_DIV2_CLK>; + clock-names = "aux", + "cfg_ahb", + "ref", + "rchng", + "pipe", + "pipediv2"; + + assigned-clocks = <&gcc GCC_PCIE_0_PHY_RCHNG_CLK>; + assigned-clock-rates = <100000000>; + + resets = <&gcc GCC_PCIE_0_PHY_BCR>, + <&gcc GCC_PCIE_0_NOCSR_COM_PHY_BCR>; + reset-names = "phy", + "phy_nocsr"; + + power-domains = <&gcc GCC_PCIE_0_PHY_GDSC>; + + #clock-cells = <0>; + clock-output-names = "pcie0_pipe_clk"; + + #phy-cells = <0>; + + status = "disabled"; + }; + + pcie1: pcie@1c08000 { + device_type = "pci"; + compatible = "qcom,eliza-pcie", "qcom,pcie-sm8550"; + reg = <0 0x01c08000 0 0x3000>, + <0 0x44000000 0 0xf1d>, + <0 0x44000f20 0 0xa8>, + <0 0x44001000 0 0x1000>, + <0 0x44100000 0 0x100000>, + <0 0x01c0b000 0 0x1000>; + reg-names = "parf", + "dbi", + "elbi", + "atu", + "config", + "mhi"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0 0x00000000 0 0x44200000 0 0x100000>, + <0x02000000 0 0x44300000 0 0x44300000 0 0x3d00000>; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "global"; + + clocks = <&gcc GCC_PCIE_1_AUX_CLK>, + <&gcc GCC_PCIE_1_CFG_AHB_CLK>, + <&gcc GCC_PCIE_1_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_1_SLV_AXI_CLK>, + <&gcc GCC_PCIE_1_SLV_Q2A_AXI_CLK>, + <&gcc GCC_DDRSS_PCIE_SF_QTB_CLK>, + <&gcc GCC_AGGRE_NOC_PCIE_AXI_CLK>, + <&gcc GCC_CNOC_PCIE_SF_AXI_CLK>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "ddrss_sf_tbu", + "noc_aggr", + "cnoc_sf_axi"; + + resets = <&gcc GCC_PCIE_1_BCR>, + <&gcc GCC_PCIE_1_LINK_DOWN_BCR>; + reset-names = "pci", + "link_down"; + + interconnects = <&pcie_noc MASTER_PCIE_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &cnoc_main SLAVE_PCIE_1 QCOM_ICC_TAG_ACTIVE_ONLY>; + interconnect-names = "pcie-mem", + "cpu-pcie"; + + power-domains = <&gcc GCC_PCIE_1_GDSC>; + + operating-points-v2 = <&pcie1_opp_table>; + + iommu-map = <0 &apps_smmu 0x1400 0x1>, + <0x100 &apps_smmu 0x1401 0x1>; + + interrupt-map = <0 0 0 1 &intc 0 0 0 149 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc 0 0 0 150 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc 0 0 0 151 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc 0 0 0 152 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map-mask = <0 0 0 0x7>; + #interrupt-cells = <1>; + + linux,pci-domain = <1>; + num-lanes = <2>; + bus-range = <0 0xff>; + + dma-coherent; + + pinctrl-0 = <&pcie1_default_state>; + pinctrl-names = "default"; + + status = "disabled"; + + pcie1_opp_table: opp-table { + compatible = "operating-points-v2"; + + /* 2.5 GT/s x1 */ + opp-2500000-1 { + opp-hz = /bits/ 64 <2500000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <250000 1>; + opp-level = <1>; + }; + + /* 2.5 GT/s x2 */ + opp-5000000-1 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <500000 1>; + opp-level = <1>; + }; + + /* 5 GT/s x1 */ + opp-5000000-2 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <500000 1>; + opp-level = <2>; + }; + + /* 5 GT/s x2 */ + opp-10000000-2 { + opp-hz = /bits/ 64 <10000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <1000000 1>; + opp-level = <2>; + }; + + /* 8 GT/s x1 */ + opp-8000000-3 { + opp-hz = /bits/ 64 <8000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <984500 1>; + opp-level = <3>; + }; + + /* 8 GT/s x2 */ + opp-16000000-3 { + opp-hz = /bits/ 64 <16000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <1969000 1>; + opp-level = <3>; + }; + + }; + pcie1port0: pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + phys = <&pcie1_phy>; + }; + }; + + pcie1_phy: phy@1c0e000 { + compatible = "qcom,eliza-qmp-gen3x2-pcie-phy"; + reg = <0 0x01c0e000 0 0x2000>; + + clocks = <&gcc GCC_PCIE_1_AUX_CLK>, + <&gcc GCC_PCIE_1_CFG_AHB_CLK>, + <&tcsr TCSR_PCIE_1_CLKREF_EN>, + <&gcc GCC_PCIE_1_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_1_PIPE_CLK>, + <&gcc GCC_PCIE_1_PIPE_DIV2_CLK>; + clock-names = "aux", + "cfg_ahb", + "ref", + "rchng", + "pipe", + "pipediv2"; + + assigned-clocks = <&gcc GCC_PCIE_1_PHY_RCHNG_CLK>; + assigned-clock-rates = <100000000>; + + resets = <&gcc GCC_PCIE_1_PHY_BCR>, + <&gcc GCC_PCIE_1_NOCSR_COM_PHY_BCR>; + reset-names = "phy", + "phy_nocsr"; + + power-domains = <&gcc GCC_PCIE_1_PHY_GDSC>; + + #clock-cells = <0>; + clock-output-names = "pcie1_pipe_clk"; + + #phy-cells = <0>; + + status = "disabled"; + }; + ufs_mem_phy: phy@1d80000 { compatible = "qcom,eliza-qmp-ufs-phy", "qcom,sm8650-qmp-ufs-phy"; @@ -2904,6 +3265,52 @@ gpio-ranges = <&tlmm 0 0 184>; wakeup-parent = <&pdc>; + pcie0_default_state: pcie0-default-state { + perst-pins { + pins = "gpio78"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + clkreq-pins { + pins = "gpio80"; + function = "pcie0_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + wake-pins { + pins = "gpio114"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + pcie1_default_state: pcie1-default-state { + perst-pins { + pins = "gpio54"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + clkreq-pins { + pins = "gpio52"; + function = "pcie1_clk_req_n"; + drive-strength = <2>; + bias-pull-up; + }; + + wake-pins { + pins = "gpio53"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + qup_i2c0_data_clk: qup-i2c0-data-clk-state { pins = "gpio28", "gpio29"; function = "qup1_se0"; From 9eb780f755f45b50eb6f47bf966afbb73431b541 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Thu, 16 Jul 2026 11:48:16 +0300 Subject: [PATCH 071/243] FROMLIST: arm64: dts: qcom: eliza-evk: Add support for USB and SD card Even though the EVK comes with 3 Type-C ports, the Eliza EVK only has one USB controller and a single set of PHYs, which are connected to the port marked as JUSB. Also, the EVK comes with an SD card slot. So describe the PMIC GLINK node, the connector graph, the PHYs and repeater supplies and enable the USB controller. Also enable the second SD host controller, describe the card detect GPIO and the board specific supplies. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-evk.dtsi | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-evk.dtsi b/arch/arm64/boot/dts/qcom/eliza-evk.dtsi index e47b24f8b8277..2fc9456583005 100644 --- a/arch/arm64/boot/dts/qcom/eliza-evk.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza-evk.dtsi @@ -11,6 +11,70 @@ chosen { stdout-path = "serial0:115200n8"; }; + + pmic-glink { + compatible = "qcom,eliza-pmic-glink", + "qcom,sm8550-pmic-glink", + "qcom,pmic-glink"; + #address-cells = <1>; + #size-cells = <0>; + orientation-gpios = <&tlmm 122 GPIO_ACTIVE_HIGH>; + + connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + pmic_glink_hs_in: endpoint { + remote-endpoint = <&usb_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + pmic_glink_ss_in: endpoint { + remote-endpoint = <&usb_dp_qmpphy_out>; + }; + }; + }; + }; + }; +}; + +&pm7550ba_eusb2_repeater { + vdd18-supply = <&vreg_l7b>; + vdd3-supply = <&vreg_l17b>; +}; + +&sdhc_2 { + vmmc-supply = <&vreg_l13b>; + vqmmc-supply = <&vreg_l23b>; + + cd-gpios = <&tlmm 58 GPIO_ACTIVE_LOW>; + + pinctrl-0 = <&sdc2_default>, <&sd_cd>; + pinctrl-1 = <&sdc2_sleep>, <&sd_cd>; + pinctrl-names = "default", "sleep"; + + status = "okay"; +}; + +&tlmm { + sd_cd: sd-cd-state { + pins = "gpio58"; + function = "gpio"; + bias-pull-up; + }; }; &uart13 { @@ -18,3 +82,31 @@ status = "okay"; }; + +&usb { + status = "okay"; +}; + +&usb_dp_qmpphy { + vdda-phy-supply = <&vreg_l3g>; + vdda-pll-supply = <&vreg_l7k>; + + status = "okay"; +}; + +&usb_dp_qmpphy_out { + remote-endpoint = <&pmic_glink_ss_in>; +}; + +&usb_dwc3_hs { + remote-endpoint = <&pmic_glink_hs_in>; +}; + +&usb_hsphy { + vdd-supply = <&vreg_l7k>; + vdda12-supply = <&vreg_l4b>; + + phys = <&pm7550ba_eusb2_repeater>; + + status = "okay"; +}; From 06514a00c4f479144ef714df31688c66971834ee Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Thu, 16 Jul 2026 11:51:23 +0300 Subject: [PATCH 072/243] FROMLIST: arm64: dts: qcom: eliza: Describe the ADSP GPR node Describe the ADSP Generic Packet Router (GPR) devicetree node as part of audio subsystem on Qualcomm Eliza SoC. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 3cc115b87cdc2..565ad57835761 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -21,6 +21,7 @@ #include #include #include +#include #include / { @@ -2392,6 +2393,46 @@ label = "lpass"; qcom,remote-pid = <2>; + + gpr { + compatible = "qcom,gpr"; + qcom,glink-channels = "adsp_apps"; + qcom,domain = ; + qcom,intents = <512 20>; + #address-cells = <1>; + #size-cells = <0>; + + q6apm: service@1 { + compatible = "qcom,q6apm"; + reg = ; + #sound-dai-cells = <0>; + qcom,protection-domain = "avs/audio", + "msm/adsp/audio_pd"; + + q6apmbedai: bedais { + compatible = "qcom,q6apm-lpass-dais"; + #sound-dai-cells = <1>; + }; + + q6apmdai: dais { + compatible = "qcom,q6apm-dais"; + iommus = <&apps_smmu 0x1001 0x80>, + <&apps_smmu 0x1061 0x0>; + }; + }; + + q6prm: service@2 { + compatible = "qcom,q6prm"; + reg = ; + qcom,protection-domain = "avs/audio", + "msm/adsp/audio_pd"; + + q6prmcc: clock-controller { + compatible = "qcom,q6prm-lpass-clocks"; + #clock-cells = <2>; + }; + }; + }; }; }; From 59ab3ce42d8da3cc2af3ace83f139c2d37187c56 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Sun, 5 Jul 2026 13:44:21 +0530 Subject: [PATCH 073/243] FROMLIST: arm64: dts: qcom: eliza: Add GPU SMMU node Add the nodes to describe the GPU SMMU. Signed-off-by: Akhil P Oommen Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 565ad57835761..3311e5d8f2cdc 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -2436,6 +2436,44 @@ }; }; + adreno_smmu: iommu@3da0000 { + compatible = "qcom,eliza-smmu-500", "qcom,adreno-smmu", + "qcom,smmu-500", "arm,mmu-500"; + reg = <0x0 0x03da0000 0x0 0x40000>; + #iommu-cells = <2>; + #global-interrupts = <1>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + clocks = <&gpucc GPU_CC_HLOS1_VOTE_GPU_SMMU_CLK>; + clock-names = "hlos"; + power-domains = <&gpucc GPU_CC_CX_GDSC>; + dma-coherent; + }; + gpucc: clock-controller@3d90000 { compatible = "qcom,eliza-gpucc"; reg = <0x0 0x03d90000 0x0 0xa000>; From 5400ecf8fdfa49801b273ace739ff99af33df4e8 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sun, 5 Jul 2026 13:44:22 +0530 Subject: [PATCH 074/243] FROMLIST: arm64: dts: qcom: eliza: Add GPU nodes Adreno 722 found in Eliza chipset belongs to the A7x GEN1 family. It is derived from A730 and shares the same IP-level configurations: HWCG registers, protected registers, GBIF CX registers and gmu_cgc_mode. Major differences include lower cache/core counts, 1MB GMEM, no Concurrent Binning & LPAC support. Some of the peripheral blocks like RSCC are from A740 that resulted in updates to RSC layout. Add the necessary devicetree nodes to describe this GPU. Signed-off-by: Puranam V G Tejaswi Signed-off-by: Akhil P Oommen Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 152 ++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 3311e5d8f2cdc..05a8865e70a77 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -4190,6 +4190,158 @@ qcom,bcm-voters = <&apps_bcm_voter>; #interconnect-cells = <2>; }; + + gpu: gpu@3d00000 { + compatible = "qcom,adreno-43020100", "qcom,adreno"; + reg = <0x0 0x03d00000 0x0 0x40000>, + <0x0 0x03d9e000 0x0 0x1000>, + <0x0 0x03d61000 0x0 0x800>; + reg-names = "kgsl_3d0_reg_memory", + "cx_mem", + "cx_dbgc"; + + interrupts = ; + + iommus = <&adreno_smmu 0x0 0x0>; + + operating-points-v2 = <&gpu_opp_table>; + qcom,gmu = <&gmu>; + #cooling-cells = <2>; + interconnects = <&gem_noc MASTER_GFX3D QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "gfx-mem"; + + status = "disabled"; + + gpu_zap_shader: zap-shader { + memory-region = <&gpu_micro_code_mem>; + }; + + gpu_opp_table: opp-table { + compatible = "operating-points-v2-adreno", + "operating-points-v2"; + + opp-259000000 { + opp-hz = /bits/ 64 <259000000>; + opp-level = ; + opp-peak-kBps = <781250>; + qcom,opp-acd-level = <0xc82f5ffd>; + }; + + opp-345000000 { + opp-hz = /bits/ 64 <345000000>; + opp-level = ; + opp-peak-kBps = <2136718>; + qcom,opp-acd-level = <0xc82f5ffd>; + }; + + opp-515000000 { + opp-hz = /bits/ 64 <515000000>; + opp-level = ; + opp-peak-kBps = <5285156>; + qcom,opp-acd-level = <0xc02d5ffd>; + }; + + opp-645000000 { + opp-hz = /bits/ 64 <645000000>; + opp-level = ; + opp-peak-kBps = <6074218>; + qcom,opp-acd-level = <0x882f5ffd>; + }; + + opp-724000000 { + opp-hz = /bits/ 64 <724000000>; + opp-level = ; + opp-peak-kBps = <6671875>; + qcom,opp-acd-level = <0xa82d5ffd>; + }; + + opp-796000000 { + opp-hz = /bits/ 64 <796000000>; + opp-level = ; + opp-peak-kBps = <10687500>; + qcom,opp-acd-level = <0x882d5ffd>; + }; + + opp-900000000 { + opp-hz = /bits/ 64 <900000000>; + opp-level = ; + opp-peak-kBps = <10687500>; + qcom,opp-acd-level = <0x882d5ffd>; + }; + + opp-975000000 { + opp-hz = /bits/ 64 <975000000>; + opp-level = ; + opp-peak-kBps = <12449218>; + qcom,opp-acd-level = <0x882c5ffd>; + }; + + opp-1075000000 { + opp-hz = /bits/ 64 <1075000000>; + opp-level = ; + opp-peak-kBps = <16500000>; + qcom,opp-acd-level = <0xa82a5ffd>; + }; + + opp-1150000000 { + opp-hz = /bits/ 64 <1150000000>; + opp-level = ; + opp-peak-kBps = <16500000>; + qcom,opp-acd-level = <0xa8295ffd>; + }; + }; + }; + + gmu: gmu@3d6a000 { + compatible = "qcom,adreno-gmu-722.0", "qcom,adreno-gmu"; + reg = <0x0 0x03d6a000 0x0 0x35000>, + <0x0 0x03d50000 0x0 0x10000>, + <0x0 0x0b290000 0x0 0x10000>; + reg-names = "gmu", "rscc", "gmu_pdc"; + + interrupts = , + ; + interrupt-names = "hfi", "gmu"; + + clocks = <&gpucc GPU_CC_AHB_CLK>, + <&gpucc GPU_CC_CX_GMU_CLK>, + <&gpucc GPU_CC_CXO_CLK>, + <&gcc GCC_DDRSS_GPU_AXI_CLK>, + <&gcc GCC_GPU_GEMNOC_GFX_CLK>, + <&gpucc GPU_CC_HUB_CX_INT_CLK>, + <&gpucc GPU_CC_DEMET_CLK>; + clock-names = "ahb", + "gmu", + "cxo", + "axi", + "memnoc", + "hub", + "demet"; + + power-domains = <&gpucc GPU_CC_CX_GDSC>, + <&gpucc GPU_CC_GX_GDSC>; + power-domain-names = "cx", + "gx"; + + iommus = <&adreno_smmu 0x5 0x0>; + qcom,qmp = <&aoss_qmp>; + operating-points-v2 = <&gmu_opp_table>; + + gmu_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-220000000 { + opp-hz = /bits/ 64 <220000000>; + opp-level = ; + }; + + opp-550000000 { + opp-hz = /bits/ 64 <550000000>; + opp-level = ; + }; + }; + }; }; thermal-zones { From 2bc62f1f9d889e0ffe81d6c2c4862fa3517241d0 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sun, 5 Jul 2026 13:44:23 +0530 Subject: [PATCH 075/243] FROMLIST: arm64: dts: qcom: eliza-mtp: Enable Adreno A722 GPU Enable the Adreno A722 GPU on the Eliza MTP board and provide the zap shader firmware path. Signed-off-by: Puranam V G Tejaswi Signed-off-by: Akhil P Oommen Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-mtp.dts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-mtp.dts b/arch/arm64/boot/dts/qcom/eliza-mtp.dts index a7d6f9d52ef31..37dca6cf0e72a 100644 --- a/arch/arm64/boot/dts/qcom/eliza-mtp.dts +++ b/arch/arm64/boot/dts/qcom/eliza-mtp.dts @@ -422,6 +422,14 @@ }; }; +&gpu { + status = "okay"; +}; + +&gpu_zap_shader { + firmware-name = "qcom/eliza/gen70e00_zap.mbn"; +}; + &mdss { status = "okay"; }; From 6ad877cba1493ed76da0f1dc1f8616c21a77c1c2 Mon Sep 17 00:00:00 2001 From: Ravi Hothi Date: Mon, 13 Jul 2026 17:48:15 +0530 Subject: [PATCH 076/243] FROMLIST: arm64: dts: qcom: eliza: Add LPASS macro and SoundWire support Add SoC-level LPASS WSA macro, VA macro, SoundWire controller and LPASS LPI pin controller nodes. DMIC and WSA SoundWire pinctrl states are defined inside the LPASS LPI pin controller node. The hardware is similar to the SM8750 platform. The WSA macro, VA macro and SoundWire controller are kept disabled so board DTS files can selectively enable and configure them. Signed-off-by: Ravi Hothi Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 193 ++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 05a8865e70a77..73e8860069c21 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -23,6 +23,7 @@ #include #include #include +#include / { interrupt-parent = <&intc>; @@ -2436,6 +2437,79 @@ }; }; + lpass_wsamacro: codec@6b00000 { + compatible = "qcom,eliza-lpass-wsa-macro", + "qcom,sm8550-lpass-wsa-macro"; + reg = <0x0 0x06b00000 0x0 0x1000>; + + clocks = <&q6prmcc LPASS_CLK_ID_WSA_CORE_TX_MCLK + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_HW_MACRO_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_HW_DCODEC_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&lpass_vamacro>; + clock-names = "mclk", + "macro", + "dcodec", + "fsgen"; + + #clock-cells = <0>; + clock-output-names = "mclk"; + #sound-dai-cells = <1>; + + status = "disabled"; + }; + + swr0: soundwire@6b10000 { + compatible = "qcom,soundwire-v2.1.0", + "qcom,soundwire-v2.0.0"; + reg = <0x0 0x06b10000 0x0 0x10000>; + interrupts = ; + + clocks = <&lpass_wsamacro>; + clock-names = "iface"; + + label = "WSA"; + + qcom,din-ports = <4>; + qcom,dout-ports = <9>; + + qcom,ports-sinterval = + /bits/ 16 <0x07 0x1f 0x3f 0x07 0x1f 0x3f + 0x18f 0x18f 0x18f 0x0f 0x0f 0xff 0x31f>; + qcom,ports-offset1 = + /bits/ 8 <0x01 0x03 0x05 0x02 0x04 0x15 + 0x00 0x00 0x00 0x06 0x0d 0xff 0x00>; + qcom,ports-offset2 = + /bits/ 8 <0xff 0x07 0x1f 0xff 0x07 0x1f + 0xff 0xff 0xff 0xff 0xff 0xff 0xff>; + qcom,ports-hstart = + /bits/ 8 <0xff 0xff 0xff 0xff 0xff 0xff + 0x08 0x0e 0x0e 0xff 0xff 0xff 0x0f>; + qcom,ports-hstop = + /bits/ 8 <0xff 0xff 0xff 0xff 0xff 0xff + 0x08 0x0e 0x0e 0xff 0xff 0xff 0x0f>; + qcom,ports-word-length = + /bits/ 8 <0xff 0xff 0xff 0xff 0xff 0xff + 0x08 0x0f 0x0f 0x00 0xff 0xff 0x18>; + qcom,ports-block-pack-mode = + /bits/ 8 <0x00 0x01 0x01 0x00 0x01 0x01 + 0x00 0x01 0x01 0x01 0x01 0x00 0x00>; + qcom,ports-block-group-count = + /bits/ 8 <0xff 0xff 0xff 0xff 0xff 0xff + 0xff 0x00 0x00 0xff 0xff 0xff 0xff>; + qcom,ports-lane-control = + /bits/ 8 <0xff 0xff 0xff 0xff 0xff 0xff + 0xff 0x00 0x00 0xff 0xff 0xff 0xff>; + + #address-cells = <2>; + #size-cells = <0>; + #sound-dai-cells = <1>; + + status = "disabled"; + }; + adreno_smmu: iommu@3da0000 { compatible = "qcom,eliza-smmu-500", "qcom,adreno-smmu", "qcom,smmu-500", "arm,mmu-500"; @@ -2505,6 +2579,125 @@ #interconnect-cells = <2>; }; + lpass_vamacro: codec@7660000 { + compatible = "qcom,eliza-lpass-va-macro", + "qcom,sm8550-lpass-va-macro"; + reg = <0x0 0x07660000 0x0 0x2000>; + + clocks = <&q6prmcc LPASS_CLK_ID_TX_CORE_MCLK + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_HW_MACRO_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_HW_DCODEC_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", + "macro", + "dcodec"; + + #clock-cells = <0>; + clock-output-names = "fsgen"; + #sound-dai-cells = <1>; + + status = "disabled"; + }; + + lpass_tlmm: pinctrl@7760000 { + compatible = "qcom,eliza-lpass-lpi-pinctrl"; + reg = <0x0 0x07760000 0x0 0x20000>; + + clocks = <&q6prmcc LPASS_HW_MACRO_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_HW_DCODEC_VOTE + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "core", "audio"; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&lpass_tlmm 0 0 23>; + + dmic01_default: dmic01-default-state { + clk-pins { + pins = "gpio6"; + function = "dmic1_clk"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio7"; + function = "dmic1_data"; + drive-strength = <8>; + input-enable; + }; + }; + + dmic23_default: dmic23-default-state { + clk-pins { + pins = "gpio8"; + function = "dmic2_clk"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio9"; + function = "dmic2_data"; + drive-strength = <8>; + input-enable; + }; + }; + + dmic45_default: dmic45-default-state { + clk-pins { + pins = "gpio12"; + function = "dmic3_clk"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio13"; + function = "dmic3_data"; + drive-strength = <8>; + input-enable; + }; + }; + + dmic67_default: dmic67-default-state { + clk-pins { + pins = "gpio21"; + function = "dmic4_clk"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio22"; + function = "dmic4_data"; + drive-strength = <8>; + input-enable; + }; + }; + + wsa_swr_active: wsa-swr-active-state { + clk-pins { + pins = "gpio10"; + function = "wsa_swr_clk"; + drive-strength = <2>; + slew-rate = <1>; + bias-disable; + }; + + data-pins { + pins = "gpio11"; + function = "wsa_swr_data"; + drive-strength = <2>; + slew-rate = <1>; + bias-bus-hold; + }; + }; + }; + sdhc_2: mmc@8804000 { compatible = "qcom,eliza-sdhci", "qcom,sdhci-msm-v5"; reg = <0x0 0x08804000 0x0 0x1000>; From f3c79efda8d5c3e27f0d3ea461405e48e9466e00 Mon Sep 17 00:00:00 2001 From: Ravi Hothi Date: Mon, 13 Jul 2026 17:48:16 +0530 Subject: [PATCH 077/243] FROMLIST: arm64: dts: qcom: eliza-cqs-evk: Enable sound card support with WSA8845 and DMIC Enable the sound card on the Eliza CQS EVK platform, including the WSA8845 external speaker path and DMIC microphone capture via VA macro. Enable the required LPASS WSA macro, VA macro and SoundWire controller along with the necessary pinctrl configurations for DMIC and WSA SoundWire interfaces. Signed-off-by: Ravi Hothi Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts | 150 +++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts b/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts index 43d428a4ed2df..74d8c860f970f 100644 --- a/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/eliza-cqs-evk.dts @@ -5,10 +5,160 @@ /dts-v1/; +#include + #include "eliza-cqs-som.dtsi" #include "eliza-evk.dtsi" / { model = "Qualcomm Technologies, Inc. Eliza CQS EVK"; compatible = "qcom,eliza-cqs-evk", "qcom,eliza-cqs-som", "qcom,eliza"; + + sound { + compatible = "qcom,eliza-sndcard", "qcom,sm8450-sndcard"; + model = "eliza-cqs-evk"; + + audio-routing = "SpkrLeft IN", "WSA_SPK1 OUT", + "SpkrRight IN", "WSA_SPK2 OUT", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb", + "VA DMIC4", "vdd-micb", + "VA DMIC5", "vdd-micb", + "VA DMIC6", "vdd-micb", + "VA DMIC7", "vdd-micb"; + + va-dai-link { + link-name = "VA Capture"; + + cpu { + sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>; + }; + + codec { + sound-dai = <&lpass_vamacro 0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wsa-dai-link { + link-name = "WSA Playback"; + + cpu { + sound-dai = <&q6apmbedai WSA_CODEC_DMA_RX_0>; + }; + + codec { + sound-dai = <&left_spkr>, <&right_spkr>, + <&swr0 0>, <&lpass_wsamacro 0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + }; + + dmic_eldo: regulator-dmic-eldo { + compatible = "regulator-fixed"; + regulator-name = "dmic-eldo"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + gpio = <&pm7550_gpios 8 GPIO_ACTIVE_HIGH>; + enable-active-high; + vin-supply = <&vreg_bob>; + regulator-boot-on; + }; +}; + +&tlmm { + spkr_1_sd_n_active: spkr-1-sd-n-active-state { + pins = "gpio59"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + output-low; + }; + + spkr_2_sd_n_active: spkr-2-sd-n-active-state { + pins = "gpio79"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + output-low; + }; +}; + +&lpass_vamacro { + pinctrl-0 = <&dmic01_default>, <&dmic23_default>, + <&dmic45_default>, <&dmic67_default>; + pinctrl-names = "default"; + + vdd-micb-supply = <&dmic_eldo>; + qcom,dmic-sample-rate = <4800000>; + + status = "okay"; +}; + +&lpass_wsamacro { + status = "okay"; +}; + +&swr0 { + pinctrl-0 = <&wsa_swr_active>; + pinctrl-names = "default"; + + status = "okay"; + + /* WSA8845, Speaker Left */ + left_spkr: speaker@0,0 { + compatible = "sdw20217020400"; + reg = <0 0>; + pinctrl-0 = <&spkr_1_sd_n_active>; + pinctrl-names = "default"; + powerdown-gpios = <&tlmm 59 GPIO_ACTIVE_LOW>; + vdd-1p8-supply = <&vreg_l7b>; + vdd-io-supply = <&vreg_l7b>; + + #sound-dai-cells = <0>; + sound-name-prefix = "SpkrLeft"; + + /* + * WSA8845 Port 1 (DAC) <=> SWR0 Port 1 (SPKR_L) + * WSA8845 Port 2 (COMP) <=> SWR0 Port 2 (SPKR_L_COMP) + * WSA8845 Port 3 (BOOST) <=> SWR0 Port 3 (SPKR_L_BOOST) + * WSA8845 Port 4 (PBR) <=> SWR0 Port 7 (PBR) + * WSA8845 Port 5 (VISENSE) <=> SWR0 Port 10 (SPKR_L_VI) + * WSA8845 Port 6 (CPS) <=> SWR0 Port 13 (CPS) + */ + qcom,port-mapping = <1 2 3 7 10 13>; + }; + + /* WSA8845, Speaker Right */ + right_spkr: speaker@0,1 { + compatible = "sdw20217020400"; + reg = <0 1>; + pinctrl-0 = <&spkr_2_sd_n_active>; + pinctrl-names = "default"; + powerdown-gpios = <&tlmm 79 GPIO_ACTIVE_LOW>; + vdd-1p8-supply = <&vreg_l7b>; + vdd-io-supply = <&vreg_l7b>; + + #sound-dai-cells = <0>; + sound-name-prefix = "SpkrRight"; + + /* + * WSA8845 Port 1 (DAC) <=> SWR0 Port 4 (SPKR_R) + * WSA8845 Port 2 (COMP) <=> SWR0 Port 5 (SPKR_R_COMP) + * WSA8845 Port 3 (BOOST) <=> SWR0 Port 6 (SPKR_R_BOOST) + * WSA8845 Port 4 (PBR) <=> SWR0 Port 7 (PBR) + * WSA8845 Port 5 (VISENSE) <=> SWR0 Port 11 (SPKR_R_VI) + * WSA8845 Port 6 (CPS) <=> SWR0 Port 13 (CPS) + */ + qcom,port-mapping = <4 5 6 7 11 13>; + }; }; From 3dfb23c4b153db5fbd2315ee73adcfb49720d79c Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 8 Jul 2026 15:29:23 +0300 Subject: [PATCH 078/243] FROMLIST: arm64: dts: qcom: eliza: Add CPU and LLCC BWMONs Describe the CPU and LLCC bandwidth monitor nodes for Eliza, together with the corresponding OPP tables. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 73e8860069c21..64f3bc7ac70be 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -4356,6 +4356,102 @@ #clock-cells = <1>; }; + pmu@24091000 { + compatible = "qcom,eliza-llcc-bwmon", "qcom,sc7280-llcc-bwmon"; + reg = <0x0 0x24091000 0x0 0x1000>; + + interrupts = ; + + interconnects = <&mc_virt MASTER_LLCC QCOM_ICC_TAG_ACTIVE_ONLY + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>; + + operating-points-v2 = <&llcc_bwmon_opp_table>; + + llcc_bwmon_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-0 { + opp-peak-kBps = <2188800>; + }; + + opp-1 { + opp-peak-kBps = <5414400>; + }; + + opp-2 { + opp-peak-kBps = <6220800>; + }; + + opp-3 { + opp-peak-kBps = <6835200>; + }; + + opp-4 { + opp-peak-kBps = <8371200>; + }; + + opp-5 { + opp-peak-kBps = <10944000>; + }; + + opp-6 { + opp-peak-kBps = <12748800>; + }; + + opp-7 { + opp-peak-kBps = <14745600>; + }; + + opp-8 { + opp-peak-kBps = <16896000>; + }; + }; + }; + + pmu@240b7400 { + compatible = "qcom,eliza-cpu-bwmon", "qcom,sdm845-bwmon"; + reg = <0x0 0x240b7400 0x0 0x600>; + + interrupts = ; + + interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &gem_noc SLAVE_LLCC QCOM_ICC_TAG_ACTIVE_ONLY>; + + operating-points-v2 = <&cpu_bwmon_opp_table>; + + cpu_bwmon_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-0 { + opp-peak-kBps = <307200>; + }; + + opp-1 { + opp-peak-kBps = <5600000>; + }; + + opp-2 { + opp-peak-kBps = <8528000>; + }; + + opp-3 { + opp-peak-kBps = <11120000>; + }; + + opp-4 { + opp-peak-kBps = <14000000>; + }; + + opp-5 { + opp-peak-kBps = <14928000>; + }; + + opp-6 { + opp-peak-kBps = <17056000>; + }; + }; + }; + gem_noc: interconnect@24100000 { compatible = "qcom,eliza-gem-noc"; reg = <0x0 0x24100000 0x0 0x163080>; From d58e1b6cb72783ec67f38549e34824ffaacabefb Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 8 Jul 2026 13:18:26 +0300 Subject: [PATCH 079/243] FROMLIST: arm64: dts: qcom: eliza-mtp: Enable touchscreen The Eliza MTP uses a Goodix GT9916 touchscreen controller connected over SPI. Describe the controller, its power supply, interrupt and reset GPIOs to enable touchscreen support. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-mtp.dts | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-mtp.dts b/arch/arm64/boot/dts/qcom/eliza-mtp.dts index 37dca6cf0e72a..5cc600772bb35 100644 --- a/arch/arm64/boot/dts/qcom/eliza-mtp.dts +++ b/arch/arm64/boot/dts/qcom/eliza-mtp.dts @@ -492,6 +492,30 @@ status = "okay"; }; +&spi8 { + status = "okay"; + + touchscreen@0 { + compatible = "goodix,gt9916"; + reg = <0>; + + interrupt-parent = <&tlmm>; + interrupts = <13 IRQ_TYPE_LEVEL_LOW>; + + reset-gpios = <&tlmm 16 GPIO_ACTIVE_LOW>; + + avdd-supply = <&vreg_l22b>; + + spi-max-frequency = <1000000>; + + touchscreen-size-x = <1080>; + touchscreen-size-y = <2400>; + + pinctrl-0 = <&ts_irq>, <&ts_reset>; + pinctrl-names = "default"; + }; +}; + &tlmm { gpio-reserved-ranges = <20 4>, /* NFC SPI */ <111 2>, /* WCN UART1 */ @@ -517,6 +541,21 @@ drive-strength = <2>; bias-pull-down; }; + + ts_irq: ts-irq-state { + pins = "gpio13"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-disable; + }; + + ts_reset: ts-reset-state { + pins = "gpio16"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; }; &uart13 { From 20cea14fdff3f7923f25bf8bd55575b774f51fc2 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Thu, 9 Jul 2026 12:12:11 +0300 Subject: [PATCH 080/243] FROMLIST: arm64: dts: qcom: eliza: Enable first QUPv3 wrapper by default Since each serial engine will be enabled as needed in each board dts, there is no point of disabling the first QUPv3 wrapper in SoC dtsi. So enable it by default. This is also now in line with the other SoCs, and also with the second QUPv3 wrapper. Fixes: 844807e1f89d ("arm64: dts: qcom: eliza: Add QUPv3, GPI DMA, SDHCI and LLCC nodes") Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260709-dts-qcom-eliza-enable-qupv3-1st-v1-1-e9a6904d0dea@oss.qualcomm.com Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 64f3bc7ac70be..0dbfb60be4e7c 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -1238,8 +1238,6 @@ #size-cells = <2>; ranges; - status = "disabled"; - i2c0: i2c@a80000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x00a80000 0x0 0x4000>; From 08a1e131b66934cd4e346632471fdc9e32dad902 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Mon, 2 Feb 2026 14:30:57 +0800 Subject: [PATCH 081/243] FROMLIST: wifi: ath12k: fix incorrect channel survey index A wrong channel survey index was introduced in ath12k_mac_op_get_survey by [1], which can cause ACS to fail. The index is decremented before being used, resulting in an incorrect value when accessing the channel survey data. Fix the index handling to ensure the correct survey entry is used and avoid ACS failures. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 4f242b1d6996 ("wifi: ath12k: support get_survey mac op for single wiphy") # [1] Signed-off-by: Yingying Tang --- drivers/net/wireless/ath/ath12k/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index af354bef5c0d7..6bc7c8c994a51 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -13584,6 +13584,7 @@ int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, struct ath12k *ar; struct ieee80211_supported_band *sband; struct survey_info *ar_survey; + int orig_idx = idx; lockdep_assert_wiphy(hw->wiphy); @@ -13618,7 +13619,7 @@ int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, return -ENOENT; } - ar_survey = &ar->survey[idx]; + ar_survey = &ar->survey[orig_idx]; ath12k_mac_update_bss_chan_survey(ar, &sband->channels[idx]); From cd050b877652dbb7e3fdf7f268f1b93aa9cdb40a Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sun, 12 Apr 2026 21:23:17 +0800 Subject: [PATCH 082/243] FROMLIST: wifi: ath12k: fix TLV32 length mask HAL_TLV_HDR_LEN was using the wrong bitmask; fix it to cover bits [21:10]. Also drop HAL_SRNG_TLV_HDR_{TAG,LEN} and use the generic TLV header bit definitions for TLV32/TLV64 encode/decode to avoid redundant macros. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260509025819.1641630-2-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/hal.c | 8 ++++---- drivers/net/wireless/ath/ath12k/hal.h | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index a164563fff289..f03817b2fbc52 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -828,8 +828,8 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len) { struct hal_tlv_64_hdr *tlv64 = tlv; - tlv64->tl = le64_encode_bits(tag, HAL_TLV_HDR_TAG) | - le64_encode_bits(len, HAL_TLV_HDR_LEN); + tlv64->tl = le64_encode_bits(tag, HAL_TLV_64_HDR_TAG) | + le64_encode_bits(len, HAL_TLV_64_HDR_LEN); return tlv64->value; } @@ -851,7 +851,7 @@ u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) struct hal_tlv_64_hdr *tlv64 = tlv; u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); *desc = tlv64->value; return tag; @@ -863,7 +863,7 @@ u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) struct hal_tlv_hdr *tlv32 = tlv; u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); *desc = tlv32->value; return tag; diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 21c551d8b2481..3ee49d93e24a0 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1444,7 +1444,7 @@ struct hal_ops { }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_TLV_HDR_LEN GENMASK(25, 10) +#define HAL_TLV_HDR_LEN GENMASK(21, 10) #define HAL_TLV_USR_ID GENMASK(31, 26) #define HAL_TLV_ALIGN 4 @@ -1464,9 +1464,6 @@ struct hal_tlv_64_hdr { u8 value[]; } __packed; -#define HAL_SRNG_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_SRNG_TLV_HDR_LEN GENMASK(25, 10) - dma_addr_t ath12k_hal_srng_get_tp_addr(struct ath12k_base *ab, struct hal_srng *srng); dma_addr_t ath12k_hal_srng_get_hp_addr(struct ath12k_base *ab, From 2b491368f8a54b5b7122c8848ae3fb2d23ed202d Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Fri, 17 Apr 2026 15:25:55 +0800 Subject: [PATCH 083/243] FROMLIST: wifi: ath12k: refactor HAL TLV32/64 decode helpers Change TLV decode helpers to return the TLV value pointer and optionally decode tag/len/usrid via out parameters. This allows reusing the helpers for DP monitor RX status header TLV parsing and avoids duplicated header decoding in callers. No functional change intended. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260509025819.1641630-3-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/hal.c | 26 ++++++++++++------- drivers/net/wireless/ath/ath12k/hal.h | 4 +-- .../wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 +- .../wireless/ath/ath12k/wifi7/hal_qcn9274.c | 11 +++++++- .../wireless/ath/ath12k/wifi7/hal_wcn7850.c | 11 +++++++- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index f03817b2fbc52..d940f83cd92fb 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -846,26 +846,32 @@ void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len) } EXPORT_SYMBOL(ath12k_hal_encode_tlv32_hdr); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_64_hdr *tlv64 = tlv; - u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); - *desc = tlv64->value; + if (tag) + *tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); + if (len) + *len = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_LEN); + if (usrid) + *usrid = le64_get_bits(tlv64->tl, HAL_TLV_64_USR_ID); - return tag; + return tlv64->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv64_hdr); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_hdr *tlv32 = tlv; - u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); - *desc = tlv32->value; + if (tag) + *tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); + if (len) + *len = le32_get_bits(tlv32->tl, HAL_TLV_HDR_LEN); + if (usrid) + *usrid = le32_get_bits(tlv32->tl, HAL_TLV_USR_ID); - return tag; + return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 3ee49d93e24a0..d141c516c97fa 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1553,6 +1553,6 @@ void ath12k_hal_rx_reo_ent_buf_paddr_get(struct ath12k_hal *hal, void *rx_desc, u8 *rbm, u32 *msdu_cnt); void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc); +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 8cebb229ebed8..6383d69656252 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -455,7 +455,7 @@ static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcc2072(void *tlv, void **desc) struct hal_reo_get_queue_stats_status_qcc2072 *status_tlv; u16 tag; - tag = ath12k_hal_decode_tlv32_hdr(tlv, (void **)&status_tlv); + status_tlv = ath12k_hal_decode_tlv32_hdr(tlv, &tag, NULL, NULL); /* * actual desc of REO status entry starts after tlv32_padding, * see hal_reo_get_queue_stats_status_qcc2072 diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 9d5180ef83b48..156a861e021de 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -950,6 +950,15 @@ void ath12k_hal_extract_rx_desc_data_qcn9274(struct hal_rx_desc_data *rx_desc_da rx_desc_data->err_bitmap = ath12k_hal_rx_h_mpdu_err_qcn9274(rx_desc); } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcn9274(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, .wbm2sw_cc_enable = HAL_WBM_SW_COOKIE_CONV_CFG_WBM2SW0_EN | @@ -1138,5 +1147,5 @@ const struct hal_ops hal_qcn9274_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index efbbc1cbd3e42..1ae4ff9b94486 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -756,6 +756,15 @@ int ath12k_hal_srng_create_config_wcn7850(struct ath12k_hal *hal) return 0; } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_wcn7850(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hal_tcl_to_wbm_rbm_map ath12k_hal_tcl_to_wbm_rbm_map_wcn7850[DP_TCL_NUM_RING_MAX] = { { @@ -821,5 +830,5 @@ const struct hal_ops hal_wcn7850_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, }; From 8ec32762067606378160f24c7bb0d2aeaa9723ef Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Fri, 17 Apr 2026 16:25:09 +0800 Subject: [PATCH 084/243] FROMLIST: wifi: ath12k: add HAL ops for monitor TLV header decode and alignment Wi-Fi 7 monitor RX status TLV parsing needs to decode TLV headers and advance the pointer with the correct header alignment. Different targets use different TLV header layouts (32-bit vs 64-bit), but the HAL ops for dp_mon RX status header decode and header alignment were not populated for all wifi7 targets. Add dp_mon RX status TLV header decode callbacks and TLV header alignment helpers to the wifi7 HAL ops for QCC2072, QCN9274 and WCN7850. Export helpers to query the required TLV header alignment for 32-bit and 64-bit TLV headers so the caller can align the TLV walk correctly across targets. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260509025819.1641630-4-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/hal.c | 12 ++++++++++++ drivers/net/wireless/ath/ath12k/hal.h | 4 ++++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c | 2 ++ 5 files changed, 22 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index d940f83cd92fb..c0c3d2f047ef0 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -875,3 +875,15 @@ void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); + +u32 ath12k_hal_get_tlv64_hdr_align(void) +{ + return HAL_TLV_64_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv64_hdr_align); + +u32 ath12k_hal_get_tlv32_hdr_align(void) +{ + return HAL_TLV_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv32_hdr_align); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index d141c516c97fa..ba2c22fb2982b 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1441,6 +1441,8 @@ struct hal_ops { u8 *rbm, u32 *msdu_cnt); void *(*reo_cmd_enc_tlv_hdr)(void *tlv, u64 tag, u64 len); u16 (*reo_status_dec_tlv_hdr)(void *tlv, void **desc); + void *(*mon_rx_status_dec_tlv_hdr)(void *tlv, u16 *tag, u16 *len, u16 *usrid); + u32 (*get_tlv_hdr_align)(void); }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) @@ -1555,4 +1557,6 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +u32 ath12k_hal_get_tlv64_hdr_align(void); +u32 ath12k_hal_get_tlv32_hdr_align(void); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 6383d69656252..7cf00fda996f3 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -506,6 +506,8 @@ const struct hal_ops hal_qcc2072_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv32_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcc2072, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv32_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv32_hdr_align, }; u32 ath12k_hal_rx_desc_get_mpdu_start_offset_qcc2072(void) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 156a861e021de..052b59265af85 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -1148,4 +1148,6 @@ const struct hal_ops hal_qcn9274_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index 1ae4ff9b94486..61be8443e46ee 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -831,4 +831,6 @@ const struct hal_ops hal_wcn7850_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; From 2c1fd7b0b6857d3f1756e9f401b3a391087eb9cc Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Fri, 17 Apr 2026 20:19:00 +0800 Subject: [PATCH 085/243] FROMLIST: wifi: ath12k: add dp_mon support 32-bit TLV headers Wi-Fi 7 monitor status parsing in dp_mon currently assumes a 64-bit TLV header and directly decodes tag/len/userid from struct hal_tlv_64_hdr. On chips using a 32-bit TLV header (e.g. QCC2072), this causes monitor RX status packets to be dropped during TLV parsing. Introduce HAL helpers to decode TLV header fields (tag/len/userid/value) for both 32-bit and 64-bit header layouts. Without changing the actual TLV parsing logic. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260509025819.1641630-5-miaoqing.pan@oss.qualcomm.com/ --- .../net/wireless/ath/ath12k/wifi7/dp_mon.c | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 7dd4a49d64d5f..06ca96c3cc7e7 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -1565,16 +1565,17 @@ ath12k_wifi7_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon, static enum hal_rx_mon_status ath12k_wifi7_dp_mon_rx_parse_status_tlv(struct ath12k_pdev_dp *dp_pdev, struct ath12k_mon_data *pmon, - const struct hal_tlv_64_hdr *tlv) + const void *tlv) { struct hal_rx_mon_ppdu_info *ppdu_info = &pmon->mon_ppdu_info; - const void *tlv_data = tlv->value; - u32 info[7], userid; - u16 tlv_tag, tlv_len; + struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); + struct ath12k_hal *hal = &ar->ab->hal; + u16 tlv_tag, tlv_len, userid; + void *tlv_data; + u32 info[7]; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); - userid = le64_get_bits(tlv->tl, HAL_TLV_64_USR_ID); + tlv_data = hal->ops->mon_rx_status_dec_tlv_hdr((void *)tlv, &tlv_tag, + &tlv_len, &userid); if (ppdu_info->tlv_aggr.in_progress && ppdu_info->tlv_aggr.tlv_tag != tlv_tag) { ath12k_wifi7_dp_mon_parse_eht_sig_hdr(ppdu_info, @@ -2930,11 +2931,12 @@ static enum dp_mon_status_buf_state ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, struct dp_rxdma_mon_ring *rx_ring) { + struct ath12k_hal *hal = &ab->hal; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; struct sk_buff *skb; void *status_desc; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; int buf_id; u8 rbm; @@ -2959,8 +2961,8 @@ ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != HAL_RX_STATUS_BUFFER_DONE) + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) return DP_MON_STATUS_NO_DMA; return DP_MON_STATUS_REPLINISH; @@ -2972,39 +2974,38 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *skb) { struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); - struct hal_tlv_64_hdr *tlv; + struct ath12k_hal *hal = &ar->ab->hal; + u8 *tlv_value, *tlv = skb->data; struct ath12k_skb_rxcb *rxcb; enum hal_rx_mon_status hal_status; u16 tlv_tag, tlv_len; - u8 *ptr = skb->data; + u32 tlv_hdr_len; + + tlv_hdr_len = hal->ops->get_tlv_hdr_align(); do { - tlv = (struct hal_tlv_64_hdr *)ptr; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); + tlv_value = hal->ops->mon_rx_status_dec_tlv_hdr(tlv, &tlv_tag, + &tlv_len, NULL); /* The actual length of PPDU_END is the combined length of many PHY * TLVs that follow. Skip the TLV header and * rx_rxpcu_classification_overview that follows the header to get to * next TLV. */ - if (tlv_tag == HAL_RX_PPDU_END) tlv_len = sizeof(struct hal_rx_rxpcu_classification_overview); - else - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); hal_status = ath12k_wifi7_dp_mon_rx_parse_status_tlv(dp_pdev, pmon, tlv); if (ar->monitor_started && ar->ab->hw_params->rxdma1_enable && ath12k_wifi7_dp_mon_parse_rx_dest_tlv(dp_pdev, pmon, hal_status, - tlv->value)) + tlv_value)) return HAL_RX_MON_STATUS_PPDU_DONE; - ptr += sizeof(*tlv) + tlv_len; - ptr = PTR_ALIGN(ptr, HAL_TLV_64_ALIGN); + tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((ptr - skb->data) > skb->len) + if ((tlv - skb->data) > skb->len) break; } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || @@ -3056,15 +3057,16 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, int buf_id, srng_id, num_buffs_reaped = 0; enum dp_mon_status_buf_state reap_status; struct dp_rxdma_mon_ring *rx_ring; + struct ath12k_hal *hal = &ab->hal; struct ath12k_mon_data *pmon; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; void *rx_mon_status_desc; struct hal_srng *srng; struct ath12k_dp *dp; struct sk_buff *skb; struct ath12k *ar; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; u8 rbm; @@ -3109,14 +3111,13 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != - HAL_RX_STATUS_BUFFER_DONE) { + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, + NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) { pmon->buf_state = DP_MON_STATUS_NO_DMA; ath12k_warn(ab, - "mon status DONE not set %llx, buf_id %d\n", - le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG), - buf_id); + "mon status DONE not set %x, buf_id %d\n", + tlv_tag, buf_id); /* RxDMA status done bit might not be set even * though tp is moved by HW. */ From 12d31db3420f93f83ed75a82b01c96e33f49b661 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sun, 12 Apr 2026 21:21:46 +0800 Subject: [PATCH 086/243] FROMLIST: wifi: ath12k: tighten RX monitor TLV bounds check Validate the pointer to the next RX monitor TLV more strictly by ensuring that at least a full TLV header is available within the status buffer before continuing TLV parsing. Prevent potential out-of-bounds access when handling malformed or truncated RX monitor status data. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260509025819.1641630-6-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 06ca96c3cc7e7..c84c42a3d3774 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -3005,9 +3005,9 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((tlv - skb->data) > skb->len) + if (unlikely(tlv - skb->data > skb->len || + skb->len - (tlv - skb->data) < tlv_hdr_len)) break; - } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || (hal_status == HAL_RX_MON_STATUS_BUF_ADDR) || (hal_status == HAL_RX_MON_STATUS_MPDU_START) || From 38bb8234cf2cbb0e95997af5de45007d8a3d4594 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 12 May 2026 10:17:08 +0800 Subject: [PATCH 087/243] FROMLIST: genirq: export irq_can_set_affinity() for module drivers Export irq_can_set_affinity() for loadable drivers that need a runtime check for IRQ affinity capability. In hierarchical IRQ setups where the effective irqchip path lacks .irq_set_affinity(), drivers may need to switch to a fallback policy. Without this export, module drivers cannot use the core helper and have to open-code equivalent checks. Signed-off-by: Hangtian Zhu Link: https://lore.kernel.org/all/20260519011627.713068-1-hangtian.zhu@oss.qualcomm.com/ --- kernel/irq/manage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 7eb07e3bdb4c2..f73fda08417ab 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -171,6 +171,7 @@ int irq_can_set_affinity(unsigned int irq) { return __irq_can_set_affinity(irq_to_desc(irq)); } +EXPORT_SYMBOL_GPL(irq_can_set_affinity); /** * irq_can_set_affinity_usr - Check if affinity of a irq can be set from user space From aaf97443c9d611f65d2e6afe51ce7593f802637b Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 12 May 2026 10:17:08 +0800 Subject: [PATCH 088/243] FROMLIST: wifi: ath12k: enable threaded NAPI when DP IRQ affinity is unavailable Determine threaded NAPI policy from runtime IRQ capability of the DP MSI IRQ. If irq_can_set_affinity() reports that affinity cannot be set, enable threaded NAPI for DP interrupt groups so datapath processing is not constrained by a single-CPU softirq context. On RB3Gen2, where IRQ affinity is unavailable in the effective IRQ path, EHT160 UDP downlink throughput improved from 802 Mbps to 2.58 Gbps after enabling threaded NAPI. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1 Signed-off-by: Hangtian Zhu Link: https://lore.kernel.org/all/20260519011627.713068-1-hangtian.zhu@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/pci.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index d9a22d6afbb02..6073157fcedb7 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -537,6 +538,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) int i, j, n, ret, num_vectors = 0; u32 user_base_data = 0, base_vector = 0, base_idx; struct ath12k_ext_irq_grp *irq_grp; + bool threaded_napi = false; + int irq; base_idx = ATH12K_PCI_IRQ_CE0_OFFSET + CE_COUNT_MAX; ret = ath12k_pci_get_user_msi_assignment(ab, "DP", @@ -546,6 +549,10 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) if (ret < 0) return ret; + irq = ath12k_pci_get_msi_irq(ab->dev, base_vector); + if (irq >= 0) + threaded_napi = !irq_can_set_affinity(irq); + for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) { irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; @@ -560,6 +567,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath12k_pci_ext_grp_napi_poll); + if (threaded_napi) + netif_threaded_enable(irq_grp->napi_ndev); if (ab->hw_params->ring_mask->tx[i] || ab->hw_params->ring_mask->rx[i] || @@ -578,7 +587,7 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) for (j = 0; j < irq_grp->num_irq; j++) { int irq_idx = irq_grp->irqs[j]; int vector = (i % num_vectors) + base_vector; - int irq = ath12k_pci_get_msi_irq(ab->dev, vector); + irq = ath12k_pci_get_msi_irq(ab->dev, vector); ab->irq_num[irq_idx] = irq; From d38737507639f2cf2240ce95a11d1bd6fa95eaa6 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Fri, 27 Mar 2026 21:10:11 +0800 Subject: [PATCH 089/243] wifi: ath12k: change MAC buffer ring size to 4096 For WCN7850, MAC buffer ring size is updated to 2048 in 955df16f2a4c3 ("wifi: ath12k: change MAC buffer ring size to 2048") to increase peak throughput. But during the RX process, a phenomenon can still be observed where the throughput drops by about 30% from its peak value and then recovers, and this behavior repeats during RX. After increasing MAC buffer ring size to 4096, the data rate drop has gone. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Yingying Tang --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index f8cfc7bb29dd7..6596089d41688 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -205,7 +205,7 @@ struct ath12k_pdev_dp { #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 #define DP_RXDMA_BUF_RING_SIZE 4096 -#define DP_RX_MAC_BUF_RING_SIZE 2048 +#define DP_RX_MAC_BUF_RING_SIZE 4096 #define DP_RXDMA_REFILL_RING_SIZE 2048 #define DP_RXDMA_ERR_DST_RING_SIZE 1024 #define DP_RXDMA_MON_STATUS_RING_SIZE 1024 From f526aaf6d57624f52650eb0612b89a035b9141f1 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Wed, 10 Jun 2026 08:46:48 +0530 Subject: [PATCH 090/243] wifi: ath12k: Fix low MLO RX throughput on WCN7850 Commit [1] introduced a regression causing severely degraded MLO RX throughput on WCN7850. On WCN7850, there is only a single ar instance, but MLO uses two link IDs. ath12k_dp_peer->hw_links[] is indexed using ar->hw_link_id, which causes both MLO link IDs to be stored at the same index. As a result, an incorrect link ID is assigned to MSDUs in ath12k_dp_rx_deliver_msdu(), leading to severe MLO RX throughput loss. Different chipsets identify the per-MSDU link differently: - On QCN9274 / IPQ5332, the host owns multiple ar instances and the per-MSDU hw_link_id from the RX descriptor maps cleanly through dp_peer->hw_links[hw_link_id] to the IEEE link_id. - On single-ar chipsets like WCN7850 / QCC2072, there is only one ar instance for both MLO links, so dp_peer->hw_links[] has just one valid slot and cannot be used to distinguish the two links. To resolve the link, walk dp_peer->link_peers[] and match by rxcb->peer_id, which on the link_peer side identifies the link peer for the MSDU. Add a new hw_op set_rx_link_id() so each chipset resolves the link on the RX fast path using whatever signal it actually has, and let the op itself decide whether to populate rx_status::link_valid and rx_status::link_id: QCN9274 / IPQ5332 : always derive link_id from dp_peer->hw_links[rxcb->hw_link_id] and set link_valid. WCN7850 / QCC2072 : walk the link_peers[] of dp_peer to find the link_peer whose peer_id matches rxcb->peer_id, and set link_valid only when a match is found. Otherwise leave link_valid clear so that mac80211 can fall back to its own link resolution path (via addr2 / deflink). For WCN7850 / QCC2072, walking dp_peer->link_peers[] is bounded by the number of links actually populated, so introduce a link_peers_map bitmap (unsigned long) in struct ath12k_dp_peer that tracks populated slots and use for_each_set_bit() to iterate. Non-MLO clients hit one slot, current MLO clients hit two; the full ATH12K_NUM_MAX_LINKS array is never scanned. The bitmap is maintained with WRITE_ONCE() on the write side (under dp_hw->peer_lock) paired with READ_ONCE() on both the lockless RX read side and the write-side RMW for KCSAN correctness. Also guard the dp_peer dereference in ath12k_mac_peer_cleanup_all() with a NULL check, since peer->dp_peer can be NULL for self-peers or peers not yet fully assigned, the pre-existing rcu_assign_pointer() call there had the same latent issue. This restores the correct link ID on WCN7850 without changing the QCN9274 / IPQ5332 data path, which keeps its O(1) hw_links[] indexing. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 11157e0910fd ("wifi: ath12k: Use ath12k_dp_peer in per packet Tx & Rx paths") # [1] Signed-off-by: Yingying Tang --- drivers/net/wireless/ath/ath12k/dp_peer.c | 4 +++ drivers/net/wireless/ath/ath12k/dp_peer.h | 1 + drivers/net/wireless/ath/ath12k/dp_rx.c | 7 ++-- drivers/net/wireless/ath/ath12k/hw.h | 16 +++++++++ drivers/net/wireless/ath/ath12k/mac.c | 10 ++++-- drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c | 33 +++++++++++++++++++ drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h | 6 ++++ drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 ++ 8 files changed, 73 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index 47d009a0d61fe..2660b4c7449be 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -570,6 +570,8 @@ int ath12k_dp_link_peer_assign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw, peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); rcu_assign_pointer(dp_peer->link_peers[peer->link_id], peer); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) | BIT(peer->link_id)); rcu_assign_pointer(dp_hw->dp_peers[peerid_index], dp_peer); @@ -632,6 +634,8 @@ void ath12k_dp_link_peer_unassign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_ peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id)); rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h index f5067e66f1e18..7c9709bf717b4 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.h +++ b/drivers/net/wireless/ath/ath12k/dp_peer.h @@ -140,6 +140,7 @@ struct ath12k_dp_peer { /* Info used in MMIC verification of * RX fragments */ struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1]; + unsigned long link_peers_map; struct ath12k_dp_link_peer __rcu *link_peers[ATH12K_NUM_MAX_LINKS]; struct ath12k_reoq_buf reoq_bufs[IEEE80211_NUM_TIDS + 1]; struct ath12k_dp_rx_tid rx_tid[IEEE80211_NUM_TIDS + 1]; diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 06e74124e57eb..8fa0e90b45316 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -1384,10 +1384,9 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc pubsta = peer ? peer->sta : NULL; - if (pubsta && pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->hw_links[rxcb->hw_link_id]; - } + status->link_valid = 0; + if (pubsta && pubsta->valid_links) + ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status); ath12k_dbg(dp->ab, ATH12K_DBG_DATA, "rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index d135b29363788..86fb8b7196135 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -13,6 +13,10 @@ #include "wmi.h" #include "hal.h" +struct ath12k_dp_peer; +struct ath12k_skb_rxcb; +struct ieee80211_rx_status; + /* Target configuration defines */ /* Num VDEVS per radio */ @@ -243,6 +247,9 @@ struct ath12k_hw_ops { bool (*dp_srng_is_tx_comp_ring)(int ring_num); bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif, struct ieee80211_mgmt *mgmt); + void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); }; static inline @@ -273,6 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw, return 0; } +static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw, + struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + if (hw->hw_ops->set_rx_link_id) + hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status); +} + struct ath12k_fw_ie { __le32 id; __le32 len; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 6bc7c8c994a51..04d78dd64ac53 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1238,9 +1238,13 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar) /* cleanup dp peer */ spin_lock_bh(&dp_hw->peer_lock); dp_peer = peer->dp_peer; - peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); - rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); - rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); + if (dp_peer) { + peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); + rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id)); + rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); + } spin_unlock_bh(&dp_hw->peer_lock); ath12k_dp_link_peer_rhash_delete(dp, peer); diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c index cb9dd8cb28b67..95d87dd67872f 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c @@ -5,6 +5,7 @@ */ #include "dp_rx.h" +#include "../dp_peer.h" #include "../dp_tx.h" #include "../peer.h" #include "hal_qcn9274.h" @@ -2315,3 +2316,35 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, return tlv_tag == HAL_RX_MPDU_START; } + +void +ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + status->link_valid = 1; + status->link_id = dp_peer->hw_links[rxcb->hw_link_id]; +} + +void +ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + struct ath12k_dp_link_peer *link_peer; + unsigned long links_map; + int i; + + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), + "ath12k set rx link id called without rcu lock"); + + links_map = READ_ONCE(dp_peer->link_peers_map); + for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) { + link_peer = rcu_dereference(dp_peer->link_peers[i]); + if (link_peer && link_peer->peer_id == rxcb->peer_id) { + status->link_valid = 1; + status->link_id = link_peer->link_id; + return; + } + } +} diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h index 8aa79faf567fd..1d3a4788a2dd9 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h @@ -57,4 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, struct hal_rx_desc *rx_desc); int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab, struct ath12k_dp_rx_tid_rxq *rx_tid); +void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); +void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 3d59fa452ec03..d9fdd2fc82986 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -158,6 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274, }; static const struct ath12k_hw_ops wcn7850_ops = { @@ -168,6 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, }; static const struct ath12k_hw_ops qcc2072_ops = { @@ -178,6 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, }; #define ATH12K_TX_RING_MASK_0 0x1 From c1471e971fcd7fb955b7943286850613c31aea13 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Fri, 22 May 2026 13:47:45 +0800 Subject: [PATCH 091/243] FROMLIST: Revert "wifi: ath12k: add panic handler" This reverts commit 809055628bce824b7fe18331abb65e44d02b0ecf. Call trace: rcu_note_context_switch+0x4c4/0x508 (P) __schedule+0xbc/0x1204 schedule+0x34/0x110 schedule_timeout+0x84/0x11c __mhi_device_get_sync+0x164/0x228 [mhi] mhi_device_get_sync+0x1c/0x3c [mhi] ath12k_wifi7_pci_bus_wake_up+0x20/0x2c [ath12k_wifi7] ath12k_pci_read32+0x58/0x350 [ath12k] ath12k_pci_clear_dbg_registers+0x28/0xb8 [ath12k] ath12k_pci_panic_handler+0x20/0x44 [ath12k] ath12k_core_panic_handler+0x28/0x3c [ath12k] notifier_call_chain+0x78/0x1c0 atomic_notifier_call_chain+0x3c/0x5c ath12k_core_panic_handler() is invoked via atomic_notifier_call_chain(), which runs inside an RCU read-side critical section. The current code calls ath12k_pci_sw_reset() synchronously from this context, which eventually reaches mhi_device_get_sync() and schedule_timeout(), triggering a voluntary context switch within RCU. Revert change "wifi: ath12k: add panic handler" to avoid this issue. Tested-on: WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Link: https://lore.kernel.org/all/20260612032332.2278338-1-yingying.tang@oss.qualcomm.com/ Signed-off-by: Yingying Tang --- drivers/net/wireless/ath/ath12k/core.c | 33 +------------------------- drivers/net/wireless/ath/ath12k/core.h | 3 --- drivers/net/wireless/ath/ath12k/hif.h | 9 ------- drivers/net/wireless/ath/ath12k/pci.c | 8 ------- 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 742d4fd1b598c..4bbfaaa446ec0 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1793,29 +1793,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2231,18 +2208,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2267,8 +2239,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2277,7 +2247,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index fc5127b5c1a37..d41b8a556ea2c 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1138,8 +1137,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e91..aecdb0c5f2858 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6073157fcedb7..45704c8264894 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1478,13 +1478,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1502,7 +1495,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif From 219e683ef2e2db0aeca9713d5393b30bfb3f3113 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Mon, 8 Sep 2025 09:50:25 +0800 Subject: [PATCH 092/243] wifi: ath12k: Fix missing station power save configuration Commit afbab6e4e88d ("wifi: ath12k: modify ath12k_mac_op_bss_info_changed() for MLO") replaced the bss_info_changed() callback with vif_cfg_changed() and link_info_changed() to support Multi-Link Operation (MLO). As a result, the station power save configuration is no longer correctly applied in ath12k_mac_bss_info_changed(). Move the handling of 'BSS_CHANGED_PS' into ath12k_mac_op_vif_cfg_changed() to align with the updated callback structure introduced for MLO, ensuring proper power-save behavior for station interfaces. Tested-on: WCN7850 hw2.0 PCI WLAN.IOE_HMT.1.1-00011-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: afbab6e4e88d ("wifi: ath12k: modify ath12k_mac_op_bss_info_changed() for MLO") Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/all/20250908015025.1301398-1-miaoqing.pan@oss.qualcomm.com/ Signed-off-by: Daizhuang Bai --- drivers/net/wireless/ath/ath12k/mac.c | 124 ++++++++++++++------------ 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 04d78dd64ac53..4bbbaf4a99c60 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -4365,12 +4365,68 @@ static int ath12k_mac_fils_discovery(struct ath12k_link_vif *arvif, return ret; } +static void ath12k_mac_vif_setup_ps(struct ath12k_link_vif *arvif) +{ + struct ath12k *ar = arvif->ar; + struct ieee80211_vif *vif = arvif->ahvif->vif; + struct ieee80211_conf *conf = &ath12k_ar_to_hw(ar)->conf; + enum wmi_sta_powersave_param param; + struct ieee80211_bss_conf *info; + enum wmi_sta_ps_mode psmode; + int ret; + int timeout; + bool enable_ps; + + lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + enable_ps = arvif->ahvif->ps; + if (enable_ps) { + psmode = WMI_STA_PS_MODE_ENABLED; + param = WMI_STA_PS_PARAM_INACTIVITY_TIME; + + timeout = conf->dynamic_ps_timeout; + if (timeout == 0) { + info = ath12k_mac_get_link_bss_conf(arvif); + if (!info) { + ath12k_warn(ar->ab, "unable to access bss link conf in setup ps for vif %pM link %u\n", + vif->addr, arvif->link_id); + return; + } + + /* firmware doesn't like 0 */ + timeout = ieee80211_tu_to_usec(info->beacon_int) / 1000; + } + + ret = ath12k_wmi_set_sta_ps_param(ar, arvif->vdev_id, param, + timeout); + if (ret) { + ath12k_warn(ar->ab, "failed to set inactivity time for vdev %d: %i\n", + arvif->vdev_id, ret); + return; + } + } else { + psmode = WMI_STA_PS_MODE_DISABLED; + } + + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac vdev %d psmode %s\n", + arvif->vdev_id, psmode ? "enable" : "disable"); + + ret = ath12k_wmi_pdev_set_ps_mode(ar, arvif->vdev_id, psmode); + if (ret) + ath12k_warn(ar->ab, "failed to set sta power save mode %d for vdev %d: %d\n", + psmode, arvif->vdev_id, ret); +} + void ath12k_mac_op_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u64 changed) { struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif); unsigned long links = ahvif->links_map; + struct ieee80211_vif_cfg *vif_cfg; struct ieee80211_bss_conf *info; struct ath12k_link_vif *arvif; struct ieee80211_sta *sta; @@ -4434,63 +4490,26 @@ void ath12k_mac_op_vif_cfg_changed(struct ieee80211_hw *hw, } } } -} -EXPORT_SYMBOL(ath12k_mac_op_vif_cfg_changed); - -static void ath12k_mac_vif_setup_ps(struct ath12k_link_vif *arvif) -{ - struct ath12k *ar = arvif->ar; - struct ieee80211_vif *vif = arvif->ahvif->vif; - struct ieee80211_conf *conf = &ath12k_ar_to_hw(ar)->conf; - enum wmi_sta_powersave_param param; - struct ieee80211_bss_conf *info; - enum wmi_sta_ps_mode psmode; - int ret; - int timeout; - bool enable_ps; - lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); + if (changed & BSS_CHANGED_PS) { + links = ahvif->links_map; + vif_cfg = &vif->cfg; - if (vif->type != NL80211_IFTYPE_STATION) - return; + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + arvif = wiphy_dereference(hw->wiphy, ahvif->link[link_id]); + if (!arvif || !arvif->ar) + continue; - enable_ps = arvif->ahvif->ps; - if (enable_ps) { - psmode = WMI_STA_PS_MODE_ENABLED; - param = WMI_STA_PS_PARAM_INACTIVITY_TIME; + ar = arvif->ar; - timeout = conf->dynamic_ps_timeout; - if (timeout == 0) { - info = ath12k_mac_get_link_bss_conf(arvif); - if (!info) { - ath12k_warn(ar->ab, "unable to access bss link conf in setup ps for vif %pM link %u\n", - vif->addr, arvif->link_id); - return; + if (ar->ab->hw_params->supports_sta_ps) { + ahvif->ps = vif_cfg->ps; + ath12k_mac_vif_setup_ps(arvif); } - - /* firmware doesn't like 0 */ - timeout = ieee80211_tu_to_usec(info->beacon_int) / 1000; - } - - ret = ath12k_wmi_set_sta_ps_param(ar, arvif->vdev_id, param, - timeout); - if (ret) { - ath12k_warn(ar->ab, "failed to set inactivity time for vdev %d: %i\n", - arvif->vdev_id, ret); - return; } - } else { - psmode = WMI_STA_PS_MODE_DISABLED; } - - ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac vdev %d psmode %s\n", - arvif->vdev_id, psmode ? "enable" : "disable"); - - ret = ath12k_wmi_pdev_set_ps_mode(ar, arvif->vdev_id, psmode); - if (ret) - ath12k_warn(ar->ab, "failed to set sta power save mode %d for vdev %d: %d\n", - psmode, arvif->vdev_id, ret); } +EXPORT_SYMBOL(ath12k_mac_op_vif_cfg_changed); static bool ath12k_mac_supports_tpc(struct ath12k *ar, struct ath12k_vif *ahvif, const struct cfg80211_chan_def *chandef) @@ -4695,7 +4714,6 @@ static void ath12k_mac_bss_info_changed(struct ath12k *ar, { struct ath12k_vif *ahvif = arvif->ahvif; struct ieee80211_vif *vif = ath12k_ahvif_to_vif(ahvif); - struct ieee80211_vif_cfg *vif_cfg = &vif->cfg; struct ath12k_link_vif *tx_arvif; struct cfg80211_chan_def def; u32 param_id, param_value; @@ -5033,12 +5051,6 @@ static void ath12k_mac_bss_info_changed(struct ath12k *ar, } ath12k_mac_fils_discovery(arvif, info); - - if (changed & BSS_CHANGED_PS && - ar->ab->hw_params->supports_sta_ps) { - ahvif->ps = vif_cfg->ps; - ath12k_mac_vif_setup_ps(arvif); - } } static struct ath12k_vif_cache *ath12k_ahvif_get_link_cache(struct ath12k_vif *ahvif, From 5d5c772c42036caf690acb86265a15eb7fc67ae2 Mon Sep 17 00:00:00 2001 From: Gaole Zhang Date: Fri, 24 Apr 2026 14:52:11 +0800 Subject: [PATCH 093/243] FROMLIST: wifi: ath11k: fix NULL pointer dereference in ath11k_hal_srng_access_begin In ATH11K_QMI_EVENT_FW_READY, ATH11K_FLAG_REGISTERED is set unconditionally even when ath11k_core_qmi_firmware_ready() fails. This leaves the driver in an inconsistent state where initialization is considered complete although the firmware ready handling did not finish successfully. During the subsequent SSR, the driver enters the restart path based on this incorrect state and dereferences uninitialized srng members, resulting in a NULL pointer dereference. Call trace: ath11k_hal_srng_access_begin+0xc/0x60 [ath11k] (P) ath11k_ce_cleanup_pipes+0x17c/0x180 [ath11k] ath11k_core_restart+0x40/0x168 [ath11k] Fix this by: - skipping firmware_ready if ATH11K_FLAG_REGISTERED is already set - setting ATH11K_FLAG_REGISTERED only when firmware_ready succeeds - setting ATH11K_FLAG_QMI_FAIL and aborting the FW_READY handling on error Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 6fe62a8cec51c ("wifi: ath11k: Add cold boot calibration support on WCN6750") Signed-off-by: Gaole Zhang Link: https://lore.kernel.org/linux-wireless/20260609090609.4041009-1-gaole.zhang@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath11k/qmi.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 410a7ee076a02..7dc07339b9579 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -3294,9 +3294,14 @@ static void ath11k_qmi_driver_event_work(struct work_struct *work) clear_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags); clear_bit(ATH11K_FLAG_RECOVERY, &ab->dev_flags); - ath11k_core_qmi_firmware_ready(ab); - set_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags); - + if (!test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) { + ret = ath11k_core_qmi_firmware_ready(ab); + if (ret) { + set_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags); + break; + } + set_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags); + } break; case ATH11K_QMI_EVENT_COLD_BOOT_CAL_DONE: break; From 2d3329febcb44be907f65155e3a4124506c891b0 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 19 Jun 2026 21:47:50 +0800 Subject: [PATCH 094/243] FROMLIST: wifi: ath12k: fix rx_mpdu_start layout for QCC2072 QCC2072's rx_mpdu_start TLV has a different field layout from QCN9274. Reusing struct rx_mpdu_start_qcn9274 in hal_rx_desc_qcc2072 causes the RX datapath to read the wrong offsets for info2, info4, pn[] and phy_ppdu_id, producing corrupted sequence number, PN, ppdu_id and mpdu-info flags (encrypted, fragment, addr2/addr4 valid). Add a dedicated struct rx_mpdu_start_qcc2072 that matches the actual hardware descriptor layout, and use it in hal_rx_desc_qcc2072. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00188-QCACOLSWPL_V1_TO_SILICONZ-1 Fixes: 28badc78142e ("wifi: ath12k: add HAL descriptor and ops for QCC2072") Link: https://lore.kernel.org/linux-wireless/20260629061529.1993932-1-wei.zhang@oss.qualcomm.com/ Signed-off-by: Wei Zhang --- .../wireless/ath/ath12k/wifi7/hal_rx_desc.h | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h index 0d19a9cbb68ce..6d69851e529d8 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h @@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 { __le32 res1; } __packed; +struct rx_mpdu_start_qcc2072 { + __le32 info0; + __le32 info2; + __le32 reo_queue_desc_lo; + __le32 info1; + __le32 pn[4]; + __le32 info4; + __le32 peer_meta_data; + __le16 ast_index; + __le16 sw_peer_id; + __le16 info3; + __le16 phy_ppdu_id; + __le32 info5; + __le32 info6; + __le16 frame_ctrl; + __le16 duration; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + __le16 qos_ctrl; + __le32 ht_ctrl; + __le32 info7; + __le32 res0; + __le32 res1; + __le32 res2; + __le32 info8; + __le32 res3; + __le32 res4; +} __packed; + #define QCN9274_MPDU_START_SELECT_MPDU_START_TAG BIT(0) #define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO BIT(1) #define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0 BIT(2) @@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 { struct rx_msdu_end_qcn9274 msdu_end; u8 rx_padding0[RX_BE_PADDING0_BYTES]; __le32 mpdu_start_tag; - struct rx_mpdu_start_qcn9274 mpdu_start; + struct rx_mpdu_start_qcc2072 mpdu_start; struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv; u8 msdu_payload[]; }; From cb074f9a383194ff8030c0bf42ef06e537c3004a Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Fri, 13 Mar 2026 15:39:16 +0800 Subject: [PATCH 095/243] FROMLIST: wifi: ath12k: fix ML-STA authentication timeout Firmware interprets the MLO_LINK_ADD and MLO_START_AS_ACTIVE flags to control the link state during MLO vdev start. MLO_LINK_ADD indicates that a link is being added, while MLO_START_AS_ACTIVE specifies that the link should become active during the start. When an association link is added without setting MLO_START_AS_ACTIVE, the firmware may transition the link into a suspended state. In this case, authentication frames transmitted by the host can be dropped, leading to repeated authentication retries and eventual timeout, for example: wlp1s0: send auth to (try 1/3) wlp1s0: send auth to (try 2/3) wlp1s0: send auth to (try 3/3) wlp1s0: authentication with timed out Avoid triggering this behavior by setting the MLO_START_AS_ACTIVE flag when MLO_ASSOC_LINK is set, which tells the firmware that the current vdev must not enter suspend mode Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d8e1f4a19310 ("wifi: ath12k: enable QCC2072 support") Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260704073000.3300099-1-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath12k/wmi.c | 4 +++- drivers/net/wireless/ath/ath12k/wmi.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 84a31b953db81..5c455b5e3f702 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1228,7 +1228,9 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, le32_encode_bits(arg->ml.mcast_link, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, - ATH12K_WMI_FLAG_MLO_LINK_ADD); + ATH12K_WMI_FLAG_MLO_LINK_ADD) | + le32_encode_bits(arg->ml.assoc_link, + ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE); ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %d start ml flags 0x%x\n", arg->vdev_id, ml_params->flags); diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c452e3d57a29a..468f1bef7363a 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2954,6 +2954,7 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE BIT(17) struct wmi_vdev_start_mlo_params { __le32 tlv_header; From 2b06f26aed645301b687de3c0c80fb175edb9dea Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Mon, 29 Jun 2026 10:46:56 +0800 Subject: [PATCH 096/243] FROMLIST: wifi: ath11k: add purwa-iot-evk and qcs6490-rb3gen2 to usecase firmware table Add purwa-iot-evk and qcs6490-rb3gen2 platform support to the usecase firmware lookup table for WCN6855 hw2.1. These platforms use the nfa765 firmware path for usecase-based firmware selection. Also reorder the table entries by compatible string. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 Signed-off-by: Miaoqing Pan Link: https://lore.kernel.org/linux-wireless/20260713020359.3618193-1-miaoqing.pan@oss.qualcomm.com/ --- drivers/net/wireless/ath/ath11k/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 8dacc878c0068..8039124e78327 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1049,9 +1049,11 @@ static const struct __ath11k_core_usecase_firmware_table { const char *compatible; const char *firmware_name; } ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, - { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,purwa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,qcs6490-rb3gen2", "nfa765"}, { /* Sentinel */ } }; From 009795ab406cb11f63c6d885437c67617b811913 Mon Sep 17 00:00:00 2001 From: Daizhuang Bai Date: Thu, 9 Jul 2026 16:25:13 +0530 Subject: [PATCH 097/243] FROMLIST: wifi: ath12k: Set DTIM policy to stick mode for station interface Currently, the station always follows the listen interval regardless of the DTIM value. The DTIM function does not work as expected. The default value of the listen interval is 5 so that the STA wakes up every 500ms when power save is on. This can cause a data transmission delay. Set the DTIM policy to DTIM stick mode so that the station follows the AP DTIM interval rather than the listen interval, which is set in the peer assoc command. DTIM stick mode is preferable per the firmware team's request. Apply this only for STA vdevs and only when STA power save is supported, to avoid affecting unsupported targets and P2P client vdevs. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Link: https://lore.kernel.org/all/20260717014302.284034-1-daizhuang.bai@oss.qualcomm.com/ Signed-off-by: Daizhuang Bai --- drivers/net/wireless/ath/ath12k/mac.c | 11 +++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 4bbbaf4a99c60..b25097026c1ec 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3989,6 +3989,17 @@ static void ath12k_bss_assoc(struct ath12k *ar, ath12k_warn(ar->ab, "failed to set vdev %i OBSS PD parameters: %d\n", arvif->vdev_id, ret); + if (ar->ab->hw_params->supports_sta_ps && + ahvif->vdev_type == WMI_VDEV_TYPE_STA && + ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) { + ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, + WMI_VDEV_PARAM_DTIM_POLICY, + WMI_DTIM_POLICY_STICK); + if (ret) + ath12k_warn(ar->ab, "failed to set vdev %d stick DTIM policy: %d\n", + arvif->vdev_id, ret); + } + if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ar->ab->wmi_ab.svc_map) && ahvif->vdev_type == WMI_VDEV_TYPE_STA && ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 468f1bef7363a..3728cb67c7529 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2330,6 +2330,13 @@ enum wmi_slot_time { WMI_VDEV_SLOT_TIME_SHORT = 2, }; +enum wmi_dtim_policy { + WMI_DTIM_POLICY_IGNORE = 1, + WMI_DTIM_POLICY_NORMAL = 2, + WMI_DTIM_POLICY_STICK = 3, + WMI_DTIM_POLICY_AUTO = 4, +}; + enum wmi_preamble { WMI_VDEV_PREAMBLE_LONG = 1, WMI_VDEV_PREAMBLE_SHORT = 2, From ddda8e1c99ee212340048e1ff58f696cc7a273f8 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:16 +0800 Subject: [PATCH 098/243] FROMLIST: wifi: ath12k: fix out-of-bounds clear_bit in ath12k_mac_dp_peer_cleanup() ath12k_mac_dp_peer_cleanup() clears the ML peer ID slot on the free_ml_peer_id_map bitmap by indexing it with dp_peer->peer_id. That is wrong: dp_peer->peer_id for an MLO peer always carries the ATH12K_PEER_ML_ID_VALID bit (BIT(13)), so clear_bit() is invoked with index >= 0x2000, which is far outside the bitmap of ATH12K_MAX_MLO_PEERS (256) bits and corrupts memory adjacent to ah->free_ml_peer_id_map. The intended bitmap entry also never gets cleared, so subsequent ath12k_peer_ml_alloc() calls eventually run out of IDs. The ID without the VALID bit is what ath12k_peer_ml_alloc() returned and is stored in ahsta->ml_peer_id. Use that instead. While there, also reset ahsta->ml_peer_id to ATH12K_MLO_PEER_ID_INVALID so the bitmap and ahsta->ml_peer_id stay in sync; Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: ee16dcf573d5 ("wifi: ath12k: Define ath12k_dp_peer structure & APIs for create & delete") Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-1-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index b25097026c1ec..9ee8b5da1b919 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1287,8 +1287,11 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { + struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(dp_peer->peer_id, ah->free_ml_peer_id_map); + clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } list_move(&dp_peer->list, &peers); From a1f231f993edd30decf24883c84e85bc78f334a4 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:17 +0800 Subject: [PATCH 099/243] FROMLIST: wifi: ath12k: factor out peer assoc send-and-wait into a helper ath12k_bss_assoc(), ath12k_mac_station_assoc() and ath12k_sta_rc_update_wk() all open-code the same sequence: reinit the peer_assoc_done completion, send the peer assoc WMI command, then wait for the firmware confirmation event. The reinit_completion() was buried in ath12k_peer_assoc_prepare(), far from the wait_for_completion_timeout() that consumes it, making the reinit/send/wait sequence hard to follow, and the three open-coded copies are easy to get out of sync. Move the sequence into a new helper ath12k_mac_peer_assoc() and call it from all three sites. The reinit, send and wait now live together so the completion's lifecycle is easy to read. While at it, ath12k_sta_rc_update_wk() previously warned but still waited the full timeout when the peer assoc command failed to send. Now a send failure returns immediately and skips the pointless 1 second wait, matching the other two callers. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-2-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 59 +++++++++++++-------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 9ee8b5da1b919..0c7646e40b3f4 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3598,8 +3598,6 @@ static void ath12k_peer_assoc_prepare(struct ath12k *ar, memset(arg, 0, sizeof(*arg)); - reinit_completion(&ar->peer_assoc_done); - arg->peer_new_assoc = !reassoc; ath12k_peer_assoc_h_basic(ar, arvif, arsta, arg); ath12k_peer_assoc_h_crypto(ar, arvif, arsta, arg); @@ -3839,6 +3837,29 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, return bw; } +static int ath12k_mac_peer_assoc(struct ath12k *ar, + struct ath12k_wmi_peer_assoc_arg *peer_arg) +{ + int ret; + + reinit_completion(&ar->peer_assoc_done); + + ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); + if (ret) { + ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", + peer_arg->peer_mac, peer_arg->vdev_id, ret); + return ret; + } + + if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + + return 0; +} + static void ath12k_bss_assoc(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_bss_conf *bss_conf) @@ -3919,18 +3940,10 @@ static void ath12k_bss_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", - bss_conf->bssid, arvif->vdev_id, ret); - return; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - bss_conf->bssid, arvif->vdev_id); + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) return; - } ret = ath12k_setup_peer_smps(ar, arvif, bss_conf->bssid, &link_sta->ht_cap, &link_sta->he_6ghz_capa); @@ -6507,18 +6520,10 @@ static int ath12k_mac_station_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, ret); - return ret; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); - return -ETIMEDOUT; - } + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) + return ret; num_vht_rates = ath12k_mac_bitrate_mask_num_vht_rates(ar, band, mask); num_he_rates = ath12k_mac_bitrate_mask_num_he_rates(ar, band, mask); @@ -6867,14 +6872,8 @@ static void ath12k_sta_rc_update_wk(struct wiphy *wiphy, struct wiphy_work *wk) peer_arg, true); peer_arg->is_assoc = false; - err = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (err) - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, err); - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); + ath12k_mac_peer_assoc(ar, peer_arg); } } } From c19072c16b9400ad750dd48e020495d55d00222b Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:18 +0800 Subject: [PATCH 100/243] FROMLIST: wifi: ath12k: keep ATH12K_PEER_ML_ID_VALID set in ath12k_sta::ml_peer_id Several pieces of host bookkeeping for MLD peer IDs encode the same fact in different ways: - ath12k_sta::ml_peer_id stores the raw ID in [0, ATH12K_MAX_MLO_PEERS); - ath12k_dp_peer::peer_id, ath12k_dp_link_peer::ml_id and the index used on ath12k_dp_hw::dp_peers[] always carry the ATH12K_PEER_ML_ID_VALID bit (BIT(13)) when the ID is real; - WMI_MLO_PEER_ASSOC_PARAMS::ml_peer_id sent down to firmware is raw, without the bookkeeping bit. The mismatch leaks into call sites that have to remember to OR the bit in (ath12k_peer_create(), ath12k_mac_op_sta_state()) or remember not to (ath12k_peer_assoc_h_mlo()). Make ath12k_sta::ml_peer_id carry the VALID bit when valid, the same way ath12k_dp_peer::peer_id and ath12k_dp_link_peer::ml_id do: - ath12k_peer_ml_alloc() OR-s the bit in once on the way out; the internal bitmap stays raw [0, ATH12K_MAX_MLO_PEERS); - ath12k_peer_create() and ath12k_mac_op_sta_state() drop the explicit OR; - ath12k_peer_assoc_h_mlo() masks the bit off when populating the WMI ml_peer_id; While there, introduce ath12k_peer_ml_free() to mirror ath12k_peer_ml_alloc(), which helps avoid code duplication. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-3-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 27 +++++++++++++------------- drivers/net/wireless/ath/ath12k/peer.c | 17 +++++++++++++--- drivers/net/wireless/ath/ath12k/peer.h | 1 + 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 0c7646e40b3f4..2edef32116b96 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1282,16 +1282,15 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) struct ath12k_dp_peer *dp_peer, *tmp; struct ath12k_dp_hw *dp_hw = &ah->dp_hw; + lockdep_assert_wiphy(ah->hw->wiphy); + INIT_LIST_HEAD(&peers); spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } list_move(&dp_peer->list, &peers); @@ -3551,7 +3550,11 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; - ml->ml_peer_id = ahsta->ml_peer_id; + /* + * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + */ + ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; @@ -7291,10 +7294,8 @@ static void ath12k_mac_ml_station_remove(struct ath12k_vif *ahvif, ath12k_mac_free_unassign_link_sta(ah, ahsta, link_id); } - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); } static int ath12k_mac_handle_link_sta_state(struct ieee80211_hw *hw, @@ -7766,7 +7767,7 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, } dp_params.is_mlo = true; - dp_params.peer_id = ahsta->ml_peer_id | ATH12K_PEER_ML_ID_VALID; + dp_params.peer_id = ahsta->ml_peer_id; } dp_params.sta = sta; @@ -7903,10 +7904,8 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, peer_delete: ath12k_dp_peer_delete(&ah->dp_hw, sta->addr, sta); ml_peer_id_clear: - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); exit: /* update the state if everything went well */ if (!ret) diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index c222bdaa333c5..ae93731b4177a 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -230,7 +230,7 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id | ATH12K_PEER_ML_ID_VALID; + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -276,9 +276,20 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah) } if (ml_peer_id == ATH12K_MAX_MLO_PEERS) - ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + return ATH12K_MLO_PEER_ID_INVALID; - return ml_peer_id; + return ml_peer_id | ATH12K_PEER_ML_ID_VALID; +} + +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) +{ + lockdep_assert_wiphy(ah->hw->wiphy); + + if (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, + ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 49d89796bc46e..0f7f25b8e89c0 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -26,4 +26,5 @@ int ath12k_link_sta_rhash_add(struct ath12k_base *ab, struct ath12k_link_sta *ar struct ath12k_link_sta *ath12k_link_sta_find_by_addr(struct ath12k_base *ab, const u8 *addr); u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah); +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta); #endif /* _PEER_H_ */ From 90821e66a9098437d9f91bb088a2893e1056a03d Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:19 +0800 Subject: [PATCH 101/243] FROMLIST: wifi: ath12k: add support for HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP Firmware on chips that allocate the MLD peer ID itself (WCN7850 and QCC2072) reports the assignment back to the host through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The message carries the chosen MLD peer id, the MLD MAC address etc. Add the message type, the on-the-wire struct, the field masks and a handler that parses them out. The host-side state update (publishing the dp peer into ath12k_dp_hw::dp_peers[], propagating the ID to ath12k_dp_link_peer::ml_id and ath12k_sta::ml_peer_id) is added in a follow-up patch; Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-4-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/dp_htt.c | 30 ++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_htt.h | 12 ++++++++++ 2 files changed, 42 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index 52e10059c6d5a..150b190f9c7fc 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -575,6 +575,33 @@ static void ath12k_htt_mlo_offset_event_handler(struct ath12k_base *ab, rcu_read_unlock(); } +static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, + struct sk_buff *skb) +{ + struct htt_resp_msg *resp = (struct htt_resp_msg *)skb->data; + struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; + u16 raw_peer_id, peer_id, addr_h16; + u8 peer_addr[ETH_ALEN]; + + if (skb->len < sizeof(*ev)) { + ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", + skb->len); + return; + } + + raw_peer_id = le32_get_bits(ev->info0, + HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID); + peer_id = raw_peer_id | ATH12K_PEER_ML_ID_VALID; + + addr_h16 = le32_get_bits(ev->info1, + HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16); + ath12k_dp_get_mac_addr(le32_to_cpu(ev->mac_addr_l32), addr_h16, + peer_addr); + + ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", + peer_addr, peer_id); +} + void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, struct sk_buff *skb) { @@ -659,6 +686,9 @@ void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, case HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND: ath12k_htt_mlo_offset_event_handler(ab, skb); break; + case HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP: + ath12k_dp_htt_mlo_peer_map_handler(ab, skb); + break; default: ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "dp_htt event %d not handled\n", type); diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.h b/drivers/net/wireless/ath/ath12k/dp_htt.h index 987689f11cda4..2db7fb27c0361 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.h +++ b/drivers/net/wireless/ath/ath12k/dp_htt.h @@ -930,6 +930,7 @@ enum htt_t2h_msg_type { HTT_T2H_MSG_TYPE_EXT_STATS_CONF = 0x1c, HTT_T2H_MSG_TYPE_BKPRESSURE_EVENT_IND = 0x24, HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND = 0x28, + HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP = 0x29, HTT_T2H_MSG_TYPE_PEER_MAP3 = 0x2b, HTT_T2H_MSG_TYPE_VDEV_TXRX_STATS_PERIODIC_IND = 0x2c, }; @@ -974,11 +975,22 @@ struct htt_t2h_peer_unmap_event { __le32 info1; } __packed; +#define HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID GENMASK(23, 8) +#define HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16 GENMASK(15, 0) + +struct htt_t2h_mlo_peer_map_event { + __le32 info0; + __le32 mac_addr_l32; + __le32 info1; + __le32 reserved[5]; +} __packed; + struct htt_resp_msg { union { struct htt_t2h_version_conf_msg version_msg; struct htt_t2h_peer_map_event peer_map_ev; struct htt_t2h_peer_unmap_event peer_unmap_ev; + struct htt_t2h_mlo_peer_map_event mlo_peer_map_ev; }; } __packed; From e27f354b1f9a570fb070777d322c54c6a3d68cbf Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:20 +0800 Subject: [PATCH 102/243] FROMLIST: wifi: ath12k: introduce host_alloc_ml_id hardware parameter Different ath12k devices diverge on who allocates MLD peer id: WCN7850/QCC2072 have the firmware allocate it and notify the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP event; While others let the host allocate it and pass it down through WMI_PEER_ASSOC_CMDID with ATH12K_WMI_FLAG_MLO_PEER_ID_VALID set. Currently ath12k host allocates this ID and sends it to firmware by default for all devices. This breaks WCN7850/QCC2072, because the host maintained ID may be different from the firmware-allocated one. Consequently data path may fail to find the dp peer and drop some received packets. From user point of view, this results in bugs reported in [1] or the 4-way handshake timeout issue. Add host_alloc_ml_id flag to struct ath12k_hw_params (and a copy on struct ath12k_hw for hot-path access) so subsequent patches can branch on it. Set true for QCN9274/IPQ5332/IPQ5424, false for WCN7850/QCC2072. The flag will be consumed by subsequent patches. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://bugzilla.kernel.org/show_bug.cgi?id=221039 # 1 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-5-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/hw.h | 2 ++ drivers/net/wireless/ath/ath12k/mac.c | 17 ++++++++++++++++- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 12 ++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index d41b8a556ea2c..6fc372aa64687 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -792,6 +792,7 @@ struct ath12k_hw { enum ath12k_hw_state state; bool regd_updated; bool use_6ghz_regd; + bool host_alloc_ml_id; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 86fb8b7196135..8d2fa0bfb96c1 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -236,6 +236,8 @@ struct ath12k_hw_params { u32 max_client_dbs; u32 max_client_dbs_sbs; } client; + + bool host_alloc_ml_id; }; struct ath12k_hw_ops { diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 2edef32116b96..1344f5558d9a3 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15409,8 +15409,9 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) int mac_id, device_id, total_radio, num_hw; struct ath12k_base *ab; struct ath12k_hw *ah; - int ret, i, j; + bool conf = false; u8 radio_per_hw; + int ret, i, j; total_radio = 0; for (i = 0; i < ag->num_devices; i++) { @@ -15450,6 +15451,19 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ab = ag->ab[device_id]; + + /* + * the assumption is all devices within an ah + * share the same host_alloc_ml_id configuration + */ + if (j == 0) { + conf = ab->hw_params->host_alloc_ml_id; + } else if (conf != ab->hw_params->host_alloc_ml_id) { + ath12k_warn(ab, "inconsistent ML ID config within ah, device 0 uses %s allocated ID, while device %u doesn't\n", + conf ? "host" : "firmware", device_id); + goto err; + } + pdev_map[j].ab = ab; pdev_map[j].pdev_idx = mac_id; mac_id++; @@ -15474,6 +15488,7 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ah->dev = ab->dev; + ah->host_alloc_ml_id = conf; ag->ah[i] = ah; ag->num_hw++; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index d9fdd2fc82986..03dedfd907fc7 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -442,6 +442,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "wcn7850 hw2.0", @@ -533,6 +535,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = false, }, { .name = "qcn9274 hw2.0", @@ -620,6 +624,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "ipq5332 hw1.0", @@ -700,6 +706,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "qcc2072 hw1.0", @@ -792,6 +800,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = false, }, { .name = "ipq5424 hw1.0", @@ -876,6 +886,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, }; From 94c6cb76e4963d9bac6b18cfd9454fe9b831136d Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:21 +0800 Subject: [PATCH 103/243] FROMLIST: wifi: ath12k: do not advertise MLD peer ID for firmware-allocate devices ath12k_peer_assoc_h_mlo() unconditionally sets ml->peer_id_valid and copies ahsta->ml_peer_id (with the ATH12K_PEER_ML_ID_VALID bookkeeping bit masked off) into the WMI_PEER_ASSOC_CMDID ML params, which causes ath12k_wmi_send_peer_assoc_cmd() to set ATH12K_WMI_FLAG_MLO_PEER_ID_VALID. This needs to be gated on chips where the firmware allocates the MLD peer ID: - WCN7850/QCC2072 firmware always picks the ID itself and does not honor a host-supplied one, so the value would be silently ignored anyway; - QCC2072 firmware additionally crashes during MLO disconnect when ATH12K_WMI_FLAG_MLO_PEER_ID_VALID was set in the preceding peer assoc, so the bit must not be sent at all. Branch on ah->host_alloc_ml_id: - When true (QCN9274 etc.), behavior is unchanged: peer_id_valid is set and the raw ahsta->ml_peer_id (without the VALID bit) is sent down. - When false (WCN7850, QCC2072), peer_id_valid stays unset and ml_peer_id is sent as 0. The firmware ignores both fields and reports the ID it allocated through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The early-return on ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID only applies on the host-alloc path, since on the firmware-alloc path the value is ATH12K_MLO_PEER_ID_PENDING here, not INVALID. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-6-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 1344f5558d9a3..c28427c1f4171 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3533,11 +3533,16 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, struct ath12k_sta *ahsta = arsta->ahsta; struct ath12k_link_sta *arsta_p; struct ath12k_link_vif *arvif; + struct ath12k_hw *ah = arsta->arvif->ar->ah; unsigned long links; u8 link_id; int i; - if (!sta->mlo || ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) + if (!sta->mlo) + return; + + if (ah->host_alloc_ml_id && + ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) return; ml->enabled = true; @@ -3545,16 +3550,25 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, /* For now considering the primary umac based on assoc link */ ml->primary_umac = arsta->is_assoc_link; - ml->peer_id_valid = true; + /* + * Only chips that allocate the MLD peer ID on the host send a valid + * ml_peer_id in WMI_PEER_ASSOC_CMDID. For chips where the firmware + * picks the ID, leave peer_id_valid false to avoid unexpected issues. + */ + ml->peer_id_valid = ah->host_alloc_ml_id; ml->logical_link_idx_valid = true; ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; /* * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without - * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. For chips + * where the firmware allocates the ID, the field is unused (the + * firmware always allocates regardless of the value here); send 0 + * to make that intent explicit. */ - ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; + ml->ml_peer_id = ah->host_alloc_ml_id ? + (ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID) : 0; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; From 6f0eb77000154630956a8444591331bf8b92a7d1 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:22 +0800 Subject: [PATCH 104/243] FROMLIST: wifi: ath12k: defer dp_peer registration when firmware allocates MLD peer ID For chips with host_alloc_ml_id=true (QCN9274 etc.), the host allocates the MLD peer ID up front; ath12k_dp_peer_create() publishes the dp_peer into dp_hw->dp_peers[] using that ID immediately. WCN7850/QCC2072 does not work that way: the firmware picks the ID and only tells the host afterwards via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP, so the publication has to be delayed until the event arrives. Introduce ATH12K_MLO_PEER_ID_PENDING (0xFFFE) as a sentinel for "is_mlo, but ID not yet known". On the firmware-allocates path: - ath12k_mac_op_sta_state(NOTEXIST->NONE) skips ath12k_peer_ml_alloc() and stores PENDING in ahsta->ml_peer_id and dp_params.peer_id; - ath12k_dp_peer_create() skips dp_peer registration until a real ID is known; - ath12k_peer_create() leaves peer->ml_id at INVALID so consumer sites do not treat PENDING as a real ID; - ath12k_peer_ml_free() and ath12k_mac_dp_peer_cleanup() skip the dp_peers[] write and the free_ml_peer_id_map clear when host_alloc_ml_id is false or the ID is still PENDING. The HTT handler change that resolves the PENDING ID is added in a follow-up patch. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-7-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_peer.c | 23 +++++++++++++++-------- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++------ drivers/net/wireless/ath/ath12k/peer.c | 20 +++++++++++++++++--- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 6fc372aa64687..a6c7f668b1bbf 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -71,6 +71,7 @@ #define ATH12K_MAX_MLO_PEERS 256 #define ATH12K_MLO_PEER_ID_INVALID 0xFFFF +#define ATH12K_MLO_PEER_ID_PENDING 0xFFFE #define ATH12K_INVALID_RSSI_FULL -1 #define ATH12K_INVALID_RSSI_EMPTY -128 diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index 2660b4c7449be..f0239bcca2a33 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -472,7 +472,9 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, dp_peer->is_mlo = params->is_mlo; /* - * For MLO client, the host assigns the ML peer ID, so set peer_id in dp_peer + * For MLO client, the ML peer ID, either known or PENDING, needs to be + * initialized here since the following logic depends on it. + * * For non-MLO client, host gets link peer ID from firmware and will be * assigned at the time of link peer creation */ @@ -488,13 +490,17 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, list_add(&dp_peer->list, &dp_hw->dp_peers_list); /* - * For MLO client, the peer_id for ath12k_dp_peer is allocated by host - * and that peer_id is known at this point, and hence this ath12k_dp_peer - * can be added to the RCU table using the peer_id. - * For non-MLO client, this addition to RCU table shall be done at the - * time of assignment of ath12k_dp_link_peer to ath12k_dp_peer. + * For an MLO client whose ML peer ID is allocated by the host, the + * peer_id is known here and the dp_peer can be added to the RCU + * table using it. For an MLO client on chips where the firmware + * allocates the ID, peer_id is ATH12K_MLO_PEER_ID_PENDING and the + * RCU table publish is deferred to the + * HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For a non-MLO client + * the publish happens later, at the time of assignment of + * ath12k_dp_link_peer to ath12k_dp_peer. */ - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], dp_peer); spin_unlock_bh(&dp_hw->peer_lock); @@ -515,7 +521,8 @@ void ath12k_dp_peer_delete(struct ath12k_dp_hw *dp_hw, u8 *addr, return; } - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); list_del(&dp_peer->list); diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index c28427c1f4171..a0873798077c2 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1289,7 +1289,9 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); + if (dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], + NULL); ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } @@ -7773,11 +7775,19 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, /* ML sta */ if (sta->mlo && !ahsta->links_map && (hweight16(sta->valid_links) == 1)) { - ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); - if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { - ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", - sta->addr); - goto exit; + if (ah->host_alloc_ml_id) { + ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); + if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { + ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", + sta->addr); + goto exit; + } + } else { + /* + * firmware allocates the ML peer ID and notifies + * the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP + */ + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_PENDING; } dp_params.is_mlo = true; diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index ae93731b4177a..25e4b79f11d63 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -230,7 +230,16 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id; + /* + * For chips where firmware allocates the ML peer ID, + * ml_peer_id is ATH12K_MLO_PEER_ID_PENDING here. The + * MLO_RX_PEER_MAP HTT event handler fixes up + * peer->ml_id once the ID is known. + */ + if (ml_peer_id == ATH12K_MLO_PEER_ID_PENDING) + peer->ml_id = ATH12K_MLO_PEER_ID_INVALID; + else + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -285,8 +294,13 @@ void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) { lockdep_assert_wiphy(ah->hw->wiphy); - if (ahsta->ml_peer_id < - (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + /* + * Only devices that allocate the ID on the host own a slot in + * free_ml_peer_id_map. + */ + if (ah->host_alloc_ml_id && + (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID))) clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, ah->free_ml_peer_id_map); ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; From 1d975625be7d814a4c3951cd3de7658040a491f0 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:23 +0800 Subject: [PATCH 105/243] FROMLIST: wifi: ath12k: resolve PENDING ML peer ID from MLO_PEER_MAP HTT event Add ath12k_dp_peer_fixup_peer_id() and call it from the HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For devices where the firmware allocates the MLD peer ID, this is the point at which all data structures that were left with ATH12K_MLO_PEER_ID_PENDING or ATH12K_MLO_PEER_ID_INVALID get their real ID: - dp_peer->peer_id is updated and the dp_peer is published into dp_hw->dp_peers[]; - every existing dp_link_peer in dp_peer->link_peers[] gets its ml_id set to the same value; - ahsta->ml_peer_id is updated to the same value so peer_assoc, sta_state and cleanup paths see a consistent ID. Devices with host_alloc_ml_id == true also receive the same HTT event, but the firmware-reported ID always matches the host-allocated one and everything has already been populated by ath12k_dp_peer_create(); Skips the helper entirely on those devices. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221039 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-8-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.c | 2 + drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_htt.c | 19 +++++++++ drivers/net/wireless/ath/ath12k/dp_peer.c | 52 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_peer.h | 2 + drivers/net/wireless/ath/ath12k/mac.c | 24 +++++++++++ 6 files changed, 100 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 4bbfaaa446ec0..151b93e50ba64 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1544,6 +1544,8 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) } wiphy_unlock(ah->hw->wiphy); + + complete(&ah->peer_ml_id_done); } wake_up(&ab->wmi_ab.tx_credits_wq); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a6c7f668b1bbf..6d30ed07be9e5 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -794,6 +794,7 @@ struct ath12k_hw { bool regd_updated; bool use_6ghz_regd; bool host_alloc_ml_id; + struct completion peer_ml_id_done; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index 150b190f9c7fc..68968f96b4f1b 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -6,6 +6,7 @@ #include "core.h" #include "peer.h" +#include "dp_peer.h" #include "htc.h" #include "dp_htt.h" #include "debugfs_htt_stats.h" @@ -582,6 +583,7 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; u16 raw_peer_id, peer_id, addr_h16; u8 peer_addr[ETH_ALEN]; + int ret; if (skb->len < sizeof(*ev)) { ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", @@ -600,6 +602,23 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", peer_addr, peer_id); + + /* + * Fix up the dp_peer entry created with ATH12K_MLO_PEER_ID_PENDING + * earlier; on chips with host_alloc_ml_id == false this is the only + * point at which the host learns the firmware-assigned ID. Chips + * that allocate the ID on the host also receive this event but the + * firmware-reported ID matches the host-allocated one, so there is + * nothing to fix up. + */ + if (!ab->hw_params->host_alloc_ml_id) { + ret = ath12k_dp_peer_fixup_peer_id(ab, peer_addr, + peer_id); + if (ret) + ath12k_warn(ab, + "failed to fix up peer id %u for dp peer %pM: %d\n", + peer_id, peer_addr, ret); + } } void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index f0239bcca2a33..7ee7a705e87a8 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -699,3 +699,55 @@ void ath12k_dp_link_peer_reset_rx_stats(struct ath12k_dp *dp, const u8 *addr) if (rx_stats) memset(rx_stats, 0, sizeof(*rx_stats)); } + +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, + const u8 *peer_addr, u16 peer_id) +{ + struct ath12k_dp_link_peer *link_peer; + struct ath12k_dp_peer *dp_peer = NULL; + struct ath12k_hw_group *ag = ab->ag; + struct ath12k_dp_hw *dp_hw = NULL; + struct ath12k_hw *ah; + int i; + + if (peer_id >= (ATH12K_PEER_ML_ID_VALID | ATH12K_MAX_MLO_PEERS)) + return -EINVAL; + + for (i = 0; i < ag->num_hw; i++) { + ah = ag->ah[i]; + if (!ah) + continue; + + spin_lock_bh(&ah->dp_hw.peer_lock); + dp_peer = ath12k_dp_peer_find_by_addr(&ah->dp_hw, + (u8 *)peer_addr); + if (dp_peer) { + dp_hw = &ah->dp_hw; + break; + } + spin_unlock_bh(&ah->dp_hw.peer_lock); + } + + if (!dp_peer) + return -ENOENT; + + /* dp_hw->peer_lock is held */ + + dp_peer->peer_id = peer_id; + rcu_assign_pointer(dp_hw->dp_peers[peer_id], dp_peer); + + for (i = 0; i < ATH12K_NUM_MAX_LINKS; i++) { + link_peer = rcu_dereference_protected(dp_peer->link_peers[i], + lockdep_is_held(&dp_hw->peer_lock)); + if (link_peer) + link_peer->ml_id = peer_id; + } + + ath12k_sta_to_ahsta(dp_peer->sta)->ml_peer_id = peer_id; + + spin_unlock_bh(&dp_hw->peer_lock); + + complete(&ah->peer_ml_id_done); + + return 0; +} diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h index 7c9709bf717b4..3503840b03295 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.h +++ b/drivers/net/wireless/ath/ath12k/dp_peer.h @@ -181,4 +181,6 @@ struct ath12k_dp_peer *ath12k_dp_peer_find_by_peerid(struct ath12k_pdev_dp *dp_p struct ath12k_dp_link_peer * ath12k_dp_link_peer_find_by_peerid(struct ath12k_pdev_dp *dp_pdev, u16 peer_id); void ath12k_dp_link_peer_free(struct ath12k_dp_link_peer *peer); +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, const u8 *peer_addr, + u16 peer_id); #endif diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index a0873798077c2..12350b87b41a2 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3859,9 +3859,11 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, static int ath12k_mac_peer_assoc(struct ath12k *ar, struct ath12k_wmi_peer_assoc_arg *peer_arg) { + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); int ret; reinit_completion(&ar->peer_assoc_done); + reinit_completion(&ah->peer_ml_id_done); ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); if (ret) { @@ -3876,6 +3878,27 @@ static int ath12k_mac_peer_assoc(struct ath12k *ar, return -ETIMEDOUT; } + /* + * For devices where the firmware allocates the MLD peer ID, the host + * learns the real ID only from the MLO_RX_PEER_MAP HTT event, which is + * handled in a softirq (BH workqueue) context that cannot take the + * wiphy lock. Block here, while still holding the wiphy lock, until + * that event has fixed up the ID. This serialises the fixup against + * all other wiphy-locked ml_peer_id accesses. + * + * The firmware sends the event only once, in response to the assoc-link + * peer assoc, so block only for that link. + */ + if (!ah->host_alloc_ml_id && + peer_arg->is_assoc && + peer_arg->ml.enabled && + peer_arg->ml.assoc_link && + !wait_for_completion_timeout(&ah->peer_ml_id_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get MLO peer map event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + return 0; } @@ -15359,6 +15382,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, ah->num_radio = num_pdev_map; mutex_init(&ah->hw_mutex); + init_completion(&ah->peer_ml_id_done); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list); From 6c5111fd282a0e832eed1b6b310c0c21edb2fc43 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 25 May 2026 14:32:02 +0530 Subject: [PATCH 106/243] FROMLIST: usb: typec: ucsi: ucsi_glink: Prevent suspend during UCSI notification handling When the system is suspended and a UCSI event arrives (such as USB plug-in), the GLINK interrupt (with IRQF_NO_SUSPEND flag) fires and it eventually calls the pmic_glink_ucsi_callback(), which schedules notify_work to handle the connector change. However, since no wakeup source is held, the system can re-enter suspend soon after the interrupt handler returns, before notify_work has completed running, and the USB plug-in event would not be handled. There was an earlier attempt to address this at the GLINK driver level, by making the GLINK interrupt wakeup-capable, ("rpmsg: glink: Make glink smem interrupt wakeup capable") [1], but upstream reviewers suggested a different approach, preferring wakeup logic to be handled in the client driver. To avoid losing UCSI notifications in this way, register ucsi_glink as a wakeup-capable device in the probe, and call pm_wakeup_ws_event() with hard=true before scheduling notify_work. The hard wakeup aborts any in-progress suspend, and the timed wakeup source keeps the system awake long enough for the notify_work call to run. [1] https://lore.kernel.org/all/20240603073648.3475123-1-quic_deesin@quicinc.com/ Link: https://lore.kernel.org/all/20260710-ucsi_glink_wakeup-v1-1-7d97ea628d92@oss.qualcomm.com/ Signed-off-by: Jishnu Prakash --- drivers/usb/typec/ucsi/ucsi_glink.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 12e07b9fe6228..7fa039f0a2ba6 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,12 @@ #define UC_UCSI_WRITE_BUF_REQ 0x12 #define UC_UCSI_USBC_NOTIFY_IND 0x13 +/* + * Wakeup timeout to allow USB event notification processing to + * complete before device suspends. + */ +#define UCSI_GLINK_WAKEUP_TIMEOUT_MS 50 + struct ucsi_read_buf_req_msg { struct pmic_glink_hdr hdr; }; @@ -342,6 +349,8 @@ static void pmic_glink_ucsi_callback(const void *data, size_t len, void *priv) pmic_glink_ucsi_write_ack(ucsi, data, len); break; case UC_UCSI_USBC_NOTIFY_IND: + pm_wakeup_ws_event(ucsi->dev->power.wakeup, + UCSI_GLINK_WAKEUP_TIMEOUT_MS, true); schedule_work(&ucsi->notify_work); break; } @@ -401,6 +410,8 @@ static int pmic_glink_ucsi_probe(struct auxiliary_device *adev, ucsi->dev = dev; dev_set_drvdata(dev, ucsi); + device_init_wakeup(dev, true); + INIT_WORK(&ucsi->notify_work, pmic_glink_ucsi_notify); INIT_WORK(&ucsi->register_work, pmic_glink_ucsi_register); init_completion(&ucsi->read_ack); From 59da64d83777a375d1b41411a975e624b82c9103 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 9 Jun 2026 16:16:09 +0200 Subject: [PATCH 107/243] FROMLIST: arm64: dts: qcom: glymur: Drop fake PCIe phy 3B According to user manual / programming guide there is no separate PCIe phy 3A and 3B, but one 8-lane QMP PCIe Gen5 PHY which consists of two 4-lane blocks. This is also visible in memory map, where the 0xf00000 is marked as the main block with additional sub blocks for each 4-lane phys. Describing the sub phys without the rest is not correct from hardware description, even if it works. Link: https://lore.kernel.org/all/20260609141608.354186-2-krzysztof.kozlowski@oss.qualcomm.com/ Signed-off-by: Krzysztof Kozlowski Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 7 -- arch/arm64/boot/dts/qcom/glymur-qcb.dts | 9 --- arch/arm64/boot/dts/qcom/glymur.dtsi | 39 +---------- arch/arm64/boot/dts/qcom/mahua.dtsi | 87 ++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 3dbe44463cf2f..67d9486612144 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -885,13 +885,6 @@ pinctrl-names = "default"; }; -&pcie3b_phy { - vdda-phy-supply = <&vreg_l3c_e1_0p89>; - vdda-pll-supply = <&vreg_l2c_e1_1p14>; - vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; - vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; -}; - &pcie3b_port0 { reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; diff --git a/arch/arm64/boot/dts/qcom/glymur-qcb.dts b/arch/arm64/boot/dts/qcom/glymur-qcb.dts index bf535603a5867..570d0a23786ab 100644 --- a/arch/arm64/boot/dts/qcom/glymur-qcb.dts +++ b/arch/arm64/boot/dts/qcom/glymur-qcb.dts @@ -770,15 +770,6 @@ pinctrl-names = "default"; }; -&pcie3b_phy { - vdda-phy-supply = <&vreg_l3c_e1_0p89>; - vdda-pll-supply = <&vreg_l2c_e1_1p14>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; - vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; - vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; -}; - &pcie3b_port0 { reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 0b16f38e51f00..a99af3f82a457 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -816,7 +816,7 @@ <0>, /* USB 2 Phy PIPEGMUX */ <0>, /* USB 2 Phy SYS PCIE PIPEGMUX */ <0>, /* PCIe 3a */ - <&pcie3b_phy>, /* PCIe 3b */ + <0>, /* PCIe 3b */ <&pcie4_phy>, /* PCIe 4 */ <&pcie5_phy>, /* PCIe 5 */ <&pcie6_phy>, /* PCIe 6 */ @@ -3985,49 +3985,12 @@ reg = <0x0 0x0 0x0 0x0 0x0>; bus-range = <0x01 0xff>; - phys = <&pcie3b_phy>; - #address-cells = <3>; #size-cells = <2>; ranges; }; }; - pcie3b_phy: phy@f10000 { - compatible = "qcom,glymur-qmp-gen5x4-pcie-phy"; - reg = <0x0 0x00f10000 0x0 0x10000>; - - clocks = <&gcc GCC_PCIE_PHY_3B_AUX_CLK>, - <&gcc GCC_PCIE_3B_CFG_AHB_CLK>, - <&tcsr TCSR_PCIE_3_CLKREF_EN>, - <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>, - <&gcc GCC_PCIE_3B_PIPE_CLK>, - <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>; - clock-names = "aux", - "cfg_ahb", - "ref", - "rchng", - "pipe", - "pipediv2"; - - resets = <&gcc GCC_PCIE_3B_PHY_BCR>, - <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>; - reset-names = "phy", - "phy_nocsr"; - - assigned-clocks = <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>; - assigned-clock-rates = <100000000>; - - power-domains = <&gcc GCC_PCIE_3B_PHY_GDSC>; - - #clock-cells = <0>; - clock-output-names = "pcie3b_pipe_clk"; - - #phy-cells = <0>; - - status = "disabled"; - }; - cryptobam: dma-controller@1dc4000 { compatible = "qcom,bam-v1.7.4", "qcom,bam-v1.7.0"; reg = <0x0 0x01dc4000 0x0 0x28000>; diff --git a/arch/arm64/boot/dts/qcom/mahua.dtsi b/arch/arm64/boot/dts/qcom/mahua.dtsi index e6c0597089122..281eee14bf5ed 100644 --- a/arch/arm64/boot/dts/qcom/mahua.dtsi +++ b/arch/arm64/boot/dts/qcom/mahua.dtsi @@ -311,3 +311,90 @@ #qcom,sensors = <15>; }; +&soc { + pcie3b_phy: phy@0x1b90000 { + compatible = "qcom,glymur-qmp-gen5x4-pcie-phy"; + reg = <0x0 0x01b90000 0x0 0x10000>; + + clocks = <&gcc GCC_PCIE_PHY_3B_AUX_CLK>, + <&gcc GCC_PCIE_3B_CFG_AHB_CLK>, + <&tcsr TCSR_PCIE_3_CLKREF_EN>, + <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_3B_PIPE_CLK>, + <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>; + clock-names = "aux", + "cfg_ahb", + "ref", + "rchng", + "pipe", + "pipediv2"; + + resets = <&gcc GCC_PCIE_3B_PHY_BCR>, + <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>; + reset-names = "phy", + "phy_nocsr"; + + assigned-clocks = <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>; + assigned-clock-rates = <100000000>; + + power-domains = <&gcc GCC_PCIE_3B_PHY_GDSC>; + + #clock-cells = <0>; + clock-output-names = "pcie3b_pipe_clk"; + + #phy-cells = <0>; + + status = "disabled"; + }; +}; + + +&pcie3b_port0 { + phys = <&pcie3b_phy>; +}; + +&gcc { + clocks = <&rpmhcc RPMH_CXO_CLK>, /* Board XO source */ + <&rpmhcc RPMH_CXO_CLK_A>, /* Board XO_A source */ + <&sleep_clk>, /* Sleep */ + <0>, /* USB 0 Phy DP0 GMUX */ + <0>, /* USB 0 Phy DP1 GMUX */ + <0>, /* USB 0 Phy PCIE PIPEGMUX */ + <0>, /* USB 0 Phy PIPEGMUX */ + <0>, /* USB 0 Phy SYS PCIE PIPEGMUX */ + <0>, /* USB 1 Phy DP0 GMUX 2 */ + <0>, /* USB 1 Phy DP1 GMUX 2 */ + <0>, /* USB 1 Phy PCIE PIPEGMUX */ + <0>, /* USB 1 Phy PIPEGMUX */ + <0>, /* USB 1 Phy SYS PCIE PIPEGMUX */ + <0>, /* USB 2 Phy DP0 GMUX 2 */ + <0>, /* USB 2 Phy DP1 GMUX 2 */ + <0>, /* USB 2 Phy PCIE PIPEGMUX */ + <0>, /* USB 2 Phy PIPEGMUX */ + <0>, /* USB 2 Phy SYS PCIE PIPEGMUX */ + <0>, /* PCIe 3a pipe (no HW on Mahua) */ + <&pcie3b_phy>, /* PCIe 3b pipe */ + <&pcie4_phy>, /* PCIe 4 */ + <&pcie5_phy>, /* PCIe 5 */ + <&pcie6_phy>, /* PCIe 6 */ + <0>, /* QUSB4 0 PHY RX 0 */ + <0>, /* QUSB4 0 PHY RX 1 */ + <0>, /* QUSB4 1 PHY RX 0 */ + <0>, /* QUSB4 1 PHY RX 1 */ + <0>, /* QUSB4 2 PHY RX 0 */ + <0>, /* QUSB4 2 PHY RX 1 */ + <0>, /* UFS PHY RX Symbol 0 */ + <0>, /* UFS PHY RX Symbol 1 */ + <0>, /* UFS PHY TX Symbol 0 */ + <&usb_0_qmpphy QMP_USB43DP_USB3_PIPE_CLK>, + <&usb_1_qmpphy QMP_USB43DP_USB3_PIPE_CLK>, + <&usb_2_qmpphy QMP_USB43DP_USB3_PIPE_CLK>, + <&usb_mp_qmpphy0 QMP_USB43DP_USB3_PIPE_CLK>, + <&usb_mp_qmpphy1 QMP_USB43DP_USB3_PIPE_CLK>, + <0>, /* USB4 PHY 0 pcie pipe */ + <0>, /* USB4 PHY 0 Max pipe */ + <0>, /* USB4 PHY 1 pcie pipe */ + <0>, /* USB4 PHY 1 Max pipe */ + <0>, /* USB4 PHY 2 pcie */ + <0>; /* USB4 PHY 2 Max */ +}; From fa1455e4d56cc92f6809a5db0c537b9376456303 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 19 Jul 2026 23:53:56 -0700 Subject: [PATCH 108/243] FROMLIST: arm64: dts: qcom: glymur: Wire PCIe3a/3b to shared Gen5x8 PHY Glymur's PCIe3a and PCIe3b controllers share a single Gen5x8 QMP PHY block that can be bifurcated into two independent x4 links, rather than each controller owning its own dedicated PHY. Add a pcie3_phy node describing the shared PHY block, add the missing PCIe3a controller node, and point both PCIe3a's and PCIe3b's port phys at &pcie3_phy (index 0 and 1 respectively) so each controller picks up its half of the bifurcated PHY. Update the GCC pipe clock parent array to reference the new PHY's clock outputs instead of the placeholders. Link: https://lore.kernel.org/all/20260717-glymur_linkmode_0717-v5-0-4f9e87a61463@oss.qualcomm.com/ Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur.dtsi | 336 ++++++++++++++++++++++++++- arch/arm64/boot/dts/qcom/mahua.dtsi | 2 + 2 files changed, 336 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index a99af3f82a457..a4256dfb99252 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -815,8 +815,8 @@ <0>, /* USB 2 Phy PCIE PIPEGMUX */ <0>, /* USB 2 Phy PIPEGMUX */ <0>, /* USB 2 Phy SYS PCIE PIPEGMUX */ - <0>, /* PCIe 3a */ - <0>, /* PCIe 3b */ + <&pcie3_phy 0>, /* PCIe 3a pipe */ + <&pcie3_phy 1>, /* PCIe 3b pipe */ <&pcie4_phy>, /* PCIe 4 */ <&pcie5_phy>, /* PCIe 5 */ <&pcie6_phy>, /* PCIe 6 */ @@ -2623,6 +2623,62 @@ }; }; + pcie3_phy: phy@f00000 { + compatible = "qcom,glymur-qmp-gen5x8-pcie-phy"; + reg = <0x0 0x00f00000 0x0 0x10000>, + <0x0 0x00f10000 0x0 0x10000>; + reg-names = "port_a", + "port_b"; + + clocks = <&gcc GCC_PCIE_PHY_3A_AUX_CLK>, + <&gcc GCC_PCIE_3A_CFG_AHB_CLK>, + <&tcsr TCSR_PCIE_3_CLKREF_EN>, + <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_3A_PIPE_CLK>, + <&gcc GCC_PCIE_PHY_3B_AUX_CLK>, + <&gcc GCC_PCIE_3B_CFG_AHB_CLK>, + <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_3B_PIPE_CLK>, + <&gcc GCC_PCIE_3B_PIPE_DIV2_CLK>; + clock-names = "aux", + "cfg_ahb", + "ref", + "rchng", + "pipe", + "phy_b_aux", + "cfg_ahb_b", + "rchng_b", + "pipe_b", + "pipediv2_b"; + + resets = <&gcc GCC_PCIE_3A_PHY_BCR>, + <&gcc GCC_PCIE_3A_NOCSR_COM_PHY_BCR>, + <&gcc GCC_PCIE_3B_PHY_BCR>, + <&gcc GCC_PCIE_3B_NOCSR_COM_PHY_BCR>; + reset-names = "port_a", + "port_a_nocsr", + "port_b", + "port_b_nocsr"; + + assigned-clocks = <&gcc GCC_PCIE_3A_PHY_RCHNG_CLK>, + <&gcc GCC_PCIE_3B_PHY_RCHNG_CLK>; + assigned-clock-rates = <100000000>, <100000000>; + + power-domains = <&gcc GCC_PCIE_3A_PHY_GDSC>, + <&gcc GCC_PCIE_3B_PHY_GDSC>; + power-domain-names = "port_a", "port_b"; + + qcom,link-mode = <&tcsr 0x5000>; + + #clock-cells = <1>; + clock-output-names = "pcie3a_pipe_clk", + "pcie3b_pipe_clk"; + + #phy-cells = <1>; + + status = "disabled"; + }; + usb_hs_phy: phy@fa0000 { compatible = "qcom,glymur-m31-eusb2-phy", "qcom,sm8750-m31-eusb2-phy"; @@ -3985,6 +4041,282 @@ reg = <0x0 0x0 0x0 0x0 0x0>; bus-range = <0x01 0xff>; + phys = <&pcie3_phy 1>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; + + pcie3a: pci@1c10000 { + device_type = "pci"; + compatible = "qcom,glymur-pcie", "qcom,pcie-x1e80100"; + reg = <0x0 0x01c10000 0x0 0x3000>, + <0x0 0x70000000 0x0 0xf20>, + <0x0 0x70000f40 0x0 0xa8>, + <0x0 0x70001000 0x0 0x4000>, + <0x0 0x70100000 0x0 0x100000>, + <0x0 0x01c13000 0x0 0x1000>; + reg-names = "parf", + "dbi", + "elbi", + "atu", + "config", + "mhi"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0x0 0x00000000 0x0 0x70200000 0x0 0x100000>, + <0x02000000 0x0 0x70000000 0x0 0x70300000 0x0 0x3d00000>, + <0x03000000 0x7 0x00000000 0x7 0x00000000 0x0 0x40000000>, + <0x43000000 0x70 0x00000000 0x70 0x00000000 0x10 0x00000000>; + + bus-range = <0 0xff>; + + dma-coherent; + + linux,pci-domain = <3>; + num-lanes = <8>; + + operating-points-v2 = <&pcie3a_opp_table>; + + msi-map = <0x0 &gic_its 0xb0000 0x10000>; + iommu-map = <0x0 &pcie_smmu 0x30000 0x10000>; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "global"; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 0 0 848 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc 0 0 0 849 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc 0 0 0 850 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc 0 0 0 851 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&gcc GCC_PCIE_3A_AUX_CLK>, + <&gcc GCC_PCIE_3A_CFG_AHB_CLK>, + <&gcc GCC_PCIE_3A_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_3A_SLV_AXI_CLK>, + <&gcc GCC_PCIE_3A_SLV_Q2A_AXI_CLK>, + <&gcc GCC_AGGRE_NOC_PCIE_3A_WEST_SF_AXI_CLK>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "noc_aggr"; + + assigned-clocks = <&gcc GCC_PCIE_3A_AUX_CLK>; + assigned-clock-rates = <19200000>; + + interconnects = <&pcie_west_anoc MASTER_PCIE_3A QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &pcie_west_slv_noc SLAVE_PCIE_3A QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "pcie-mem", + "cpu-pcie"; + + resets = <&gcc GCC_PCIE_3A_BCR>, + <&gcc GCC_PCIE_3A_LINK_DOWN_BCR>; + reset-names = "pci", + "link_down"; + + power-domains = <&gcc GCC_PCIE_3A_GDSC>; + + eq-presets-8gts = /bits/ 16 <0x5555 0x5555 0x5555 0x5555 + 0x5555 0x5555 0x5555 0x5555>; + eq-presets-16gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>; + eq-presets-32gts = /bits/ 8 <0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55>; + + status = "disabled"; + + pcie3a_opp_table: opp-table { + compatible = "operating-points-v2"; + + /* GEN 1 x1 */ + opp-2500000-1 { + opp-hz = /bits/ 64 <2500000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <250000 1>; + opp-level = <1>; + }; + + /* GEN 1 x2 */ + opp-5000000-1 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <500000 1>; + opp-level = <1>; + }; + + /* GEN 1 x4 */ + opp-10000000-1 { + opp-hz = /bits/ 64 <10000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <1000000 1>; + opp-level = <1>; + }; + + /* GEN 1 x8 */ + opp-20000000-1 { + opp-hz = /bits/ 64 <20000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <2000000 1>; + opp-level = <1>; + }; + + /* GEN 2 x1 */ + opp-5000000-2 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <500000 1>; + opp-level = <2>; + }; + + /* GEN 2 x2 */ + opp-10000000-2 { + opp-hz = /bits/ 64 <10000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <1000000 1>; + opp-level = <2>; + }; + + /* GEN 2 x4 */ + opp-20000000-2 { + opp-hz = /bits/ 64 <20000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <2000000 1>; + opp-level = <2>; + }; + + /* GEN 2 x8 */ + opp-40000000-2 { + opp-hz = /bits/ 64 <40000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <4000000 1>; + opp-level = <2>; + }; + + /* GEN 3 x1 */ + opp-8000000-3 { + opp-hz = /bits/ 64 <8000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <984500 1>; + opp-level = <3>; + }; + + /* GEN 3 x2 */ + opp-16000000-3 { + opp-hz = /bits/ 64 <16000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <1969000 1>; + opp-level = <3>; + }; + + /* GEN 3 x4 */ + opp-32000000-3 { + opp-hz = /bits/ 64 <32000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <3938000 1>; + opp-level = <3>; + }; + + /* GEN 3 x8 */ + opp-64000000-3 { + opp-hz = /bits/ 64 <64000000>; + required-opps = <&rpmhpd_opp_low_svs>; + opp-peak-kBps = <7876000 1>; + opp-level = <3>; + }; + + /* GEN 4 x1 */ + opp-16000000-4 { + opp-hz = /bits/ 64 <16000000>; + required-opps = <&rpmhpd_opp_svs>; + opp-peak-kBps = <1969000 1>; + opp-level = <4>; + }; + + /* GEN 4 x2 */ + opp-32000000-4 { + opp-hz = /bits/ 64 <32000000>; + required-opps = <&rpmhpd_opp_svs>; + opp-peak-kBps = <3938000 1>; + opp-level = <4>; + }; + + /* GEN 4 x4 */ + opp-64000000-4 { + opp-hz = /bits/ 64 <64000000>; + required-opps = <&rpmhpd_opp_svs>; + opp-peak-kBps = <7876000 1>; + opp-level = <4>; + }; + + /* GEN 4 x8 */ + opp-128000000-4 { + opp-hz = /bits/ 64 <128000000>; + required-opps = <&rpmhpd_opp_svs>; + opp-peak-kBps = <15753000 1>; + opp-level = <4>; + }; + + /* GEN 5 x1 */ + opp-32000000-5 { + opp-hz = /bits/ 64 <32000000>; + required-opps = <&rpmhpd_opp_nom>; + opp-peak-kBps = <3938000 1>; + opp-level = <5>; + }; + + /* GEN 5 x2 */ + opp-64000000-5 { + opp-hz = /bits/ 64 <64000000>; + required-opps = <&rpmhpd_opp_nom>; + opp-peak-kBps = <7876000 1>; + opp-level = <5>; + }; + + /* GEN 5 x4 */ + opp-128000000-5 { + opp-hz = /bits/ 64 <128000000>; + required-opps = <&rpmhpd_opp_nom>; + opp-peak-kBps = <15753000 1>; + opp-level = <5>; + }; + + /* GEN 5 x8 */ + opp-256000000-5 { + opp-hz = /bits/ 64 <256000000>; + required-opps = <&rpmhpd_opp_nom>; + opp-peak-kBps = <31506000 1>; + opp-level = <5>; + }; + }; + + pcie3a_port0: pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + phys = <&pcie3_phy 0>; + #address-cells = <3>; #size-cells = <2>; ranges; diff --git a/arch/arm64/boot/dts/qcom/mahua.dtsi b/arch/arm64/boot/dts/qcom/mahua.dtsi index 281eee14bf5ed..3c07e2ce93c82 100644 --- a/arch/arm64/boot/dts/qcom/mahua.dtsi +++ b/arch/arm64/boot/dts/qcom/mahua.dtsi @@ -50,6 +50,8 @@ /delete-node/ &thermal_video_1; /delete-node/ &tsens6; /delete-node/ &tsens7; +/delete-node/ &pcie3a; +/delete-node/ &pcie3_phy; &aggre1_noc { compatible = "qcom,mahua-aggre1-noc", "qcom,glymur-aggre1-noc"; From 6334077ae4f30c4e6cc840f0254ae701b7e6ab2e Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 19 Jul 2026 23:54:02 -0700 Subject: [PATCH 109/243] FROMLIST: arm64: dts: qcom: glymur-crd: Add PHY supplies for pcie3_phy CRD wires the shared Gen5x8 PCIe PHY's regulator supplies through board-specific PMIC rails. Add the vdda-phy, vdda-pll, and vdda-refgen0p9/1p2 supplies for &pcie3_phy. Link: https://lore.kernel.org/all/20260717-glymur_linkmode_0717-v5-0-4f9e87a61463@oss.qualcomm.com/ Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-crd.dts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dts b/arch/arm64/boot/dts/qcom/glymur-crd.dts index d7471c823e4da..055d84fc70b47 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dts +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dts @@ -73,3 +73,10 @@ bias-disable; }; }; + +&pcie3_phy { + vdda-phy-supply = <&vreg_l3c_e1_0p89>; + vdda-pll-supply = <&vreg_l2c_e1_1p14>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; +}; From a85e2512f7e031ff85eb878c2c0a4a75927d2443 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Mon, 20 Jul 2026 15:56:08 +0800 Subject: [PATCH 110/243] PENDING: arm64: dts: qcom: glymur: Add power-supply for PCIe3 PHY Add PCIe3a required resources eg. power supplies, GPIOs. Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/glymur-qcb.dts | 127 ++++++++++++++++++++---- 1 file changed, 109 insertions(+), 18 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-qcb.dts b/arch/arm64/boot/dts/qcom/glymur-qcb.dts index 570d0a23786ab..16fcba1c830ce 100644 --- a/arch/arm64/boot/dts/qcom/glymur-qcb.dts +++ b/arch/arm64/boot/dts/qcom/glymur-qcb.dts @@ -172,20 +172,46 @@ regulator-boot-on; }; - vreg_nvmesec: regulator-nvmesec { + vreg_pcie_12v: regulator-pcie-12v { compatible = "regulator-fixed"; - regulator-name = "VREG_NVME_SEC_3P3"; + regulator-name = "VREG_PCIE_12V"; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + + gpio = <&tlmm 26 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-names = "default"; + pinctrl-0 = <&pcie_x8_12v>; + }; + + vreg_pcie_3v3: regulator-pcie-3v3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_PCIE_3P3"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; - gpio = <&pmh0110_f_e1_gpios 14 GPIO_ACTIVE_HIGH>; + gpio = <&tlmm 135 GPIO_ACTIVE_HIGH>; enable-active-high; - pinctrl-0 = <&nvme_sec_reg_en>; pinctrl-names = "default"; + pinctrl-0 = <&pm_main_3p3_en>; + }; - regulator-boot-on; + vreg_pcie_3v3_aux: regulator-pcie-3v3-aux { + compatible = "regulator-fixed"; + + regulator-name = "VREG_PCIE_3P3_AUX"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 136 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-names = "default"; + pinctrl-0 = <&pm_aux_3p3_en>; }; vreg_wcn_0p95: regulator-wcn-0p95 { @@ -763,16 +789,31 @@ }; }; -&pcie3b { - vddpe-3v3-supply = <&vreg_nvmesec>; - - pinctrl-0 = <&pcie3b_default>; +&pcie3a { + pinctrl-0 = <&pcie3a_default>; pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie3a_port0 { + compatible = "pciclass,0604"; + + vpcie3v3aux-supply = <&vreg_pcie_3v3_aux>; + vpcie12v-supply = <&vreg_pcie_12v>; + vpcie3v3-supply = <&vreg_pcie_3v3>; + + reset-gpios = <&tlmm 143 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 145 GPIO_ACTIVE_LOW>; }; -&pcie3b_port0 { - reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; - wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; +&pcie3_phy { + vdda-phy-supply = <&vreg_l3c_e1_0p89>; + vdda-pll-supply = <&vreg_l2c_e1_1p14>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; + + status = "okay"; }; &pcie4 { @@ -785,9 +826,8 @@ &pcie4_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; - + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "okay"; }; @@ -823,7 +863,8 @@ &pcie5_phy { vdda-phy-supply = <&vreg_l2f_e0_0p82>; vdda-pll-supply = <&vreg_l4h_e0_1p2>; - vdda-qref-supply = <&vreg_l3f_e0_0p72>; + vdda-refgen0p9-supply = <&vreg_l2f_e0_0p82>; + vdda-refgen1p2-supply = <&vreg_l4h_e0_1p2>; status = "okay"; }; @@ -845,8 +886,8 @@ &pcie6_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "disabled"; }; @@ -1054,6 +1095,29 @@ bias-disable; }; + pcie3a_default: pcie3a-default-state { + clkreq-n-pins { + pins = "gpio144"; + function = "pcie3a_clk"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-n-pins { + pins = "gpio143"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + wake-n-pins { + pins = "gpio145"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + pcie4_default: pcie4-default-state { clkreq-n-pins { pins = "gpio147"; @@ -1183,6 +1247,33 @@ }; }; + pcie_x8_12v: pcie-12v-default-state { + pins = "gpio26"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + + pm_aux_3p3_en: pcie-aux-3p3-default-state { + pins = "gpio136"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + + pm_main_3p3_en: pcie-main-3p3-default-state { + pins = "gpio135"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + wcn_wlan_bt_en: wcn-wlan-bt-en-state { pins = "gpio116", "gpio117"; function = "gpio"; From a350afb3f54e26203c3eedd71fdba5e484de91b2 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 13:12:21 +0800 Subject: [PATCH 111/243] PENDING: arm64: dts: qcom: mahua: Add PCIe3b support for mahua Enable PCIe3b controller and PCIe3b PHY. Add required LDOs and GPIOs. Signed-off-by: Qiang Yu --- arch/arm64/boot/dts/qcom/mahua-qcb.dts | 84 +++++++++++++++++++++----- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/mahua-qcb.dts b/arch/arm64/boot/dts/qcom/mahua-qcb.dts index 1c1066bf3a5ad..fa61ab38d904c 100644 --- a/arch/arm64/boot/dts/qcom/mahua-qcb.dts +++ b/arch/arm64/boot/dts/qcom/mahua-qcb.dts @@ -179,20 +179,46 @@ regulator-boot-on; }; - vreg_nvmesec: regulator-nvmesec { + vreg_pcie_12v: regulator-pcie-12v { compatible = "regulator-fixed"; - regulator-name = "VREG_NVME_SEC_3P3"; + regulator-name = "VREG_PCIE_12V"; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + + gpio = <&tlmm 26 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-names = "default"; + pinctrl-0 = <&pcie_x8_12v>; + }; + + vreg_pcie_3v3: regulator-pcie-3v3 { + compatible = "regulator-fixed"; + + regulator-name = "VREG_PCIE_3P3"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; - gpio = <&pmh0110_f_e1_gpios 14 GPIO_ACTIVE_HIGH>; + gpio = <&tlmm 135 GPIO_ACTIVE_HIGH>; enable-active-high; - pinctrl-0 = <&nvme_sec_reg_en>; pinctrl-names = "default"; + pinctrl-0 = <&pm_main_3p3_en>; + }; - regulator-boot-on; + vreg_pcie_3v3_aux: regulator-pcie-3v3-aux { + compatible = "regulator-fixed"; + + regulator-name = "VREG_PCIE_3P3_AUX"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + gpio = <&tlmm 136 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-names = "default"; + pinctrl-0 = <&pm_aux_3p3_en>; }; vreg_wcn_0p95: regulator-wcn-0p95 { @@ -771,8 +797,6 @@ }; &pcie3b { - vddpe-3v3-supply = <&vreg_nvmesec>; - pinctrl-0 = <&pcie3b_default>; pinctrl-names = "default"; }; @@ -780,13 +804,17 @@ &pcie3b_phy { vdda-phy-supply = <&vreg_l3c_e1_0p89>; vdda-pll-supply = <&vreg_l2c_e1_1p14>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; }; &pcie3b_port0 { + compatible = "pciclass,0604"; + + vpcie3v3aux-supply = <&vreg_pcie_3v3_aux>; + vpcie12v-supply = <&vreg_pcie_12v>; + vpcie3v3-supply = <&vreg_pcie_3v3>; + reset-gpios = <&tlmm 155 GPIO_ACTIVE_LOW>; wake-gpios = <&tlmm 157 GPIO_ACTIVE_LOW>; }; @@ -801,8 +829,8 @@ &pcie4_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "okay"; }; @@ -839,7 +867,8 @@ &pcie5_phy { vdda-phy-supply = <&vreg_l2f_e0_0p82>; vdda-pll-supply = <&vreg_l4h_e0_1p2>; - vdda-qref-supply = <&vreg_l3f_e0_0p72>; + vdda-refgen0p9-supply = <&vreg_l2f_e0_0p82>; + vdda-refgen1p2-supply = <&vreg_l4h_e0_1p2>; status = "okay"; }; @@ -861,8 +890,8 @@ &pcie6_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; - vdda-qref-supply = <&vreg_l1f_e1_0p82>; - vdda-qref2-supply = <&vreg_l2f_e1_0p83>; + vdda-refgen0p9-supply = <&vreg_l1c_e1_0p82>; + vdda-refgen1p2-supply = <&vreg_l4f_e1_1p08>; status = "disabled"; }; @@ -1199,6 +1228,33 @@ }; }; + pcie_x8_12v: pcie-12v-default-state { + pins = "gpio26"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + + pm_aux_3p3_en: pcie-aux-3p3-default-state { + pins = "gpio136"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + + pm_main_3p3_en: pcie-main-3p3-default-state { + pins = "gpio135"; + function = "gpio"; + output-enable; + output-high; + drive-strength = <16>; + bias-disable; + }; + wcn_wlan_bt_en: wcn-wlan-bt-en-state { pins = "gpio116", "gpio117"; function = "gpio"; From 00fbc1d7e9843d6ee21e195a7971231aef1e5b14 Mon Sep 17 00:00:00 2001 From: Saikiran Date: Wed, 28 Jan 2026 00:32:10 +0530 Subject: [PATCH 112/243] FROMLIST: dt-bindings: regulator: qcom,rpmh: Allow regulator-off-on-delay-us Add the standard 'regulator-off-on-delay-us' property to the list of allowed properties for RPMh regulators. This property is required for platforms where specific rails (like camera LDOs) rely on passive discharge and need a mandatory off-time constraint enforced by the regulator core. Link: https://lore.kernel.org/all/20260127190211.14312-2-bjsaikiran@gmail.com/ Signed-off-by: Saikiran --- .../devicetree/bindings/regulator/qcom,rpmh-regulator.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml index d93594304651c..21e98ef55e96d 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml @@ -133,6 +133,8 @@ properties: $ref: regulator.yaml# unevaluatedProperties: false description: BOB regulator node. + properties: + regulator-off-on-delay-us: true dependencies: regulator-allow-set-load: [ regulator-allowed-modes ] @@ -142,6 +144,8 @@ patternProperties: $ref: regulator.yaml# unevaluatedProperties: false description: smps/ldo regulator nodes(s). + properties: + regulator-off-on-delay-us: true dependencies: regulator-allow-set-load: [ regulator-allowed-modes ] From bf8b27d1205b49f302942b4059d53a17d06ab196 Mon Sep 17 00:00:00 2001 From: Saikiran Date: Wed, 28 Jan 2026 00:32:11 +0530 Subject: [PATCH 113/243] FROMLIST: regulator: qcom-rpmh: Add support for regulator-off-on-delay-us The core regulator framework supports enforcing a physical off-time via standard properties, but the `qcom-rpmh-regulator` driver currently ignores them. The issue is platform-specific: The Lenovo Yoga Slim 7x (Snapdragon X Elite) has large bulk capacitors on the camera rails (LDO1, LDO3, LDO7). When these regulators are disabled, the voltage decays very slowly (passive discharge). If the rail is re-enabled before this discharge completes, the sensor experiences a brownout and fails to initialize. Add support for parsing the 'regulator-off-on-delay-us' property from the device tree to enforce this physical constraint. Link: https://lore.kernel.org/all/20260127190211.14312-3-bjsaikiran@gmail.com/ Signed-off-by: Saikiran --- drivers/regulator/qcom-rpmh-regulator.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c index 756a4201225e7..b0f3177eff7fc 100644 --- a/drivers/regulator/qcom-rpmh-regulator.c +++ b/drivers/regulator/qcom-rpmh-regulator.c @@ -503,6 +503,9 @@ static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg, struct device *dev, vreg->always_wait_for_ack = of_property_read_bool(node, "qcom,always-wait-for-ack"); + of_property_read_u32(node, "regulator-off-on-delay-us", + &vreg->rdesc.off_on_delay); + vreg->rdesc.owner = THIS_MODULE; vreg->rdesc.type = REGULATOR_VOLTAGE; vreg->rdesc.ops = vreg->hw_data->ops; From 6c5dc96ea9713e29b1b46188d9578675dd3dfd19 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 21 Jul 2026 14:35:08 +0300 Subject: [PATCH 114/243] FROMLIST: arm64: dts: qcom: eliza: Add fallback compatible for ADSP remoteproc The ADSP found on Eliza SoC is actually fully compatible with the ones from SM8750 class. So add the SM8550 compatible as fallback, just like the rest from the SM8750 class SoCs. Fixes: 88ddafb01ec0 ("arm64: dts: qcom: eliza: Describe the ADSP and USB related nodes") Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260721-dts-qcom-eliza-fix-adsp-binding-v1-2-3a0b03af2ec7@oss.qualcomm.com Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 0dbfb60be4e7c..b56ff3fb88b41 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -2347,7 +2347,7 @@ }; remoteproc_adsp: remoteproc@3000000 { - compatible = "qcom,eliza-adsp-pas"; + compatible = "qcom,eliza-adsp-pas", "qcom,sm8550-adsp-pas"; reg = <0x0 0x03000000 0x0 0x10000>; interrupts-extended = <&pdc 6 IRQ_TYPE_EDGE_RISING>, From 1fe0ba5592d3393621348d62048c3bb61957d38b Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 14 Jul 2026 16:06:08 +0300 Subject: [PATCH 115/243] FROMLIST: arm64: dts: qcom: eliza: Describe the CDSP remoteproc Describe the CDSP remoteproc along with the related SMP2P. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index b56ff3fb88b41..78a39847f7cb1 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -632,6 +632,30 @@ }; }; + smp2p-cdsp { + compatible = "qcom,smp2p"; + qcom,smem = <94>, <432>; + interrupts-extended = <&ipcc IPCC_CLIENT_CDSP + IPCC_MPROC_SIGNAL_SMP2P + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_CLIENT_CDSP + IPCC_MPROC_SIGNAL_SMP2P>; + + qcom,local-pid = <0>; + qcom,remote-pid = <5>; + + smp2p_cdsp_out: master-kernel { + qcom,entry-name = "master-kernel"; + #qcom,smem-state-cells = <1>; + }; + + smp2p_cdsp_in: slave-kernel { + qcom,entry-name = "slave-kernel"; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + soc: soc@0 { compatible = "simple-bus"; @@ -4478,6 +4502,46 @@ #interconnect-cells = <2>; }; + remoteproc_cdsp: remoteproc@32300000 { + compatible = "qcom,eliza-cdsp-pas"; + reg = <0x0 0x32300000 0x0 0x10000>; + + interrupts-extended = <&intc GIC_SPI 578 IRQ_TYPE_EDGE_RISING>, + <&smp2p_cdsp_in 0 IRQ_TYPE_EDGE_RISING>, + <&smp2p_cdsp_in 1 IRQ_TYPE_EDGE_RISING>, + <&smp2p_cdsp_in 2 IRQ_TYPE_EDGE_RISING>, + <&smp2p_cdsp_in 3 IRQ_TYPE_EDGE_RISING>, + <&smp2p_cdsp_in 7 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "wdog", + "fatal", + "ready", + "handover", + "stop-ack", + "shutdown-ack"; + + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-names = "xo"; + + power-domains = <&rpmhpd RPMHPD_CX>, + <&rpmhpd RPMHPD_MX>, + <&rpmhpd RPMHPD_NSP>; + power-domain-names = "cx", + "mx", + "nsp"; + + interconnects = <&nsp_noc MASTER_CDSP_PROC QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + + memory-region = <&cdsp_mem>, <&q6_cdsp_dtb_mem>, <&global_sync_mem>; + + qcom,qmp = <&aoss_qmp>; + + qcom,smem-states = <&smp2p_cdsp_out 0>; + qcom,smem-state-names = "stop"; + + status = "disabled"; + }; + gpu: gpu@3d00000 { compatible = "qcom,adreno-43020100", "qcom,adreno"; reg = <0x0 0x03d00000 0x0 0x40000>, From a0cecf3f24565835971b17644d2d9afea0ac48c0 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 14 Jul 2026 16:06:09 +0300 Subject: [PATCH 116/243] FROMLIST: arm64: dts: qcom: eliza-mtp: Enable CDSP remoteproc Enable the CDSP remoteproc node on the Eliza MTP by providing the firmware names and marking the node as available. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-mtp.dts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-mtp.dts b/arch/arm64/boot/dts/qcom/eliza-mtp.dts index 5cc600772bb35..ee6a0909a65db 100644 --- a/arch/arm64/boot/dts/qcom/eliza-mtp.dts +++ b/arch/arm64/boot/dts/qcom/eliza-mtp.dts @@ -492,6 +492,13 @@ status = "okay"; }; +&remoteproc_cdsp { + firmware-name = "qcom/eliza/cdsp.mbn", + "qcom/eliza/cdsp_dtb.mbn"; + + status = "okay"; +}; + &spi8 { status = "okay"; From a157b98e3bd186592044e65233c8caa9bb72f91a Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 14 Jul 2026 16:06:10 +0300 Subject: [PATCH 117/243] FROMLIST: arm64: dts: qcom: eliza-cqs-som: Enable CDSP remoteproc Enable the CDSP remoteproc node on the Eliza CQS SoM by providing the firmware names and marking the node as available. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi index 318ae3c223515..d31267968cad4 100644 --- a/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza-cqs-som.dtsi @@ -376,6 +376,13 @@ status = "okay"; }; +&remoteproc_cdsp { + firmware-name = "qcom/eliza/cdsp.mbn", + "qcom/eliza/cdsp_dtb.mbn"; + + status = "okay"; +}; + &sdhc_1 { vmmc-supply = <&vreg_l12b>; vqmmc-supply = <&vreg_l1d>; From a80579c6b6ccb299adbbd86210378547fb9f5e5d Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 21 Jul 2026 12:30:58 +0300 Subject: [PATCH 118/243] FROMLIST: arm64: dts: qcom: eliza: Add FastRPC support Describe the glink-edge for CDSP and the FastRPC subnodes for both ADSP and CDSP. Link: https://patch.msgid.link/20260721-dts-qcom-eliza-add-fastrpc-v1-1-cbea68d8e01b@oss.qualcomm.com Signed-off-by: Abel Vesa --- arch/arm64/boot/dts/qcom/eliza.dtsi | 158 ++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 78a39847f7cb1..861c936d79b39 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -2417,6 +2417,65 @@ label = "lpass"; qcom,remote-pid = <2>; + fastrpc { + compatible = "qcom,fastrpc"; + + qcom,glink-channels = "fastrpcglink-apps-dsp"; + + label = "adsp"; + + qcom,non-secure-domain; + + #address-cells = <1>; + #size-cells = <0>; + + compute-cb@3 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <3>; + + iommus = <&apps_smmu 0x1003 0x80>, + <&apps_smmu 0x1043 0x20>; + dma-coherent; + }; + + compute-cb@4 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <4>; + + iommus = <&apps_smmu 0x1004 0x80>, + <&apps_smmu 0x1044 0x20>; + dma-coherent; + }; + + compute-cb@5 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <5>; + + iommus = <&apps_smmu 0x1005 0x80>, + <&apps_smmu 0x1045 0x20>; + dma-coherent; + }; + + compute-cb@6 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <6>; + + iommus = <&apps_smmu 0x1006 0x80>, + <&apps_smmu 0x1046 0x20>; + dma-coherent; + }; + + compute-cb@7 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <7>; + + iommus = <&apps_smmu 0x1007 0x40>, + <&apps_smmu 0x1067 0x0>, + <&apps_smmu 0x1087 0x0>; + dma-coherent; + }; + }; + gpr { compatible = "qcom,gpr"; qcom,glink-channels = "adsp_apps"; @@ -4540,6 +4599,105 @@ qcom,smem-state-names = "stop"; status = "disabled"; + + glink-edge { + interrupts-extended = <&ipcc IPCC_CLIENT_CDSP + IPCC_MPROC_SIGNAL_GLINK_QMP + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_CLIENT_CDSP + IPCC_MPROC_SIGNAL_GLINK_QMP>; + + label = "cdsp"; + qcom,remote-pid = <5>; + + fastrpc { + compatible = "qcom,fastrpc"; + qcom,glink-channels = "fastrpcglink-apps-dsp"; + label = "cdsp"; + qcom,non-secure-domain; + #address-cells = <1>; + #size-cells = <0>; + + compute-cb@1 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <1>; + iommus = <&apps_smmu 0x0c01 0x0>; + dma-coherent; + }; + + compute-cb@2 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <2>; + iommus = <&apps_smmu 0x0c02 0x0>; + dma-coherent; + }; + + compute-cb@3 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <3>; + iommus = <&apps_smmu 0x0c03 0x0>; + dma-coherent; + }; + + compute-cb@4 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <4>; + iommus = <&apps_smmu 0x0c04 0x0>; + dma-coherent; + }; + + compute-cb@5 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <5>; + iommus = <&apps_smmu 0x0c05 0x0>; + dma-coherent; + }; + + compute-cb@6 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <6>; + iommus = <&apps_smmu 0x0c06 0x0>; + dma-coherent; + }; + + compute-cb@7 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <7>; + iommus = <&apps_smmu 0x0c07 0x0>; + dma-coherent; + }; + + compute-cb@8 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <8>; + iommus = <&apps_smmu 0x0c08 0x0>; + dma-coherent; + }; + + /* note: secure cb9 in downstream */ + + compute-cb@12 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <12>; + iommus = <&apps_smmu 0x0c0c 0x0>; + dma-coherent; + }; + + compute-cb@13 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <13>; + iommus = <&apps_smmu 0x0c0d 0x0>; + dma-coherent; + }; + + compute-cb@14 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <14>; + iommus = <&apps_smmu 0x0c0e 0x0>; + dma-coherent; + }; + }; + }; }; gpu: gpu@3d00000 { From b0dcca17247e642c74e79522ba214eb7194d2cf8 Mon Sep 17 00:00:00 2001 From: Yi Zhang Date: Mon, 23 Mar 2026 12:38:52 +0530 Subject: [PATCH 119/243] 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 8699fc30f5267befd0ebe45ab06a019cb71d0e25 Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 28 Jan 2026 18:28:20 +0530 Subject: [PATCH 120/243] 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 1045a36defd04ae29bf9440611166d306f3523dc Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 23 Mar 2026 19:36:04 +0800 Subject: [PATCH 121/243] 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 16c0631adeb18..6bb1134f75c3d 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -87,6 +87,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) { @@ -178,6 +192,8 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge, static const struct drm_bridge_funcs display_connector_bridge_funcs = { .attach = display_connector_attach, .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, @@ -307,6 +323,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 fd2c42589ff9b82ac78e097dc8e29550d16f8f3e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 23 Mar 2026 19:37:45 +0800 Subject: [PATCH 122/243] 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 6bb1134f75c3d..f7a5bfd9c0754 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 * @@ -92,15 +95,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) { @@ -395,6 +412,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 649018f489a2463ca11874a0b18180013f2565fb Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Sun, 19 Apr 2026 15:45:56 +0530 Subject: [PATCH 123/243] 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 fd1a69bac36d1eab4e66f6b29144ad7debde56dd Mon Sep 17 00:00:00 2001 From: Yi Zhang Date: Mon, 4 May 2026 13:50:52 +0530 Subject: [PATCH 124/243] 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 e049d66cc72e9dbd0d641a25d43e457f086042ae Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 26 Nov 2025 17:01:18 +0530 Subject: [PATCH 125/243] 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 637f152907ac452d42d4d286ca19d5a721fec6d4 Mon Sep 17 00:00:00 2001 From: Nilesh Laad Date: Wed, 26 Nov 2025 18:52:37 +0530 Subject: [PATCH 126/243] 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 b2e2bb3d57e47eccb029dba01f304d4fb8a6ac5d Mon Sep 17 00:00:00 2001 From: Ananthu C V Date: Thu, 9 Jul 2026 23:39:44 -0700 Subject: [PATCH 127/243] FROMLIST: arm64: dts: qcom: fix SoCCP memory mappings for Glymur The currently listed SoCCP and SoCCP DTB reserved memory regions don't align with the memory requested by the SoCCP Firmware. Fix this by updating the SoCCP/SoCCP DTB memory regions to reflect the memory region requirements of the SoCCP firmware, as described in the Glymur v21 memory map release. Link: https://lore.kernel.org/lkml/20260709-glymur-soccp-v6-3-16f70227547d@oss.qualcomm.com/ Fixes: 41b6e8db400c ("arm64: dts: qcom: Introduce Glymur base dtsi") Signed-off-by: Ananthu C V Reviewed-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/glymur.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index a4256dfb99252..3a966bf14a393 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -640,13 +640,13 @@ no-map; }; - soccpdtb_mem: soccpdtb@892e0000 { - reg = <0x0 0x892e0000 0x0 0x20000>; + soccp_mem: soccp@88e00000 { + reg = <0x0 0x88e00000 0x0 0x400000>; no-map; }; - soccp_mem: soccp@89300000 { - reg = <0x0 0x89300000 0x0 0x400000>; + soccpdtb_mem: soccpdtb@89200000 { + reg = <0x0 0x89200000 0x0 0x20000>; no-map; }; From 569906c00647a042ceb094837e1c5231dced2125 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:23 +0800 Subject: [PATCH 128/243] 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 292b6a7182f5fb9499f82e2b1eb45c72ad78bad5 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:24 +0800 Subject: [PATCH 129/243] 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 377f04f148e391bba0d57e95fe1fe92354734e14 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:25 +0800 Subject: [PATCH 130/243] 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 c6215dce9dbb54f15b46b69540f1aefef1e1cb15 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:26 +0800 Subject: [PATCH 131/243] 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 d1ea7fc83b9c2f63ca3120e0487c15ce5658c75a Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:27 +0800 Subject: [PATCH 132/243] 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 87906e1790961ab146fdd39007d2009f064b8c5b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:28 +0800 Subject: [PATCH 133/243] 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 8e6a326093bd2c8613d7911fab802bfa27a95590 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:29 +0800 Subject: [PATCH 134/243] 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 bfed054d6aad3becd7fd6e17f8a5cc1a6fe687df Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:30 +0800 Subject: [PATCH 135/243] 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 e2b2963f6d117aedadce9624c6952960325614cd Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:31 +0800 Subject: [PATCH 136/243] 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 d2d93e1f51f8d7a7d19f88620dd2c943de8c752a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:32 +0800 Subject: [PATCH 137/243] 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 33f668847e53903062d011c9a57add4520682919 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:33 +0800 Subject: [PATCH 138/243] 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 9ed33663e2b1ed43929ebdfbd299e7b8b2dadb12 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:34 +0800 Subject: [PATCH 139/243] 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 ce17b12c9da2461b9c4c8e790a7a8dbab89ab1fa Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:35 +0800 Subject: [PATCH 140/243] 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 66f518e5f8fe8b5b7578030c5dbb203e3b778c7a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 9 Jun 2026 17:46:36 +0800 Subject: [PATCH 141/243] 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 9950bb9382d2f08da9b2a6356c38b16d7999fc33 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 9 Jun 2026 17:46:37 +0800 Subject: [PATCH 142/243] 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 b649a38df94dfa1eaad3de4ea0f391c7bc80fab5 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:22 +0800 Subject: [PATCH 143/243] 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 dc8e594469fb01856510c8d0b464c71a9067a761 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:23 +0800 Subject: [PATCH 144/243] 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 40a3f659149a50bc8b3cd95e88e610ee664ae29a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:24 +0800 Subject: [PATCH 145/243] 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 8193371b67eba9378cc06e7b0ba3671e875ee150 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:25 +0800 Subject: [PATCH 146/243] 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 2fde97d43498044c5fd7fb411e5be8fbfc96660b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:26 +0800 Subject: [PATCH 147/243] 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 4b1f1788633e4cd90a0a7f0ccdad2b1cb22f8933 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:27 +0800 Subject: [PATCH 148/243] 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 fae6148655666d7fc4427d6eeee408ca73746b6e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:28 +0800 Subject: [PATCH 149/243] 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 74480ad82ae61dc76c237a59d2aa8452390dc97d Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:29 +0800 Subject: [PATCH 150/243] 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 9b5a101ef1f391eb7e7eda5129cc0893e0d82ae4 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:30 +0800 Subject: [PATCH 151/243] 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 bdffd516f6e8a7df3df0883eb0ed845f99464887 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:31 +0800 Subject: [PATCH 152/243] 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 83eb389a4acbd80b03fdbd6e0a24a092a0bb8d05 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:32 +0800 Subject: [PATCH 153/243] 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 157825355044ace920a5fd819bb51e66315df1fc Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:33 +0800 Subject: [PATCH 154/243] 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 d2b3380c1e730e40d60f65e5ab317fd51fe63045 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:34 +0800 Subject: [PATCH 155/243] 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 6644e3be70e86a4266f25afca12b31178d7abfd7 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:35 +0800 Subject: [PATCH 156/243] 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 1a49bd534dfaf3d7f6f5ca05163a415c741d2b54 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:36 +0800 Subject: [PATCH 157/243] 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 c018a3984895b68f244adc45d3ef4aaeadee345d Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:37 +0800 Subject: [PATCH 158/243] 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 4843f9837138d8414c3df3a0b5ed7fea63259e32 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:38 +0800 Subject: [PATCH 159/243] 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 2da320876e1b94f2272399c5f4440a66e8394cd6 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:39 +0800 Subject: [PATCH 160/243] 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 3d9b3e8ff9af2ae4d0110f4b9ab3c3ec12ca76e1 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:40 +0800 Subject: [PATCH 161/243] 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 8172ad52c7828477b55501d82abc533038744263 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:41 +0800 Subject: [PATCH 162/243] 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 70786d3124eba665af86c71b8992647f41af6e51 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:42 +0800 Subject: [PATCH 163/243] 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 111eddf88aa15d2737ff2641cc0b4252720e9442 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:43 +0800 Subject: [PATCH 164/243] 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 6bec88a40989a6bbb736152dcc606ac39877e225 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:14:44 +0800 Subject: [PATCH 165/243] 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 c0d3ab32223650e8f567a067fa4311b71fecde8d Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:45 +0800 Subject: [PATCH 166/243] 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 d52be0d4927489585b4a4a6aff9c67cefbf7cefb Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Mon, 29 Jun 2026 22:14:46 +0800 Subject: [PATCH 167/243] 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 49959e78621c2a27a0f6d1a1a73f06098ff41de4 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:02 +0300 Subject: [PATCH 168/243] 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 150a7b1c1e4aa7626f448dfbbb8b56cd97165dab Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:03 +0300 Subject: [PATCH 169/243] 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 7ff5712f8b199..a1b7926d40491 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -6996,7 +6996,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 80e71d9e3d45223b659ef6923938240f61dd42ba Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:04 +0300 Subject: [PATCH 170/243] 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 cd3bfceb3a943ea74eb455f3247dac4f89bada23 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:05 +0300 Subject: [PATCH 171/243] 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 d32beee11b826bead8e5ce3d4fd6ad3ce51922e7 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:06 +0300 Subject: [PATCH 172/243] 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 4a39f942597531ccb1b21f5fb1967c6d8508853e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:07 +0300 Subject: [PATCH 173/243] 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 f2b1d1f86c7d8be00907617257bba59e04cfb662 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:08 +0300 Subject: [PATCH 174/243] 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 d9a65de7c4243fa54a69de39afcd62973a3f6500 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 8 Jun 2026 00:33:09 +0300 Subject: [PATCH 175/243] 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 4bec81efcc897d2ff507452a46ad5955668b14a5 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:03 +0800 Subject: [PATCH 176/243] 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 3d5d438ee7f8b5888d6aa946207d1836e158c04c Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:04 +0800 Subject: [PATCH 177/243] 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 675c0d16379b0325fae661a6e429e7ad25924096 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:05 +0800 Subject: [PATCH 178/243] 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 29bec688d4a84473c830189b1ae1efa940128c65 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:06 +0800 Subject: [PATCH 179/243] 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 514299aac0595fc4b79a65cdbbf9c1c2e492acbb Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 29 Jun 2026 22:48:07 +0800 Subject: [PATCH 180/243] 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 815a70f869a722773838e1946a8003c8787977fc Mon Sep 17 00:00:00 2001 From: Kumar Anurag Date: Tue, 23 Jun 2026 23:54:55 -0700 Subject: [PATCH 181/243] 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 71bff9f66bd6c3e21f055fde1181adb2e4a23b5e Mon Sep 17 00:00:00 2001 From: Pradyot Kumar Nayak Date: Tue, 10 Mar 2026 12:19:00 +0530 Subject: [PATCH 182/243] WORKAROUND: arm64: dts: qcom: glymur: Upgrade dispcc opp entry to turbo Switch opp entry for dispcc to turbo Signed-off-by: Pradyot Kumar Nayak --- arch/arm64/boot/dts/qcom/glymur.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 3a966bf14a393..43bc3c600bc2e 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -6121,7 +6121,7 @@ <0>, <0>; power-domains = <&rpmhpd RPMHPD_MMCX>; - required-opps = <&rpmhpd_opp_low_svs>; + required-opps = <&rpmhpd_opp_turbo>; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; From e5de7366d687d86624da067734aa48dd938ddc5d Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Wed, 22 Jul 2026 11:57:40 +0530 Subject: [PATCH 183/243] Revert "FROMLIST: dt-bindings: hwmon: Add qcom,bcl-hwmon yaml bindings" This reverts commit 66025bf1befff2a56ee290497032e28438e16b19. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- .../bindings/hwmon/qcom,bcl-hwmon.yaml | 128 ------------------ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 - 2 files changed, 132 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml diff --git a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml deleted file mode 100644 index 6ffadc4d28bcf..0000000000000 --- a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/hwmon/qcom,bcl-hwmon.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor - -maintainers: - - Manaf Meethalavalappu Pallikunhi - -description: | - SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and - alarm functionality for battery overcurrent and battery or system under - voltage conditions. It monitors battery voltage and current, and - can trigger interrupts when configurable thresholds are exceeded. - -properties: - compatible: - oneOf: - - description: v1 based BCL - items: - - enum: - - qcom,pm7250b-bcl - - qcom,pm8250b-bcl - - const: qcom,bcl-v1 - - - description: v2 based BCL - items: - - enum: - - qcom,pm8350b-bcl - - qcom,pm8350c-bcl - - const: qcom,bcl-v2 - - - description: v3 bmx based BCL - items: - - enum: - - qcom,pm8550b-bcl - - qcom,pm7550ba-bcl - - const: qcom,bcl-v3-bmx - - - description: v3 core based BCL - items: - - enum: - - qcom,pm8550-bc0l - - qcom,pm7550-bcl - - const: qcom,bcl-v3-core - - - description: v3 wb based BCL - items: - - enum: - - qcom,pmw5100-bcl - - const: qcom,bcl-v3-wb - - - description: v4 bmx based BCL - items: - - enum: - - qcom,pmih010-bcl - - const: qcom,bcl-v4-bmx - - - description: v4 bmx with different scale based BCL - items: - - enum: - - qcom,pmv010-bcl - - const: qcom,bcl-v4-pmv010 - - - description: v4 core based BCL - items: - - enum: - - qcom,pmh010-bcl - - const: qcom,bcl-v4-core - - - description: v4 wb based BCL - items: - - enum: - - qcom,pmw6100-bcl - - const: qcom,bcl-v4-wb - - reg: - maxItems: 1 - description: BCL base address in the SPMI PMIC register map - - interrupts: - minItems: 2 - maxItems: 2 - description: - BCL alarm interrupts for different threshold levels - - interrupt-names: - items: - - const: bcl-max-min - - const: bcl-critical - - overcurrent-thresholds-milliamp: - description: - Current thresholds in milliamperes for the two configurable current - alarm levels (max and critical). These values are used to override - default thresholds if a platform has different battery ocp specification. - $ref: /schemas/types.yaml#/definitions/uint32-array - minItems: 2 - maxItems: 2 - -required: - - compatible - - reg - - interrupts - - interrupt-names - -unevaluatedProperties: false - -examples: - - | - #include - - pmic { - #address-cells = <1>; - #size-cells = <0>; - - sensor@1d00 { - compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; - reg = <0x1d00>; - interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, - <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "bcl-max-min", - "bcl-critical"; - overcurrent-thresholds-milliamp = <5500 6000>; - }; - }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 0d4b44bc88c51..644c42b5e2e57 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -189,10 +189,6 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# - "^sensor@[0-9a-f]+$": - type: object - $ref: /schemas/hwmon/qcom,bcl-hwmon.yaml# - "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# From fc6dc5e7a4132cb69cb8ae40cd5b4e5462d4e725 Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Wed, 22 Jul 2026 11:57:55 +0530 Subject: [PATCH 184/243] Revert "FROMLIST: hwmon: Add Qualcomm PMIC BCL hardware monitor driver" This reverts commit 3122567e1af4ab6cf0748c2e49d3e1ab1c37a2cf. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- MAINTAINERS | 9 - drivers/hwmon/Kconfig | 9 - drivers/hwmon/Makefile | 1 - drivers/hwmon/qcom-bcl-hwmon.c | 982 --------------------------------- drivers/hwmon/qcom-bcl-hwmon.h | 311 ----------- 5 files changed, 1312 deletions(-) delete mode 100644 drivers/hwmon/qcom-bcl-hwmon.c delete mode 100644 drivers/hwmon/qcom-bcl-hwmon.h diff --git a/MAINTAINERS b/MAINTAINERS index 10b40310aef45..c20b90150b7cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22100,15 +22100,6 @@ S: Maintained F: Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml F: drivers/net/wwan/qcom_bam_dmux.c -QUALCOMM BCL HARDWARE MONITOR DRIVER -M: Manaf Meethalavalappu Pallikunhi -L: linux-hwmon@vger.kernel.org -L: linux-arm-msm@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml -F: drivers/hwmon/qcom-bcl-hwmon.c -F: drivers/hwmon/qcom-bcl-hwmon.h - QUALCOMM BLUETOOTH DRIVER M: Bartosz Golaszewski L: linux-arm-msm@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 251f5cc900b3b..5c2d3ff5fce82 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1939,15 +1939,6 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. -config SENSORS_QCOM_BCL - tristate "Qualcomm BCL hardware monitoring" - help - Say yes here to enable support for Qualcomm battery over current - and under voltage alarms monitor. - - This driver can also be built as a module. If so, the module - will be called qcom-bcl-hwmon. - config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e0293c14a7cba..63effc0ab8d11 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -202,7 +202,6 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o -obj-$(CONFIG_SENSORS_QCOM_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c deleted file mode 100644 index 9894b1d14d124..0000000000000 --- a/drivers/hwmon/qcom-bcl-hwmon.c +++ /dev/null @@ -1,982 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Qualcomm pmic BCL hardware driver for battery overcurrent and - * battery or system under voltage monitor - * - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qcom-bcl-hwmon.h" - -ADD_BCL_HWMON_ALARM_MAPS(in, min, lcrit); -ADD_BCL_HWMON_ALARM_MAPS(curr, max, crit); - -/* Interrupt names for each alarm level */ -static const char * const bcl_int_names[ALARM_MAX] = { - [LVL0] = "bcl-max-min", - [LVL1] = "bcl-critical", -}; - -static const char * const bcl_channel_label[CHANNEL_MAX] = { - "BCL Voltage", - "BCL Current", -}; - -/* Common Reg Fields */ -static const struct reg_field common_reg_fields[COMMON_FIELD_MAX] = { - [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), - [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), - [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), - [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), - [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), -}; - -/* BCL Version/Modes specific fields */ -static const struct reg_field bcl_v1_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v2_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 1), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_bmx_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_wb_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 3), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_core_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 5), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), -}; - -static const struct reg_field bcl_v4_bmx_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), -}; - -static const struct reg_field bcl_v4_wb_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 4), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), -}; - -static const struct reg_field bcl_v4_core_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), -}; - -/* V1 BMX and core */ -static const struct bcl_desc pm7250b_data = { - .reg_fields = bcl_v1_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 7, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 10000, - .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V2 BMX and core */ -static const struct bcl_desc pm8350_data = { - .reg_fields = bcl_v2_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 10000, - .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V3 BMX */ -static const struct bcl_desc pm8550b_data = { - .reg_fields = bcl_v3_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V3 WB */ -static const struct bcl_desc pmw5100_data = { - .reg_fields = bcl_v3_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 800, - .max = 2000, - .step = 100, - .default_scale_nu = 61035, - .thresh_type = {ADC, INDEX}, - }, -}; - -/* V3 CORE */ -static const struct bcl_desc pm8550_data = { - .reg_fields = bcl_v3_core_reg_fields, - .num_reg_fields = F_CURR_MON_EN + 1, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .thresh_type = {INDEX, INDEX}, - }, -}; - -/* V4 BMX */ -static const struct bcl_desc pmih010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 20000, - .default_scale_nu = 610370, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V4 WB */ -static const struct bcl_desc pmw6100_data = { - .reg_fields = bcl_v4_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 1500, - .max = 4000, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 900, - .max = 3300, - .step = 150, - .default_scale_nu = 152586, - .thresh_type = {ADC, INDEX}, - }, -}; - -/* V4 CORE */ -static const struct bcl_desc pmh010_data = { - .reg_fields = bcl_v4_core_reg_fields, - .num_reg_fields = F_CURR_MON_EN + 1, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 1500, - .max = 4000, - .step = 25, - .thresh_type = {INDEX, INDEX}, - }, -}; - -/* V4 BMX with different scale */ -static const struct bcl_desc pmv010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, - }, -}; - -/** - * bcl_convert_raw_to_milliunit - Convert raw value to milli unit - * @desc: BCL device descriptor - * @raw_val: Raw ADC value from hardware - * @type: type of the channel, in or curr - * @field_width: bits size for data or threshold field - * - * Return: value in milli unit - */ -static unsigned int bcl_convert_raw_to_milliunit(const struct bcl_desc *desc, int raw_val, - enum bcl_channel_type type, u8 field_width) -{ - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; - - return div_s64((s64)raw_val * scaling_factor, 1000000); -} - -/** - * bcl_convert_milliunit_to_raw - Convert milli unit to raw value - * @desc: BCL device descriptor - * @ma_val: threshold value in milli unit - * @type: type of the channel, in or curr - * @field_width: bits size for data or threshold field - * - * Return: Raw ADC value for hardware - */ -static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_desc *desc, int mval, - enum bcl_channel_type type, u8 field_width) -{ - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; - - return div_s64((s64)mval * 1000000, scaling_factor); -} - -/** - * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index - * @desc: BCL device descriptor - * @val: in or curr value in milli unit - * @type: type of the channel, in or curr - * - * Converts a value in milli unit to an index for BCL that use indexed thresholds. - * - * Return: Voltage index value - */ -static unsigned int bcl_convert_milliunit_to_index(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) -{ - return div_s64((s64)val - desc->channel[type].base, desc->channel[type].step); -} - -/** - * bcl_convert_index_to_milliunit - Convert in or curr index to milli unit - * @desc: BCL device descriptor - * @val: index value - * @type: type of the channel, in or curr - * - * Converts an index value to milli unit for BCL that use indexed thresholds. - * - * Return: Voltage value in millivolts - */ -static unsigned int bcl_convert_index_to_milliunit(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) -{ - return desc->channel[type].base + val * desc->channel[type].step; -} - -static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) -{ - const struct bcl_desc *desc = bcl->desc; - u32 raw_val; - - int thresh = clamp_val(value, desc->channel[IN].base, desc->channel[IN].max); - - if (desc->channel[IN].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, IN, - desc->thresh_field_bits_size); - else - raw_val = bcl_convert_milliunit_to_index(desc, thresh, IN); - - return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); -} - -static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) -{ - const struct bcl_desc *desc = bcl->desc; - u32 raw_val; - - /* Clamp only to curr max */ - int thresh = clamp_val(value, value, desc->channel[CURR].max); - - if (desc->channel[CURR].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, CURR, - desc->thresh_field_bits_size); - else - raw_val = bcl_convert_milliunit_to_index(desc, thresh, CURR); - - return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); -} - -static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) -{ - int ret, thresh; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_IN_L0_THR + lvl, &raw_val); - if (ret) - return ret; - - if (desc->channel[IN].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, IN, - desc->thresh_field_bits_size); - else - thresh = bcl_convert_index_to_milliunit(desc, raw_val, IN); - - *out = thresh; - - return 0; -} - -static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) -{ - int ret, thresh; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_CURR_H0_THR + lvl, &raw_val); - if (ret) - return ret; - - if (desc->channel[CURR].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, CURR, - desc->thresh_field_bits_size); - else - thresh = bcl_convert_index_to_milliunit(desc, raw_val, CURR); - - *out = thresh; - - return 0; -} - -static int bcl_curr_input_read(struct bcl_device *bcl, long *out) -{ - int ret; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_CURR_INPUT, &raw_val); - if (ret) - return ret; - - /* - * The sensor sometime can read a value 0 if there are - * consecutive reads - */ - if (raw_val != 0) - bcl->last_curr_input = - bcl_convert_raw_to_milliunit(desc, raw_val, CURR, - desc->data_field_bits_size); - - *out = bcl->last_curr_input; - - return 0; -} - -static int bcl_in_input_read(struct bcl_device *bcl, long *out) -{ - int ret; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_IN_INPUT, &raw_val); - if (ret) - return ret; - - if (raw_val < GENMASK(desc->data_field_bits_size - 1, 0)) - bcl->last_in_input = - bcl_convert_raw_to_milliunit(desc, raw_val, IN, - desc->data_field_bits_size); - - *out = bcl->last_in_input; - - return 0; -} - -static int bcl_read_alarm_status(struct bcl_device *bcl, - enum bcl_limit_alarm lvl, long *status) -{ - int ret; - u32 raw_val = 0; - - ret = bcl_read_field_value(bcl, F_LVL0_ALARM + lvl, &raw_val); - if (ret) - return ret; - - *status = raw_val; - - return 0; -} - -static unsigned int bcl_get_version_major(const struct bcl_device *bcl) -{ - u32 raw_val = 0; - - bcl_read_field_value(bcl, F_V_MAJOR, &raw_val); - - return raw_val; -} - -static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) -{ - u32 raw_val = 0; - - bcl_read_field_value(bcl, F_V_MINOR, &raw_val); - - return raw_val; -} - -static void bcl_hwmon_notify_event(struct bcl_device *bcl, enum bcl_limit_alarm alarm) -{ - if (bcl->in_mon_enabled) - hwmon_notify_event(bcl->hwmon_dev, hwmon_in, - in_lvl_to_attr_map[alarm], 0); - if (bcl->curr_mon_enabled) - hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, - curr_lvl_to_attr_map[alarm], 0); -} - -static void bcl_alarm_enable_poll(struct work_struct *work) -{ - struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, - alarm_poll_work.work); - struct bcl_device *bcl = alarm->device; - long status; - - guard(mutex)(&bcl->lock); - - if (bcl_read_alarm_status(bcl, alarm->type, &status)) - goto re_schedule; - - if (!status & !alarm->irq_enabled) { - bcl_enable_irq(alarm); - bcl_hwmon_notify_event(bcl, alarm->type); - return; - } - -re_schedule: - schedule_delayed_work(&alarm->alarm_poll_work, - msecs_to_jiffies(BCL_ALARM_POLLING_MS)); -} - -static irqreturn_t bcl_handle_alarm(int irq, void *data) -{ - struct bcl_alarm_data *alarm = data; - struct bcl_device *bcl = alarm->device; - long status; - - guard(mutex)(&bcl->lock); - - if (bcl_read_alarm_status(bcl, alarm->type, &status) || !status) - return IRQ_HANDLED; - - if (!bcl->hwmon_dev) - return IRQ_HANDLED; - - bcl_hwmon_notify_event(bcl, alarm->type); - - bcl_disable_irq(alarm); - schedule_delayed_work(&alarm->alarm_poll_work, - msecs_to_jiffies(BCL_ALARM_POLLING_MS)); - - dev_dbg(bcl->dev, "Irq:%d triggered for bcl type:%d\n", - irq, alarm->type); - - return IRQ_HANDLED; -} - -static umode_t bcl_hwmon_is_visible(const void *data, - enum hwmon_sensor_types type, - u32 attr, int channel) -{ - const struct bcl_device *bcl = data; - - switch (type) { - case hwmon_in: - if (!bcl->in_mon_enabled) - return 0; - switch (attr) { - case hwmon_in_input: - return bcl->in_input_enabled ? 0444 : 0; - case hwmon_in_label: - case hwmon_in_min_alarm: - case hwmon_in_lcrit_alarm: - return 0444; - case hwmon_in_min: - case hwmon_in_lcrit: - return 0644; - default: - return 0; - } - case hwmon_curr: - if (!bcl->curr_mon_enabled) - return 0; - switch (attr) { - case hwmon_curr_input: - case hwmon_curr_label: - case hwmon_curr_max_alarm: - case hwmon_curr_crit_alarm: - return 0444; - case hwmon_curr_max: - case hwmon_curr_crit: - return 0644; - default: - return 0; - } - default: - return 0; - } -} - -static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long val) -{ - struct bcl_device *bcl = dev_get_drvdata(dev); - int ret = -EOPNOTSUPP; - - guard(mutex)(&bcl->lock); - - switch (type) { - case hwmon_in: - switch (attr) { - case hwmon_in_min: - case hwmon_in_lcrit: - ret = bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); - break; - default: - ret = -EOPNOTSUPP; - } - break; - case hwmon_curr: - switch (attr) { - case hwmon_curr_max: - case hwmon_curr_crit: - ret = bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); - break; - default: - ret = -EOPNOTSUPP; - } - break; - default: - break; - } - - return ret; -} - -static int bcl_hwmon_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long *value) -{ - struct bcl_device *bcl = dev_get_drvdata(dev); - int ret; - - guard(mutex)(&bcl->lock); - - switch (type) { - case hwmon_in: - switch (attr) { - case hwmon_in_input: - ret = bcl_in_input_read(bcl, value); - break; - case hwmon_in_min: - case hwmon_in_lcrit: - ret = bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); - break; - case hwmon_in_min_alarm: - case hwmon_in_lcrit_alarm: - ret = bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; - case hwmon_curr: - switch (attr) { - case hwmon_curr_input: - ret = bcl_curr_input_read(bcl, value); - break; - case hwmon_curr_max: - case hwmon_curr_crit: - ret = bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); - break; - case hwmon_curr_max_alarm: - case hwmon_curr_crit_alarm: - ret = bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; - default: - ret = -EOPNOTSUPP; - break; - } - - return ret; -} - -static int bcl_hwmon_read_string(struct device *dev, - enum hwmon_sensor_types type, - u32 attr, int channel, const char **str) -{ - switch (type) { - case hwmon_in: - if (attr != hwmon_in_label) - break; - *str = bcl_channel_label[IN]; - return 0; - case hwmon_curr: - if (attr != hwmon_curr_label) - break; - *str = bcl_channel_label[CURR]; - return 0; - default: - break; - } - - return -EOPNOTSUPP; -} - -static const struct hwmon_ops bcl_hwmon_ops = { - .is_visible = bcl_hwmon_is_visible, - .read = bcl_hwmon_read, - .read_string = bcl_hwmon_read_string, - .write = bcl_hwmon_write, -}; - -static const struct hwmon_channel_info *bcl_hwmon_info[] = { - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_MIN | - HWMON_I_LCRIT | HWMON_I_MIN_ALARM | - HWMON_I_LCRIT_ALARM), - HWMON_CHANNEL_INFO(curr, - HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | - HWMON_C_CRIT | HWMON_C_MAX_ALARM | - HWMON_C_CRIT_ALARM), - NULL, -}; - -static const struct hwmon_chip_info bcl_hwmon_chip_info = { - .ops = &bcl_hwmon_ops, - .info = bcl_hwmon_info, -}; - -static int bcl_curr_thresh_update(struct bcl_device *bcl) -{ - int ret, i; - - if (!bcl->curr_thresholds[0]) - return 0; - - for (i = 0; i < ALARM_MAX; i++) { - ret = bcl_curr_thresh_write(bcl, bcl->curr_thresholds[i], i); - if (ret < 0) - return ret; - } - - return 0; -} - -static void bcl_hw_channel_mon_init(struct bcl_device *bcl) -{ - bcl->in_mon_enabled = bcl_in_monitor_enabled(bcl); - bcl->in_input_enabled = bcl_in_input_enabled(bcl); - bcl->curr_mon_enabled = bcl_curr_monitor_enabled(bcl); -} - -static int bcl_alarm_irq_init(struct platform_device *pdev, - struct bcl_device *bcl) -{ - int ret = 0, irq_num = 0, i = 0; - struct bcl_alarm_data *alarm; - - for (i = LVL0; i < ALARM_MAX; i++) { - alarm = &bcl->bcl_alarms[i]; - alarm->type = i; - alarm->device = bcl; - - ret = devm_delayed_work_autocancel(bcl->dev, &alarm->alarm_poll_work, - bcl_alarm_enable_poll); - if (ret) - return ret; - - irq_num = platform_get_irq_byname(pdev, bcl_int_names[i]); - if (irq_num <= 0) - continue; - - ret = devm_request_threaded_irq(&pdev->dev, irq_num, NULL, - bcl_handle_alarm, IRQF_ONESHOT, - bcl_int_names[i], alarm); - if (ret) { - dev_err(&pdev->dev, "Error requesting irq(%s).err:%d\n", - bcl_int_names[i], ret); - return ret; - } - alarm->irq = irq_num; - enable_irq_wake(alarm->irq); - alarm->irq_enabled = true; - } - - return 0; -} - -static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, - const struct bcl_desc *data) -{ - int i; - struct reg_field fields[F_MAX_FIELDS]; - - BUILD_BUG_ON(ARRAY_SIZE(common_reg_fields) != COMMON_FIELD_MAX); - - for (i = 0; i < data->num_reg_fields; i++) { - if (i < COMMON_FIELD_MAX) - fields[i] = common_reg_fields[i]; - else - fields[i] = data->reg_fields[i]; - - /* Need to adjust BCL base from regmap dynamically */ - fields[i].reg += bcl->base; - } - - return devm_regmap_field_bulk_alloc(dev, bcl->regmap, bcl->fields, - fields, data->num_reg_fields); -} - -static int bcl_get_device_property_data(struct platform_device *pdev, - struct bcl_device *bcl) -{ - struct device *dev = &pdev->dev; - int ret; - u32 reg; - - ret = device_property_read_u32(dev, "reg", ®); - if (ret < 0) - return ret; - - bcl->base = reg; - - device_property_read_u32_array(dev, "overcurrent-thresholds-milliamp", - bcl->curr_thresholds, 2); - return 0; -} - -static int bcl_probe(struct platform_device *pdev) -{ - struct bcl_device *bcl; - int ret; - - bcl = devm_kzalloc(&pdev->dev, sizeof(*bcl), GFP_KERNEL); - if (!bcl) - return -ENOMEM; - - bcl->dev = &pdev->dev; - bcl->desc = device_get_match_data(&pdev->dev); - if (!bcl->desc) - return -EINVAL; - - ret = devm_mutex_init(bcl->dev, &bcl->lock); - if (ret) - return ret; - - bcl->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!bcl->regmap) { - dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); - return -EINVAL; - } - - ret = bcl_get_device_property_data(pdev, bcl); - if (ret < 0) - return ret; - - ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); - if (ret < 0) { - dev_err(&pdev->dev, "Unable to allocate regmap fields, err:%d\n", ret); - return ret; - } - - if (!bcl_hw_is_enabled(bcl)) - return -ENODEV; - - ret = bcl_curr_thresh_update(bcl); - if (ret < 0) - return ret; - - ret = bcl_alarm_irq_init(pdev, bcl); - if (ret < 0) - return ret; - - bcl_hw_channel_mon_init(bcl); - - dev_set_drvdata(&pdev->dev, bcl); - - bcl->hwmon_name = devm_hwmon_sanitize_name(&pdev->dev, - dev_name(bcl->dev)); - if (IS_ERR(bcl->hwmon_name)) { - dev_err(&pdev->dev, "Failed to sanitize hwmon name\n"); - return PTR_ERR(bcl->hwmon_name); - } - - bcl->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, - bcl->hwmon_name, - bcl, - &bcl_hwmon_chip_info, - NULL); - if (IS_ERR(bcl->hwmon_dev)) { - dev_err(&pdev->dev, "Failed to register hwmon device: %ld\n", - PTR_ERR(bcl->hwmon_dev)); - return PTR_ERR(bcl->hwmon_dev); - } - - dev_dbg(&pdev->dev, "BCL hwmon device with version: %u.%u registered\n", - bcl_get_version_major(bcl), bcl_get_version_minor(bcl)); - - return 0; -} - -static const struct of_device_id bcl_match[] = { - { - .compatible = "qcom,bcl-v1", - .data = &pm7250b_data, - }, { - .compatible = "qcom,bcl-v2", - .data = &pm8350_data, - }, { - .compatible = "qcom,bcl-v3-bmx", - .data = &pm8550b_data, - }, { - .compatible = "qcom,bcl-v3-wb", - .data = &pmw5100_data, - }, { - .compatible = "qcom,bcl-v3-core", - .data = &pm8550_data, - }, { - .compatible = "qcom,bcl-v4-bmx", - .data = &pmih010_data, - }, { - .compatible = "qcom,bcl-v4-wb", - .data = &pmw6100_data, - }, { - .compatible = "qcom,bcl-v4-core", - .data = &pmh010_data, - }, { - .compatible = "qcom,bcl-v4-pmv010", - .data = &pmv010_data, - }, - { } -}; -MODULE_DEVICE_TABLE(of, bcl_match); - -static struct platform_driver bcl_driver = { - .probe = bcl_probe, - .driver = { - .name = BCL_DRIVER_NAME, - .of_match_table = bcl_match, - }, -}; - -MODULE_AUTHOR("Manaf Meethalavalappu Pallikunhi "); -MODULE_DESCRIPTION("QCOM BCL HWMON driver"); -module_platform_driver(bcl_driver); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/qcom-bcl-hwmon.h b/drivers/hwmon/qcom-bcl-hwmon.h deleted file mode 100644 index 28a7154d9486a..0000000000000 --- a/drivers/hwmon/qcom-bcl-hwmon.h +++ /dev/null @@ -1,311 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. - */ - -#ifndef __QCOM_BCL_HWMON_H__ -#define __QCOM_BCL_HWMON_H__ - -#define BCL_DRIVER_NAME "qcom-bcl-hwmon" - -/* BCL common regmap offset */ -#define REVISION1 0x0 -#define REVISION2 0x1 -#define STATUS 0x8 -#define INT_RT_STS 0x10 -#define EN_CTL1 0x46 - -/* BCL GEN1 regmap offsets */ -#define MODE_CTL1 0x41 -#define VADC_L0_THR 0x48 -#define VCMP_L1_THR 0x49 -#define IADC_H0_THR 0x4b -#define IADC_H1_THR 0x4c -#define VADC_CONV_REQ 0x72 -#define IADC_CONV_REQ 0x82 -#define VADC_DATA1 0x76 -#define IADC_DATA1 0x86 - -/* BCL GEN3 regmap offsets */ -#define VCMP_CTL 0x44 -#define VCMP_L0_THR 0x47 -#define PARAM_1 0x0e -#define IADC_H1_THR_GEN3 0x4d - -#define BCL_IN_INC_MV 25 -#define BCL_ALARM_POLLING_MS 50 - -/** - * enum bcl_limit_alarm - BCL alarm threshold levels - * @LVL0: Level 0 alarm threshold (mapped to in_min_alarm or curr_max_alarm) - * @LVL1: Level 1 alarm threshold (mapped to in_lcrit_alarm or curr_crit_alarm) - * @ALARM_MAX: sentinel value - * - * Defines the three threshold levels for BCL monitoring. Each level corresponds - * to different severity of in or curr conditions. - */ -enum bcl_limit_alarm { - LVL0, - LVL1, - - ALARM_MAX, -}; - -/** - * enum bcl_channel_type - BCL supported sensor channel type - * @IN: in (voltage) channel - * @CURR: curr (current) channel - * @CHANNEL_MAX: sentinel value - * - * Defines the supported channel types for bcl. - */ -enum bcl_channel_type { - IN, - CURR, - - CHANNEL_MAX, -}; - -/** - * enum bcl_thresh_type - voltage or current threshold representation type - * @ADC: Raw ADC value representation - * @INDEX: Index-based voltage or current representation - * - * Specifies how voltage or current thresholds are stored and interpreted in - * registers. Some PMICs use raw ADC values while others use indexed values. - */ -enum bcl_thresh_type { - ADC, - INDEX, -}; - -/** - * enum bcl_fields - BCL register field identifiers - * @F_V_MAJOR: Major revision info field - * @F_V_MINOR: Minor revision info field - * @F_CTL_EN: Monitor enable control field - * @F_LVL0_ALARM: Level 0 alarm status field - * @F_LVL1_ALARM: Level 1 alarm status field - * @COMMON_FIELD_MAX: sentinel value for common fields - * @F_IN_MON_EN: voltage monitor enable control field - * @F_IN_L0_THR: voltage level 0 threshold field - * @F_IN_L1_THR: voltage level 1 threshold field - * @F_IN_INPUT_EN: voltage input enable control field - * @F_IN_INPUT: voltage input data field - * @F_CURR_MON_EN: current monitor enable control field - * @F_CURR_H0_THR: current level 0 threshold field - * @F_CURR_H1_THR: current level 1 threshold field - * @F_CURR_INPUT: current input data field - * @F_MAX_FIELDS: sentinel value - * - * Enumeration of all register fields used by the BCL driver for accessing - * registers through regmap fields. - */ -enum bcl_fields { - F_V_MAJOR, - F_V_MINOR, - - F_CTL_EN, - - /* common alarm for in and curr channel */ - F_LVL0_ALARM, - F_LVL1_ALARM, - - COMMON_FIELD_MAX, - - F_IN_MON_EN = COMMON_FIELD_MAX, - F_IN_L0_THR, - F_IN_L1_THR, - - F_IN_INPUT_EN, - F_IN_INPUT, - - F_CURR_MON_EN, - F_CURR_H0_THR, - F_CURR_H1_THR, - - F_CURR_INPUT, - - F_MAX_FIELDS -}; - -#define ADD_BCL_HWMON_ALARM_MAPS(_type, lvl0_attr, lvl1_attr) \ - \ -static const u8 _type##_attr_to_lvl_map[] = { \ - [hwmon_##_type##_##lvl0_attr] = LVL0, \ - [hwmon_##_type##_##lvl1_attr] = LVL1, \ - [hwmon_##_type##_##lvl0_attr##_alarm] = LVL0, \ - [hwmon_##_type##_##lvl1_attr##_alarm] = LVL1, \ -}; \ - \ -static const u8 _type##_lvl_to_attr_map[ALARM_MAX] = { \ - [LVL0] = hwmon_##_type##_##lvl0_attr##_alarm, \ - [LVL1] = hwmon_##_type##_##lvl1_attr##_alarm, \ -} - -/** - * struct bcl_channel_cfg - BCL channel related configuration - * @default_scale_nu: Default scaling factor in nano unit - * @base: Base threshold value in milli unit - * @max: Maximum threshold value in milli unit - * @step: step increment value between two indexed threshold value - * @thresh_type: Array specifying threshold representation type for each alarm level - * - * Contains hardware-specific configuration and scaling parameters for different - * channel(voltage and current).. - */ - -struct bcl_channel_cfg { - u32 default_scale_nu; - u32 base; - u32 max; - u32 step; - u8 thresh_type[ALARM_MAX]; -}; - -/** - * struct bcl_desc - BCL device descriptor - * @reg_fields: Array of register field definitions for this device variant - * @channel: Each channel specific(voltage or current) configuration - * @num_reg_fields: Number of register field definitions for this device variant - * @data_field_bits_size: data read register bit size - * @thresh_field_bits_size: lsb bit size those are not included in threshold register - * - * Contains hardware-specific configuration and scaling parameters for different - * BCL variants. Each PMIC model may have different register layouts and - * conversion factors. - */ - -struct bcl_desc { - const struct reg_field *reg_fields; - struct bcl_channel_cfg channel[CHANNEL_MAX]; - u8 num_reg_fields; - u8 data_field_bits_size; - u8 thresh_field_bits_size; -}; - -struct bcl_device; - -/** - * struct bcl_alarm_data - BCL alarm interrupt data - * @irq: IRQ number assigned to this alarm - * @irq_enabled: Flag indicating if IRQ is enabled - * @type: Alarm level type (LVL0, or LVL1) - * @device: Pointer to parent BCL device structure - * @alarm_poll_work: delayed_work to poll alarm status - * - * Stores interrupt-related information for each alarm threshold level. - * Used by the IRQ handler to identify which alarm triggered. - */ -struct bcl_alarm_data { - int irq; - bool irq_enabled; - enum bcl_limit_alarm type; - struct bcl_device *device; - struct delayed_work alarm_poll_work; -}; - -/** - * struct bcl_device - Main BCL device structure - * @dev: Pointer to device structure - * @regmap: Regmap for accessing PMIC registers - * @fields: Array of regmap fields for register access - * @bcl_alarms: Array of alarm data structures for each threshold level - * @lock: Mutex for protecting concurrent hardware access - * @in_mon_enabled: Flag indicating if voltage monitoring is enabled - * @curr_mon_enabled: Flag indicating if current monitoring is enabled - * @curr_thresholds: Current threshold values in milliamps from dt-binding(LVL0 and LVL1) - * @base: the BCL regbase offset from regmap - * @in_input_enabled: Flag indicating if voltage input reading is enabled - * @last_in_input: Last valid voltage input reading in millivolts - * @last_curr_input: Last valid current input reading in milliamps - * @desc: Pointer to device descriptor with hardware-specific parameters - * @hwmon_dev: Pointer to registered hwmon device - * @hwmon_name: Sanitized name for hwmon device - * - * Main driver structure containing all state and configuration for a BCL - * monitoring instance. Manages voltage and current monitoring, thresholds, - * and alarm handling. - */ -struct bcl_device { - struct device *dev; - struct regmap *regmap; - u16 base; - struct regmap_field *fields[F_MAX_FIELDS]; - struct bcl_alarm_data bcl_alarms[ALARM_MAX]; - struct mutex lock; - u32 curr_thresholds[ALARM_MAX]; - u32 last_in_input; - u32 last_curr_input; - bool in_mon_enabled; - bool curr_mon_enabled; - bool in_input_enabled; - const struct bcl_desc *desc; - struct device *hwmon_dev; - char *hwmon_name; -}; - -/** - * bcl_read_field_value - Read alarm status for a given level - * @bcl: BCL device structure - * @id: Index in bcl->fields[] - * @val: Pointer to store val - * - * Return: 0 on success or regmap error code - */ -static inline int bcl_read_field_value(const struct bcl_device *bcl, enum bcl_fields id, u32 *val) -{ - return regmap_field_read(bcl->fields[id], val); -} - -/** - * bcl_field_enabled - Generic helper to check if a regmap field is enabled - * @bcl: BCL device structure - * @field: Index in bcl->fields[] - * - * Return: true if field is non-zero, false otherwise - */ -static inline bool bcl_field_enabled(const struct bcl_device *bcl, enum bcl_fields id) -{ - int ret; - u32 val = 0; - - ret = regmap_field_read(bcl->fields[id], &val); - if (ret) - return false; - - return !!val; -} - -#define bcl_in_input_enabled(bcl) bcl_field_enabled(bcl, F_IN_INPUT_EN) -#define bcl_curr_monitor_enabled(bcl) bcl_field_enabled(bcl, F_CURR_MON_EN) -#define bcl_in_monitor_enabled(bcl) bcl_field_enabled(bcl, F_IN_MON_EN) -#define bcl_hw_is_enabled(bcl) bcl_field_enabled(bcl, F_CTL_EN) - -/** - * bcl_enable_irq - Generic helper to enable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_enable_irq(struct bcl_alarm_data *alarm) -{ - if (alarm->irq_enabled) - return; - alarm->irq_enabled = true; - enable_irq(alarm->irq); - enable_irq_wake(alarm->irq); -} - -/** - * bcl_disable_irq - Generic helper to disable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_disable_irq(struct bcl_alarm_data *alarm) -{ - if (!alarm->irq_enabled) - return; - alarm->irq_enabled = false; - disable_irq_nosync(alarm->irq); - disable_irq_wake(alarm->irq); -} - -#endif /* __QCOM_BCL_HWMON_H__ */ From 582f585a2415c0b5db6e55e16cf6e20395b5bb38 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:06 +0530 Subject: [PATCH 185/243] FROMLIST: dt-bindings: hwmon: Describe Qualcomm BCL hardware Qualcomm SPMI PMIC BCL (Battery Current Limiting) is a safety feature that monitors battery or system voltage and current to alert system for overcurrent or undervoltage conditions. It provides: - Real-time voltage and current monitoring - Configurable thresholds per channel - Hardware interrupts when thresholds are violated Add a DeviceTree binding to describe the BCL on Qualcomm's PMICs. Add sensor type to SPMI device list for BCL device. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-1-febe2805e17b@oss.qualcomm.com --- .../bindings/hwmon/qcom,pm7250b-bcl.yaml | 64 +++++++++++++++++++ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 ++ MAINTAINERS | 7 ++ 3 files changed, 75 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml diff --git a/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml new file mode 100644 index 0000000000000..3c069e94a34e8 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/qcom,pm7250b-bcl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor + +maintainers: + - Manaf Meethalavalappu Pallikunhi + +description: + SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and + alarm functionality for battery overcurrent and battery or system under + voltage conditions. It monitors battery voltage and current, and + can trigger interrupts when configurable thresholds are exceeded. + +properties: + compatible: + enum: + - qcom,pm7250b-bcl + - qcom,pm8350c-bcl + - qcom,pm8550-bcl + - qcom,pmh0101-bcl + - qcom,pmih0108-bcl + - qcom,smb2360-bcl + - qcom,smb2370-bcl + + reg: + maxItems: 1 + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: max-min + - const: critical + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1d00 { + compatible = "qcom,pm7250b-bcl"; + reg = <0x1d00>; + interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e57..f51c398d5daa7 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -189,6 +189,10 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# + "^sensor@[0-9a-f]+$": + type: object + $ref: /schemas/hwmon/qcom,pm7250b-bcl.yaml# + "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# diff --git a/MAINTAINERS b/MAINTAINERS index c20b90150b7cd..654a480d10ef2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22100,6 +22100,13 @@ S: Maintained F: Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml F: drivers/net/wwan/qcom_bam_dmux.c +QUALCOMM BCL HARDWARE MONITOR DRIVER +M: Manaf Meethalavalappu Pallikunhi +L: linux-hwmon@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml + QUALCOMM BLUETOOTH DRIVER M: Bartosz Golaszewski L: linux-arm-msm@vger.kernel.org From 9fdfd8098c17c12e50322a03da455c354dbe9424 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:07 +0530 Subject: [PATCH 186/243] FROMLIST: hwmon: Add Qualcomm PMIC BCL driver Add driver for Qualcomm SPMI PMIC Battery Current Limiting (BCL) hardware monitor. The driver exposes battery voltage and current monitoring through hwmon interface. The BCL driver provides - Real-time voltage and current readings - Configurable threshold-based alarms - Interrupt-driven notifications when thresholds are exceeded - Automatic threshold management with polling-based recovery - Hardware-specific scaling factors and threshold representations Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-2-febe2805e17b@oss.qualcomm.com --- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/qcom-bcl-hwmon.c | 1443 ++++++++++++++++++++++++++++++++ 4 files changed, 1456 insertions(+) create mode 100644 drivers/hwmon/qcom-bcl-hwmon.c diff --git a/MAINTAINERS b/MAINTAINERS index 654a480d10ef2..fa52bbf3c0f57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22106,6 +22106,7 @@ L: linux-hwmon@vger.kernel.org L: linux-arm-msm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml +F: drivers/hwmon/qcom-bcl-hwmon.c QUALCOMM BLUETOOTH DRIVER M: Bartosz Golaszewski diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5c2d3ff5fce82..e7f6018a9e7da 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1939,6 +1939,17 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. +config SENSORS_QCOM_SPMI_BCL + tristate "Qualcomm SPMI BCL hardware monitoring" + depends on SPMI + select REGMAP_SPMI + help + Say yes here to enable support for Qualcomm battery over current + and under voltage alarms monitor. + + This driver can also be built as a module. If so, the module + will be called qcom-bcl-hwmon. + config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63effc0ab8d11..979caf289ba79 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -202,6 +202,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o +obj-$(CONFIG_SENSORS_QCOM_SPMI_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c new file mode 100644 index 0000000000000..33bbdd8196d16 --- /dev/null +++ b/drivers/hwmon/qcom-bcl-hwmon.c @@ -0,0 +1,1443 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm pmic BCL driver for battery overcurrent and + * battery or system under voltage monitor + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BCL common regmap offset */ +#define REVISION1 0x0 +#define REVISION2 0x1 +#define STATUS 0x8 +#define INT_RT_STS 0x10 +#define EN_CTL1 0x46 + +/* BCL GEN1 regmap offsets */ +#define MODE_CTL1 0x41 +#define VADC_L0_THR 0x48 +#define VCMP_L1_THR 0x49 +#define IADC_H0_THR 0x4b +#define IADC_H1_THR 0x4c +#define VADC_CONV_REQ 0x72 +#define IADC_CONV_REQ 0x82 +#define VADC_DATA1 0x76 +#define IADC_DATA1 0x86 + +/* BCL GEN3 regmap offsets */ +#define VCMP_CTL 0x44 +#define VCMP_L0_THR 0x47 +#define PARAM_1 0x0e +#define IADC_H1_THR_GEN3 0x4d + +#define BCL_IN_INC_MV 25 +#define BCL_ALARM_POLLING_MS 50 + +/** + * enum bcl_limit_alarm - BCL alarm threshold levels + * @BCL_LIMIT_ALARM_LVL0: Level 0 alarm threshold + * @BCL_LIMIT_ALARM_LVL1: Level 1 alarm threshold + * @BCL_LIMIT_ALARM_MAX: sentinel value + * + * Defines the two threshold levels for BCL monitoring. Each level corresponds + * to different severity of in or curr conditions. + */ +enum bcl_limit_alarm { + BCL_LIMIT_ALARM_LVL0, + BCL_LIMIT_ALARM_LVL1, + + BCL_LIMIT_ALARM_MAX, +}; + +/** + * enum bcl_channel - BCL supported sensor channel type + * @CHANNEL_IN: in (voltage) channel + * @CHANNEL_CURR: curr (current) channel + * @CHANNEL_MAX: sentinel value + * + * Defines the supported channel types for bcl. + */ +enum bcl_channel { + CHANNEL_IN, + CHANNEL_CURR, + + CHANNEL_MAX, +}; + +/** + * enum bcl_thresh_type - voltage or current threshold representation type + * @THRESH_TYPE_ADC: Raw ADC value representation + * @THRESH_TYPE_INDEX: Index-based voltage or current representation + * + * Specifies how voltage or current thresholds are stored and interpreted in + * registers. Some PMICs use raw ADC values while others use indexed values. + */ +enum bcl_thresh_type { + THRESH_TYPE_ADC, + THRESH_TYPE_INDEX, +}; + +/** + * enum bcl_battery_config - Battery configuration types + * @BCL_BATT_1S: Single cell battery + * @BCL_BATT_2S: Two cells in series + * @BCL_BATT_3S: Three cells in series + * @BCL_BATT_UNKNOWN: Unknown or not applicable + */ +enum bcl_battery_config { + BCL_BATT_1S, + BCL_BATT_2S, + BCL_BATT_3S, + BCL_BATT_UNKNOWN, +}; + +/** + * enum bcl_fields - BCL register field identifiers + * @F_V_MAJOR: Major revision info field + * @F_V_MINOR: Minor revision info field + * @F_CTL_EN: Monitor enable control field + * @F_LVL0_ALARM: Level 0 alarm status field + * @F_LVL1_ALARM: Level 1 alarm status field + * @F_IN_MON_EN: voltage monitor enable control field + * @F_IN_L0_THR: voltage level 0 threshold field + * @F_IN_L1_THR: voltage level 1 threshold field + * @F_IN_INPUT_EN: voltage input enable control field + * @F_IN_INPUT: voltage input data field (LSB for 16-bit data) + * @F_IN_INPUT1: voltage input data MSB for 16-bit voltage data + * @F_CURR_MON_EN: current monitor enable control field + * @F_CURR_H0_THR: current level 0 threshold field + * @F_CURR_H1_THR: current level 1 threshold field + * @F_CURR_INPUT: current input data field (LSB for 16-bit data) + * @F_CURR_INPUT1: current input data MSB for 16-bit current data + * @F_MAX_FIELDS: sentinel value + * + * Enumeration of all register fields used by the BCL driver for accessing + * registers through regmap fields. + */ +enum bcl_fields { + /* Common fields - present in all BCL variants */ + F_V_MAJOR, + F_V_MINOR, + F_CTL_EN, + F_LVL0_ALARM, + F_LVL1_ALARM, + + /* Voltage monitoring fields */ + F_IN_MON_EN, + F_IN_L0_THR, + F_IN_L1_THR, + F_IN_INPUT_EN, + F_IN_INPUT, + F_IN_INPUT1, /* MSB for 16-bit voltage data */ + + /* Current monitoring fields */ + F_CURR_MON_EN, + F_CURR_H0_THR, + F_CURR_H1_THR, + F_CURR_INPUT, + F_CURR_INPUT1, /* MSB for 16-bit current data */ + + F_MAX_FIELDS +}; + +/** + * struct bcl_channel_cfg - BCL channel related configuration + * @default_scale_nu: Default scaling factor in nano unit + * @base: Base threshold value in milli unit + * @max: Maximum threshold value in milli unit + * @step: step increment value between two indexed threshold value + * @thresh_type: Array specifying threshold representation type for each alarm level + * + * Contains hardware-specific configuration and scaling parameters for different + * channel(voltage and current).. + */ +struct bcl_channel_cfg { + u32 default_scale_nu; + u32 base; + u32 max; + u32 step; + u8 thresh_type[BCL_LIMIT_ALARM_MAX]; +}; + +/** + * struct bcl_desc - BCL device descriptor + * @reg_fields: Array of register field definitions for this device variant + * @channel_cfg: Array of channel configurations indexed by battery config + * PMICs without battery detection: only [BCL_BATT_1S] is defined + * PMICs with battery detection: [BCL_BATT_2S], [BCL_BATT_3S] + * @battery_config_field: Register field for battery configuration detection + * NOTE: This is an ABSOLUTE address, not relative to BCL base + * Set to REG_FIELD(0, 0, 0) if not used + * @num_reg_fields: Number of register field definitions for this device variant + * @data_field_bits_size: data read register bit size + * @thresh_field_bits_size: lsb bit size those are not included in threshold register + * + * Contains hardware-specific configuration and scaling parameters for different + * BCL variants. Each PMIC model may have different register layouts and + * conversion factors. + */ +struct bcl_desc { + const struct reg_field *reg_fields; + struct bcl_channel_cfg channel_cfg[BCL_BATT_3S + 1][CHANNEL_MAX]; + const struct reg_field battery_config_field; + u8 num_reg_fields; + u8 data_field_bits_size; + u8 thresh_field_bits_size; +}; + +/** + * struct bcl_alarm_data - BCL alarm interrupt data + * @irq: IRQ number assigned to this alarm + * @irq_enabled: Flag indicating if IRQ is enabled + * @irq_wake_enabled: Flag indicating if IRQ wake is enabled + * @shutting_down: Flag preventing work from re-enabling IRQ during teardown + * @type: Alarm level type (LVL0, or LVL1) + * @device: Pointer to parent BCL device structure + * @a_lock: Mutex for protecting alarm state + * @alarm_poll_work: delayed_work to poll alarm status + * + * Stores interrupt-related information for each alarm threshold level. + * Used by the IRQ handler to identify which alarm triggered. + */ +struct bcl_alarm_data { + int irq; + bool irq_enabled; + bool irq_wake_enabled; + bool shutting_down; + enum bcl_limit_alarm type; + void *device; + /* Protects alarm IRQ enable/disable state */ + struct mutex a_lock; + struct delayed_work alarm_poll_work; +}; + +/** + * struct bcl_device - Main BCL device structure + * @dev: Pointer to device structure + * @regmap: Regmap for accessing PMIC registers + * @fields: Array of regmap fields for register access + * @bcl_alarms: Array of alarm data structures for each threshold level + * @lock: Mutex for protecting concurrent hardware access + * @base: the BCL regbase offset from regmap + * @last_in_input: Last valid voltage input reading in millivolts + * @last_curr_input: Last valid current input reading in milliamps + * @last_in_updated: Timestamp of last voltage input update + * @last_curr_updated: Timestamp of last current input update + * @desc: Pointer to device descriptor with hardware-specific parameters + * @hwmon_dev: Pointer to registered hwmon device + * @hwmon_info: Dynamically built hwmon channel info array + * @hwmon_chip_info: Dynamically built hwmon chip info structure + * @hwmon_name: Sanitized name for hwmon device + * @batt_config: Detected battery configuration (only for SMB2360/2370) + * @batt_config_regfield: Regmap field for battery configuration register + * @in_attrs: Voltage channel attributes mask + * @curr_attrs: Current channel attributes mask + * + * Main driver structure containing all state and configuration for a BCL + * monitoring instance. Manages voltage and current monitoring, thresholds, + * and alarm handling. + */ +struct bcl_device { + struct device *dev; + struct regmap *regmap; + u16 base; + struct regmap_field *fields[F_MAX_FIELDS]; + struct bcl_alarm_data bcl_alarms[BCL_LIMIT_ALARM_MAX]; + /* Protects hardware register access and device state */ + struct mutex lock; + u32 last_in_input; + s32 last_curr_input; + unsigned long last_in_updated; + unsigned long last_curr_updated; + const struct bcl_desc *desc; + struct device *hwmon_dev; + const struct hwmon_channel_info **hwmon_info; + struct hwmon_chip_info hwmon_chip_info; + char *hwmon_name; + enum bcl_battery_config batt_config; + struct regmap_field *batt_config_regfield; + u32 in_attrs; + u32 curr_attrs; +}; + +static const u8 in_attr_to_lvl_map[] = { + [hwmon_in_min] = BCL_LIMIT_ALARM_LVL0, + [hwmon_in_lcrit] = BCL_LIMIT_ALARM_LVL1, + [hwmon_in_min_alarm] = BCL_LIMIT_ALARM_LVL0, + [hwmon_in_lcrit_alarm] = BCL_LIMIT_ALARM_LVL1, +}; + +static const u8 in_lvl_to_attr_map[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = hwmon_in_min_alarm, + [BCL_LIMIT_ALARM_LVL1] = hwmon_in_lcrit_alarm, +}; + +static const u8 curr_attr_to_lvl_map[] = { + [hwmon_curr_max] = BCL_LIMIT_ALARM_LVL0, + [hwmon_curr_crit] = BCL_LIMIT_ALARM_LVL1, + [hwmon_curr_max_alarm] = BCL_LIMIT_ALARM_LVL0, + [hwmon_curr_crit_alarm] = BCL_LIMIT_ALARM_LVL1, +}; + +static const u8 curr_lvl_to_attr_map[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = hwmon_curr_max_alarm, + [BCL_LIMIT_ALARM_LVL1] = hwmon_curr_crit_alarm, +}; + +/* Interrupt names for each alarm level */ +static const char * const bcl_int_names[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = "max-min", + [BCL_LIMIT_ALARM_LVL1] = "critical", +}; + +static const struct reg_field bcl_pm7250b_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_pm8350c_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 1), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_pm8550_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 5), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +static const struct reg_field bcl_pmh0101_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +static const struct reg_field bcl_pmih0108_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), + [F_CURR_INPUT1] = REG_FIELD(IADC_DATA1 + 1, 0, 7), +}; + +static const struct reg_field bcl_smb2360_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_smb2370_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), + [F_CURR_INPUT1] = REG_FIELD(IADC_DATA1 + 1, 0, 7), +}; + +static const struct bcl_desc pm7250b_data = { + .reg_fields = bcl_pm7250b_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 7, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pm8350c_data = { + .reg_fields = bcl_pm8350c_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pm8550_data = { + .reg_fields = bcl_pm8550_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .data_field_bits_size = 0, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, + }, +}; + +static const struct bcl_desc pmih0108_data = { + .reg_fields = bcl_pmih0108_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 610370, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pmh0101_data = { + .reg_fields = bcl_pmh0101_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 1500, + .max = 4000, + .step = 25, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, + }, +}; + +/* Register 0x2a50 mapping: 0 -> 2S, 1 -> 3S */ +static const struct bcl_desc smb2360_data = { + .reg_fields = bcl_smb2360_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0x2a50, 0, 1), + .channel_cfg[BCL_BATT_2S][CHANNEL_IN] = { + .base = 4500, + .max = 8400, + .step = 50, + .default_scale_nu = 432918, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_2S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 540679, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_IN] = { + .base = 6750, + .max = 12600, + .step = 75, + .default_scale_nu = 648790, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 540679, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc smb2370_data = { + .reg_fields = bcl_smb2370_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0x2a50, 0, 1), + .channel_cfg[BCL_BATT_2S][CHANNEL_IN] = { + .base = 4500, + .max = 8400, + .step = 50, + .default_scale_nu = 432918, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_2S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 1441603, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_IN] = { + .base = 6750, + .max = 12600, + .step = 75, + .default_scale_nu = 648790, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 1441603, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +/** + * bcl_convert_raw_to_milliunit - Convert raw value to milli unit + * @bcl: BCL device structure + * @raw_val: Raw ADC value from hardware (signed for current, unsigned for voltage) + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: value in milli unit + */ +static int bcl_convert_raw_to_milliunit(const struct bcl_device *bcl, + s32 raw_val, + enum bcl_channel type, + u8 field_width) +{ + const struct bcl_desc *desc = bcl->desc; + u32 def_scale = desc->channel_cfg[bcl->batt_config][type].default_scale_nu; + u32 scaling_factor = (field_width > 8) ? def_scale : (def_scale << field_width); + + return DIV_ROUND_CLOSEST((s64)raw_val * scaling_factor, 1000000); +} + +/** + * bcl_convert_milliunit_to_raw - Convert milli unit to raw value + * @bcl: BCL device structure + * @mval: threshold value in milli unit + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: Raw ADC value for hardware + */ +static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_device *bcl, + int mval, + enum bcl_channel type, + u8 field_width) +{ + const struct bcl_desc *desc = bcl->desc; + u32 def_scale = desc->channel_cfg[bcl->batt_config][type].default_scale_nu; + u32 scaling_factor = (field_width > 8) ? def_scale : (def_scale << field_width); + + return DIV_ROUND_CLOSEST_ULL((u64)mval * 1000000, scaling_factor); +} + +/** + * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index + * @bcl: BCL device structure + * @val: in or curr value in milli unit + * @type: type of the channel, in or curr + * + * Converts a value in milli unit to an index for BCL that use indexed thresholds. + * + * Return: Index value + */ +static unsigned int bcl_convert_milliunit_to_index(const struct bcl_device *bcl, + int val, + enum bcl_channel type) +{ + const struct bcl_desc *desc = bcl->desc; + const struct bcl_channel_cfg *cfg = &desc->channel_cfg[bcl->batt_config][type]; + u64 diff = (u64)val - cfg->base; + + return DIV_ROUND_CLOSEST_ULL(diff, cfg->step); +} + +/** + * bcl_convert_index_to_milliunit - Convert in or curr index to milli unit + * @bcl: BCL device structure + * @val: index value + * @type: type of the channel, in or curr + * + * Converts an index value to milli unit for BCL that use indexed thresholds. + * + * Return: Value in millivolts or milliamps + */ +static unsigned int bcl_convert_index_to_milliunit(const struct bcl_device *bcl, + int val, + enum bcl_channel type) +{ + const struct bcl_desc *desc = bcl->desc; + const struct bcl_channel_cfg *cfg = &desc->channel_cfg[bcl->batt_config][type]; + + return cfg->base + val * cfg->step; +} + +static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + int thresh = clamp_val(value, desc->channel_cfg[bcl->batt_config][CHANNEL_IN].base, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].max); + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_IN].thresh_type[lvl] == THRESH_TYPE_ADC) + raw_val = bcl_convert_milliunit_to_raw(bcl, thresh, CHANNEL_IN, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_IN); + + return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); +} + +static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + int thresh = clamp_val(value, 0, desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].max); + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].thresh_type[lvl] == THRESH_TYPE_ADC) + raw_val = bcl_convert_milliunit_to_raw(bcl, thresh, CHANNEL_CURR, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_CURR); + + return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); +} + +static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = regmap_field_read(bcl->fields[F_IN_L0_THR + lvl], &raw_val); + if (ret) + return ret; + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_IN].thresh_type[lvl] == THRESH_TYPE_ADC) + thresh = bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_IN, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_IN); + + *out = thresh; + + return 0; +} + +static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = regmap_field_read(bcl->fields[F_CURR_H0_THR + lvl], &raw_val); + if (ret) + return ret; + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].thresh_type[lvl] == THRESH_TYPE_ADC) + thresh = bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_CURR, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_CURR); + + *out = thresh; + + return 0; +} + +static int bcl_curr_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0, msb = 0; + s32 signed_val; + const struct bcl_desc *desc = bcl->desc; + + /* Return cached value if read too soon after last update */ + if (time_before(jiffies, bcl->last_curr_updated + HZ)) { + *out = bcl->last_curr_input; + return 0; + } + + ret = regmap_field_read(bcl->fields[F_CURR_INPUT], &raw_val); + if (ret) + return ret; + + /* For 16-bit data, read MSB and combine with LSB */ + if (desc->data_field_bits_size == 16) { + ret = regmap_field_read(bcl->fields[F_CURR_INPUT1], &msb); + if (ret) + return ret; + raw_val |= FIELD_PREP(GENMASK(15, 8), msb); + } + + /* + * Current ADC reading is in 2's complement form. + * Sign extend the value based on data field bit size. + */ + signed_val = sign_extend32(raw_val, desc->data_field_bits_size - 1); + + bcl->last_curr_input = + bcl_convert_raw_to_milliunit(bcl, signed_val, CHANNEL_CURR, + desc->data_field_bits_size); + bcl->last_curr_updated = jiffies; + + *out = bcl->last_curr_input; + + return 0; +} + +static int bcl_in_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0, msb = 0; + const struct bcl_desc *desc = bcl->desc; + + /* Return cached value if read too soon after last update */ + if (time_before(jiffies, bcl->last_in_updated + HZ)) { + *out = bcl->last_in_input; + return 0; + } + + ret = regmap_field_read(bcl->fields[F_IN_INPUT], &raw_val); + if (ret) + return ret; + + /* For 16-bit data, read MSB and combine with LSB */ + if (desc->data_field_bits_size == 16) { + ret = regmap_field_read(bcl->fields[F_IN_INPUT1], &msb); + if (ret) + return ret; + raw_val |= FIELD_PREP(GENMASK(15, 8), msb); + } + + bcl->last_in_input = + bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_IN, + desc->data_field_bits_size); + bcl->last_in_updated = jiffies; + + *out = bcl->last_in_input; + + return 0; +} + +static int bcl_read_alarm_status(struct bcl_device *bcl, + enum bcl_limit_alarm lvl, long *status) +{ + int ret; + u32 raw_val = 0; + + ret = regmap_field_read(bcl->fields[F_LVL0_ALARM + lvl], &raw_val); + if (ret) + return ret; + + *status = raw_val; + + return 0; +} + +static unsigned int bcl_get_version_major(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + regmap_field_read(bcl->fields[F_V_MAJOR], &raw_val); + + return raw_val; +} + +static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + regmap_field_read(bcl->fields[F_V_MINOR], &raw_val); + + return raw_val; +} + +static void bcl_hwmon_notify_event(struct bcl_device *bcl, enum bcl_limit_alarm alarm) +{ + if (bcl->in_attrs) + hwmon_notify_event(bcl->hwmon_dev, hwmon_in, + in_lvl_to_attr_map[alarm], 0); + + if (bcl->curr_attrs) + hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, + curr_lvl_to_attr_map[alarm], 0); +} + +static void bcl_alarm_enable_poll(struct work_struct *work) +{ + struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, + alarm_poll_work.work); + struct bcl_device *bcl = (struct bcl_device *)alarm->device; + long status; + int ret; + + scoped_guard(mutex, &bcl->lock) + ret = bcl_read_alarm_status(bcl, alarm->type, &status); + + /* If read failed or shutting down, reschedule */ + if (ret || READ_ONCE(alarm->shutting_down)) { + if (!READ_ONCE(alarm->shutting_down)) + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + return; + } + + /* Check if alarm cleared and IRQ needs re-enabling */ + scoped_guard(mutex, &alarm->a_lock) { + if (!status && !alarm->irq_enabled) { + alarm->irq_enabled = true; + enable_irq(alarm->irq); + + if (!alarm->irq_wake_enabled && !enable_irq_wake(alarm->irq)) + alarm->irq_wake_enabled = true; + + bcl_hwmon_notify_event(bcl, alarm->type); + return; + } + } + + /* Alarm still active, reschedule polling */ + if (!READ_ONCE(alarm->shutting_down)) + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); +} + +static irqreturn_t bcl_handle_alarm(int irq, void *data) +{ + struct bcl_alarm_data *alarm = data; + struct bcl_device *bcl = (struct bcl_device *)alarm->device; + long status; + + if (READ_ONCE(alarm->shutting_down)) + return IRQ_HANDLED; + + guard(mutex)(&bcl->lock); + + if (bcl_read_alarm_status(bcl, alarm->type, &status) || !status) + return IRQ_HANDLED; + + bcl_hwmon_notify_event(bcl, alarm->type); + + guard(mutex)(&alarm->a_lock); + if (alarm->shutting_down) + return IRQ_HANDLED; + + if (alarm->irq_enabled) { + alarm->irq_enabled = false; + disable_irq_nosync(alarm->irq); + } + + if (alarm->irq_wake_enabled) { + disable_irq_wake(alarm->irq); + alarm->irq_wake_enabled = false; + } + + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + + return IRQ_HANDLED; +} + +static umode_t bcl_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + return 0444; + case hwmon_in_min: + case hwmon_in_lcrit: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_label: + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + return 0444; + case hwmon_curr_max: + case hwmon_curr_crit: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + + guard(mutex)(&bcl->lock); + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_min: + case hwmon_in_lcrit: + return bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); + default: + return -EOPNOTSUPP; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_max: + case hwmon_curr_crit: + return bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int bcl_in_read(struct bcl_device *bcl, u32 attr, long *value) +{ + guard(mutex)(&bcl->lock); + + switch (attr) { + case hwmon_in_input: + return bcl_in_input_read(bcl, value); + case hwmon_in_min: + case hwmon_in_lcrit: + return bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + return bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_curr_read(struct bcl_device *bcl, u32 attr, long *value) +{ + guard(mutex)(&bcl->lock); + + switch (attr) { + case hwmon_curr_input: + return bcl_curr_input_read(bcl, value); + case hwmon_curr_max: + case hwmon_curr_crit: + return bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + return bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + return bcl_in_read(bcl, attr, value); + case hwmon_curr: + return bcl_curr_read(bcl, attr, value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type == hwmon_in && attr == hwmon_in_label) + *str = "Voltage"; + else if (type == hwmon_curr && attr == hwmon_curr_label) + *str = "Current"; + else + *str = NULL; + + return *str ? 0 : -EOPNOTSUPP; +} + +static const struct hwmon_ops bcl_hwmon_ops = { + .is_visible = bcl_hwmon_is_visible, + .read = bcl_hwmon_read, + .read_string = bcl_hwmon_read_string, + .write = bcl_hwmon_write, +}; + +static int bcl_detect_battery_config(struct bcl_device *bcl) +{ + u32 reg_val = 0; + int ret; + + ret = regmap_field_read(bcl->batt_config_regfield, ®_val); + if (ret) { + dev_err(bcl->dev, "Failed to read battery config register: %d\n", ret); + return ret; + } + + /* + * Map register value to battery configuration. + * As per hardware documentation: + * 0 -> 2S, 1 -> 3S, 2 -> 4S (unsupported) + */ + switch (reg_val) { + case 0: + bcl->batt_config = BCL_BATT_2S; + return 0; + case 1: + bcl->batt_config = BCL_BATT_3S; + return 0; + default: + dev_err(bcl->dev, "Unsupported battery configuration: 0x%x\n", + reg_val); + return -EINVAL; + } +} + +static int bcl_update_scaling_factors(struct bcl_device *bcl) +{ + const struct bcl_desc *desc = bcl->desc; + int ret; + + /* Check if battery detection is supported */ + if (desc->battery_config_field.reg == 0) { + /* No battery detection - use BCL_BATT_1S */ + bcl->batt_config = BCL_BATT_1S; + dev_dbg(bcl->dev, "Using default 1S battery configuration\n"); + return 0; + } + + bcl->batt_config_regfield = devm_regmap_field_alloc(bcl->dev, + bcl->regmap, + desc->battery_config_field); + if (IS_ERR(bcl->batt_config_regfield)) { + dev_err(bcl->dev, "Failed to allocate battery config regmap field\n"); + return PTR_ERR(bcl->batt_config_regfield); + } + + ret = bcl_detect_battery_config(bcl); + if (ret < 0) + return ret; + + dev_dbg(bcl->dev, + "%dS battery: BASE=%u mV,MAX=%u mV,STEP=%u mV,V_SCALE=%u nV, I_SCALE=%u nA\n", + bcl->batt_config + 1, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].base, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].max, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].step, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].default_scale_nu, + desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].default_scale_nu); + + return 0; +} + +static int bcl_build_hwmon_info(struct bcl_device *bcl) +{ + const struct bcl_desc *desc = bcl->desc; + struct hwmon_channel_info *in_info = NULL, *curr_info = NULL; + const struct hwmon_channel_info **info_array; + int num_channels = 0; + u32 val = 0; + bool in_mon_enabled = false; + bool in_input_enabled = false; + bool curr_mon_enabled = false; + int ret; + + /* Check if voltage monitoring is enabled */ + ret = regmap_field_read(bcl->fields[F_IN_MON_EN], &val); + if (ret) + return ret; + in_mon_enabled = !!val; + + /* Check if voltage input reading is enabled */ + if (desc->data_field_bits_size != 0) { + ret = regmap_field_read(bcl->fields[F_IN_INPUT_EN], &val); + if (ret) + return ret; + in_input_enabled = !!val; + } + + /* Check if current monitoring is enabled */ + if (desc->num_reg_fields > F_CURR_H0_THR) { + ret = regmap_field_read(bcl->fields[F_CURR_MON_EN], &val); + if (ret) + return ret; + curr_mon_enabled = !!val; + } + + /* Ensure at least one channel is enabled */ + if (!in_mon_enabled && !curr_mon_enabled) { + dev_err(bcl->dev, "No BCL channels enabled in hardware\n"); + return -ENODEV; + } + + if (in_mon_enabled) { + u32 *in_config; + + bcl->in_attrs = HWMON_I_LABEL | HWMON_I_MIN | HWMON_I_LCRIT | + HWMON_I_MIN_ALARM | HWMON_I_LCRIT_ALARM; + + if (in_input_enabled) + bcl->in_attrs |= HWMON_I_INPUT; + + in_info = devm_kzalloc(bcl->dev, sizeof(*in_info), GFP_KERNEL); + if (!in_info) + return -ENOMEM; + + in_config = devm_kzalloc(bcl->dev, 2 * sizeof(u32), GFP_KERNEL); + if (!in_config) + return -ENOMEM; + + in_config[0] = bcl->in_attrs; + in_info->type = hwmon_in; + in_info->config = in_config; + num_channels++; + } + + if (curr_mon_enabled) { + u32 *curr_config; + + bcl->curr_attrs = HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | + HWMON_C_CRIT | HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM; + + curr_info = devm_kzalloc(bcl->dev, sizeof(*curr_info), GFP_KERNEL); + if (!curr_info) + return -ENOMEM; + + curr_config = devm_kzalloc(bcl->dev, 2 * sizeof(u32), GFP_KERNEL); + if (!curr_config) + return -ENOMEM; + + curr_config[0] = bcl->curr_attrs; + curr_info->type = hwmon_curr; + curr_info->config = curr_config; + num_channels++; + } + + /* Allocate info array (num_channels + 1 for NULL terminator) */ + info_array = devm_kcalloc(bcl->dev, num_channels + 1, + sizeof(*info_array), GFP_KERNEL); + if (!info_array) + return -ENOMEM; + + num_channels = 0; + if (in_info) + info_array[num_channels++] = in_info; + if (curr_info) + info_array[num_channels++] = curr_info; + + bcl->hwmon_info = info_array; + + bcl->hwmon_chip_info.ops = &bcl_hwmon_ops; + bcl->hwmon_chip_info.info = bcl->hwmon_info; + + return 0; +} + +static void bcl_alarm_work_cleanup_action(void *data) +{ + struct bcl_alarm_data *alarm = data; + + /* Set shutting_down flag to prevent work from being rescheduled */ + scoped_guard(mutex, &alarm->a_lock) + WRITE_ONCE(alarm->shutting_down, true); + + cancel_delayed_work_sync(&alarm->alarm_poll_work); +} + +static void bcl_alarm_wake_cleanup_action(void *data) +{ + struct bcl_alarm_data *alarm = data; + + guard(mutex)(&alarm->a_lock); + if (alarm->irq_wake_enabled) { + disable_irq_wake(alarm->irq); + alarm->irq_wake_enabled = false; + } +} + +static int bcl_alarm_irq_init(struct platform_device *pdev, + struct bcl_device *bcl) +{ + int ret, irq_num, i; + struct bcl_alarm_data *alarm; + + for (i = 0; i < ARRAY_SIZE(bcl->bcl_alarms); i++) { + alarm = &bcl->bcl_alarms[i]; + alarm->type = i; + alarm->device = bcl; + ret = devm_mutex_init(bcl->dev, &alarm->a_lock); + if (ret) + return ret; + + /* Initialize work before IRQ request */ + INIT_DELAYED_WORK(&alarm->alarm_poll_work, bcl_alarm_enable_poll); + + irq_num = platform_get_irq_byname(pdev, bcl_int_names[i]); + if (irq_num < 0) + return irq_num; + + alarm->irq = irq_num; + alarm->irq_enabled = true; + + ret = devm_request_threaded_irq(&pdev->dev, irq_num, NULL, + bcl_handle_alarm, IRQF_ONESHOT, + bcl_int_names[i], alarm); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, bcl_alarm_work_cleanup_action, + alarm); + if (ret) + return ret; + + if (!enable_irq_wake(irq_num)) + alarm->irq_wake_enabled = true; + + ret = devm_add_action_or_reset(&pdev->dev, bcl_alarm_wake_cleanup_action, + alarm); + if (ret) + return ret; + } + + return 0; +} + +static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, + const struct bcl_desc *data) +{ + int i; + + /* + * Note: We use a mutable local copy of struct reg_field (not const) + * because we need to modify the .reg field to add the BCL base offset. + */ + for (i = 0; i < data->num_reg_fields; i++) { + struct reg_field field = data->reg_fields[i]; + + /* Skip uninitialized fields */ + if (field.reg == 0 && field.lsb == 0 && field.msb == 0) + continue; + + field.reg += bcl->base; + + bcl->fields[i] = devm_regmap_field_alloc(dev, bcl->regmap, field); + if (IS_ERR(bcl->fields[i])) + return PTR_ERR(bcl->fields[i]); + } + + return 0; +} + +static int bcl_probe(struct platform_device *pdev) +{ + struct bcl_device *bcl; + int ret; + u32 reg; + + bcl = devm_kzalloc(&pdev->dev, sizeof(*bcl), GFP_KERNEL); + if (!bcl) + return -ENOMEM; + + bcl->dev = &pdev->dev; + bcl->desc = device_get_match_data(&pdev->dev); + if (!bcl->desc) + return dev_err_probe(&pdev->dev, -EINVAL, "Failed to get device match data\n"); + + ret = devm_mutex_init(bcl->dev, &bcl->lock); + if (ret) + return ret; + + bcl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bcl->regmap) + return dev_err_probe(&pdev->dev, -EINVAL, "Couldn't get parent's regmap\n"); + + ret = device_property_read_u32(&pdev->dev, "reg", ®); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to read 'reg' property\n"); + + bcl->base = reg; + + ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Unable to allocate regmap fields\n"); + + ret = regmap_field_read(bcl->fields[F_CTL_EN], ®); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to read BCL enable status\n"); + + if (!reg) + return dev_err_probe(&pdev->dev, -ENODEV, "BCL is not enabled by bootloader\n"); + + ret = bcl_update_scaling_factors(bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to update scaling factors\n"); + + ret = bcl_build_hwmon_info(bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to build hwmon info\n"); + + dev_set_drvdata(&pdev->dev, bcl); + + bcl->hwmon_name = devm_hwmon_sanitize_name(&pdev->dev, + dev_name(bcl->dev)); + if (IS_ERR(bcl->hwmon_name)) + return PTR_ERR(bcl->hwmon_name); + + bcl->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + bcl->hwmon_name, + bcl, + &bcl->hwmon_chip_info, + NULL); + if (IS_ERR(bcl->hwmon_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(bcl->hwmon_dev), + "Failed to register hwmon device\n"); + + ret = bcl_alarm_irq_init(pdev, bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to initialize alarm IRQs\n"); + + dev_dbg(&pdev->dev, "BCL hwmon device with version: %u.%u registered\n", + bcl_get_version_major(bcl), bcl_get_version_minor(bcl)); + + return 0; +} + +static const struct of_device_id bcl_match[] = { + { + .compatible = "qcom,pm7250b-bcl", + .data = &pm7250b_data, + }, { + .compatible = "qcom,pm8350c-bcl", + .data = &pm8350c_data, + }, { + .compatible = "qcom,pm8550-bcl", + .data = &pm8550_data, + }, { + .compatible = "qcom,pmh0101-bcl", + .data = &pmh0101_data, + }, { + .compatible = "qcom,pmih0108-bcl", + .data = &pmih0108_data, + }, { + .compatible = "qcom,smb2360-bcl", + .data = &smb2360_data, + }, { + .compatible = "qcom,smb2370-bcl", + .data = &smb2370_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, bcl_match); + +static struct platform_driver bcl_driver = { + .probe = bcl_probe, + .driver = { + .name = "qcom-bcl-hwmon", + .of_match_table = bcl_match, + }, +}; + +module_platform_driver(bcl_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI BCL HWMON driver"); +MODULE_LICENSE("GPL"); From ea489e26bfd167712636635ec7f04770a0375e84 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:11 +0530 Subject: [PATCH 187/243] FROMLIST: arm64: dts: qcom: pmh0101: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for pmh0101 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-6-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/pmh0101.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pmh0101.dtsi b/arch/arm64/boot/dts/qcom/pmh0101.dtsi index b1ec413259583..fd632b592d2e1 100644 --- a/arch/arm64/boot/dts/qcom/pmh0101.dtsi +++ b/arch/arm64/boot/dts/qcom/pmh0101.dtsi @@ -43,6 +43,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pmh0101-bcl"; + reg = <0x4700>; + interrupts = <0x1 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x1 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pmh0101_gpios: gpio@8800 { compatible = "qcom,pmh0101-gpio", "qcom,spmi-gpio"; reg = <0x8800>; From b8ca7ea106bb665aea45454ca829fdbf4d867a33 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:15 +0530 Subject: [PATCH 188/243] FROMLIST: arm64: dts: qcom: smb2370: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for smb2370 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-10-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/smb2370.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/smb2370.dtsi b/arch/arm64/boot/dts/qcom/smb2370.dtsi index 80f3fdae57050..79b013fa2c113 100644 --- a/arch/arm64/boot/dts/qcom/smb2370.dtsi +++ b/arch/arm64/boot/dts/qcom/smb2370.dtsi @@ -10,6 +10,15 @@ #address-cells = <1>; #size-cells = <0>; + sensor@4700 { + compatible = "qcom,smb2370-bcl"; + reg = <0x4700>; + interrupts = <0x9 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x9 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + smb2370_j_e2_eusb2_repeater: phy@fd00 { compatible = "qcom,smb2370-eusb2-repeater"; reg = <0xfd00>; From 6aad78e5c4f990ee4fe2b1a5678e88a5d66dda1b Mon Sep 17 00:00:00 2001 From: Sunyun Yang Date: Fri, 8 May 2026 21:40:08 +0800 Subject: [PATCH 189/243] 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/lkml/20260716-lt9611c-v7-v7-1-7553a14735fc@oss.qualcomm.com/ Signed-off-by: Sunyun Yang Signed-off-by: Mohit Dsor --- .../bindings/display/bridge/lontium,lt9611.yaml | 8 ++++++-- 1 file changed, 6 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..e0821a63d9d74 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 From 263b0dd76916d709bc8f504291406e795637ac10 Mon Sep 17 00:00:00 2001 From: Sunyun Yang Date: Fri, 8 May 2026 21:40:09 +0800 Subject: [PATCH 190/243] FROMLIST : drm/bridge: Add Lontium LT9609C(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/lkml/20260716-lt9611c-v7-v7-2-7553a14735fc@oss.qualcomm.com/ Signed-off-by: Sunyun Yang Co-developed-by: Mohit Dsor Signed-off-by: Mohit Dsor --- MAINTAINERS | 7 + drivers/gpu/drm/bridge/Kconfig | 18 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt9611c.c | 1293 ++++++++++++++++++++++ 4 files changed, 1319 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c diff --git a/MAINTAINERS b/MAINTAINERS index 806bd2d80d153..901f27e301bfe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15172,6 +15172,13 @@ S: Maintained F: Documentation/devicetree/bindings/display/bridge/lontium,lt8912b.yaml F: drivers/gpu/drm/bridge/lontium-lt8912b.c +LONTIUM LT9611C MIPI DSI TO HDMI BRIDGE +M: Sunyun Yang +M: Mohit Dsor +S: Maintained +F: Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml +F: drivers/gpu/drm/bridge/lontium-lt9611c.c + LOONGARCH M: Huacai Chen R: WANG Xuerui diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d3..b8959b8e2ad05 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 + 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 Lontium DSI to HDMI bridge + chip driver that converts dual DSI and I2S to + HDMI signals + Please say Y if you have such hardware. + 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..e8bb385d15206 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c @@ -0,0 +1,1293 @@ +// 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 + +#define FW_SIZE (64 * 1024) +#define LT_PAGE_SIZE 256 +#define FW_FILE "Lontium/lt9611c_fw.bin" +#define LT9611C_CRC_POLYNOMIAL 0x31 +#define LT9611C_PAGE_CONTROL 0xff +#define LT9611C_INFOFRAME_MAX_SIZE 32 + +enum lt9611_chip_type { + CHIP_LT9611C = 0, + CHIP_LT9611EX, + CHIP_LT9611UXD, +}; + +struct lt9611c { + struct device *dev; + struct i2c_client *client; + struct drm_bridge bridge; + struct regmap *regmap; + /* Protects all accesses to registers by stopping the on-chip MCU */ + struct mutex ocm_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; + /* HDMI cable connection status */ + 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, u8 *params, + unsigned int param_count, u8 *return_buffer, + unsigned int return_count) +{ + 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; + + for (i = 0; i < param_count && i < max_params; i++) + regmap_write(lt9611c->regmap, 0xe0b0 + i, params[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; + + return regmap_bulk_read(lt9611c->regmap, 0xe085, return_buffer, + return_count); +} + +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 void read_flash_reg_status(struct lt9611c *lt9611c, unsigned int *status) +{ + 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), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); + + regmap_read(lt9611c->regmap, 0xe05f, 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 void lt9611c_block_erase(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + int i; + unsigned int block_num; + unsigned int flash_status = 0; + u32 flash_addr = 0; + + for (block_num = 0; block_num < 2; block_num++) { + flash_addr = (block_num * 0x008000); + lt9611c_erase_op(lt9611c, flash_addr); + msleep(100); + i = 0; + while (1) { + read_flash_reg_status(lt9611c, &flash_status); + if ((flash_status & 0x01) == 0) + break; + + if (i > 50) + break; + + i++; + msleep(50); + } + } + + dev_dbg(dev, "erase flash done.\n"); +} + +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 = (size + LT_PAGE_SIZE - 1) / 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); + + dev_dbg(lt9611c->dev, "lt9611c reset"); +} + +static int lt9611c_upgrade_result(struct lt9611c *lt9611c, u8 fw_crc) +{ + struct device *dev = lt9611c->dev; + unsigned int crc_result; + + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + regmap_read(lt9611c->regmap, 0xe021, &crc_result); + + 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 -1; + } + + dev_dbg(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; + + /* 1. load firmware */ + ret = request_firmware(&fw, FW_FILE, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE); + + /* 2. check size */ + 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); + + /* 3. calculate crc8 */ + buffer = kzalloc(total_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out_release_fw; + } + + memset(buffer, 0xff, total_size); + memcpy(buffer, fw->data, fw->size); + + fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0); + kfree(buffer); + + dev_dbg(dev, "firmware crc: 0x%02x\n", fw_crc); + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", fw->size); + + /* 4. firmware upgrade */ + lt9611c_config_parameters(lt9611c); + lt9611c_block_erase(lt9611c); + + ret = lt9611c_write_data(lt9611c, fw, 0); + if (ret < 0) { + dev_err(dev, "failed to write firmware data\n"); + goto out_release_fw; + } + + ret = lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1); + if (ret < 0) { + dev_err(dev, "failed to write firmware crc\n"); + goto out_release_fw; + } + + /* 5. check upgrade of result */ + lt9611c_reset(lt9611c); + ret = lt9611c_upgrade_result(lt9611c, fw_crc); + +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); +} + +/*read only*/ +static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge) +{ + return container_of(bridge, const struct lt9611c, bridge); +} + +static void lt9611c_lock(struct lt9611c *lt9611c) +{ + mutex_lock(<9611c->ocm_lock); + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); +} + +static void lt9611c_unlock(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe0ee, 0x00); + mutex_unlock(<9611c->ocm_lock); +} + +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; + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00}; + u8 data[5]; + + guard(mutex)(<9611c->ocm_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_HANDLED; + + msleep(100); + + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), data, ARRAY_SIZE(data)); + if (ret) { + dev_err(dev, "failed to read HPD status\n"); + } else { + lt9611c->hdmi_connected = (data[4] == 0x02); + dev_dbg(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected"); + } + + /*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); + bool connected; + + mutex_lock(<9611c->ocm_lock); + connected = lt9611c->hdmi_connected; + mutex_unlock(<9611c->ocm_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 (lt9611c->chip_type == CHIP_LT9611UXD) { + if (tmds_rate > 600000000) + return MODE_CLOCK_HIGH; + + } else { + if (tmds_rate > 340000000) + 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_set_cmd[26] = {0x57, 0x4d, 0x33, 0x3a}; + u8 return_param[3]; + u8 framerate; + u8 vic = 0x00; + + guard(mutex)(<9611c->ocm_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); + + timing_set_cmd[4] = (h_total >> 8) & 0xff; + timing_set_cmd[5] = h_total & 0xff; + timing_set_cmd[6] = (hactive >> 8) & 0xff; + timing_set_cmd[7] = hactive & 0xff; + timing_set_cmd[8] = (hfront_porch >> 8) & 0xff; + timing_set_cmd[9] = hfront_porch & 0xff; + timing_set_cmd[10] = (hsync_len >> 8) & 0xff; + timing_set_cmd[11] = hsync_len & 0xff; + timing_set_cmd[12] = (hback_porch >> 8) & 0xff; + timing_set_cmd[13] = hback_porch & 0xff; + timing_set_cmd[14] = (v_total >> 8) & 0xff; + timing_set_cmd[15] = v_total & 0xff; + timing_set_cmd[16] = (vactive >> 8) & 0xff; + timing_set_cmd[17] = vactive & 0xFF; + timing_set_cmd[18] = (vfront_porch >> 8) & 0xff; + timing_set_cmd[19] = vfront_porch & 0xff; + timing_set_cmd[20] = (vsync_len >> 8) & 0xff; + timing_set_cmd[21] = vsync_len & 0xff; + timing_set_cmd[22] = (vback_porch >> 8) & 0xff; + timing_set_cmd[23] = vback_porch & 0xff; + timing_set_cmd[24] = framerate; + timing_set_cmd[25] = vic; + + ret = lt9611c_read_write_flow(lt9611c, + timing_set_cmd, ARRAY_SIZE(timing_set_cmd), + return_param, ARRAY_SIZE(return_param)); + 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; + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00}; + u8 data[5]; + + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), data, ARRAY_SIZE(data)); + if (ret) { + dev_err(dev, "failed to read HPD status (err=%d)\n", ret); + connected = lt9611c->hdmi_connected; + } else { + connected = (data[4] == 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 cmd[5] = {0x52, 0x48, 0x33, 0x3a, 0x00}; + u8 packet[37]; + int ret, i, offset = 0; + + if (len != 128) + return -EINVAL; + guard(mutex)(<9611c->ocm_lock); + + for (i = 0; i < 4; i++) { + cmd[4] = block * 4 + i; + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), + packet, ARRAY_SIZE(packet)); + if (ret) { + dev_err(dev, "Failed to read EDID block %u packet %d\n", + block, i); + return ret; + } + memcpy(buf + offset, &packet[5], 32); + offset += 32; + } + + 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_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 *cmd; + u8 data[5]; + int ret; + + guard(mutex)(<9611c->ocm_lock); + + cmd = kmalloc(5 + len, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd[0] = 0x57; + cmd[1] = 0x48; + cmd[2] = 0x35; + cmd[3] = 0x3a; + cmd[4] = 0x01;/*write avi*/ + memcpy(cmd + 5, buffer, len); + + ret = lt9611c_read_write_flow(lt9611c, cmd, 5 + len, + data, ARRAY_SIZE(data)); + kfree(cmd); + + if (ret < 0) { + dev_err(lt9611c->dev, "write avi infoframe failed!\n"); + return ret; + } + + return 0; +} + +static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 cmd[5] = {0x57, 0x48, 0x42, 0x3a, 0x01}; + u8 data[5]; + int ret; + + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), + data, ARRAY_SIZE(data)); + + if (ret < 0) { + dev_err(lt9611c->dev, "clear avi infoframe failed!\n"); + return ret; + } + + return 0; +} + +static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 cmd[5 + LT9611C_INFOFRAME_MAX_SIZE]; + u8 data[5]; + int ret; + + cmd[0] = 0x57; + cmd[1] = 0x48; + cmd[2] = 0x35; + cmd[3] = 0x3a; + cmd[4] = 0x04;/*write vsif*/ + memcpy(cmd + 5, buffer, len); + + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, cmd, 5 + len, + data, ARRAY_SIZE(data)); + if (ret < 0) { + dev_err(lt9611c->dev, "write hdmi infoframe failed!\n"); + return ret; + } + + return 0; +} + +static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 cmd[5] = {0x57, 0x48, 0x42, 0x3a, 0x04}; /*clear vsif*/ + u8 data[5]; + int ret; + + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), + data, ARRAY_SIZE(data)); + + if (ret < 0) { + dev_err(lt9611c->dev, "clear hdmi infoframe failed!\n"); + return ret; + } + + return 0; +} + +static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 *cmd; + u8 data[5]; + int ret; + + guard(mutex)(<9611c->ocm_lock); + + cmd = kmalloc(5 + len, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd[0] = 0x57; + cmd[1] = 0x48; + cmd[2] = 0x35; + cmd[3] = 0x3a; + cmd[4] = 0x02;/*write audio*/ + memcpy(cmd + 5, buffer, len); + + ret = lt9611c_read_write_flow(lt9611c, cmd, 5 + len, + data, ARRAY_SIZE(data)); + + kfree(cmd); + + if (ret < 0) { + dev_err(lt9611c->dev, "write audio infoframe failed!\n"); + return ret; + } + + return 0; +} + +static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 cmd[5] = {0x57, 0x48, 0x42, 0x3a, 0x02}; + u8 data[5]; + int ret; + + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), + data, ARRAY_SIZE(data)); + + if (ret < 0) { + dev_err(lt9611c->dev, "clear audio infoframe failed!\n"); + return ret; + } + + return 0; +} + +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_cmd[6] = {0x57, 0x48, 0x36, 0x3a}; + u8 data[5]; + int ret; + + if (hparms->sample_width == 32) + return -EINVAL; + + switch (fmt->fmt) { + case HDMI_I2S: + audio_cmd[4] = 0x01; + break; + case HDMI_SPDIF: + audio_cmd[4] = 0x02; + break; + default: + return -EINVAL; + } + + audio_cmd[5] = hparms->channels; + guard(mutex)(<9611c->ocm_lock); + + ret = lt9611c_read_write_flow(lt9611c, audio_cmd, sizeof(audio_cmd), + data, sizeof(data)); + 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 void lt9611c_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00}; + u8 data[5]; + int ret; + + mutex_lock(<9611c->ocm_lock); + ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), + data, ARRAY_SIZE(data)); + if (!ret) + lt9611c->hdmi_connected = (data[4] == 0x02); + mutex_unlock(<9611c->ocm_lock); + + schedule_work(<9611c->work); +} + +static int lt9611c_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + return 0; +} + +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, + .hpd_enable = lt9611c_bridge_hpd_enable, + + .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_startup = lt9611c_hdmi_audio_startup, + .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); + + ret = drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, <9611c->bridge.next_bridge); + if (ret) { + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); + return ret; + } + drm_bridge_get(lt9611c->bridge.next_bridge); + return 0; +} + +static int lt9611c_gpio_init(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611c->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lt9611c->reset_gpio), + "failed to acquire reset gpio\n"); + + return 0; +} + +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 lt9611c_firmware_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + lt9611c_lock(lt9611c); + + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) + dev_err(dev, "upgrade failure\n"); + + lt9611c_unlock(lt9611c); + + return ret < 0 ? ret : len; +} + +static ssize_t lt9611c_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(lt9611c_firmware); + +static struct attribute *lt9611c_attrs[] = { + &dev_attr_lt9611c_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; + lt9611c->chip_type = (uintptr_t)i2c_get_match_data(client); + + ret = devm_mutex_init(dev, <9611c->ocm_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"); + + ret = lt9611c_gpio_init(lt9611c); + if (ret < 0) + 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; + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) { + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + + 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; + + devm_drm_bridge_add(dev, <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); + + lt9611c_reset(lt9611c); + return 0; + +err_remove_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); + + cancel_work_sync(<9611c->work); + 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; + + dev_dbg(lt9611c->dev, "suspend\n"); + disable_irq(lt9611c->client->irq); + + 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"); + + return ret; +} + +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; + } + enable_irq(lt9611c->client->irq); + lt9611c_reset(lt9611c); + dev_dbg(lt9611c->dev, "resume\n"); + + 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[] = { + /* chip_type */ + { "lontium,lt9611c", CHIP_LT9611C }, + { "lontium,lt9611ex", CHIP_LT9611EX }, + { "lontium,lt9611uxd", CHIP_LT9611UXD }, + { /* sentinel */ } +}; + +static const struct of_device_id lt9611c_match_table[] = { + { .compatible = "lontium,lt9611c", .data = (void *)CHIP_LT9611C }, + { .compatible = "lontium,lt9611ex", .data = (void *)CHIP_LT9611EX }, + { .compatible = "lontium,lt9611uxd", .data = (void *)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 bb9e1611d259b5ee53a0abae689a085e203f8de1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 19 Jun 2026 14:24:11 +0200 Subject: [PATCH 191/243] 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 814ffbc01b45bf3355b299e2d1dab047671cc82a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 19 Jun 2026 14:24:12 +0200 Subject: [PATCH 192/243] 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 f2ee87014e914a598c6fd52f7faf2a3ad0ad96cf Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Tue, 24 Mar 2026 14:59:48 +0800 Subject: [PATCH 193/243] WORKAROUND: arm64: dts: qcom: hamoa-iot-evk: support Bluetooth over both USB and UART On Hamoa boards, a single M.2 slot may host either a UART-based or a USB-based Bluetooth device. As a result, the UART controller node is always present in DT, while the USB path is hot-pluggable. When Bluetooth operates over USB, the presence of the UART DT node still causes the hci_qca UART driver to probe. During probe or power sequencing, the driver may deassert BT_EN, cutting power to the shared Bluetooth device and disconnecting the USB interface. Model BT_EN as an always-on fixed regulator so it cannot be toggled by the UART probe. This prevents the UART driver from interfering with Bluetooth operation when the device is connected over USB. Workaround will be reverted once the M.2 solutionis available upstream. Signed-off-by: Shuai Zhang --- arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts index c7ba4b353ddd2..0ab15ea5aa73a 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts +++ b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts @@ -522,6 +522,23 @@ regulator-boot-on; }; + vreg_wcn_bt_en: regulator-wcn-bt-en { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_BT_EN"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + gpio = <&tlmm 116 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wcn_bt_en>; + pinctrl-names = "default"; + + regulator-always-on; + regulator-boot-on; + }; + vreg_wwan: regulator-wwan { compatible = "regulator-fixed"; @@ -687,10 +704,9 @@ vddrfa1p2-supply = <&vreg_wcn_1p9>; vddrfa1p8-supply = <&vreg_wcn_1p9>; - bt-enable-gpios = <&tlmm 116 GPIO_ACTIVE_HIGH>; wlan-enable-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; - pinctrl-0 = <&wcn_bt_en>, <&wcn_wlan_en>; + pinctrl-0 = <&wcn_wlan_en>; pinctrl-names = "default"; regulators { @@ -1519,13 +1535,13 @@ compatible = "qcom,wcn7850-bt"; max-speed = <3200000>; - vddaon-supply = <&vreg_pmu_aon_0p59>; - vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; - vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; - vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; - 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>; + vddrfa0p8-supply = <&vreg_wcn_3p3>; + vddrfa1p2-supply = <&vreg_wcn_3p3>; + vddrfa1p8-supply = <&vreg_wcn_3p3>; }; }; From 5a472e184be99f6f0094c85ccb96db51453f4656 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Thu, 19 Mar 2026 11:56:41 +0800 Subject: [PATCH 194/243] WORKAROUND: power: sequencing: qcom-wcn: skip BT devices without bt-enable GPIO If a Bluetooth consumer device does not have a bt-enable GPIO configured in the power sequencer (for example, when BT_EN is tied high via a hardware pull-up and therefore absent from the DT), the power sequencer should not match the device. In this case, the Bluetooth consumer driver will fall back to its legacy power control path and correctly set power_ctrl_enabled to false. Bluetooth device nodes are conventionally named "bluetooth" in the device tree, so use of_node_name_eq() as a generic check instead of enumerating specific compatible strings. Workaround will be reverted once the M.2 solution is available upstream. Signed-off-by: Shuai Zhang --- drivers/power/sequencing/pwrseq-qcom-wcn.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index b55b4317e21b6..dc83d32415a2d 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -432,6 +432,20 @@ static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq, reg_node->parent->parent != ctx->of_node) return PWRSEQ_NO_MATCH; + /* + * If this is a Bluetooth consumer device but the bt-enable GPIO is not + * configured in the power sequencer (e.g. BT_EN is tied high via a + * hardware pull-up and therefore absent from the DT), don't match. + * The consumer driver will fall back to its legacy power control path + * and correctly set power_ctrl_enabled to false. + * + * BT device nodes are conventionally named "bluetooth" in the DT, + * so use of_node_name_eq() as a generic check rather than enumerating + * specific compatible strings. + */ + if (!ctx->bt_gpio && of_node_name_eq(dev_node, "bluetooth")) + return PWRSEQ_NO_MATCH; + return PWRSEQ_MATCH_OK; } From 149f712c98ecd5e697823ad1896876863b911202 Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Fri, 8 May 2026 12:46:38 +0530 Subject: [PATCH 195/243] WORKAROUND: Revert "drm/msm/dpu: enable virtual planes by default" Bootup crash seen on kaanapali-mtp board. [ 8.114249][ C0] [drm:dpu_encoder_frame_done_timeout:2731] [dpu error]enc33 frame done timeout [ 8.116480][ T284] Unable to handle kernel paging request at virtual address ffff800080e5e000 [ 8.116488][ T284] Mem abort info: [ 8.116492][ T284] ESR = 0x0000000096000007 [ 8.116497][ T284] EC = 0x25: DABT (current EL), IL = 32 bits [ 8.116502][ T284] SET = 0, FnV = 0 [ 8.116507][ T284] EA = 0, S1PTW = 0 [ 8.116511][ T284] FSC = 0x07: level 3 translation fault [ 8.116516][ T284] Data abort info: [ 8.116519][ T284] ISV = 0, ISS = 0x00000007, ISS2 = 0x00000000 [ 8.116524][ T284] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 8.116529][ T284] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 8.116535][ T284] swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000009bf36c000 [ 8.116541][ T284] [ffff800080e5e000] pgd=0000000000000000, p4d=1000000880346403, pud=1000000880347403, pmd=1000000881e2d403, pte=0000000000000000 [ 8.116567][ T284] Internal error: Oops: 0000000096000007 [#1] SMP Revert the change for now to unblock. This reverts commit b0907ee59e24d3dad572b4ccc6db018b00ca14c8. Signed-off-by: Salendarsingh Gaud --- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index da3556eb6ecc2..dbc43e0dc18e3 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -52,7 +52,7 @@ #define DPU_DEBUGFS_DIR "msm_dpu" #define DPU_DEBUGFS_HWMASKNAME "hw_log_mask" -bool dpu_use_virtual_planes = true; +bool dpu_use_virtual_planes; module_param(dpu_use_virtual_planes, bool, 0); static int dpu_kms_hw_init(struct msm_kms *kms); From 3aff69522614ad58d4b470d7758852c62cd28275 Mon Sep 17 00:00:00 2001 From: Wei Deng Date: Thu, 7 May 2026 12:53:20 +0530 Subject: [PATCH 196/243] WORKAROUND: arm64: dts: qcom: purwa-iot-evk: support Bluetooth over both USB and UART On Purwa boards, a single M.2 slot may host either a UART-based or a USB-based Bluetooth device. As a result, the UART controller node is always present in DT, while the USB path is hot-pluggable. When Bluetooth operates over USB, the presence of the UART DT node still causes the hci_qca UART driver to probe. During probe or power sequencing, the driver may deassert BT_EN, cutting power to the shared Bluetooth device and disconnecting the USB interface. Model BT_EN as an always-on fixed regulator so it cannot be toggled by the UART probe. This prevents the UART driver from interfering with Bluetooth operation when the device is connected over USB. Workaround will be reverted once the M.2 solutionis available upstream. Signed-off-by: Wei Deng --- arch/arm64/boot/dts/qcom/purwa-iot-evk.dts | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/purwa-iot-evk.dts b/arch/arm64/boot/dts/qcom/purwa-iot-evk.dts index ad503beec1d3d..c39081b064b10 100644 --- a/arch/arm64/boot/dts/qcom/purwa-iot-evk.dts +++ b/arch/arm64/boot/dts/qcom/purwa-iot-evk.dts @@ -514,6 +514,23 @@ regulator-boot-on; }; + vreg_wcn_bt_en: regulator-wcn-bt-en { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_BT_EN"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + gpio = <&tlmm 116 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wcn_bt_en>; + pinctrl-names = "default"; + + regulator-always-on; + regulator-boot-on; + }; + vreg_wwan: regulator-wwan { compatible = "regulator-fixed"; @@ -628,10 +645,9 @@ vddrfa1p2-supply = <&vreg_wcn_1p9>; vddrfa1p8-supply = <&vreg_wcn_1p9>; - bt-enable-gpios = <&tlmm 116 GPIO_ACTIVE_HIGH>; wlan-enable-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; - pinctrl-0 = <&wcn_bt_en>, <&wcn_wlan_en>; + pinctrl-0 = <&wcn_wlan_en>; pinctrl-names = "default"; regulators { @@ -1504,13 +1520,13 @@ compatible = "qcom,wcn7850-bt"; max-speed = <3200000>; - vddaon-supply = <&vreg_pmu_aon_0p59>; - vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; - vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; - vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; - vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; - vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; - vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + vddaon-supply = <&vreg_wcn_3p3>; + vddwlcx-supply = <&vreg_wcn_3p3>; + vddwlmx-supply = <&vreg_wcn_3p3>; + vddrfacmn-supply = <&vreg_wcn_3p3>; + vddrfa0p8-supply = <&vreg_wcn_3p3>; + vddrfa1p2-supply = <&vreg_wcn_3p3>; + vddrfa1p8-supply = <&vreg_wcn_3p3>; }; }; From a2e72b2fa858db472f56d8d25347daf0b0a07fad Mon Sep 17 00:00:00 2001 From: Ziyue Zhang Date: Fri, 15 May 2026 00:00:00 +0800 Subject: [PATCH 197/243] WORKAROUND: phy: qcom: qmp-pcie: Add vdda-refgen and vdda-qref supplies for Monaco The PCIe QMP PHYs on Monaco (QCS8300) require stable reference voltage provided by refgen and reference clock provided by qref. The refgen and qref require power supplies. Add a new sa8775p_qmp_phy_vreg_l list with vdda-qref and vdda-refgen supplies, and use it for qcs8300_qmp_gen4x2, sa8775p_qmp_gen4x2 and sa8775p_qmp_gen4x4 PCIe PHY configurations. Workaround will be reverted once the vote qref regulator for PCIe available in upstream. Signed-off-by: Ziyue Zhang --- drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c index d3effad7a074b..77b60bd32cd9e 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c @@ -3488,6 +3488,10 @@ static const char * const sm8550_qmp_phy_vreg_l[] = { "vdda-phy", "vdda-pll", "vdda-qref", }; +static const char * const sa8775p_qmp_phy_vreg_l[] = { + "vdda-phy", "vdda-pll", "vdda-qref", "vdda-refgen", +}; + /* list of resets */ static const char * const ipq8074_pciephy_reset_l[] = { "phy", "common", @@ -3853,8 +3857,8 @@ static const struct qmp_phy_cfg qcs8300_qmp_gen4x2_pciephy_cfg = { .reset_list = sdm845_pciephy_reset_l, .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), - .vreg_list = qmp_phy_vreg_l, - .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .vreg_list = sa8775p_qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(sa8775p_qmp_phy_vreg_l), .regs = pciephy_v5_regs_layout, .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, @@ -4563,8 +4567,8 @@ static const struct qmp_phy_cfg sa8775p_qmp_gen4x2_pciephy_cfg = { .reset_list = sdm845_pciephy_reset_l, .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), - .vreg_list = qmp_phy_vreg_l, - .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .vreg_list = sa8775p_qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(sa8775p_qmp_phy_vreg_l), .regs = pciephy_v5_regs_layout, .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, @@ -4604,8 +4608,8 @@ static const struct qmp_phy_cfg sa8775p_qmp_gen4x4_pciephy_cfg = { .reset_list = sdm845_pciephy_reset_l, .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), - .vreg_list = qmp_phy_vreg_l, - .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .vreg_list = sa8775p_qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(sa8775p_qmp_phy_vreg_l), .regs = pciephy_v5_regs_layout, .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, From 95316f8b5f2d1d85a117e216361eef5dfb07f010 Mon Sep 17 00:00:00 2001 From: Ziyue Zhang Date: Fri, 15 May 2026 00:00:00 +0800 Subject: [PATCH 198/243] WORKAROUND: arm64: dts: qcom: qcs8300-ride: Add vdda-qref and vdda-refgen supplies for PCIe PHYs The PCIe QMP PHYs on QCS8300 require stable reference voltage provided by refgen and reference clock provided by qref. Add vdda-qref-supply and vdda-refgen-supply to pcie0_phy and pcie1_phy nodes on the qcs8300-ride board. Workaround will be reverted once the vote qref regulator for PCIe available in upstream. Signed-off-by: Ziyue Zhang --- arch/arm64/boot/dts/qcom/qcs8300-ride.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qcs8300-ride.dts b/arch/arm64/boot/dts/qcom/qcs8300-ride.dts index e9a8553a8d821..25be329a98829 100644 --- a/arch/arm64/boot/dts/qcom/qcs8300-ride.dts +++ b/arch/arm64/boot/dts/qcom/qcs8300-ride.dts @@ -638,6 +638,8 @@ &pcie0_phy { vdda-phy-supply = <&vreg_l6a>; vdda-pll-supply = <&vreg_l5a>; + vdda-qref-supply = <&vreg_l7a>; + vdda-refgen-supply = <&refgen>; status = "okay"; }; @@ -657,6 +659,8 @@ &pcie1_phy { vdda-phy-supply = <&vreg_l6a>; vdda-pll-supply = <&vreg_l5a>; + vdda-qref-supply = <&vreg_l7a>; + vdda-refgen-supply = <&refgen>; status = "okay"; }; From 06f2b9c608345b2c4b8ff3259cd91941aa569150 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Thu, 28 May 2026 20:23:39 +0800 Subject: [PATCH 199/243] WORKAROUND: power: sequencing: qcom-wcn: skip BT devices without bt-enable GPIO Add a bt_gpio_required flag to the per-platform data to indicate that a chip's BT enable path requires a dedicated GPIO. Only skip matching the "bluetooth" device node when this flag is set and bt_gpio is absent. Previously the bt_gpio check was applied unconditionally, which caused chips like WCN3990 that have no separate BT/WLAN enable pins by design to fail matching even when bt-enable GPIO is legitimately absent from the DT. Set bt_gpio_required for WCN6855 and WCN7850 which do require a dedicated BT enable GPIO. Signed-off-by: Shuai Zhang --- drivers/power/sequencing/pwrseq-qcom-wcn.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index dc83d32415a2d..a68e6fb344fd6 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -25,6 +25,7 @@ struct pwrseq_qcom_wcn_pdata { unsigned int gpio_enable_delay_ms; const struct pwrseq_target_data **targets; bool has_vddio; /* separate VDD IO regulator */ + bool bt_gpio_required; /* BT enable path requires a dedicated GPIO */ int (*match)(struct pwrseq_device *pwrseq, struct device *dev); }; @@ -383,6 +384,7 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6855_of_data = { .pwup_delay_ms = 50, .gpio_enable_delay_ms = 5, .targets = pwrseq_qcom_wcn6855_targets, + .bt_gpio_required = true, }; static const char *const pwrseq_wcn7850_vregs[] = { @@ -400,6 +402,7 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = { .num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs), .pwup_delay_ms = 50, .targets = pwrseq_qcom_wcn_targets, + .bt_gpio_required = true, }; static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq, @@ -439,11 +442,17 @@ static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq, * The consumer driver will fall back to its legacy power control path * and correctly set power_ctrl_enabled to false. * + * Only apply this check for chips whose BT enable path requires a + * dedicated GPIO (bt_gpio_required). Chips like WCN3990 have no + * separate BT/WLAN enable pins by design and must always be matched + * even when bt_gpio is NULL. + * * BT device nodes are conventionally named "bluetooth" in the DT, * so use of_node_name_eq() as a generic check rather than enumerating * specific compatible strings. */ - if (!ctx->bt_gpio && of_node_name_eq(dev_node, "bluetooth")) + if (ctx->pdata->bt_gpio_required && !ctx->bt_gpio && + of_node_name_eq(dev_node, "bluetooth")) return PWRSEQ_NO_MATCH; return PWRSEQ_MATCH_OK; From d88b882e260600379c83fcac065edd937dd34e1b Mon Sep 17 00:00:00 2001 From: Yepuri Siddu Date: Mon, 8 Jun 2026 12:20:46 +0530 Subject: [PATCH 200/243] FROMLIST: dt-bindings: net: bluetooth: qcom: document QCC2072 Add the new compatible string for the QCC2072 BT SoC to the Qualcomm Bluetooth DT binding schema. Link: https://lore.kernel.org/all/20260529175342.3363935-1-yepuri.siddu@oss.qualcomm.com/ Signed-off-by: Yepuri Siddu --- .../net/bluetooth/qcom,qcc2072-bt.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/bluetooth/qcom,qcc2072-bt.yaml diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,qcc2072-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,qcc2072-bt.yaml new file mode 100644 index 0000000000000..8e2f15a75d620 --- /dev/null +++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,qcc2072-bt.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/bluetooth/qcom,qcc2072-bt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm QCC2072 Bluetooth + +maintainers: + - Balakrishna Godavarthi + - Rocky Liao + +description: + Qualcomm QCC2072 is a UART-based Bluetooth controller. + +properties: + compatible: + enum: + - qcom,qcc2072-bt + +required: + - compatible + +allOf: + - $ref: bluetooth-controller.yaml# + - $ref: qcom,bluetooth-common.yaml# + - $ref: /schemas/serial/serial-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + serial { + bluetooth { + compatible = "qcom,qcc2072-bt"; + max-speed = <3200000>; + }; + }; From f086e50898137df252189358977028287fd07434 Mon Sep 17 00:00:00 2001 From: Yepuri Siddu Date: Mon, 8 Jun 2026 12:33:14 +0530 Subject: [PATCH 201/243] FROMLIST: Bluetooth: qca: add QCC2072 support QCC2072 is a BT/WiFi combo SoC that uses different firmware filenames and requires no external voltage regulators, so add it as a new SoC type. The chip supports the wideband speech and valid LE states capabilities. Its firmware is named using the "orn" prefix and follows the standard rom-version-based scheme: - qca/ornbtfw.tlv - qca/ornnv.bin These firmware files are already present in the linux-firmware repository. Link: https://lore.kernel.org/all/20260529175822.3366535-1-yepuri.siddu@oss.qualcomm.com/ Signed-off-by: Yepuri Siddu --- drivers/bluetooth/btqca.c | 9 +++++++++ drivers/bluetooth/btqca.h | 1 + drivers/bluetooth/hci_qca.c | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 04ebe290bc784..8b2ad10ec2d92 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -845,6 +845,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, snprintf(config.fwname, sizeof(config.fwname), "qca/hmtbtfw%02x.tlv", rom_ver); break; + case QCA_QCC2072: + snprintf(config.fwname, sizeof(config.fwname), + "qca/ornbtfw%02x.tlv", rom_ver); + break; default: snprintf(config.fwname, sizeof(config.fwname), "qca/rampatch_%08x.bin", soc_ver); @@ -939,6 +943,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname), "hmtnv", soc_type, ver, rom_ver, boardid); break; + case QCA_QCC2072: + snprintf(config.fwname, sizeof(config.fwname), + "qca/ornnv%02x.bin", rom_ver); + break; default: snprintf(config.fwname, sizeof(config.fwname), "qca/nvm_%08x.bin", soc_ver); @@ -1001,6 +1009,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: /* get fw build info */ err = qca_read_fw_build_info(hdev); if (err < 0) diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h index 8f3c1b1c77b3d..a175ac31e7b23 100644 --- a/drivers/bluetooth/btqca.h +++ b/drivers/bluetooth/btqca.h @@ -158,6 +158,7 @@ enum qca_btsoc_type { QCA_WCN6750, QCA_WCN6855, QCA_WCN7850, + QCA_QCC2072, }; #if IS_ENABLED(CONFIG_BT_QCA) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 2444471956197..3f9e3d966747d 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -1372,6 +1372,7 @@ static int qca_set_baudrate(struct hci_dev *hdev, uint8_t baudrate) /* Give the controller time to process the request */ switch (qca_soc_type(hu)) { + case QCA_QCC2072: case QCA_WCN3950: case QCA_WCN3988: case QCA_WCN3990: @@ -1459,6 +1460,7 @@ static unsigned int qca_get_speed(struct hci_uart *hu, static int qca_check_speeds(struct hci_uart *hu) { switch (qca_soc_type(hu)) { + case QCA_QCC2072: case QCA_WCN3950: case QCA_WCN3988: case QCA_WCN3990: @@ -1510,6 +1512,7 @@ static int qca_set_speed(struct hci_uart *hu, enum qca_speed_type speed_type) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: hci_uart_set_flow_control(hu, true); break; @@ -1545,6 +1548,7 @@ static int qca_set_speed(struct hci_uart *hu, enum qca_speed_type speed_type) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: hci_uart_set_flow_control(hu, false); break; @@ -1861,6 +1865,7 @@ static int qca_power_on(struct hci_dev *hdev) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: ret = qca_regulator_init(hu); break; @@ -1960,6 +1965,10 @@ static int qca_setup(struct hci_uart *hu) soc_name = "wcn7850"; break; + case QCA_QCC2072: + soc_name = "qcc2072"; + break; + default: soc_name = "ROME/QCA6390"; } @@ -1983,6 +1992,7 @@ static int qca_setup(struct hci_uart *hu) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: if (qcadev && qcadev->bdaddr_property_broken) hci_set_quirk(hdev, HCI_QUIRK_BDADDR_PROPERTY_BROKEN); @@ -2016,6 +2026,7 @@ static int qca_setup(struct hci_uart *hu) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: break; default: @@ -2169,6 +2180,12 @@ static const struct qca_device_data qca_soc_data_wcn3998 __maybe_unused = { .num_vregs = 4, }; +static const struct qca_device_data qca_soc_data_qcc2072 __maybe_unused = { + .soc_type = QCA_QCC2072, + .num_vregs = 0, + .capabilities = QCA_CAP_WIDEBAND_SPEECH | QCA_CAP_VALID_LE_STATES, +}; + static const struct qca_device_data qca_soc_data_wcn6750 __maybe_unused = { .soc_type = QCA_WCN6750, .vregs = (struct qca_vreg []) { @@ -2271,6 +2288,7 @@ static void qca_power_off(struct hci_uart *hu) case QCA_WCN6750: case QCA_WCN6855: + case QCA_QCC2072: gpiod_set_value_cansleep(qcadev->bt_en, 0); msleep(100); qca_regulator_disable(qcadev); @@ -2417,6 +2435,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) qcadev->btsoc_type = QCA_ROME; switch (qcadev->btsoc_type) { + case QCA_QCC2072: case QCA_QCA6390: case QCA_WCN3950: case QCA_WCN3988: @@ -2445,6 +2464,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCC2072: if (!device_property_present(&serdev->dev, "enable-gpios")) { /* * Backward compatibility with old DT sources. If the @@ -2487,6 +2507,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) if (!qcadev->bt_en && (data->soc_type == QCA_WCN6750 || data->soc_type == QCA_WCN6855 || + data->soc_type == QCA_QCC2072 || data->soc_type == QCA_WCN7850)) power_ctrl_enabled = false; @@ -2495,6 +2516,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) if (IS_ERR(qcadev->sw_ctrl) && (data->soc_type == QCA_WCN6750 || data->soc_type == QCA_WCN6855 || + data->soc_type == QCA_QCC2072 || data->soc_type == QCA_WCN7850)) { dev_err(&serdev->dev, "failed to acquire SW_CTRL gpio\n"); return PTR_ERR(qcadev->sw_ctrl); @@ -2573,6 +2595,7 @@ static void qca_serdev_remove(struct serdev_device *serdev) struct qca_power *power = qcadev->bt_power; switch (qcadev->btsoc_type) { + case QCA_QCC2072: case QCA_WCN3988: case QCA_WCN3990: case QCA_WCN3991: @@ -2782,6 +2805,7 @@ static const struct of_device_id qca_bluetooth_of_match[] = { { .compatible = "qcom,wcn6750-bt", .data = &qca_soc_data_wcn6750}, { .compatible = "qcom,wcn6855-bt", .data = &qca_soc_data_wcn6855}, { .compatible = "qcom,wcn7850-bt", .data = &qca_soc_data_wcn7850}, + { .compatible = "qcom,qcc2072-bt", .data = &qca_soc_data_qcc2072}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match); From 8549859c11ab2a780ade9a93ef5d0f829625cae4 Mon Sep 17 00:00:00 2001 From: Yepuri Siddu Date: Mon, 8 Jun 2026 12:39:25 +0530 Subject: [PATCH 202/243] FROMLIST: arm64: dts: qcom: Add M.2 QCC2072 support on qcs6490-rb3gen2 industrial mezzanine Add DTS support for M.2 QCC2072 on Qualcomm qcs6490-rb3gen2 industrial mezzanine board. Link: https://lore.kernel.org/all/20260529180234.3373056-1-yepuri.siddu@oss.qualcomm.com/ Signed-off-by: Yepuri Siddu --- arch/arm64/boot/dts/qcom/Makefile | 2 + ...3gen2-industrial-mezzanine-m2-cologne.dtso | 110 ++++++++++++++++++ .../qcs6490-rb3gen2-industrial-mezzanine.dtso | 4 + 3 files changed, 116 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 6f33c4e2f09c3..28eccecea5dfe 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -179,11 +179,13 @@ dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine.dtbo qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo +qcs6490-rb3gen2-industrial-mezzanine-m2-cologne-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-thundercomm-minipc-g1iot.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-thundercomm-rubikpi3.dtb +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs8300-ride.dtb qcs8300-ride-el2-dtbs := qcs8300-ride.dtb monaco-el2.dtbo diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso new file mode 100644 index 0000000000000..905a40f4b938f --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. +*/ + +/dts-v1/; +/plugin/; +#include + +&tlmm { + qup_uart4_sleep_cts: qup-uart4-sleep-cts-state { + pins = "gpio16"; + function = "gpio"; + /* + * Configure a bias-bus-hold on CTS to lower power + * usage when Bluetooth is turned off. Bus hold will + * maintain a low power state regardless of whether + * the Bluetooth module drives the pin in either + * direction or leaves the pin fully unpowered. + */ + bias-bus-hold; + }; + + qup_uart4_sleep_rts: qup-uart4-sleep-rts-state { + pins = "gpio17"; + function = "gpio"; + /* + * Configure pull-down on RTS. As RTS is active low + * signal, pull it low to indicate the BT SoC that it + * can wakeup the system anytime from suspend state by + * pulling RX low (by sending wakeup bytes). + */ + bias-pull-down; + }; + + qup_uart4_sleep_rx: qup-uart4-sleep-rx-state { + pins = "gpio19"; + function = "gpio"; + /* + * Configure a pull-up on RX. This is needed to avoid + * garbage data when the TX pin of the Bluetooth module + * is floating which may cause spurious wakeups. + */ + bias-pull-up; + }; + + qup_uart4_sleep_tx: qup-uart4-sleep-tx-state { + pins = "gpio18"; + function = "gpio"; + /* + * Configure pull-up on TX when it isn't actively driven + * to prevent BT SoC from receiving garbage during sleep. + */ + bias-pull-up; + }; + + sw_ctrl: sw-ctrl-state { + pins = "gpio86"; + function = "gpio"; + bias-pull-down; + }; +}; + +&qup_uart4_cts { + /* + * Configure a bias-bus-hold on CTS to lower power + * usage when Bluetooth is turned off. Bus hold will + * maintain a low power state regardless of whether + * the Bluetooth module drives the pin in either + * direction or leaves the pin fully unpowered. + */ + bias-bus-hold; +}; + +&qup_uart4_rts { + /* We'll drive RTS, so no pull */ + drive-strength = <2>; + bias-disable; +}; + +&qup_uart4_rx { + /* + * Configure a pull-up on RX. This is needed to avoid + * garbage data when the TX pin of the Bluetooth module is + * in tri-state (module powered off or not driving the + * signal yet). + */ + bias-pull-up; +}; + +&qup_uart4_tx { + /* We'll drive TX, so no pull */ + drive-strength = <2>; + bias-disable; +}; + +&uart4 { + status = "okay"; + /delete-property/ interrupts; + interrupts-extended = <&intc GIC_SPI 605 IRQ_TYPE_LEVEL_HIGH>, + <&tlmm 19 IRQ_TYPE_EDGE_FALLING>; + pinctrl-names = "default", "sleep"; + pinctrl-1 = <&qup_uart4_sleep_cts>, <&qup_uart4_sleep_rts>, + <&qup_uart4_sleep_tx>, <&qup_uart4_sleep_rx>; + + bluetooth: bluetooth { + compatible = "qcom,qcc2072-bt"; + max-speed = <3200000>; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine.dtso b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine.dtso index 83908db335afa..c96c7f52ea7a0 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine.dtso +++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine.dtso @@ -288,3 +288,7 @@ &wifi { status = "disabled"; }; + +&uart7 { + status = "disabled"; +}; From 816f79aee281312db4e443c6065194f3625267ff Mon Sep 17 00:00:00 2001 From: Yepuri Siddu Date: Mon, 8 Jun 2026 12:41:26 +0530 Subject: [PATCH 203/243] FROMLIST: Bluetooth: qca: combine NVM and calibration data for QCC2072 QCC2072 requires the NVM and calibration data to be delivered to the controller bundled together in an outer TLV of type 4. After loading the NVM file, load the calibration file (qca/ornbcscal.bin) and combine both into a single buffer with the outer TLV header before passing it to qca_tlv_check_data(). The outer TLV header encodes the combined payload length in the high 24 bits and type 4 in the low 8 bits of the type_len field. If the calibration file is unavailable, fall back to downloading the NVM alone. Link: https://lore.kernel.org/all/20260529180431.3373856-1-yepuri.siddu@oss.qualcomm.com/ Signed-off-by: Yepuri Siddu --- drivers/bluetooth/btqca.c | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 8b2ad10ec2d92..ae3f6e7d8ea95 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -614,6 +614,53 @@ static int qca_download_firmware(struct hci_dev *hdev, memcpy(data, fw->data, size); release_firmware(fw); + /* For QCC2072, combine the NVM (type 2) with the calibration file + * into a single TLV of outer type 4. + */ + if (soc_type == QCA_QCC2072 && config->type == TLV_TYPE_NVM) { + const struct firmware *calib_fw = NULL; + char calib_name[32]; + u8 *combined_data = NULL; + size_t inner_len, combined_size; + struct tlv_type_hdr *outer_hdr; + int err; + + snprintf(calib_name, sizeof(calib_name), + "qca/ornbcscal%02x.bin", rom_ver); + err = request_firmware(&calib_fw, calib_name, &hdev->dev); + if (err) { + bt_dev_err(hdev, "QCA Failed to request file: %s (%d)", + calib_name, err); + goto skip_combination; + } + + bt_dev_info(hdev, "QCA Downloading %s", calib_name); + + inner_len = size + calib_fw->size; + combined_size = sizeof(*outer_hdr) + inner_len; + combined_data = vmalloc(combined_size); + if (!combined_data) { + bt_dev_warn(hdev, + "QCA Failed to allocate memory for file: %s", + calib_name); + release_firmware(calib_fw); + goto skip_combination; + } + + outer_hdr = (struct tlv_type_hdr *)combined_data; + /* high 24 bits = payload length, low 8 bits = type */ + outer_hdr->type_len = cpu_to_le32((inner_len << 8) | 4); + memcpy(combined_data + sizeof(*outer_hdr), data, size); + memcpy(combined_data + sizeof(*outer_hdr) + size, + calib_fw->data, calib_fw->size); + release_firmware(calib_fw); + vfree(data); + data = combined_data; + size = combined_size; +skip_combination: + ; + } + ret = qca_tlv_check_data(hdev, config, data, size, soc_type); if (ret) goto out; From 719d1dbde019730aa33dd40bcda40735cc9509b5 Mon Sep 17 00:00:00 2001 From: Ravi Agola Date: Wed, 22 Jul 2026 18:12:31 +0530 Subject: [PATCH 204/243] 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 9e407ef2aaca21e8e9c2ebd8c5135f66c4563b38 Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Fri, 17 Jul 2026 16:56:13 +0530 Subject: [PATCH 205/243] 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 6f7f620b5643804ade610014cae835a114551af3 Mon Sep 17 00:00:00 2001 From: Anandu Krishnan E Date: Wed, 1 Jul 2026 10:12:21 +0530 Subject: [PATCH 206/243] FROMLIST: misc: fastrpc: map ADSP remote heap into remoteproc IOMMU domain When the remoteproc has an IOMMU (kernel running at EL2 without a separate hypervisor), memory carveouts must be explicitly mapped into the remoteproc's IOMMU domain so the DSP can access them. Without this mapping the DSP triggers an SMMU translation fault when accessing the remote heap carveout used for audio PD static process creation. Add has_iommu to fastrpc_channel_ctx, set from the "iommus" property of the remoteproc DT node. When set, map the ADSP remote heap carveout into the remoteproc's IOMMU domain using an identity mapping (IOVA == PA) via iommu_map(), and skip qcom_scm_assign_mem() which is only needed when a separate hypervisor manages inter-VM memory access control. Introduce fastrpc_remote_heap_map() and fastrpc_remote_heap_unmap() helpers to encapsulate the IOMMU domain lookup and map/unmap. Link: https://lore.kernel.org/all/20260618-audio_fix_clean_v3-v1-1-ec1ee66fe455@oss.qualcomm.com/ Signed-off-by: Anandu Krishnan E --- drivers/misc/fastrpc.c | 142 +++++++++++++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 28 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 614992f80be67..0ffb135f27a0b 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -287,6 +288,8 @@ struct fastrpc_channel_ctx { struct list_head invoke_interrupted_mmaps; bool secure; bool unsigned_support; + /* set when remoteproc has an IOMMU; use iommu_map instead of hyp_assign */ + bool has_iommu; u64 dma_mask; const struct fastrpc_soc_data *soc_data; }; @@ -2391,10 +2394,65 @@ static const struct fastrpc_soc_data default_soc_data = { .dma_addr_bits_default = 32, }; +static int fastrpc_remote_heap_map(struct device *rdev, + struct device_node *rproc_node, + struct fastrpc_buf *heap) +{ + struct platform_device *rproc_pdev; + struct iommu_domain *domain; + int ret; + + rproc_pdev = of_find_device_by_node(rproc_node); + if (!rproc_pdev) { + dev_err(rdev, "failed to find remoteproc platform device\n"); + return -ENODEV; + } + + domain = iommu_get_domain_for_dev(&rproc_pdev->dev); + if (!domain) { + put_device(&rproc_pdev->dev); + dev_err(rdev, "no IOMMU domain for remoteproc\n"); + return -ENODEV; + } + + ret = iommu_map(domain, heap->dma_addr, heap->dma_addr, heap->size, + IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); + if (ret) + dev_err(rdev, "failed to map remote heap phys=0x%llx size=0x%llx err=%d\n", + heap->dma_addr, heap->size, ret); + + put_device(&rproc_pdev->dev); + return ret; +} + +static void fastrpc_remote_heap_unmap(struct rpmsg_device *rpdev, + struct fastrpc_buf *heap) +{ + struct device_node *rproc_node; + struct platform_device *rproc_pdev; + struct iommu_domain *domain; + + rproc_node = of_get_parent(of_get_parent(rpdev->dev.of_node)); + if (!rproc_node) + return; + + rproc_pdev = of_find_device_by_node(rproc_node); + of_node_put(rproc_node); + if (!rproc_pdev) + return; + + domain = iommu_get_domain_for_dev(&rproc_pdev->dev); + if (domain) + iommu_unmap(domain, heap->dma_addr, heap->size); + + put_device(&rproc_pdev->dev); +} + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; struct fastrpc_channel_ctx *data; + struct device_node *rproc_node; int i, err, domain_id = -1, vmcount; const char *domain; bool secure_dsp; @@ -2438,9 +2496,12 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) } } + rproc_node = of_get_parent(of_get_parent(rdev->of_node)); + if (rproc_node) + data->has_iommu = of_property_present(rproc_node, "iommus"); + if (domain_id == SDSP_DOMAIN_ID || domain_id == ADSP_DOMAIN_ID) { struct resource res; - u64 src_perms; err = of_reserved_mem_region_to_resource(rdev->of_node, 0, &res); if (!err) { @@ -2449,21 +2510,41 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) kzalloc_obj(*data->remote_heap, GFP_KERNEL); if (!data->remote_heap) { err = -ENOMEM; - goto err_free_data; + goto err_put_node; } data->remote_heap->dma_addr = res.start; data->remote_heap->size = resource_size(&res); + + if (data->has_iommu) { + err = fastrpc_remote_heap_map(rdev, + rproc_node, + data->remote_heap); + if (err) { + kfree(data->remote_heap); + data->remote_heap = NULL; + goto err_put_node; + } + } } - src_perms = BIT(QCOM_SCM_VMID_HLOS); - err = qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms, - data->vmperms, data->vmcount); - if (err) - goto err_free_data; + if (!data->has_iommu) { + u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); + + err = qcom_scm_assign_mem(res.start, + resource_size(&res), + &src_perms, + data->vmperms, + data->vmcount); + if (err) + goto err_put_node; + } } } + of_node_put(rproc_node); + rproc_node = NULL; + secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); data->secure = secure_dsp; data->soc_data = soc_data; @@ -2519,8 +2600,10 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) if (data->secure_fdevice) misc_deregister(&data->secure_fdevice->miscdev); +err_put_node: + of_node_put(rproc_node); + err_free_data: - kfree(data->remote_heap); kfree(data); return err; } @@ -2561,28 +2644,31 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev) list_for_each_entry_safe(buf, b, &cctx->invoke_interrupted_mmaps, node) list_del(&buf->node); - if (cctx->remote_heap && cctx->vmcount) { - u64 src_perms = 0; - struct qcom_scm_vmperm dst_perms; - - for (u32 i = 0; i < cctx->vmcount; i++) - src_perms |= BIT(cctx->vmperms[i].vmid); - - dst_perms.vmid = QCOM_SCM_VMID_HLOS; - dst_perms.perm = QCOM_SCM_PERM_RWX; - - err = qcom_scm_assign_mem(cctx->remote_heap->dma_addr, - cctx->remote_heap->size, &src_perms, - &dst_perms, 1); - if (err) - dev_err(&rpdev->dev, - "Failed to assign memory back to HLOS: dma_addr %pad size %#llx err %d\n", - &cctx->remote_heap->dma_addr, cctx->remote_heap->size, err); + if (cctx->remote_heap) { + if (cctx->has_iommu) { + fastrpc_remote_heap_unmap(rpdev, cctx->remote_heap); + kfree(cctx->remote_heap); + cctx->remote_heap = NULL; + } else if (cctx->vmcount) { + u64 src_perms = 0; + struct qcom_scm_vmperm dst_perms; + + for (u32 i = 0; i < cctx->vmcount; i++) + src_perms |= BIT(cctx->vmperms[i].vmid); + + dst_perms.vmid = QCOM_SCM_VMID_HLOS; + dst_perms.perm = QCOM_SCM_PERM_RWX; + + err = qcom_scm_assign_mem(cctx->remote_heap->dma_addr, + cctx->remote_heap->size, + &src_perms, &dst_perms, 1); + if (!err) { + kfree(cctx->remote_heap); + cctx->remote_heap = NULL; + } + } } - kfree(cctx->remote_heap); - cctx->remote_heap = NULL; - of_platform_depopulate(&rpdev->dev); fastrpc_channel_ctx_put(cctx); From c5ab0d602e44a84e2fac436a17b9ad981a3b4501 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Thu, 21 May 2026 11:15:36 +0530 Subject: [PATCH 207/243] FROMLIST: misc: fastrpc: Move fdlist to invoke context structure The fdlist is currently part of the meta buffer which is set during fastrpc_get_args(), this fdlist is getting recalculated during fastrpc_put_args(). Move fdlist to the invoke context structure to improve maintainability and reduce redundancy. This centralizes its handling and simplifies meta buffer preparation and reading logic. Link: https://lore.kernel.org/all/20260713052236.3033744-2-ekansh.gupta@oss.qualcomm.com/ Reviewed-by: Dmitry Baryshkov Signed-off-by: Ekansh Gupta --- drivers/misc/fastrpc.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 0ffb135f27a0b..7a7c52aea5fcb 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -234,6 +234,7 @@ struct fastrpc_invoke_ctx { int pid; int client_id; u32 sc; + u64 *fdlist; u32 *crc; u64 ctxid; u64 msg_sz; @@ -1107,6 +1108,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) rpra = ctx->buf->virt; list = fastrpc_invoke_buf_start(rpra, ctx->nscalars); pages = fastrpc_phy_page_start(list, ctx->nscalars); + ctx->fdlist = (u64 *)(pages + ctx->nscalars); args = (uintptr_t)ctx->buf->virt + metalen; rlen = pkt_size - metalen; ctx->rpra = rpra; @@ -1209,18 +1211,11 @@ static int fastrpc_put_args(struct fastrpc_invoke_ctx *ctx, union fastrpc_remote_arg *rpra = ctx->rpra; struct fastrpc_user *fl = ctx->fl; struct fastrpc_map *mmap = NULL; - struct fastrpc_invoke_buf *list; - struct fastrpc_phy_page *pages; - u64 *fdlist; - int i, inbufs, outbufs, handles; + u64 *fdlist = ctx->fdlist; + int i, inbufs; int ret = 0; inbufs = REMOTE_SCALARS_INBUFS(ctx->sc); - outbufs = REMOTE_SCALARS_OUTBUFS(ctx->sc); - handles = REMOTE_SCALARS_INHANDLES(ctx->sc) + REMOTE_SCALARS_OUTHANDLES(ctx->sc); - list = fastrpc_invoke_buf_start(rpra, ctx->nscalars); - pages = fastrpc_phy_page_start(list, ctx->nscalars); - fdlist = (uint64_t *)(pages + inbufs + outbufs + handles); for (i = inbufs; i < ctx->nbufs; ++i) { if (!ctx->maps[i]) { From b9da2e7d66f2ec2ebddec76a2897dceca71591ea Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Thu, 21 May 2026 11:15:37 +0530 Subject: [PATCH 208/243] FROMLIST: misc: fastrpc: Replace hardcoded ctxid mask with GENMASK Replace the hardcoded context ID mask (0xFF0) with GENMASK(11, 4) to improve readability and follow kernel bitfield conventions. Use FIELD_PREP and FIELD_GET instead of manual shifts for setting and extracting ctxid values. Link: https://lore.kernel.org/all/20260713052236.3033744-3-ekansh.gupta@oss.qualcomm.com/ Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Ekansh Gupta --- drivers/misc/fastrpc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 7a7c52aea5fcb..715f02d37d50e 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -24,6 +24,7 @@ #include #include #include +#include #define ADSP_DOMAIN_ID (0) #define MDSP_DOMAIN_ID (1) @@ -38,7 +39,7 @@ #define FASTRPC_CTX_MAX (256) #define FASTRPC_INIT_HANDLE 1 #define FASTRPC_DSP_UTILITIES_HANDLE 2 -#define FASTRPC_CTXID_MASK (0xFF0) +#define FASTRPC_CTXID_MASK GENMASK(11, 4) #define INIT_FILELEN_MAX (2 * 1024 * 1024) #define INIT_FILE_NAMELEN_MAX (128) #define FASTRPC_DEVICE_NAME "fastrpc" @@ -600,7 +601,7 @@ static void fastrpc_context_free(struct kref *ref) fastrpc_buf_free(ctx->buf); spin_lock_irqsave(&cctx->lock, flags); - idr_remove(&cctx->ctx_idr, ctx->ctxid >> 4); + idr_remove(&cctx->ctx_idr, FIELD_GET(FASTRPC_CTXID_MASK, ctx->ctxid)); spin_unlock_irqrestore(&cctx->lock, flags); kfree(ctx->maps); @@ -738,7 +739,7 @@ static struct fastrpc_invoke_ctx *fastrpc_context_alloc( spin_unlock_irqrestore(&cctx->lock, flags); goto err_idr; } - ctx->ctxid = ret << 4; + ctx->ctxid = FIELD_PREP(FASTRPC_CTXID_MASK, ret); spin_unlock_irqrestore(&cctx->lock, flags); kref_init(&ctx->refcount); @@ -2684,7 +2685,7 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, if (!cctx) return -ENODEV; - ctxid = ((rsp->ctx & FASTRPC_CTXID_MASK) >> 4); + ctxid = FIELD_GET(FASTRPC_CTXID_MASK, rsp->ctx); spin_lock_irqsave(&cctx->lock, flags); ctx = idr_find(&cctx->ctx_idr, ctxid); From ccc829d709c3c795ed87d0aebdb153e746a5d22c Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Thu, 21 May 2026 11:15:38 +0530 Subject: [PATCH 209/243] FROMLIST: misc: fastrpc: Expand context ID mask for DSP polling mode support Current FastRPC context uses a 12-bit mask: [ID(8 bits)][PD type(4 bits)] = GENMASK(11, 4) This works for normal calls but fails for DSP polling mode. Polling mode expects a 16-bit layout: [15:8] = context ID (8 bits) [7:5] = reserved [4] = async mode bit [3:0] = PD type (4 bits) If async bit (bit 4) is set, DSP disables polling. With current mask, odd IDs can set this bit, causing DSP to skip poll updates. Update FASTRPC_CTXID_MASK to GENMASK(15, 8) so IDs occupy upper byte and lower byte is left for DSP flags and PD type. Reserved bits remain unused. This change is compatible with polling mode and does not break non-polling behavior. Bit layout: [15:8] = CCCCCCCC (context ID) [7:5] = xxx (reserved) [4] = A (async mode) [3:0] = PPPP (PD type) Link: https://lore.kernel.org/all/20260713052236.3033744-4-ekansh.gupta@oss.qualcomm.com/ Reviewed-by: Dmitry Baryshkov Signed-off-by: Ekansh Gupta --- drivers/misc/fastrpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 715f02d37d50e..f539e1a018e3d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -39,7 +39,7 @@ #define FASTRPC_CTX_MAX (256) #define FASTRPC_INIT_HANDLE 1 #define FASTRPC_DSP_UTILITIES_HANDLE 2 -#define FASTRPC_CTXID_MASK GENMASK(11, 4) +#define FASTRPC_CTXID_MASK GENMASK(15, 8) #define INIT_FILELEN_MAX (2 * 1024 * 1024) #define INIT_FILE_NAMELEN_MAX (128) #define FASTRPC_DEVICE_NAME "fastrpc" From 7afcba9c20a55cd0265b0f9254bfce2cd6f21dee Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Thu, 21 May 2026 11:15:39 +0530 Subject: [PATCH 210/243] FROMLIST: misc: fastrpc: Add polling mode support for fastRPC driver For any remote call to DSP, after sending an invocation message, the fastRPC driver waits for a glink response, during which the CPU can enter low power modes. This adds latency to the fastRPC call due to CPU wakeup and scheduling overhead. Add polling mode support where the fastRPC driver polls a shared memory location for completion after sending the invocation, avoiding CPU wakeup and scheduling latency and reducing fastRPC overhead. If the poll times out, the call falls back to the normal interrupt/glink-based completion path. Poll mode is only applied to dynamic modules running in a user PD (handle > FASTRPC_MAX_STATIC_HANDLE), since static/root-PD handles are not expected to benefit from, or require, this optimization. Support is advertised per SoC via fastrpc_soc_data, with a closed exception list for older platforms whose DSP firmware is known to support polling but which otherwise use the default soc_data. Poll mode can be enabled by userspace via the FASTRPC_IOCTL_SET_OPTION ioctl with the FASTRPC_POLL_MODE request id. Since context IDs (ctxid) are allocated from a fixed-size, per-channel cyclic IDR shared by all processes on a DSP, a context ID can be recycled for a new request soon after it is freed. In poll mode the context can be considered complete (and released) as soon as the poll memory is updated, while the corresponding glink COMPLETE response from the DSP may still be in flight. If that response arrives after the ctxid has been reused, it would otherwise match the new context and incorrectly signal completion for it while the DSP may still be operating on the new context's buffers. To prevent this, embed a monotonically increasing per-channel sequence number in the unused upper bits of the ctxid/message context and validate it in the rpmsg callback, dropping any response whose sequence number does not match the current owner of that ctxid slot. Link: https://lore.kernel.org/all/20260713052236.3033744-5-ekansh.gupta@oss.qualcomm.com/ Signed-off-by: Ekansh Gupta --- drivers/misc/fastrpc.c | 195 ++++++++++++++++++++++++++++++++++-- include/uapi/misc/fastrpc.h | 29 ++++++ 2 files changed, 216 insertions(+), 8 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index f539e1a018e3d..8eff67e2a4c2a 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #define ADSP_DOMAIN_ID (0) #define MDSP_DOMAIN_ID (1) @@ -39,7 +41,16 @@ #define FASTRPC_CTX_MAX (256) #define FASTRPC_INIT_HANDLE 1 #define FASTRPC_DSP_UTILITIES_HANDLE 2 +/* + * Maximum handle value for static handles. + * Static handles are pre-defined, fixed numeric values statically assigned + * in the IDL file or FastRPC framework. + */ +#define FASTRPC_MAX_STATIC_HANDLE (20) #define FASTRPC_CTXID_MASK GENMASK(15, 8) +/* Sequence number occupies bits 63:16 of the ctxid / message context */ +#define FASTRPC_CTXID_SEQ_SHIFT 16 +#define FASTRPC_CTXID_SEQ_MASK GENMASK_ULL(63, 16) #define INIT_FILELEN_MAX (2 * 1024 * 1024) #define INIT_FILE_NAMELEN_MAX (128) #define FASTRPC_DEVICE_NAME "fastrpc" @@ -107,6 +118,12 @@ #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) +/* Poll response number from remote processor for call completion */ +#define FASTRPC_POLL_RESPONSE (0xdecaf) + +/* Polling mode timeout limit */ +#define FASTRPC_POLL_MAX_TIMEOUT_US (10000) + struct fastrpc_phy_page { dma_addr_t addr; /* dma address */ u64 size; /* size of contiguous region */ @@ -237,8 +254,14 @@ struct fastrpc_invoke_ctx { u32 sc; u64 *fdlist; u32 *crc; + /* Poll memory that DSP updates */ + u32 *poll_addr; u64 ctxid; u64 msg_sz; + /* work done status flag */ + bool is_work_done; + /* process updates poll memory instead of glink response */ + bool is_polled; struct kref refcount; struct list_head node; /* list of ctxs */ struct completion work; @@ -266,6 +289,7 @@ struct fastrpc_soc_data { u32 sid_pos; u32 dma_addr_bits_cdsp; u32 dma_addr_bits_default; + bool poll_mode_supported; }; struct fastrpc_channel_ctx { @@ -290,6 +314,9 @@ struct fastrpc_channel_ctx { struct list_head invoke_interrupted_mmaps; bool secure; bool unsigned_support; + bool poll_mode_supported; + /* Per-channel sequence counter; incremented on every context allocation */ + atomic_t ctx_seq; /* set when remoteproc has an IOMMU; use iommu_map instead of hyp_assign */ bool has_iommu; u64 dma_mask; @@ -315,6 +342,8 @@ struct fastrpc_user { int client_id; int pd; bool is_secure_dev; + /* Flags poll mode state */ + bool poll_mode; /* Lock for lists */ spinlock_t lock; /* lock for allocations */ @@ -739,7 +768,9 @@ static struct fastrpc_invoke_ctx *fastrpc_context_alloc( spin_unlock_irqrestore(&cctx->lock, flags); goto err_idr; } - ctx->ctxid = FIELD_PREP(FASTRPC_CTXID_MASK, ret); + ctx->ctxid = FIELD_PREP(FASTRPC_CTXID_MASK, ret) | + FIELD_PREP(FASTRPC_CTXID_SEQ_MASK, + (u64)atomic_inc_return(&cctx->ctx_seq)); spin_unlock_irqrestore(&cctx->lock, flags); kref_init(&ctx->refcount); @@ -1014,7 +1045,8 @@ static int fastrpc_get_meta_size(struct fastrpc_invoke_ctx *ctx) sizeof(struct fastrpc_invoke_buf) + sizeof(struct fastrpc_phy_page)) * ctx->nscalars + sizeof(u64) * FASTRPC_MAX_FDLIST + - sizeof(u32) * FASTRPC_MAX_CRCLIST; + sizeof(u32) * FASTRPC_MAX_CRCLIST + + sizeof(u32); return size; } @@ -1110,6 +1142,9 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) list = fastrpc_invoke_buf_start(rpra, ctx->nscalars); pages = fastrpc_phy_page_start(list, ctx->nscalars); ctx->fdlist = (u64 *)(pages + ctx->nscalars); + ctx->poll_addr = (u32 *)((uintptr_t)ctx->fdlist + sizeof(u64) * FASTRPC_MAX_FDLIST + + sizeof(u32) * FASTRPC_MAX_CRCLIST); + args = (uintptr_t)ctx->buf->virt + metalen; rlen = pkt_size - metalen; ctx->rpra = rpra; @@ -1279,6 +1314,71 @@ static int fastrpc_invoke_send(struct fastrpc_session_ctx *sctx, } +static u32 fastrpc_read_poll_addr(struct fastrpc_invoke_ctx *ctx) +{ + dma_rmb(); + return READ_ONCE(*ctx->poll_addr); +} + +static int poll_for_remote_response(struct fastrpc_invoke_ctx *ctx) +{ + u32 val; + int ret; + + /* + * Poll until DSP writes FASTRPC_POLL_RESPONSE into *ctx->poll_addr + * or until another path marks the work done. + */ + ret = read_poll_timeout_atomic(fastrpc_read_poll_addr, val, + (val == FASTRPC_POLL_RESPONSE) || ctx->is_work_done, 1, + FASTRPC_POLL_MAX_TIMEOUT_US, false, ctx); + + if (!ret && val == FASTRPC_POLL_RESPONSE) { + /* + * DSP writes FASTRPC_POLL_RESPONSE to signal successful + * completion via the poll path. + */ + ctx->is_work_done = true; + ctx->retval = 0; + } + + if (ret == -ETIMEDOUT) + ret = -EIO; + + return ret; +} + +static inline int fastrpc_wait_for_response(struct fastrpc_invoke_ctx *ctx, + u32 kernel) +{ + int err = 0; + + if (kernel) { + if (!wait_for_completion_timeout(&ctx->work, 10 * HZ)) + err = -ETIMEDOUT; + } else { + err = wait_for_completion_interruptible(&ctx->work); + } + + return err; +} + +static int fastrpc_wait_for_completion(struct fastrpc_invoke_ctx *ctx, + u32 kernel) +{ + int err; + + if (ctx->is_polled) { + err = poll_for_remote_response(ctx); + if (!err) + return 0; + /* If polling timed out or failed, move to normal response mode */ + ctx->is_polled = false; + } + + return fastrpc_wait_for_response(ctx, kernel); +} + static int fastrpc_internal_invoke(struct fastrpc_user *fl, u32 kernel, u32 handle, u32 sc, struct fastrpc_invoke_args *args) @@ -1314,12 +1414,14 @@ static int fastrpc_internal_invoke(struct fastrpc_user *fl, u32 kernel, if (err) goto bail; - if (kernel) { - if (!wait_for_completion_timeout(&ctx->work, 10 * HZ)) - err = -ETIMEDOUT; - } else { - err = wait_for_completion_interruptible(&ctx->work); - } + /* + * Set message context as polled if the call is for a user PD + * dynamic module and user has enabled poll mode. + */ + if (handle > FASTRPC_MAX_STATIC_HANDLE && fl->pd == USER_PD && fl->poll_mode) + ctx->is_polled = true; + + err = fastrpc_wait_for_completion(ctx, kernel); if (err) goto bail; @@ -1857,6 +1959,35 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, return 0; } +static int fastrpc_set_option(struct fastrpc_user *fl, char __user *argp) +{ + struct fastrpc_ioctl_set_option opt = {0}; + int i; + + if (copy_from_user(&opt, argp, sizeof(opt))) + return -EFAULT; + + for (i = 0; i < ARRAY_SIZE(opt.reserved); i++) { + if (opt.reserved[i] != 0) + return -EINVAL; + } + + if (opt.request_id != FASTRPC_POLL_MODE) + return -EINVAL; + + if (!fl->cctx->poll_mode_supported) + return -EOPNOTSUPP; + + if (opt.value == FASTRPC_POLL_MODE_ENABLE) + fl->poll_mode = true; + else if (opt.value == FASTRPC_POLL_MODE_DISABLE) + fl->poll_mode = false; + else + return -EINVAL; + + return 0; +} + static int fastrpc_get_dsp_info(struct fastrpc_user *fl, char __user *argp) { struct fastrpc_ioctl_capability cap = {0}; @@ -2218,6 +2349,9 @@ static long fastrpc_device_ioctl(struct file *file, unsigned int cmd, case FASTRPC_IOCTL_MEM_UNMAP: err = fastrpc_req_mem_unmap(fl, argp); break; + case FASTRPC_IOCTL_SET_OPTION: + err = fastrpc_set_option(fl, argp); + break; case FASTRPC_IOCTL_GET_DSP_INFO: err = fastrpc_get_dsp_info(fl, argp); break; @@ -2382,6 +2516,7 @@ static const struct fastrpc_soc_data kaanapali_soc_data = { .sid_pos = 56, .dma_addr_bits_cdsp = 34, .dma_addr_bits_default = 32, + .poll_mode_supported = true, }; static const struct fastrpc_soc_data default_soc_data = { @@ -2390,6 +2525,29 @@ static const struct fastrpc_soc_data default_soc_data = { .dma_addr_bits_default = 32, }; +/* + * Exception list for older platforms that use default_soc_data but whose + * DSP firmware supports FastRPC polling mode. + * + * NOTE: This list is intentionally closed. + * Do NOT add new platforms here. New SoCs must advertise polling mode + * support via their soc_data. + */ + +static const struct of_device_id fastrpc_poll_supported_machines[] __maybe_unused = { + { .compatible = "qcom,milos" }, + { .compatible = "qcom,qcs8300" }, + { .compatible = "qcom,sa8775p" }, + { .compatible = "qcom,sar2130p" }, + { .compatible = "qcom,sm8450" }, + { .compatible = "qcom,sm8550" }, + { .compatible = "qcom,sm8650" }, + { .compatible = "qcom,sm8750" }, + { .compatible = "qcom,x1e80100" }, + { .compatible = "qcom,x1p42100" }, + {}, +}; + static int fastrpc_remote_heap_map(struct device *rdev, struct device_node *rproc_node, struct fastrpc_buf *heap) @@ -2544,6 +2702,8 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); data->secure = secure_dsp; data->soc_data = soc_data; + data->poll_mode_supported = soc_data->poll_mode_supported || + of_machine_get_match(fastrpc_poll_supported_machines); switch (domain_id) { case ADSP_DOMAIN_ID: @@ -2573,6 +2733,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) } kref_init(&data->refcount); + atomic_set(&data->ctx_seq, 0); rdev->dma_mask = &data->dma_mask; dma_set_mask_and_coherent(rdev, DMA_BIT_MASK(32)); @@ -2696,7 +2857,25 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, return -ENOENT; } + /* + * Validate the sequence number embedded in the upper bits of the + * context ID. Under high concurrency the IDR slot can be recycled + * for a new request before a late (or duplicate) glink COMPLETE + * response for the previous request arrives. Without this check the + * driver would call complete() on the wrong context, waking a thread + * whose buffers are still being accessed by the DSP. + */ + if (FIELD_GET(FASTRPC_CTXID_SEQ_MASK, rsp->ctx) != + FIELD_GET(FASTRPC_CTXID_SEQ_MASK, ctx->ctxid)) { + dev_dbg(&rpdev->dev, + "Stale glink response ctx 0x%llx (expected seq 0x%llx), dropping\n", + rsp->ctx, + FIELD_GET(FASTRPC_CTXID_SEQ_MASK, ctx->ctxid)); + return 0; + } + ctx->retval = rsp->retval; + ctx->is_work_done = true; complete(&ctx->work); /* diff --git a/include/uapi/misc/fastrpc.h b/include/uapi/misc/fastrpc.h index c6e2925f47e69..ba1ea5ed426c4 100644 --- a/include/uapi/misc/fastrpc.h +++ b/include/uapi/misc/fastrpc.h @@ -16,6 +16,7 @@ #define FASTRPC_IOCTL_INIT_CREATE_STATIC _IOWR('R', 9, struct fastrpc_init_create_static) #define FASTRPC_IOCTL_MEM_MAP _IOWR('R', 10, struct fastrpc_mem_map) #define FASTRPC_IOCTL_MEM_UNMAP _IOWR('R', 11, struct fastrpc_mem_unmap) +#define FASTRPC_IOCTL_SET_OPTION _IOWR('R', 12, struct fastrpc_ioctl_set_option) #define FASTRPC_IOCTL_GET_DSP_INFO _IOWR('R', 13, struct fastrpc_ioctl_capability) /** @@ -67,6 +68,28 @@ enum fastrpc_proc_attr { /* Fastrpc attribute for memory protection of buffers */ #define FASTRPC_ATTR_SECUREMAP (1) +/** + * FASTRPC_POLL_MODE - Enable/disable poll mode for FastRPC invocations + * + * Poll mode is an optimization that allows the CPU to poll shared memory + * for completion instead of waiting for an interrupt-based response. + * This reduces latency for fast-completing operations. + * + * Restrictions: + * - Only supported for USER_PD (User Protection Domain) + * - Only applies to dynamic modules (handle > 20) + * - Static modules always use interrupt-based completion + * + * Values: + * - 0: Disable poll mode (use interrupt-based completion) + * - 1: Enable poll mode (poll shared memory for completion) + */ +#define FASTRPC_POLL_MODE (1) + +/* Values for FASTRPC_POLL_MODE request */ +#define FASTRPC_POLL_MODE_DISABLE 0 +#define FASTRPC_POLL_MODE_ENABLE 1 + struct fastrpc_invoke_args { __u64 ptr; __u64 length; @@ -133,6 +156,12 @@ struct fastrpc_mem_unmap { __s32 reserved[5]; }; +struct fastrpc_ioctl_set_option { + __u32 request_id; /* Request type (e.g., FASTRPC_POLL_MODE) */ + __u32 value; /* Request-specific value */ + __s32 reserved[6]; +}; + struct fastrpc_ioctl_capability { __u32 unused; /* deprecated, ignored by the kernel */ __u32 attribute_id; From a538dfe78fdf1d64ffbdf57ddebae0c0a173a295 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Tue, 28 Jul 2026 13:00:35 +0530 Subject: [PATCH 211/243] Revert "FROMLIST: arm64: dts: qcom: glymur: Add iris video node" This reverts commit 2d19aeec16d5dcc566f4850786bd8f73d6aa0ea2. Signed-off-by: Vishnu Reddy --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 7 -- arch/arm64/boot/dts/qcom/glymur.dtsi | 116 ----------------------- 2 files changed, 123 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 67d9486612144..3966308ee1099 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -871,13 +871,6 @@ }; }; -&iris { - status = "okay"; - video-firmware { - iommus = <&apps_smmu 0x19e2 0x0>; - }; -}; - &pcie3b { vddpe-3v3-supply = <&vreg_nvmesec>; diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 43bc3c600bc2e..fa369db44694e 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -5525,122 +5525,6 @@ status = "disabled"; }; - iris: video-codec@aa00000 { - compatible = "qcom,glymur-iris"; - reg = <0x0 0xaa00000 0x0 0xf0000>; - - clocks = <&gcc GCC_VIDEO_AXI0_CLK>, - <&videocc VIDEO_CC_MVS0C_CLK>, - <&videocc VIDEO_CC_MVS0_CLK>, - <&gcc GCC_VIDEO_AXI0C_CLK>, - <&videocc VIDEO_CC_MVS0C_FREERUN_CLK>, - <&videocc VIDEO_CC_MVS0_FREERUN_CLK>, - <&gcc GCC_VIDEO_AXI1_CLK>, - <&videocc VIDEO_CC_MVS1_CLK>, - <&videocc VIDEO_CC_MVS1_FREERUN_CLK>; - clock-names = "iface", - "core", - "vcodec0_core", - "iface1", - "core_freerun", - "vcodec0_core_freerun", - "iface2", - "vcodec1_core", - "vcodec1_core_freerun"; - - dma-coherent; - - interconnects = <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY - &config_noc SLAVE_VENUS_CFG QCOM_ICC_TAG_ACTIVE_ONLY>, - <&mmss_noc MASTER_VIDEO QCOM_ICC_TAG_ALWAYS - &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; - interconnect-names = "cpu-cfg", - "video-mem"; - - interrupts = ; - - iommus = <&apps_smmu 0x1940 0x0>, - <&apps_smmu 0x1943 0x0>, - <&apps_smmu 0x1944 0x0>, - <&apps_smmu 0x19e0 0x0>; - - - memory-region = <&video_mem>; - - operating-points-v2 = <&iris_opp_table>; - - power-domains = <&videocc VIDEO_CC_MVS0C_GDSC>, - <&videocc VIDEO_CC_MVS0_GDSC>, - <&rpmhpd RPMHPD_MXC>, - <&rpmhpd RPMHPD_MMCX>, - <&videocc VIDEO_CC_MVS1_GDSC>; - power-domain-names = "venus", - "vcodec0", - "mxc", - "mmcx", - "vcodec1"; - - resets = <&gcc GCC_VIDEO_AXI0_CLK_ARES>, - <&gcc GCC_VIDEO_AXI0C_CLK_ARES>, - <&videocc VIDEO_CC_MVS0C_FREERUN_CLK_ARES>, - <&videocc VIDEO_CC_MVS0_FREERUN_CLK_ARES>, - <&gcc GCC_VIDEO_AXI1_CLK_ARES>, - <&videocc VIDEO_CC_MVS1_FREERUN_CLK_ARES>; - reset-names = "bus0", - "bus1", - "core", - "vcodec0_core", - "bus2", - "vcodec1_core"; - - /* - * IRIS firmware is signed by vendors, only - * enable on boards where the proper signed firmware - * is available. - */ - status = "disabled"; - - iris_opp_table: opp-table { - compatible = "operating-points-v2"; - - opp-240000000 { - opp-hz = /bits/ 64 <240000000 240000000 360000000>; - required-opps = <&rpmhpd_opp_svs>, - <&rpmhpd_opp_low_svs>; - }; - - opp-338000000 { - opp-hz = /bits/ 64 <338000000 338000000 507000000>; - required-opps = <&rpmhpd_opp_svs>, - <&rpmhpd_opp_svs>; - }; - - opp-366000000 { - opp-hz = /bits/ 64 <366000000 366000000 549000000>; - required-opps = <&rpmhpd_opp_svs_l1>, - <&rpmhpd_opp_svs_l1>; - }; - - opp-444000000 { - opp-hz = /bits/ 64 <444000000 444000000 666000000>; - required-opps = <&rpmhpd_opp_svs_l1>, - <&rpmhpd_opp_nom>; - }; - - opp-533333334 { - opp-hz = /bits/ 64 <533333334 533333334 800000000>; - required-opps = <&rpmhpd_opp_svs_l1>, - <&rpmhpd_opp_turbo>; - }; - - opp-655000000 { - opp-hz = /bits/ 64 <655000000 655000000 982000000>; - required-opps = <&rpmhpd_opp_nom>, - <&rpmhpd_opp_turbo_l1>; - }; - }; - }; - camcc: clock-controller@ade0000 { compatible = "qcom,glymur-camcc"; reg = <0x0 0x0ade0000 0x0 0x20000>; From 0f3dd3113dbe9e7cc24ab6ac3c310e8458af0486 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Sun, 26 Jul 2026 11:19:43 +0530 Subject: [PATCH 212/243] FROMLIST: arm64: dts: qcom: glymur: Add iris video node Add iris video codec to glymur SoC, which comes with significantly different powering up sequence than previous platforms, thus different clocks and resets. https://lore.kernel.org/all/20260726-glymur-v10-13-de451559e88b@oss.qualcomm.com Reviewed-by: Vikash Garodia Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio Signed-off-by: Vishnu Reddy --- arch/arm64/boot/dts/qcom/glymur.dtsi | 123 +++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index fa369db44694e..e1909defdddd9 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -709,6 +709,10 @@ alignment = <0x0 0x400000>; size = <0x0 0x800000>; }; + + iris_resv: reservation-iris { + iommu-addresses = <&iris_non_pixel 0x0 0x0 0x0 0x25800000>; + }; }; smp2p-adsp { @@ -5967,6 +5971,125 @@ }; }; + iris: video-codec@aa00000 { + compatible = "qcom,glymur-iris"; + reg = <0x0 0x0aa00000 0x0 0xf0000>; + + clocks = <&gcc GCC_VIDEO_AXI0C_CLK>, + <&videocc VIDEO_CC_MVS0C_CLK>, + <&videocc VIDEO_CC_MVS0_CLK>, + <&gcc GCC_VIDEO_AXI0_CLK>, + <&videocc VIDEO_CC_MVS0C_FREERUN_CLK>, + <&videocc VIDEO_CC_MVS0_FREERUN_CLK>, + <&gcc GCC_VIDEO_AXI1_CLK>, + <&videocc VIDEO_CC_MVS1_CLK>, + <&videocc VIDEO_CC_MVS1_FREERUN_CLK>; + clock-names = "core_iface", + "core", + "vcodec0_core", + "vcodec0_iface", + "core_freerun", + "vcodec0_core_freerun", + "vcodec1_iface", + "vcodec1_core", + "vcodec1_core_freerun"; + + dma-coherent; + + interconnects = <&hsc_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_VENUS_CFG QCOM_ICC_TAG_ACTIVE_ONLY>, + <&mmss_noc MASTER_VIDEO QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "cpu-cfg", + "video-mem"; + + interrupts = ; + + memory-region = <&video_mem>; + + operating-points-v2 = <&iris_opp_table>; + + power-domains = <&videocc VIDEO_CC_MVS0C_GDSC>, + <&videocc VIDEO_CC_MVS0_GDSC>, + <&rpmhpd RPMHPD_MXC>, + <&rpmhpd RPMHPD_MMCX>, + <&videocc VIDEO_CC_MVS1_GDSC>; + power-domain-names = "venus", + "vcodec0", + "mxc", + "mmcx", + "vcodec1"; + + resets = <&gcc GCC_VIDEO_AXI0C_CLK_ARES>, + <&gcc GCC_VIDEO_AXI0_CLK_ARES>, + <&videocc VIDEO_CC_MVS0C_FREERUN_CLK_ARES>, + <&videocc VIDEO_CC_MVS0_FREERUN_CLK_ARES>, + <&gcc GCC_VIDEO_AXI1_CLK_ARES>, + <&videocc VIDEO_CC_MVS1_FREERUN_CLK_ARES>; + reset-names = "core_bus", + "vcodec0_bus", + "core", + "vcodec0_core", + "vcodec1_bus", + "vcodec1_core"; + + #address-cells = <2>; + #size-cells = <2>; + + status = "disabled"; + + iris_non_pixel: non-pixel { + iommus = <&apps_smmu 0x1940 0x0>, + <&apps_smmu 0x1944 0x0>, + <&apps_smmu 0x19e0 0x0>; + memory-region = <&iris_resv>; + }; + + pixel { + iommus = <&apps_smmu 0x1943 0x0>; + }; + + iris_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-240000000 { + opp-hz = /bits/ 64 <240000000 240000000 360000000>; + required-opps = <&rpmhpd_opp_svs>, + <&rpmhpd_opp_low_svs>; + }; + + opp-338000000 { + opp-hz = /bits/ 64 <338000000 338000000 507000000>; + required-opps = <&rpmhpd_opp_svs>, + <&rpmhpd_opp_svs>; + }; + + opp-366000000 { + opp-hz = /bits/ 64 <366000000 366000000 549000000>; + required-opps = <&rpmhpd_opp_svs_l1>, + <&rpmhpd_opp_svs_l1>; + }; + + opp-444000000 { + opp-hz = /bits/ 64 <444000000 444000000 666000000>; + required-opps = <&rpmhpd_opp_svs_l1>, + <&rpmhpd_opp_nom>; + }; + + opp-533333334 { + opp-hz = /bits/ 64 <533333334 533333334 800000000>; + required-opps = <&rpmhpd_opp_svs_l1>, + <&rpmhpd_opp_turbo>; + }; + + opp-655000000 { + opp-hz = /bits/ 64 <655000000 655000000 982000000>; + required-opps = <&rpmhpd_opp_nom>, + <&rpmhpd_opp_turbo_l1>; + }; + }; + }; + videocc: clock-controller@aaf0000 { compatible = "qcom,glymur-videocc"; reg = <0x0 0x0aaf0000 0x0 0x10000>; From df51f08b3ef53bac7cc4216b8d2c085dcf4a4799 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Sun, 26 Jul 2026 11:19:44 +0530 Subject: [PATCH 213/243] FROMLIST: arm64: dts: qcom: glymur-crd: Enable iris video codec node The iris hardware block is described in the glymur SoC DTSI, and enabling it here allows the media iris driver to probe and use the video codec functionality. Link: https://lore.kernel.org/all/20260726-glymur-v10-14-de451559e88b@oss.qualcomm.com Reviewed-by: Vikash Garodia Reviewed-by: Dmitry Baryshkov Signed-off-by: Vishnu Reddy --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 3966308ee1099..93bcb56258b9d 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -849,6 +849,16 @@ }; }; +&iris { + firmware-name = "qcom/vpu/vpu36_p4_s7.mbn"; + + status = "okay"; + + video-firmware { + iommus = <&apps_smmu 0x19e2 0x0>; + }; +}; + &lpass_vamacro { pinctrl-0 = <&dmic01_default>, <&dmic23_default>; pinctrl-names = "default"; From 17d49b1bcac818cc103b300b1c47e0e0a3d22104 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:19 +0200 Subject: [PATCH 214/243] FROMLIST: media: qcom: camss: csiphy-3ph: Fix lane mask calculation The lane mask must be multiplied by 2, but this was accidentally omitted. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-1-83da8a8e4e44@ixit.cz Reviewed-by: Vladimir Zapolskiy Suggested-by: Nihal Kumar Gupta Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 75c0aef3725e2..4d99d334dad4c 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1236,7 +1236,7 @@ static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) lane_mask = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; for (i = 0; i < lane_cfg->num_data; i++) - lane_mask |= 1 << lane_cfg->data[i].pos; + lane_mask |= BIT(lane_cfg->data[i].pos * 2); return lane_mask; } From 5ae2ab50574fc363d04c848eca92b42a20a40cb5 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:20 +0200 Subject: [PATCH 215/243] FROMLIST: media: qcom: camss: csiphy: Introduce PHY configuration Read PHY configuration from the device-tree bus-type and save it into the csiphy structure for later use. For C-PHY, skip clock line configuration, as there is none. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-2-83da8a8e4e44@ixit.cz Acked-by: Cory Keitz Reviewed-by: Bryan O'Donoghue Reviewed-by: Frank Li Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss-csiphy.h | 2 ++ drivers/media/platform/qcom/camss/camss.c | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h index 1879826034aac..e8a7445f146db 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.h +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -31,11 +31,13 @@ struct csiphy_lane { /** * struct csiphy_lanes_cfg - CSIPHY lanes configuration + * @phy_cfg: interface selection (C-PHY or D-PHY) * @num_data: number of data lanes * @data: data lanes configuration * @clk: clock lane configuration (only for D-PHY) */ struct csiphy_lanes_cfg { + enum v4l2_mbus_type phy_cfg; int num_data; struct csiphy_lane *data; struct csiphy_lane clk; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index a00af0c718dcd..de848db438bca 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -5566,9 +5566,13 @@ static int camss_parse_endpoint_node(struct device *dev, csd->interface.csiphy_id = vep.base.port; mipi_csi2 = &vep.bus.mipi_csi2; - lncfg->clk.pos = mipi_csi2->clock_lane; - lncfg->clk.pol = mipi_csi2->lane_polarities[0]; lncfg->num_data = mipi_csi2->num_data_lanes; + lncfg->phy_cfg = vep.bus_type; + + if (lncfg->phy_cfg != V4L2_MBUS_CSI2_CPHY) { + lncfg->clk.pos = mipi_csi2->clock_lane; + lncfg->clk.pol = mipi_csi2->lane_polarities[0]; + } lncfg->data = devm_kcalloc(dev, lncfg->num_data, sizeof(*lncfg->data), From b9f74b60366b3e5456a7aa39faaa9cfa7c869254 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:21 +0200 Subject: [PATCH 216/243] FROMLIST: media: qcom: camss: csiphy-3ph: Use odd bits for configuring C-PHY lanes So far, only D-PHY mode was supported, which uses even bits when enabling or masking lanes. For C-PHY configuration, the hardware instead requires using the odd bits. Since there can be unrecognized configuration allow returning failure. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-3-83da8a8e4e44@ixit.cz Acked-by: Cory Keitz Reviewed-by: Bryan O'Donoghue Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 4d99d334dad4c..639d320b313b9 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -14,6 +14,7 @@ #include #include #include +#include #define CSIPHY_3PH_LNn_CFG1(n) (0x000 + 0x100 * (n)) #define CSIPHY_3PH_LNn_CFG1_SWI_REC_DLY_PRG (BIT(7) | BIT(6)) @@ -1230,13 +1231,22 @@ static void csiphy_gen2_config_lanes(struct csiphy_device *csiphy, static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) { - u8 lane_mask; - int i; + u8 lane_mask = 0; + u8 offset = 0; - lane_mask = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; + switch (lane_cfg->phy_cfg) { + case V4L2_MBUS_CSI2_CPHY: + offset = 1; + break; + case V4L2_MBUS_CSI2_DPHY: + lane_mask = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; + break; + default: + break; + } - for (i = 0; i < lane_cfg->num_data; i++) - lane_mask |= BIT(lane_cfg->data[i].pos * 2); + for (int i = 0; i < lane_cfg->num_data; i++) + lane_mask |= BIT((lane_cfg->data[i].pos * 2) + offset); return lane_mask; } @@ -1282,10 +1292,7 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, settle_cnt = csiphy_settle_cnt_calc(link_freq, csiphy->timer_clk_rate); - val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; - for (i = 0; i < c->num_data; i++) - val |= BIT(c->data[i].pos * 2); - + val = csiphy_get_lane_mask(c); writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 5)); From b2b8807ac0018bcda318cedb5a996863016ae749 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:22 +0200 Subject: [PATCH 217/243] FROMLIST: media: qcom: camss: Prepare CSID for C-PHY support Inherit C-PHY information from CSIPHY, so we can configure CSID properly. CSI2_RX_CFG0_PHY_TYPE_SEL must be set to 1, when C-PHY mode is used. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-4-83da8a8e4e44@ixit.cz Reviewed-by: Bryan O'Donoghue Acked-by: Cory Keitz Reviewed-by: Frank Li Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss-csid-gen2.c | 1 + drivers/media/platform/qcom/camss/camss-csid.c | 5 +++++ drivers/media/platform/qcom/camss/camss-csid.h | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen2.c b/drivers/media/platform/qcom/camss/camss-csid-gen2.c index eadcb2f7e3aaa..a5b406cc8ead3 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-gen2.c +++ b/drivers/media/platform/qcom/camss/camss-csid-gen2.c @@ -183,6 +183,7 @@ static void __csid_configure_rx(struct csid_device *csid, val = (lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; val |= phy->csiphy_id << CSI2_RX_CFG0_PHY_NUM_SEL; + val |= csid->phy.phy_sel << CSI2_RX_CFG0_PHY_TYPE_SEL; writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG0); val = 1 << CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN; diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index 48459b46a981b..bcc34ac9dd212 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -1291,6 +1291,11 @@ static int csid_link_setup(struct media_entity *entity, lane_cfg = &csiphy->cfg.csi2->lane_cfg; csid->phy.lane_cnt = lane_cfg->num_data; + if (lane_cfg->phy_cfg == V4L2_MBUS_CSI2_CPHY) + csid->phy.phy_sel = CSID_PHY_SEL_CPHY; + else + csid->phy.phy_sel = CSID_PHY_SEL_DPHY; + csid->phy.lane_assign = csid_get_lane_assign(lane_cfg, lane_cfg->num_data); csid->tpg_linked = false; } diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h index dd9c0868e9c73..2b2094c5e9e16 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.h +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -46,6 +46,11 @@ enum csid_testgen_mode { CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN2 = 9, /* excluding disabled */ }; +enum csid_phy_sel { + CSID_PHY_SEL_DPHY = 0, + CSID_PHY_SEL_CPHY = 1 +}; + struct csid_format_info { u32 code; u8 data_type; @@ -72,6 +77,7 @@ struct csid_phy_config { u32 lane_assign; u32 en_vc; u8 need_vc_update; + enum csid_phy_sel phy_sel; }; struct csid_device; From bee8bb57e5b16671953d31229f80b2de3ef2b1b1 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:23 +0200 Subject: [PATCH 218/243] FROMLIST: media: qcom: camss: Initialize lanes after lane configuration is available The lanes must not be initialized before the driver has access to the lane configuration, as it depends on whether D-PHY or C-PHY mode is in use. Move the lane initialization to csiphy_lanes_enable which is called when the configuration structures are available. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-5-83da8a8e4e44@ixit.cz Co-developed-by: Petr Hodina Signed-off-by: Petr Hodina Reviewed-by: Bryan O'Donoghue Acked-by: Cory Keitz Reviewed-by: Frank Li Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 147 +++++++++++++----- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 639d320b313b9..b205d16d4bcab 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1290,6 +1290,111 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, u8 val; int i; + switch (csiphy->camss->res->version) { + case CAMSS_845: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sdm845[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_2290: + case CAMSS_2390: + case CAMSS_6150: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_qcm2290[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_qcm2290); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_6350: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sm6350[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sm6350); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_7280: + case CAMSS_8250: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sm8250[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8250); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_8280XP: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sc8280xp[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sc8280xp); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_GLYMUR: + case CAMSS_X1E80100: + case CAMSS_X1P42100: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_x1e80100[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_x1e80100); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_8550: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sm8550[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8550); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_8650: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sm8650[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8650); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_8300: + case CAMSS_8775P: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_sa8775p[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + case CAMSS_8750: + case CAMSS_KAANAPALI: + if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { + regs->lane_regs = &lane_regs_2_4_0[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_2_4_0); + } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = NULL; + regs->lane_array_size = 0; + } + break; + default: + break; + } + + if (!regs->lane_regs) + WARN_ONCE(1, "Missing lane_regs definition!\n"); + settle_cnt = csiphy_settle_cnt_calc(link_freq, csiphy->timer_clk_rate); val = csiphy_get_lane_mask(c); @@ -1342,64 +1447,24 @@ static int csiphy_init(struct csiphy_device *csiphy) return -ENOMEM; csiphy->regs = regs; - regs->offset = 0x800; regs->common_status_offset = 0xb0; switch (csiphy->camss->res->version) { - case CAMSS_845: - regs->lane_regs = &lane_regs_sdm845[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845); - break; - case CAMSS_2290: - case CAMSS_2390: - case CAMSS_6150: - regs->lane_regs = &lane_regs_qcm2290[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_qcm2290); - break; - case CAMSS_6350: - regs->lane_regs = &lane_regs_sm6350[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sm6350); - break; - case CAMSS_7280: - case CAMSS_8250: - regs->lane_regs = &lane_regs_sm8250[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8250); - break; - case CAMSS_8280XP: - regs->lane_regs = &lane_regs_sc8280xp[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sc8280xp); - break; case CAMSS_GLYMUR: case CAMSS_X1E80100: case CAMSS_X1P42100: - regs->lane_regs = &lane_regs_x1e80100[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_x1e80100); - regs->offset = 0x1000; - break; case CAMSS_8550: - regs->lane_regs = &lane_regs_sm8550[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8550); - regs->offset = 0x1000; - break; case CAMSS_8650: - regs->lane_regs = &lane_regs_sm8650[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sm8650); regs->offset = 0x1000; break; - case CAMSS_8300: - case CAMSS_8775P: - regs->lane_regs = &lane_regs_sa8775p[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); - break; case CAMSS_8750: case CAMSS_KAANAPALI: /* CSPHY v2.4.0 is backward compatible with v2.3.0 settings */ - regs->lane_regs = &lane_regs_2_4_0[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_2_4_0); regs->offset = 0x1000; regs->common_status_offset = 0x138; break; default: + regs->offset = 0x800; break; } From 84b7d7844246893e008cf5b8f0a8b2fde3501391 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:24 +0200 Subject: [PATCH 219/243] FROMLIST: media: qcom: camss: csiphy-3ph: Add Gen2 v1.1 MIPI CSI-2 C-PHY init Add a PHY configuration sequence for the sdm845 which uses a Qualcomm Gen 2 version 1.1 CSI-2 PHY. The PHY can be configured as two phase or three phase in C-PHY or D-PHY mode. This configuration supports three-phase C-PHY mode. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-6-83da8a8e4e44@ixit.cz Acked-by: Cory Keitz Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index b205d16d4bcab..01bae2d37dc67 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -50,6 +50,13 @@ #define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, common_status_offset, n) \ ((offset) + (common_status_offset) + 0x4 * (n)) +#define CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(n) \ + (0x0100 + ((n) * 0x4)) +#define CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(n) \ + (0x0300 + ((n) * 0x4)) +#define CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(n) \ + (0x0500 + ((n) * 0x4)) + #define CSIPHY_DEFAULT_PARAMS 0 #define CSIPHY_LANE_ENABLE 1 #define CSIPHY_SETTLE_CNT_LOWER_BYTE 2 @@ -146,6 +153,7 @@ csiphy_lane_regs lane_regs_sa8775p[] = { }; /* GEN2 1.0 2PH */ +/* 5 entries: clock + 4 lanes */ static const struct csiphy_lane_regs lane_regs_sdm845[] = { {0x0004, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, @@ -220,6 +228,72 @@ csiphy_lane_regs lane_regs_sdm845[] = { {0x0664, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, }; +/* GEN2 1.0 3PH */ +/* 3 entries: 3 lanes (C-PHY) */ +static const struct +csiphy_lane_regs lane_regs_sdm845_3ph[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(5), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(20), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(6), 0x3e, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(8), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(9), 0x7f, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(10), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(17), 0x12, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(24), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, + + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(5), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(20), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(6), 0x3e, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(8), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(9), 0x7f, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(10), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(17), 0x12, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(24), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, + + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(5), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(20), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(6), 0x3e, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(8), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(9), 0x7f, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(10), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(17), 0x12, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(24), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, + +}; + /* GEN2 1.1 2PH */ static const struct csiphy_lane_regs lane_regs_sc8280xp[] = { @@ -1296,8 +1370,8 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, regs->lane_regs = &lane_regs_sdm845[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845); } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { - regs->lane_regs = NULL; - regs->lane_array_size = 0; + regs->lane_regs = &lane_regs_sdm845_3ph[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845_3ph); } break; case CAMSS_2290: From 43b8192beea811380a0f329ede3f406e08914122 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:25 +0200 Subject: [PATCH 220/243] FROMLIST: media: qcom: camss: csiphy-3ph: Update Gen2 v1.1 MIPI CSI-2 C-PHY init These values should improve C-PHY behaviour. Should match most recent Qualcomm code. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-7-83da8a8e4e44@ixit.cz Acked-by: Cory Keitz Suggested-by: Konrad Dybcio Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 01bae2d37dc67..8e01b23d297a4 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -232,9 +232,9 @@ csiphy_lane_regs lane_regs_sdm845[] = { /* 3 entries: 3 lanes (C-PHY) */ static const struct csiphy_lane_regs lane_regs_sdm845_3ph[] = { - {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x63, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0xac, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0xa5, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, @@ -252,9 +252,9 @@ csiphy_lane_regs lane_regs_sdm845_3ph[] = { {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x63, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0xac, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0xa5, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, @@ -272,9 +272,9 @@ csiphy_lane_regs lane_regs_sdm845_3ph[] = { {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x43, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0xa0, 0x00, CSIPHY_DEFAULT_PARAMS}, - {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x63, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0xac, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0xa5, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, @@ -291,7 +291,6 @@ csiphy_lane_regs lane_regs_sdm845_3ph[] = { {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(25), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(55), 0x51, 0x00, CSIPHY_DEFAULT_PARAMS}, - }; /* GEN2 1.1 2PH */ From 05677979e73468312b27a3ac55dcaacdd9bc350c Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:26 +0200 Subject: [PATCH 221/243] FROMLIST: media: qcom: camss: Account for C-PHY when calculating link frequency Ensure that the link frequency divider correctly accounts for C-PHY operation. The divider differs between D-PHY and C-PHY, as described in the MIPI CSI-2 specification. For more details, see: https://docs.kernel.org/driver-api/media/tx-rx.html#pixel-rate Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-8-83da8a8e4e44@ixit.cz Suggested-by: Sakari Ailus Link: https://docs.kernel.org/driver-api/media/tx-rx.html#pixel-rate Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss-csid.c | 6 ++++-- drivers/media/platform/qcom/camss/camss-csiphy.c | 9 ++++++--- drivers/media/platform/qcom/camss/camss.c | 15 +++++++++++++-- drivers/media/platform/qcom/camss/camss.h | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index bcc34ac9dd212..c8cb6f1a3d3bc 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -540,14 +540,16 @@ static int csid_set_clock_rates(struct csid_device *csid) { struct device *dev = csid->camss->dev; const struct csid_format_info *fmt; + const bool cphy = (csid->phy.phy_sel == CSID_PHY_SEL_CPHY); + s64 link_freq; int i, j; int ret; fmt = csid_get_fmt_entry(csid->res->formats->formats, csid->res->formats->nformats, csid->fmt[MSM_CSIPHY_PAD_SINK].code); - link_freq = camss_get_link_freq(&csid->subdev.entity, fmt->bpp, - csid->phy.lane_cnt); + + link_freq = camss_get_link_freq(&csid->subdev.entity, fmt->bpp, csid->phy.lane_cnt, cphy); if (link_freq < 0) link_freq = 0; diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index 57bc0eda43a18..7904076f48843 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -146,8 +146,9 @@ static int csiphy_set_clock_rates_legacy(struct csiphy_device *csiphy) u8 bpp = csiphy_get_bpp(csiphy->res->formats->formats, csiphy->res->formats->nformats, csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + const bool cphy = (csiphy->cfg.csi2->lane_cfg.phy_cfg == V4L2_MBUS_CSI2_CPHY); - link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); + link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes, cphy); if (link_freq < 0) link_freq = 0; @@ -272,9 +273,10 @@ static int csiphy_stream_on_legacy(struct csiphy_device *csiphy) u8 bpp = csiphy_get_bpp(csiphy->res->formats->formats, csiphy->res->formats->nformats, csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + const bool cphy = (csiphy->cfg.csi2->lane_cfg.phy_cfg == V4L2_MBUS_CSI2_CPHY); u8 val; - link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); + link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes, cphy); if (link_freq < 0) { dev_err(csiphy->camss->dev, @@ -331,13 +333,14 @@ static int csiphy_stream_on(struct csiphy_device *csiphy) union phy_configure_opts dphy_opts = { 0 }; struct device *dev = csiphy->camss->dev; u8 num_lanes = lncfg->num_data; + const bool cphy = (lncfg->phy_cfg == V4L2_MBUS_CSI2_CPHY); s64 link_freq; int i; int ret; dphy_cfg = &dphy_opts.mipi_dphy; - link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); + link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes, cphy); if (link_freq < 0) { dev_err(dev, diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index de848db438bca..bceef21f8c3c6 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -33,6 +33,14 @@ #define CAMSS_CLOCK_MARGIN_NUMERATOR 105 #define CAMSS_CLOCK_MARGIN_DENOMINATOR 100 +/* + * C-PHY encodes data by 16/7 ~ 2.28 bits/symbol + * D-PHY doesn't encode data, thus 16/16 = 1 b/s + */ +#define CAMSS_COMMON_PHY_DIVIDENT 16 +#define CAMSS_CPHY_DIVISOR 7 +#define CAMSS_DPHY_DIVISOR 16 + static const struct parent_dev_ops vfe_parent_dev_ops; static const struct camss_subdev_resources csiphy_res_kaanapali[] = { @@ -5424,19 +5432,22 @@ struct media_pad *camss_find_sensor_pad(struct media_entity *entity) * @entity: Media entity in the current pipeline * @bpp: Number of bits per pixel for the current format * @lanes: Number of lanes in the link to the sensor + * @cphy: If C-PHY encoding is used. * * Return link frequency on success or a negative error code otherwise */ s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp, - unsigned int lanes) + unsigned int lanes, const bool cphy) { struct media_pad *sensor_pad; + unsigned int div = lanes * 2 * (cphy ? CAMSS_CPHY_DIVISOR : + CAMSS_DPHY_DIVISOR); sensor_pad = camss_find_sensor_pad(entity); if (!sensor_pad) return -ENODEV; - return v4l2_get_link_freq(sensor_pad, bpp, 2 * lanes); + return v4l2_get_link_freq(sensor_pad, CAMSS_COMMON_PHY_DIVIDENT * bpp, div); } /* diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 91438769419e5..b447815b353f3 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -175,7 +175,7 @@ int camss_enable_clocks(int nclocks, struct camss_clock *clock, void camss_disable_clocks(int nclocks, struct camss_clock *clock); struct media_pad *camss_find_sensor_pad(struct media_entity *entity); s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp, - unsigned int lanes); + unsigned int lanes, const bool cphy); int camss_get_pixel_clock(struct media_entity *entity, u64 *pixel_clock); int camss_pm_domain_on(struct camss *camss, int id); void camss_pm_domain_off(struct camss *camss, int id); From caf35e121fd5cfde3bb941fc561965891ef5b49f Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Wed, 17 Jun 2026 10:32:27 +0200 Subject: [PATCH 222/243] FROMLIST: media: qcom: camss: Enable C-PHY where available After all the changes done we can now safely enable C-PHY for a SoC where it's available. Link: https://lore.kernel.org/r/20260617-qcom-cphy-v9-9-83da8a8e4e44@ixit.cz Acked-by: Cory Keitz Reviewed-by: Bryan O'Donoghue Reviewed-by: Frank Li Signed-off-by: David Heidelberg Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index bceef21f8c3c6..8c04efef80bb8 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -5565,11 +5565,11 @@ static int camss_parse_endpoint_node(struct device *dev, if (ret) return ret; - /* - * Most SoCs support both D-PHY and C-PHY standards, but currently only - * D-PHY is supported in the driver. - */ - if (vep.bus_type != V4L2_MBUS_CSI2_DPHY) { + switch (vep.bus_type) { + case V4L2_MBUS_CSI2_CPHY: + case V4L2_MBUS_CSI2_DPHY: + break; + default: dev_err(dev, "Unsupported bus type %d\n", vep.bus_type); return -EINVAL; } From b08a6c5af83296643024e2304c778f6d8ea23f5c Mon Sep 17 00:00:00 2001 From: Jigarkumar Zala Date: Fri, 17 Jul 2026 16:13:26 -0700 Subject: [PATCH 223/243] FROMLIST: media: qcom: camss: Program CSIPHY common control registers Program the CSIPHY common-control registers during lane enable so SA8775P uses the required 3-phase 1.5 Gsps reset-release values for both C-PHY and D-PHY. Keep the existing reset-release value for the other CAMSS variants. Link: https://lore.kernel.org/r/20260717231331.1229693-2-anusha.nandi@oss.qualcomm.com Co-developed-by: Anusha Arun Nandi Signed-off-by: Anusha Arun Nandi Signed-off-by: Jigarkumar Zala --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 8e01b23d297a4..899d3fd052dbb 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1148,8 +1148,6 @@ static void csiphy_reset(struct csiphy_device *csiphy) writel_relaxed(0x1, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 0)); usleep_range(5000, 8000); - writel_relaxed(0x0, csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 0)); } static irqreturn_t csiphy_isr(int irq, void *dev) @@ -1478,9 +1476,37 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 6)); - val = 0x02; - writel_relaxed(val, csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 7)); + switch (csiphy->camss->res->version) { + case CAMSS_8300: + case CAMSS_8775P: + if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + val = 0x5A; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 7)); + val = 0xE; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 0)); + } else { + val = 0x02; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 7)); + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 0)); + } + break; + case CAMSS_7280: + case CAMSS_8250: + case CAMSS_8280XP: + case CAMSS_845: + default: + val = 0x02; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 7)); + val = 0x0; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 0)); + break; + } val = 0x00; writel_relaxed(val, csiphy->base + From ad70e63f8dbe923baa93e7e284c8bdd11ff5f0fa Mon Sep 17 00:00:00 2001 From: Jigarkumar Zala Date: Fri, 17 Jul 2026 16:13:27 -0700 Subject: [PATCH 224/243] FROMLIST: media: qcom: camss: Prepare CSID for C-PHY support in gen3 Program the gen3 CSID CSI2 RX PHY type field from the configured PHY type. The RX path needs this value to distinguish C-PHY from D-PHY input, so leaving it at the default makes C-PHY configuration incomplete. Link: https://lore.kernel.org/r/20260717231331.1229693-3-anusha.nandi@oss.qualcomm.com Co-developed-by: Anusha Arun Nandi Signed-off-by: Anusha Arun Nandi Signed-off-by: Jigarkumar Zala --- drivers/media/platform/qcom/camss/camss-csid-gen3.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c index da9458cd178bd..b8be2da78aba7 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c +++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c @@ -66,6 +66,7 @@ #define CSI2_RX_CFG0_VC_MODE 3 #define CSI2_RX_CFG0_DL0_INPUT_SEL 4 #define CSI2_RX_CFG0_PHY_NUM_SEL 20 +#define CSI2_RX_CFG0_PHY_TYPE_SEL 24 #define CSI2_RX_CFG0_TPG_MUX_EN BIT(27) #define CSI2_RX_CFG0_TPG_MUX_SEL GENMASK(29, 28) @@ -114,6 +115,7 @@ static void __csid_configure_rx(struct csid_device *csid, camss = csid->camss; val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; + val |= phy->phy_sel << CSI2_RX_CFG0_PHY_TYPE_SEL; val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; if (camss->tpg && csid->tpg_linked && From 013356932920c5764a8078bd71f4349f7068e3e5 Mon Sep 17 00:00:00 2001 From: Jigarkumar Zala Date: Fri, 17 Jul 2026 16:13:28 -0700 Subject: [PATCH 225/243] FROMLIST: media: qcom: camss: Add sa8775p C-PHY 3ph lane config Add the lane_regs_sa8775p_3ph[] register table for the sa8775p Gen3 CSIPHY at 1.5 Gsps, and select it in csiphy_lanes_enable() for CAMSS_8775P when the endpoint is configured for C-PHY, falling back to the existing sa8775p D-PHY table otherwise. Link: https://lore.kernel.org/r/20260717231331.1229693-4-anusha.nandi@oss.qualcomm.com Co-developed-by: Anusha Arun Nandi Signed-off-by: Anusha Arun Nandi Signed-off-by: Jigarkumar Zala --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 111 +++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 899d3fd052dbb..f717862db6c12 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -56,6 +56,12 @@ (0x0300 + ((n) * 0x4)) #define CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(n) \ (0x0500 + ((n) * 0x4)) +#define CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(n) \ + (0x0900 + ((n) * 0x4)) +#define CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(n) \ + (0x0A00 + ((n) * 0x4)) +#define CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(n) \ + (0x0B00 + ((n) * 0x4)) #define CSIPHY_DEFAULT_PARAMS 0 #define CSIPHY_LANE_ENABLE 1 @@ -152,6 +158,101 @@ csiphy_lane_regs lane_regs_sa8775p[] = { {0x0664, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, }; +/* GEN3 3PH sa8775p 1.5Gsps */ +/* 3 entries: 3 lanes (C-PHY) */ +static const struct +csiphy_lane_regs lane_regs_sa8775p_3ph[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(34), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(35), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(36), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(6), 0x3E, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(9), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(17), 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(25), 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(55), 0x50, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(5), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(20), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(34), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(35), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(36), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(6), 0x3E, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(8), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(9), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(10), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(17), 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(24), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(25), 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(55), 0x50, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(1), 0x06, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(2), 0x00, 0x00, CSIPHY_SETTLE_CNT_HIGHER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(5), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(20), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(34), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(35), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(36), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(6), 0x3E, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(7), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(8), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(9), 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(10), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(11), 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(17), 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(24), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(51), 0x41, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(25), 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(55), 0x50, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(33), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(32), 0x61, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(44), 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(33), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(32), 0x61, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(44), 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(33), 0x20, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(32), 0x61, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(44), 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x24, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x24, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x24, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + /* GEN2 1.0 2PH */ /* 5 entries: clock + 4 lanes */ static const struct @@ -1440,7 +1541,6 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, } break; case CAMSS_8300: - case CAMSS_8775P: if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { regs->lane_regs = &lane_regs_sa8775p[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); @@ -1459,6 +1559,15 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, regs->lane_array_size = 0; } break; + case CAMSS_8775P: + if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { + regs->lane_regs = &lane_regs_sa8775p_3ph[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p_3ph); + } else { + regs->lane_regs = &lane_regs_sa8775p[0]; + regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); + } + break; default: break; } From b508f466f2c205eb9ba1bb925a99f167d909d4a9 Mon Sep 17 00:00:00 2001 From: Anusha Arun Nandi Date: Fri, 17 Jul 2026 16:13:29 -0700 Subject: [PATCH 226/243] FROMLIST: media: qcom: camss: Add sa8300 C-PHY 3ph lane config Share the C-PHY/D-PHY lane_regs selection for CAMSS_8300 so the sa8300 platform uses the same 3ph handling as CAMSS_8775P. Also drop CAMSS_8300 from the missing-C-PHY-table guard now that a C-PHY table is provided. Link: https://lore.kernel.org/r/20260717231331.1229693-5-anusha.nandi@oss.qualcomm.com Co-developed-by: Jigarkumar Zala Signed-off-by: Jigarkumar Zala Signed-off-by: Anusha Arun Nandi --- .../media/platform/qcom/camss/camss-csiphy-3ph-1-0.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index f717862db6c12..bee15b3b81b1b 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1540,15 +1540,6 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, regs->lane_array_size = 0; } break; - case CAMSS_8300: - if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { - regs->lane_regs = &lane_regs_sa8775p[0]; - regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); - } else if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { - regs->lane_regs = NULL; - regs->lane_array_size = 0; - } - break; case CAMSS_8750: case CAMSS_KAANAPALI: if (c->phy_cfg == V4L2_MBUS_CSI2_DPHY) { @@ -1559,6 +1550,7 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, regs->lane_array_size = 0; } break; + case CAMSS_8300: case CAMSS_8775P: if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { regs->lane_regs = &lane_regs_sa8775p_3ph[0]; From 301874ab70c67272b2152a6e5513a681629a7693 Mon Sep 17 00:00:00 2001 From: Anusha Arun Nandi Date: Fri, 17 Jul 2026 16:13:30 -0700 Subject: [PATCH 227/243] FROMLIST: media: qcom: camss: Dynamic data-rate specific C-PHY register settings The C-PHY PHY register configuration needs to vary with the link (symbol) data rate. Until now only a single base lane_regs table was applied per platform, regardless of the negotiated rate. Introduce a data-rate selection mechanism for C-PHY: - Add struct data_rate_reg_info describing a per-bandwidth register override table. - Add csiphy_cphy_data_rate_config(), which selects the first table entry whose bandwidth satisfies the required PHY data rate (derived from the resolved link frequency) and writes those overrides on top of the base lane_regs configuration. The settle-count parameter types are handled specially. When the link frequency cannot be resolved, it falls back to the lowest supported rate. - Add per-data-rate register tables and data_rate_settings tables for sa8775p and 8300(1.5/1.7/2.5/3.5/4.5 GSpS) and sm8250 (2.5/3.5/4.5 GSpS). - Select the appropriate data-rate settings table in csiphy_lanes_enable() for both platforms when the endpoint is configured for C-PHY. Link: https://lore.kernel.org/r/20260717231331.1229693-6-anusha.nandi@oss.qualcomm.com Co-developed-by: Jigarkumar Zala Signed-off-by: Jigarkumar Zala Signed-off-by: Anusha Arun Nandi --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 249 ++++++++++++++++++ .../media/platform/qcom/camss/camss-csiphy.h | 6 + 2 files changed, 255 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index bee15b3b81b1b..82aaeffffbfab 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -72,6 +72,8 @@ #define CSIPHY_3PH_REGS 6 #define CSIPHY_SKEW_CAL 7 +#define CSIPHY_CPHY_DATA_RATE_DEFAULT_IDX 0 + struct csiphy_lane_regs { s32 reg_addr; s32 reg_data; @@ -236,6 +238,9 @@ csiphy_lane_regs lane_regs_sa8775p_3ph[] = { {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(32), 0x61, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(44), 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sa8775p_3ph_1p5Gsps[] = { {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, @@ -253,6 +258,183 @@ csiphy_lane_regs lane_regs_sa8775p_3ph[] = { {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, }; +struct csiphy_lane_regs datarate_sa8775p_3ph_1p7Gsps[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x56, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0xAE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x65, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x56, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0xAE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x65, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x56, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0xAE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x65, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x12, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sa8775p_3ph_2p5Gsps[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0xC8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sa8775p_3ph_3p5Gsps[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sa8775p_3ph_4p5Gsps[] = { + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN1_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN3_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(26), 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(23), 0x46, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(27), 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN5_CSI_3PH_CTRLn_ADDR(3), 0x08, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(45), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN9_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(45), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN10_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(45), 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {CSIPHY_LN11_CSI_3PH_CTRLn_ADDR(34), 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +static struct data_rate_reg_info data_rate_settings_sa8775p_3ph[] = { + { + /* 1.5 GSpS */ + .bandwidth = 1500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sa8775p_3ph_1p5Gsps), + .data_rate_reg_array = datarate_sa8775p_3ph_1p5Gsps, + }, + { + /* 1.7 GSpS */ + .bandwidth = 1700000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sa8775p_3ph_1p7Gsps), + .data_rate_reg_array = datarate_sa8775p_3ph_1p7Gsps, + }, + { + /* 2.5 GSpS */ + .bandwidth = 2500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sa8775p_3ph_2p5Gsps), + .data_rate_reg_array = datarate_sa8775p_3ph_2p5Gsps, + }, + { + /* 3.5 GSpS */ + .bandwidth = 3500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sa8775p_3ph_3p5Gsps), + .data_rate_reg_array = datarate_sa8775p_3ph_3p5Gsps, + }, + { + /* 4.5 GSpS */ + .bandwidth = 4500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sa8775p_3ph_4p5Gsps), + .data_rate_reg_array = datarate_sa8775p_3ph_4p5Gsps, + }, +}; + +struct csiphy_lane_regs datarate_sm8250_3ph_2p5Gsps[] = { + {0x144, 0x32, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x164, 0x0B, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x16C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x344, 0x32, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x364, 0x0B, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x36C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x544, 0x32, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x564, 0x0B, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x56C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sm8250_3ph_3p5Gsps[] = { + {0x144, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x164, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x16C, 0x1D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x9B4, 0x03, 0x01, CSIPHY_DEFAULT_PARAMS}, + {0x344, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x364, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x36C, 0x1D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0xAB4, 0x03, 0x01, CSIPHY_DEFAULT_PARAMS}, + {0x544, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x564, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x56C, 0x1D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0xBB4, 0x03, 0x01, CSIPHY_DEFAULT_PARAMS}, +}; + +struct csiphy_lane_regs datarate_sm8250_3ph_4p5Gsps[] = { + {0x144, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x164, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x16C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x9B4, 0x02, 0x01, CSIPHY_DEFAULT_PARAMS}, + {0x344, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x364, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x36C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0xAB4, 0x02, 0x01, CSIPHY_DEFAULT_PARAMS}, + {0x544, 0xB2, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x564, 0x33, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x56C, 0x25, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0xBB4, 0x02, 0x01, CSIPHY_DEFAULT_PARAMS}, +}; + +static struct data_rate_reg_info data_rate_settings_sm8250_3ph[] = { + { + /* 2.5 GSpS */ + .bandwidth = 2500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sm8250_3ph_2p5Gsps), + .data_rate_reg_array = datarate_sm8250_3ph_2p5Gsps, + }, + { + /* 3.5 GSpS */ + .bandwidth = 3500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sm8250_3ph_3p5Gsps), + .data_rate_reg_array = datarate_sm8250_3ph_3p5Gsps, + }, + { + /* 4.5 GSpS */ + .bandwidth = 4500000000, + .data_rate_reg_array_size = ARRAY_SIZE(datarate_sm8250_3ph_4p5Gsps), + .data_rate_reg_array = datarate_sm8250_3ph_4p5Gsps, + }, +}; + /* GEN2 1.0 2PH */ /* 5 entries: clock + 4 lanes */ static const struct @@ -1374,6 +1556,60 @@ static void csiphy_gen1_config_lanes(struct csiphy_device *csiphy, writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_MISC1(l)); } +/* + * csiphy_cphy_data_rate_config - Apply data-rate specific C-PHY overrides + * + * Applied on top of the base lane_regs. Selects the first table entry whose + * bandwidth >= the data rate derived from @link_freq (highest entry if none + * qualifies, default entry if @link_freq is unknown) and writes its overrides. + */ +static void csiphy_cphy_data_rate_config(struct csiphy_device *csiphy, + struct data_rate_reg_info *settings, + size_t num_settings, + s64 link_freq, u8 settle_cnt) +{ + struct device *dev = csiphy->camss->dev; + const struct csiphy_lane_regs *r; + size_t idx, i; + u32 val; + + if (!settings || !num_settings) + return; + + if (!link_freq) { + /* Link frequency unknown; use the default (lowest) entry. */ + idx = CSIPHY_CPHY_DATA_RATE_DEFAULT_IDX; + } else { + /* First entry that satisfies the rate, else the highest. */ + for (idx = 0; idx < num_settings; idx++) { + if (settings[idx].bandwidth >= link_freq) + break; + } + } + + dev_dbg(dev, + "CSIPHY using specific bandwidth %llu bits/s (entry %zu) for link_freq %lld\n", + settings[idx].bandwidth, idx, link_freq); + + r = settings[idx].data_rate_reg_array; + for (i = 0; i < settings[idx].data_rate_reg_array_size; i++, r++) { + switch (r->csiphy_param_type) { + case CSIPHY_SETTLE_CNT_LOWER_BYTE: + val = settle_cnt & 0xff; + break; + case CSIPHY_SETTLE_CNT_HIGHER_BYTE: + val = (settle_cnt >> 8) & 0xff; + break; + default: + val = r->reg_data; + break; + } + writel_relaxed(val, csiphy->base + r->reg_addr); + if (r->delay_us) + udelay(r->delay_us); + } +} + static void csiphy_gen2_config_lanes(struct csiphy_device *csiphy, u8 settle_cnt) { @@ -1458,6 +1694,8 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, { struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg; struct csiphy_device_regs *regs = csiphy->regs; + struct data_rate_reg_info *data_rate_settings = NULL; + size_t num_data_rate_settings = 0; u8 settle_cnt; u8 val; int i; @@ -1555,6 +1793,8 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY) { regs->lane_regs = &lane_regs_sa8775p_3ph[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p_3ph); + data_rate_settings = data_rate_settings_sa8775p_3ph; + num_data_rate_settings = ARRAY_SIZE(data_rate_settings_sa8775p_3ph); } else { regs->lane_regs = &lane_regs_sa8775p[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_sa8775p); @@ -1618,6 +1858,15 @@ static void csiphy_lanes_enable(struct csiphy_device *csiphy, else csiphy_gen1_config_lanes(csiphy, cfg, settle_cnt); + /* + * For C-PHY platforms with a data-rate settings table, apply the + * data-rate specific overrides on top of the base lane_regs config. + */ + if (c->phy_cfg == V4L2_MBUS_CSI2_CPHY && data_rate_settings) + csiphy_cphy_data_rate_config(csiphy, data_rate_settings, + num_data_rate_settings, + link_freq, settle_cnt); + /* IRQ_MASK registers - disable all interrupts */ for (i = 11; i < 22; i++) { writel_relaxed(0, csiphy->base + diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h index e8a7445f146db..805f040290f62 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.h +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -63,6 +63,12 @@ struct csiphy_formats { const struct csiphy_format_info *formats; }; +struct data_rate_reg_info { + u64 bandwidth; + ssize_t data_rate_reg_array_size; + struct csiphy_lane_regs *data_rate_reg_array; +}; + struct csiphy_device; struct csiphy_hw_ops { From 3bcce109b344b86cfb74af481d8d786dc16fdf76 Mon Sep 17 00:00:00 2001 From: Anusha Arun Nandi Date: Fri, 17 Jul 2026 16:13:31 -0700 Subject: [PATCH 228/243] FROMLIST: media: qcom: camss: validate local/remote endpoint bus-type A link is invalid if the local CSIPHY endpoint and the remote sensor endpoint describe different bus types. Such a mismatch can otherwise go undetected until later probe or streaming failures. Parse the remote endpoint in camss_parse_endpoint_node() and compare its bus-type with the local endpoint. Reject the link with -EINVAL if the two ends disagree, so C-PHY/D-PHY mismatches are caught early during probe. Link: https://lore.kernel.org/r/20260717231331.1229693-7-anusha.nandi@oss.qualcomm.com Co-developed-by: Jigarkumar Zala Signed-off-by: Jigarkumar Zala Signed-off-by: Anusha Arun Nandi --- drivers/media/platform/qcom/camss/camss.c | 28 ++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 8c04efef80bb8..da07ce3f32683 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -5557,7 +5557,9 @@ static int camss_parse_endpoint_node(struct device *dev, { struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg; struct v4l2_mbus_config_mipi_csi2 *mipi_csi2; - struct v4l2_fwnode_endpoint vep = { { 0 } }; + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct v4l2_fwnode_endpoint remote_vep = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct fwnode_handle *remote_ep; unsigned int i; int ret; @@ -5574,6 +5576,30 @@ static int camss_parse_endpoint_node(struct device *dev, return -EINVAL; } + /* Get the remote (sensor) endpoint handle, e.g. imx686_ep1 */ + remote_ep = fwnode_graph_get_remote_endpoint(ep); + if (!remote_ep) { + dev_dbg(dev, "No remote endpoint found for %pfw\n", ep); + return -ENODEV; + } + + /* Parse the remote bus type and release the handle */ + ret = v4l2_fwnode_endpoint_parse(remote_ep, &remote_vep); + fwnode_handle_put(remote_ep); + if (ret) { + dev_dbg(dev, "Failed to parse remote endpoint\n"); + return ret; + } + + /* The local (CSIPHY) and remote (sensor) ends must agree */ + if (vep.bus_type != remote_vep.bus_type) { + dev_dbg(dev, "Bus type mismatch! Local (CSI-PHY): %u, Remote (Sensor): %u\n", + vep.bus_type, remote_vep.bus_type); + return -EINVAL; + } + + dev_dbg(dev, "Verified link: both ends use bus-type %u\n", vep.bus_type); + csd->interface.csiphy_id = vep.base.port; mipi_csi2 = &vep.bus.mipi_csi2; From a364d47f8586aed0d8cd67fecc3043d4525310da Mon Sep 17 00:00:00 2001 From: Linghui Wu Date: Tue, 30 Jun 2026 10:58:04 +0800 Subject: [PATCH 229/243] FROMLIST: wifi: ath10k: snoc: use memcpy_fromio() for MSA ramdump On WCN3990/SNOC the MSA region is mapped with devm_memremap(MEMREMAP_WT). On arm64 such a mapping is not Normal-cacheable, so unaligned accesses to it are not permitted. ath10k_msa_dump_memory() copies the region with a plain memcpy(), whose optimized __pi_memcpy_generic implementation issues wide/unaligned loads. This triggers an alignment fault (FSC=0x21) Oops in ath10k_snoc_fw_crashed_dump() while collecting the devcoredump: Unable to handle kernel paging request ... FSC=0x21: alignment fault pc : __pi_memcpy_generic lr : ath10k_snoc_fw_crashed_dump [ath10k_snoc] The Oops both leaves the firmware RAM dump buffer zeroed (no dump is captured) and crashes the kernel, which in turn breaks modem SSR recovery. Use memcpy_fromio(), which only performs accesses that are valid for such a device-memory mapping. The generic memcpy_fromio() implementation aligns the source before issuing word-sized reads and stores the destination with put_unaligned(), so it is also safe for the coherent DMA allocation used on the non-reserved-memory path. ath11k and ath12k use the same pattern when copying target memory into crash dumps, so call it unconditionally here too. The MEMREMAP_WT pointer is a plain void *, so an explicit __iomem cast is needed; use __force to keep sparse happy. Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.3.7.c5-00107-QCAHLSWMTPL-1 Fixes: 3f14b73c3843 ("ath10k: Enable MSA region dump support for WCN3990") Signed-off-by: Linghui Wu Link: https://lore.kernel.org/linux-wireless/20260727072629.2297208-1-linghui.wu%40oss.qualcomm.com/ --- drivers/net/wireless/ath/ath10k/snoc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index 3106502275781..33c98927e8feb 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -1475,11 +1476,15 @@ static void ath10k_msa_dump_memory(struct ath10k *ar, hdr->length = cpu_to_le32(ar->msa.mem_size); if (current_region->len < ar->msa.mem_size) { - memcpy(buf, ar->msa.vaddr, current_region->len); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + current_region->len); ath10k_warn(ar, "msa dump length is less than msa size %x, %x\n", current_region->len, ar->msa.mem_size); } else { - memcpy(buf, ar->msa.vaddr, ar->msa.mem_size); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + ar->msa.mem_size); } } From d288220fabe63109566fcff0ced31e128bb888c7 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Thu, 23 Jul 2026 22:31:22 +0530 Subject: [PATCH 230/243] WORKAROUND: arm64: dts: qcom: Disable usb hs controller on Glymur CRD On Glymur, there is an issue while working with the FP sensor connected to USB Hs only controller. The issue is as follows: 1) During boot, the usb controller initialises fine, but an error stating that the descriptor read from FP sensor failed and hence the controller is in an unknown state with failed enumeration of FP sensor. 2) At this point, the problem may be resource voting, but if unbind and rebind is done on the controller, enumeration is fine. All resources were thorughly checked and also schematic was checked to ensure that no GPIOs (if any) are left un-initialised. But resources seem to be fine. 3) When in this state, if we try to enter system suspend, GDSC too gets off, and also on resume we see that usb controller goes dead. And further attempts to enter suspend are gated by this dead controller. 4) If we re-bind controller on boot and then let enumration be successful, and then enter suspend, we see a SMMU crash on resume. On upstream code however, enumeration is fine on every reboot. This suspend-resume behavior is gating XO SD and hence disable it on CRD. Signed-off-by: Krishna Kurapati Signed-off-by: Pratham Pratap --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 25 ------------------------ 1 file changed, 25 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index 93bcb56258b9d..539dde4e02d54 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -643,18 +643,6 @@ #phy-cells = <0>; }; - ptn3222_1: redriver@47 { - compatible = "nxp,ptn3222"; - reg = <0x47>; - - reset-gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; - - vdd3v3-supply = <&vreg_l8b_e0_1p50>; - vdd1v8-supply = <&vreg_l15b_e0_1p8>; - - #phy-cells = <0>; - }; - pm8010: pmic@8 { compatible = "qcom,pm8008"; reg = <0x8>; @@ -1406,19 +1394,6 @@ remote-endpoint = <&pmic_glink_ss_in1>; }; -&usb_hs { - status = "okay"; -}; - -&usb_hs_phy { - vdd-supply = <&vreg_l2h_e0_0p72>; - vdda12-supply = <&vreg_l4h_e0_1p2>; - - phys = <&ptn3222_1>; - - status = "okay"; -}; - &usb_mp { status = "okay"; }; From 5d2c2d7398d2c5395a8bc63825a0514ed4fd159d Mon Sep 17 00:00:00 2001 From: Rakesh Kota Date: Fri, 24 Jul 2026 16:30:24 +0530 Subject: [PATCH 231/243] FROMLIST: thermal: qcom-spmi-adc-tm5: drop IIO_VAL_INT check in adc_tm5_get_temp Commit bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") fixed the iio_read_channel_processed_scale to return 0 on success instead of IIO_VAL_INT (1). The existing check in adc_tm5_get_temp() treated a successful return as an error because it expected IIO_VAL_INT. Drop the redundant `ret != IIO_VAL_INT` condition and rely solely on the negative error check. Link: https://lore.kernel.org/all/20260724-adc-tm5-drop-iio-val-int-check-v1-1-0b85a0895dd7@oss.qualcomm.com/ Fixes: bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") Signed-off-by: Rakesh Kota Reviewed-by: Jonathan Cameron --- drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index d7f2e6ca92c2c..d1b086737bcd2 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -369,9 +369,6 @@ static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp) if (ret < 0) return ret; - if (ret != IIO_VAL_INT) - return -EINVAL; - return 0; } From ddb736b66734eced8150adede4f8e235b8a7783b Mon Sep 17 00:00:00 2001 From: Haritha S K Date: Fri, 24 Jul 2026 10:46:27 +0530 Subject: [PATCH 232/243] FROMLIST: arm64: dts: qcom: eliza: Enable cpufreq cooling devices Add cooling-cells property to the CPU nodes to support cpufreq cooling devices. Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260724-eliza-cpufreq-v1-1-38f01e47f0f1@oss.qualcomm.com Signed-off-by: Haritha S K --- arch/arm64/boot/dts/qcom/eliza.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 861c936d79b39..d3c0fca4775d1 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -51,6 +51,7 @@ dynamic-power-coefficient = <100>; qcom,freq-domain = <&cpufreq_hw 0>; + #cooling-cells = <2>; l2_0: l2-cache { compatible = "cache"; @@ -82,6 +83,7 @@ dynamic-power-coefficient = <100>; qcom,freq-domain = <&cpufreq_hw 0>; + #cooling-cells = <2>; }; cpu2: cpu@200 { @@ -100,6 +102,7 @@ dynamic-power-coefficient = <100>; qcom,freq-domain = <&cpufreq_hw 0>; + #cooling-cells = <2>; l2_2: l2-cache { compatible = "cache"; @@ -125,6 +128,7 @@ dynamic-power-coefficient = <238>; qcom,freq-domain = <&cpufreq_hw 1>; + #cooling-cells = <2>; l2_3: l2-cache { compatible = "cache"; @@ -150,6 +154,7 @@ dynamic-power-coefficient = <238>; qcom,freq-domain = <&cpufreq_hw 1>; + #cooling-cells = <2>; l2_4: l2-cache { compatible = "cache"; @@ -175,6 +180,7 @@ dynamic-power-coefficient = <238>; qcom,freq-domain = <&cpufreq_hw 1>; + #cooling-cells = <2>; l2_5: l2-cache { compatible = "cache"; @@ -200,6 +206,7 @@ dynamic-power-coefficient = <238>; qcom,freq-domain = <&cpufreq_hw 1>; + #cooling-cells = <2>; l2_6: l2-cache { compatible = "cache"; @@ -225,6 +232,7 @@ dynamic-power-coefficient = <588>; qcom,freq-domain = <&cpufreq_hw 2>; + #cooling-cells = <2>; l2_7: l2-cache { compatible = "cache"; From 3a884519b83398c6c9c47dcf06463ad78ec9099c Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 22 Jun 2026 11:34:44 +0530 Subject: [PATCH 233/243] WORKAROUND: arch: arm64: qcom: glymur-crd: Disable PCIe6 instance On compute targets, PCIe is dependent on the UEFI. In UEFI, when a PCIe link-up fails on a given root port, the default behavior is to power down that root port. The power-down sequence disables the PHY, removes all clock votes, and drops the Interconnect Bandwidth (ICB) votes that were applied in UEFI for that PCIe controller. The HLOS PCIe driver when probes the PCIe6 root port, but probe fails because the PHY is already powered down and there is no PHY bring-up sequence in HLOS for any controller. Since PCIe probe fails, the Interconnect driver's sync_state callback never gets invoked. As a result, the max boot votes applied to other peripherals are never released or reduced, keeping the system stuck at max bandwidth votes and causing the sync state errors observed, this also blocks XO shutdown. Fix this temporarly, by disabling pcie6 now as modem is not functional in this release. we will revert this after finding proper fix. Signed-off-by: Krishna Chaitanya Chundru --- arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi index e784b538f42e1..2a507d1692232 100644 --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi @@ -522,14 +522,14 @@ pinctrl-0 = <&pcie6_default>; pinctrl-names = "default"; - status = "okay"; + status = "disabled"; }; &pcie6_phy { vdda-phy-supply = <&vreg_l1c_e1_0p82>; vdda-pll-supply = <&vreg_l4f_e1_1p08>; - status = "okay"; + status = "disabled"; }; &pcie6_port0 { From a2ced8808906bb574def108940af56addd80b53f Mon Sep 17 00:00:00 2001 From: Prakash Gupta Date: Tue, 14 Jul 2026 15:57:00 +0530 Subject: [PATCH 234/243] WORKAROUND: iommu/arm-smmu: Work around Qualcomm SMMU-500 TLBIVAL granule issue Qualcomm SMMU-500 has an issue with TLBIVA/TLBIVAL where only the base-page-size entry at the base IOVA is invalidated, leaving stale TLB entries for the rest of the range. This causes use-after-free: after dma_free_coherent() unmaps a large buffer, the device can still access freed physical memory through stale TLB entries. On FastRPC workloads this manifests as ADSP crashes when the ELF loader writes to a freed PA that has been reallocated. Force the TLB invalidation step granule to the minimum page size for all Qualcomm SMMU-500 domains, ensuring each page in the range is individually invalidated. The minimum page size from pgsize_bitmap is used rather than hardcoded 4K to correctly handle 16K and 64K granule configurations. This increases the number of TLB invalidation operations for large ranges, but correctness takes precedence. Signed-off-by: Prakash Gupta Signed-off-by: Sibi Sankar --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 9 +++++++++ drivers/iommu/arm/arm-smmu/arm-smmu.c | 10 ++++++++++ drivers/iommu/arm/arm-smmu/arm-smmu.h | 1 + 3 files changed, 20 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index e2c914fccd6fc..4a98e58159037 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -438,6 +438,15 @@ static int qcom_smmu_init_context(struct arm_smmu_domain *smmu_domain, int cbndx = smmu_domain->cfg.cbndx; smmu_domain->cfg.flush_walk_prefer_tlbiasid = true; + /* + * Qualcomm SMMU-500 has an issue with TLBIVA/TLBIVAL where only + * the base-page-size entry at the base IOVA is invalidated. Glymur + * SoCs boot by default at EL2 and is the currently the only SoC + * affected by it. Force the minimum page granule to ensure the full + * range is covered. + */ + if (of_device_is_compatible(smmu->dev->of_node, "qcom,glymur-smmu-500")) + smmu_domain->cfg.force_min_tlbival_granule = true; client_match = qsmmu->data->client_match; diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 0bd21d206eb3e..75657c74f2910 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -286,6 +286,16 @@ static void arm_smmu_tlb_inv_range_s1(unsigned long iova, size_t size, struct arm_smmu_cfg *cfg = &smmu_domain->cfg; int idx = cfg->cbndx; + /* + * Override the step granule with the minimum supported page size. + * Placed here (rather than in tlb_add_page alone) to cover both + * TLBIVAL and TLBIVA paths. + */ + if (cfg->force_min_tlbival_granule) { + WARN_ON_ONCE(!smmu_domain->domain.pgsize_bitmap); + granule = 1UL << __ffs(smmu_domain->domain.pgsize_bitmap); + } + if (smmu->features & ARM_SMMU_FEAT_COHERENT_WALK) wmb(); diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h index 26d2e33cd328b..85d1f2d7ec506 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.h +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h @@ -359,6 +359,7 @@ struct arm_smmu_cfg { enum arm_smmu_cbar_type cbar; enum arm_smmu_context_fmt fmt; bool flush_walk_prefer_tlbiasid; + bool force_min_tlbival_granule; }; #define ARM_SMMU_INVALID_IRPTNDX 0xff From 76916d976b08e4d1f5347079f02c99b229259555 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Wed, 8 Jul 2026 14:54:08 +0800 Subject: [PATCH 235/243] FROMLIST: wifi: ath11k: fix sporadic WLAN initialization failures On WCN6750 platforms, reboot stress testing occasionally results in WLAN initialization failures after boot. The WPSS firmware reaches the running state successfully, but no WLAN interface is created. Analysis shows that ath11k_ahb depends on the QRTR SMD transport for QMI communication with WPSS firmware. However, this dependency is not currently expressed in Kconfig, allowing qrtr_smd and ath11k_ahb to load in either order when built as modules. If ath11k_ahb is loaded before qrtr_smd becomes available, WLAN initialization may not complete successfully. Make the QRTR and QRTR_SMD dependencies explicit and add a soft dependency to ensure qrtr_smd is loaded before ath11k_ahb. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 00402f49d26f ("ath11k: Add support for WCN6750 device") Link: https://lore.kernel.org/linux-wireless/20260730010505.143458-2-miaoqing.pan@oss.qualcomm.com/ Signed-off-by: Miaoqing Pan --- drivers/net/wireless/ath/ath11k/Kconfig | 3 +++ drivers/net/wireless/ath/ath11k/ahb.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig index 122726f84492a..44b520d2e66ee 100644 --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -14,6 +14,9 @@ config ATH11K_AHB tristate "Atheros ath11k AHB support" depends on ATH11K depends on REMOTEPROC + select RPMSG + select QRTR + select QRTR_SMD help This module adds support for AHB bus diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index f566d699d074a..98fff4b164a32 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1311,5 +1311,6 @@ static struct platform_driver ath11k_ahb_driver = { module_platform_driver(ath11k_ahb_driver); +MODULE_SOFTDEP("pre: qrtr_smd"); MODULE_DESCRIPTION("Driver support for Qualcomm Technologies 802.11ax WLAN AHB devices"); MODULE_LICENSE("Dual BSD/GPL"); From 936014607c2b8121199840ae3137e42b06bbf6ca Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Thu, 9 Jul 2026 10:39:30 +0800 Subject: [PATCH 236/243] FROMLIST: wifi: ath11k: fix NULL dereference in ahb remove when QMI init incomplete On WCN6750, if QMI messages never arrive (for example when qrtr_smd is not ready), WLAN initialization stops before the device is fully registered. In this case ATH11K_FLAG_QMI_FAIL is not set because no QMI event handler is executed. When the driver is removed, ath11k_ahb_remove() still calls ath11k_core_deinit(), which eventually triggers ath11k_ce_cleanup_pipes() on uninitialized CE pipes and results in a NULL pointer dereference in ath11k_hal_srng_access_begin(): ath11k_hal_srng_access_begin+0x14/0x68 [ath11k] ath11k_ce_cleanup_pipes+0x184/0x190 [ath11k] ath11k_pcic_stop+0x24/0x38 [ath11k] ath11k_core_deinit+0xfc/0x1c0 [ath11k] ath11k_ahb_remove+0x38/0xa0 [ath11k_ahb] Fix this by invoking ath11k_ahb_remove_prepare() before the state check and skipping ath11k_core_deinit() when either QMI initialization failed or the device was never registered. If ATH11K_FLAG_REGISTERED is not set, core initialization did not complete and CE pipes may remain uninitialized, making ath11k_core_deinit() unsafe. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 00402f49d26f ("ath11k: Add support for WCN6750 device") Link: https://lore.kernel.org/linux-wireless/20260730010505.143458-3-miaoqing.pan@oss.qualcomm.com/ Signed-off-by: Miaoqing Pan --- drivers/net/wireless/ath/ath11k/ahb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 98fff4b164a32..d272b89ffbcae 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1264,14 +1264,16 @@ static void ath11k_ahb_remove(struct platform_device *pdev) { struct ath11k_base *ab = platform_get_drvdata(pdev); - if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) { + ath11k_ahb_remove_prepare(ab); + + if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags) || + !test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) { ath11k_ahb_power_down(ab, false); ath11k_debugfs_soc_destroy(ab); ath11k_qmi_deinit_service(ab); goto qmi_fail; } - ath11k_ahb_remove_prepare(ab); ath11k_core_deinit(ab); qmi_fail: From 900d23710fe0c165804dc8c25f7df5756d00db0f Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Tue, 21 Jul 2026 15:19:47 +0800 Subject: [PATCH 237/243] FROMLIST: wifi: ath11k: unregister PM notifier on QMI init failure path ath11k_core_init() registers a PM notifier before the QMI server becomes available. If the QMI server never arrives, the device remove() path can take the early-exit path introduced for QMI initialization failures, skipping ath11k_core_deinit(). As a result, the PM notifier remains registered after the ath11k base object has been freed. A subsequent suspend or resume event may invoke the stale notifier and trigger a use-after-free. Fix this by explicitly unregistering the PM notifier in the QMI failure cleanup path before releasing ath11k resources. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 32d93b51bc7e ("wifi: ath11k: choose default PM policy for hibernation") Link: https://lore.kernel.org/linux-wireless/20260730010505.143458-4-miaoqing.pan@oss.qualcomm.com/ Signed-off-by: Miaoqing Pan --- drivers/net/wireless/ath/ath11k/ahb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index d272b89ffbcae..5fd20fe4d4bac 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1271,6 +1271,7 @@ static void ath11k_ahb_remove(struct platform_device *pdev) ath11k_ahb_power_down(ab, false); ath11k_debugfs_soc_destroy(ab); ath11k_qmi_deinit_service(ab); + ath11k_core_pm_notifier_unregister(ab); goto qmi_fail; } From eabc21191bff309327aeba20092eb034207b873f Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 29 Jul 2026 10:03:26 +0530 Subject: [PATCH 238/243] 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 ffab7e0c9e51e..1ec262939a0ce 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; @@ -2920,7 +2912,6 @@ q6apmdai: dais { compatible = "qcom,q6apm-dais"; iommus = <&apps_smmu 0x2001 0x0>; - memory-region = <&audio_cma_mem>; }; }; From 70c668cc14f9dd986dd65b5e5e68490d146db338 Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 22 Jul 2026 09:23:27 +0530 Subject: [PATCH 239/243] Revert "arm64: dts: qcom: hamoa-iot-evk: add MCP2518FD CAN on spi18" This is causing bootup crash on hamoa-iot-evk as the dependency firmware is still not present. [ 8.804602][ T243] pstate: 41400005 (nZcv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) [ 8.804603][ T243] pc : spi_geni_init+0x44/0x858 [spi_geni_qcom] [ 8.804609][ T243] lr : spi_geni_init+0x38/0x858 [spi_geni_qcom] [ 8.804611][ T243] sp : ffff8000821eb7f0 [ 8.804612][ T243] x29: ffff8000821eb7f0 x28: 0000000000000000 x27: 000000000000000c [ 8.804614][ T243] x26: ffffd1d854b495f8 x25: ffff0008129508e0 x24: ffffd1d854b49638 [ 8.804616][ T243] x23: ffff800082d54068 x22: ffff0008124a1000 x21: ffff000802ed2010 [ 8.804618][ T243] x20: a89ad1d854b41a5c x19: ffff0008124a16c0 x18: ffffd1d874482008 [ 8.804620][ T243] x17: ffffd1d874481ff0 x16: ffffd1d872769b48 x15: 0000000000802c80 [ 8.804622][ T243] x14: ffff8000821eb130 x13: 0000000160000008 x12: 0000000000000010 [ 8.804624][ T243] x11: 0000000000000000 x10: ffff00080d8ee868 x9 : ffffd1d872b9c9c4 [ 8.804626][ T243] x8 : ffff8000821eb698 x7 : 0000000000000000 x6 : 0000000000000001 [ 8.804628][ T243] x5 : ffffd1d87402b000 x4 : ffffd1d87402b460 x3 : 0000000000000000 [ 8.804630][ T243] x2 : ffff00080fc94d40 x1 : ffff00080fc94d40 x0 : 0000000000000000 [ 8.804632][ T243] Call trace: [ 8.804633][ T243] spi_geni_init+0x44/0x858 [spi_geni_qcom] (P) [ 8.804636][ T243] spi_geni_probe+0x30c/0x3f0 [spi_geni_qcom] [ 8.804638][ T243] platform_probe+0x64/0xa8 [ 8.804642][ T243] really_probe+0xc8/0x3f0 [ 8.804645][ T243] __driver_probe_device+0x1b4/0x1f0 [ 8.804647][ T243] driver_probe_device+0x44/0x128 Revert the commit for now, to resolve bootup issue. This reverts commit 181bef739979466b6ede95b34d6c94d0250b1c0e. Signed-off-by: Salendarsingh Gaud --- arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts index 27bec1a8496dd..0b9dd8b81f2ca 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts +++ b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts @@ -29,14 +29,6 @@ pinctrl-names = "default"; }; - clocks { - mcp2518fd_osc: clock-40000000 { - compatible = "fixed-clock"; - clock-frequency = <40000000>; - #clock-cells = <0>; - }; - }; - wcd938x: audio-codec { compatible = "qcom,wcd9385-codec"; @@ -1261,19 +1253,6 @@ }; }; -&spi18 { - status = "okay"; - - can@0 { - compatible = "microchip,mcp2518fd"; - reg = <0>; - interrupts-extended = <&tlmm 68 IRQ_TYPE_LEVEL_LOW>; - clocks = <&mcp2518fd_osc>; - spi-max-frequency = <14000000>; - microchip,xstbyen; - }; -}; - &swr0 { status = "okay"; From a18645724ccfa83438a106e8c6d7e0261e5b31cb Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Wed, 5 Aug 2026 10:52:25 +0530 Subject: [PATCH 240/243] 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 6c8372b3f9d187d181d4294aa723877e5e3069af Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Mon, 10 Aug 2026 17:27:23 +0530 Subject: [PATCH 241/243] 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 e3fb330a357f5a369258d8f484eec50b4198657a Mon Sep 17 00:00:00 2001 From: Salendarsingh Gaud Date: Thu, 30 Jul 2026 12:01:03 +0530 Subject: [PATCH 242/243] Add qcom-next log files for 20260730 Adding merge log file and topic_SHA1 file Signed-off-by: Salendarsingh Gaud --- qcom-next/merge.log | 492 +++++++++++++++++++++++++++++++++++++++++++ qcom-next/topic_SHA1 | 48 +++++ 2 files changed, 540 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..600e215d2080e --- /dev/null +++ b/qcom-next/merge.log @@ -0,0 +1,492 @@ +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 +Done, added 74 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 +Done, updated 0 remote(s). +Updating baseline ... +Fetching baseline +latest tag/id is f5098b6bae761e346ebcd9da7f95622c04733cff +Done, updated baseline. +Latest tag is f5098b6bae761e346ebcd9da7f95622c04733cff +Create a new integration branch based on f5098b6bae761e346ebcd9da7f95622c04733cff +Merging topic branches... +------------------------------------------ + ** Merging topic branch: tech/bsp/clk/tech/bsp/clk +Merge successful : tech/bsp/clk : d8a14b94230bd89676818c2ba22807ff63dd45ca : 30 +------------------------------------------ + ** 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 : fedd8c628af7b28992224012386fec13483c514c : 6 +------------------------------------------ + ** Merging topic branch: tech/bus/pci/all/tech/bus/pci/all +Merge successful : tech/bus/pci/all : 9f6650aeee7a42a9fc041aa452d757e33644b29d : 43 +------------------------------------------ + ** 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 : 5d2c2d7398d2c5395a8bc63825a0514ed4fd159d : 12 +------------------------------------------ + ** 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 : 3bcce109b344b86cfb74af481d8d786dc16fdf76 : 47 +------------------------------------------ + ** Merging topic branch: tech/mm/drm/tech/mm/drm +Merge successful : tech/mm/drm : 9e407ef2aaca21e8e9c2ebd8c5135f66c4563b38 : 68 +------------------------------------------ + ** Merging topic branch: tech/mm/fastrpc/tech/mm/fastrpc +Merge successful : tech/mm/fastrpc : 7afcba9c20a55cd0265b0f9254bfce2cd6f21dee : 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 : d5379b446bc58783f702c928299ff2ce01daad94 : 122 +------------------------------------------ + ** 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 : 900d23710fe0c165804dc8c25f7df5756d00db0f : 29 +------------------------------------------ + ** 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 successful : tech/net/bluetooth : 33aa6a3d201fcfea2158f463a0bfb3ddc62df6d0 : 1 +------------------------------------------ + ** 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 : bb887016f3998f14184b38d1ae3ced78a6d80567 : 13 +------------------------------------------ + ** Merging topic branch: tech/pm/thermal/tech/pm/thermal +Merge failed, manual merge +No files need merging +[qcom-next d2d94f01bfcf] Merge remote-tracking branch tech/pm/thermal into qcom-next +Merge successful : tech/pm/thermal : 9fdfd8098c17c12e50322a03da455c354dbe9424 : 12 +------------------------------------------ + ** 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 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 751cca7de8e8] 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 d348cd9f33f5] Merge remote-tracking branch tech/all/dt/qcs8300 into qcom-next +Merge successful : tech/all/dt/qcs8300 : 5cb63a5ffd1a0aaded5cb13cebebea47c16e32c6 : 25 +------------------------------------------ + ** Merging topic branch: tech/all/dt/qcs615/tech/all/dt/qcs615 +Merge successful : tech/all/dt/qcs615 : 9a58c35e3d57c1330179010a8b236b158e243c50 : 9 +------------------------------------------ + ** 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 : ddb736b66734eced8150adede4f8e235b8a7783b : 23 +------------------------------------------ + ** Merging topic branch: tech/all/dt/hamoa/tech/all/dt/hamoa +Merge successful : tech/all/dt/hamoa : 2e751daa3d1219206d7af2646722c8212bbca6b4 : 42 +------------------------------------------ + ** Merging topic branch: tech/all/dt/glymur/tech/all/dt/glymur +Merge successful : tech/all/dt/glymur : d288220fabe63109566fcff0ced31e128bb888c7 : 52 +------------------------------------------ + ** Merging topic branch: tech/all/dt/kaanapali/tech/all/dt/kaanapali +Merge successful : tech/all/dt/kaanapali : 625c17193ba48e659936f4b252f6895c852f82d4 : 21 +------------------------------------------ + ** Merging topic branch: tech/all/dt/pakala/tech/all/dt/pakala +Merge failed, manual merge +No files need merging +[qcom-next 99495ecaf2bf] Merge remote-tracking branch tech/all/dt/pakala into qcom-next +Merge successful : tech/all/dt/pakala : 960924d516b2476657df65d735d81844d9b5bb60 : 13 +------------------------------------------ + ** Merging topic branch: tech/all/config/tech/all/config +Merge successful : tech/all/config : 90a294dc9c7c1006322d48bb86d527a842530812 : 72 +------------------------------------------ + ** Merging topic branch: tech/overlay/dt/tech/overlay/dt +Merge failed, manual merge +No files need merging +[qcom-next fc67250409b9] 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 b4e516704949] Merge remote-tracking branch tech/all/workaround into qcom-next +Merge successful : tech/all/workaround : a2ced8808906bb574def108940af56addd80b53f : 20 +------------------------------------------ + ** 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 1e56ed69ff66] Merge remote-tracking branch tech/noup/debug/all into qcom-next +Merge successful : tech/noup/debug/all : 80bf7fe7995cf60fef5293b067f8bdafa0638d5f : 26 +------------------------------------------ + ** 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 09dc0ec326fe] 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 49eafc700a26] Merge remote-tracking branch early/hwe/shikra/dt into qcom-next +Merge successful : early/hwe/shikra/dt : dd90d88cfaad62aec33e4278bdd69b95aff0480d : 130 +Done, merged 46 topic(s). +[main cc3c6da] New rr-cache entries from ci-merge + 1 file changed, 1875 insertions(+) + create mode 100644 rr-cache/4d6c8343b887a7016bc840660222d66e24012b57/thisimage.2 diff --git a/qcom-next/topic_SHA1 b/qcom-next/topic_SHA1 new file mode 100644 index 0000000000000..7960f4a919fe7 --- /dev/null +++ b/qcom-next/topic_SHA1 @@ -0,0 +1,48 @@ +Name SHA Commits +------------------------------------------------------------------------------------ +tech/bsp/clk d8a14b94230bd89676818c2ba22807ff63dd45ca 30 +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 fedd8c628af7b28992224012386fec13483c514c 6 +tech/bus/pci/all 9f6650aeee7a42a9fc041aa452d757e33644b29d 43 +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 5d2c2d7398d2c5395a8bc63825a0514ed4fd159d 12 +tech/mem/iommu cdc9e8034c64ae939077d3780be625bea6b923e9 9 +tech/mm/audio/all 88b8f29570c5211e654cec357e6a1282f92737b7 8 +tech/mm/camss 3bcce109b344b86cfb74af481d8d786dc16fdf76 47 +tech/mm/drm 9e407ef2aaca21e8e9c2ebd8c5135f66c4563b38 68 +tech/mm/fastrpc 7afcba9c20a55cd0265b0f9254bfce2cd6f21dee 12 +tech/mm/video d5379b446bc58783f702c928299ff2ce01daad94 122 +tech/mm/gpu 0b8d9f42795065bb8e25714b5a169b59ae7e23a2 7 +tech/mproc/rpmsg 55dc46433784180276e93105b359bdce601d492e 1 +tech/net/ath 900d23710fe0c165804dc8c25f7df5756d00db0f 29 +tech/net/bluetooth 33aa6a3d201fcfea2158f463a0bfb3ddc62df6d0 1 +tech/pm/power bb887016f3998f14184b38d1ae3ced78a6d80567 13 +tech/pm/thermal 9fdfd8098c17c12e50322a03da455c354dbe9424 12 +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 5cb63a5ffd1a0aaded5cb13cebebea47c16e32c6 25 +tech/all/dt/qcs615 9a58c35e3d57c1330179010a8b236b158e243c50 9 +tech/all/dt/agatti c828f10cd2c53b7ff2cc061e73b239973ee17bc6 1 +tech/all/dt/eliza ddb736b66734eced8150adede4f8e235b8a7783b 23 +tech/all/dt/hamoa 2e751daa3d1219206d7af2646722c8212bbca6b4 42 +tech/all/dt/glymur d288220fabe63109566fcff0ced31e128bb888c7 52 +tech/all/dt/kaanapali 625c17193ba48e659936f4b252f6895c852f82d4 21 +tech/all/dt/pakala 960924d516b2476657df65d735d81844d9b5bb60 13 +tech/all/config 90a294dc9c7c1006322d48bb86d527a842530812 72 +tech/overlay/dt b895d0e7537337daeeb814cea5cf764664b1bc36 78 +tech/all/workaround a2ced8808906bb574def108940af56addd80b53f 20 +tech/mproc/all 104969c5b070fdc7bf93c7109c625b8b655a93ee 2 +tech/noup/debug/all 80bf7fe7995cf60fef5293b067f8bdafa0638d5f 26 +tech/hwe/unoq a2d85fe547167dbd8fc59618907c20a6f17d2e5a 4 +early/hwe/shikra/drivers b19d2ec290c8ae6fa2d3e8c1955413c4f20d4c3e 184 +early/hwe/shikra/dt dd90d88cfaad62aec33e4278bdd69b95aff0480d 130 From 0e6550c36bc0f507ef945ce07847cdc21795e8c0 Mon Sep 17 00:00:00 2001 From: Tingguo Cheng Date: Tue, 11 Aug 2026 18:39:56 +0800 Subject: [PATCH 243/243] FROMLIST: 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 490137a23d00e..8ff2371c1464f 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1237,11 +1237,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);