Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 02/11] firmware: arm_scmi: Refactor powercap domain layout
From: Philip Radford @ 2026-04-01  9:53 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, quic_sibis,
	dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
	Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>

From: Cristian Marussi <cristian.marussi@arm.com>

SCMIv4.0 introduces the idea of an optional Concurrent Power Limit (CPL)
for each powercap domain, where CPL0 coincides with the one and only
per-domain constraint limit that was available in pre-v4.0 SCMI Powercap.

Refactor the powercap domain descriptors and powercap operations to allow
future v4.0 extensions to cope with multiple CPLs.

While at that generalize the powercap protocol API to drop PAI references
in favour of a more generic avg_ivl naming, since from v4.0 the number and
types of averaging intervals will change in a non-backward compatible way,
so let's bury these changes within the protocol layer.

Last but not least, make the necessary changes to the ARM SCMI Powwercap
driver in order to support all of these new capabilities.

No functional change.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Adjusted domain_id comparitor in scmi_powercap_pai_get]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
 drivers/firmware/arm_scmi/powercap.c | 182 +++++++++++++++++----------
 drivers/powercap/arm_scmi_powercap.c |  50 ++++----
 include/linux/scmi_protocol.h        |  74 +++++++----
 3 files changed, 188 insertions(+), 118 deletions(-)

diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 22aff71c75e9..47aa6dde4a52 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -2,7 +2,7 @@
 /*
  * System Control and Management Interface (SCMI) Powercap Protocol
  *
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
  */
 
 #define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt
@@ -20,6 +20,8 @@
 /* Updated only after ALL the mandatory features for that version are merged */
 #define SCMI_PROTOCOL_SUPPORTED_VERSION		0x20000
 
+#define CPL0	0
+
 enum scmi_powercap_protocol_cmd {
 	POWERCAP_DOMAIN_ATTRIBUTES = 0x3,
 	POWERCAP_CAP_GET = 0x4,
@@ -192,27 +194,26 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
 
 static int
 scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
-				    struct powercap_info *pinfo, u32 domain)
+				    struct powercap_info *pinfo,
+				    struct scmi_powercap_info *dom_info)
 {
 	int ret;
 	u32 flags;
 	struct scmi_xfer *t;
-	struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
 	struct scmi_msg_resp_powercap_domain_attributes *resp;
 
 	ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
-				      sizeof(domain), sizeof(*resp), &t);
+				      sizeof(dom_info->id), sizeof(*resp), &t);
 	if (ret)
 		return ret;
 
-	put_unaligned_le32(domain, t->tx.buf);
+	put_unaligned_le32(dom_info->id, t->tx.buf);
 	resp = t->rx.buf;
 
 	ret = ph->xops->do_xfer(ph, t);
 	if (!ret) {
 		flags = le32_to_cpu(resp->attributes);
 
-		dom_info->id = domain;
 		if (pinfo->notify_cap_cmd)
 			dom_info->notify_powercap_cap_change =
 				SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
@@ -221,12 +222,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
 				SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
 		dom_info->async_powercap_cap_set =
 			SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
-		dom_info->powercap_cap_config =
-			SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+
 		dom_info->powercap_monitoring =
 			SUPPORTS_POWERCAP_MONITORING(flags);
-		dom_info->powercap_pai_config =
-			SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
 		dom_info->powercap_scale_mw =
 			SUPPORTS_POWER_UNITS_MW(flags);
 		dom_info->powercap_scale_uw =
@@ -236,13 +234,29 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
 
 		strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
 
-		dom_info->min_pai = le32_to_cpu(resp->min_pai);
-		dom_info->max_pai = le32_to_cpu(resp->max_pai);
-		dom_info->pai_step = le32_to_cpu(resp->pai_step);
-		ret = scmi_powercap_validate(dom_info->min_pai,
-					     dom_info->max_pai,
-					     dom_info->pai_step,
-					     dom_info->powercap_pai_config);
+		dom_info->sustainable_power =
+			le32_to_cpu(resp->sustainable_power);
+		dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+		dom_info->parent_id = le32_to_cpu(resp->parent_id);
+		if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+		    (dom_info->parent_id >= pinfo->num_domains ||
+		     dom_info->parent_id == dom_info->id)) {
+			dev_err(ph->dev,
+				"Platform reported inconsistent parent ID for domain %d - %s\n",
+				dom_info->id, dom_info->name);
+			ret = -ENODEV;
+		}
+
+		dom_info->cpli[0].avg_ivl_config =
+			SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+		dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
+		dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
+		dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
+		ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+					     dom_info->cpli[0].max_avg_ivl,
+					     dom_info->cpli[0].avg_ivl_step,
+					     dom_info->cpli[0].avg_ivl_config);
 		if (ret) {
 			dev_err(ph->dev,
 				"Platform reported inconsistent PAI config for domain %d - %s\n",
@@ -250,13 +264,15 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
 			goto clean;
 		}
 
-		dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap);
-		dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap);
-		dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step);
-		ret = scmi_powercap_validate(dom_info->min_power_cap,
-					     dom_info->max_power_cap,
-					     dom_info->power_cap_step,
-					     dom_info->powercap_cap_config);
+		dom_info->cpli[0].cap_config =
+			SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+		dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+		dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+		dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+		ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+					     dom_info->cpli[0].max_power_cap,
+					     dom_info->cpli[0].power_cap_step,
+					     dom_info->cpli[0].cap_config);
 		if (ret) {
 			dev_err(ph->dev,
 				"Platform reported inconsistent CAP config for domain %d - %s\n",
@@ -264,19 +280,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
 			goto clean;
 		}
 
-		dom_info->sustainable_power =
-			le32_to_cpu(resp->sustainable_power);
-		dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
-		dom_info->parent_id = le32_to_cpu(resp->parent_id);
-		if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
-		    (dom_info->parent_id >= pinfo->num_domains ||
-		     dom_info->parent_id == dom_info->id)) {
-			dev_err(ph->dev,
-				"Platform reported inconsistent parent ID for domain %d - %s\n",
-				dom_info->id, dom_info->name);
-			ret = -ENODEV;
-		}
+		/* Just using same short name */
+		strscpy(dom_info->cpli[0].name, dom_info->name,
+			SCMI_SHORT_NAME_MAX_SIZE);
 	}
 
 clean:
@@ -288,12 +294,30 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
 	 */
 	if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
 		ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
-					    domain, NULL, dom_info->name,
+					    dom_info->id, NULL, dom_info->name,
 					    SCMI_MAX_STR_SIZE);
 
 	return ret;
 }
 
+static int
+scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
+				struct powercap_info *pinfo, u32 domain)
+{
+	struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+	dom_info->num_cpli = 1;
+	dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+				      sizeof(*dom_info->cpli), GFP_KERNEL);
+	if (!dom_info->cpli)
+		return -ENOMEM;
+
+	dom_info->id = domain;
+	dom_info->cpli[0].id = CPL0;
+
+	return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
+}
+
 static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
 {
 	struct powercap_info *pi = ph->get_priv(ph);
@@ -335,10 +359,11 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
 
 static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
 				   const struct scmi_powercap_info *dom,
-				   u32 *power_cap)
+				   u32 cpl_id, u32 *power_cap)
 {
-	if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
-		*power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
+	if (dom->cpli[cpl_id].fc_info &&
+	    dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
+		*power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
 		trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
 				   dom->id, *power_cap, 0);
 		return 0;
@@ -348,7 +373,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
 }
 
 static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
-				 u32 domain_id, u32 *power_cap)
+				 u32 domain_id, u32 cpl_id, u32 *power_cap)
 {
 	const struct scmi_powercap_info *dom;
 
@@ -359,12 +384,13 @@ static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
 	if (!dom)
 		return -EINVAL;
 
-	return __scmi_powercap_cap_get(ph, dom, power_cap);
+	return __scmi_powercap_cap_get(ph, dom, cpl_id, power_cap);
 }
 
 static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
 				      const struct scmi_powercap_info *pc,
-				      u32 power_cap, bool ignore_dresp)
+				      u32 cpl_id, u32 power_cap,
+				      bool ignore_dresp)
 {
 	int ret;
 	struct scmi_xfer *t;
@@ -406,21 +432,23 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
 
 static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
 				   struct powercap_info *pi, u32 domain_id,
-				   u32 power_cap, bool ignore_dresp)
+				   u32 cpl_id, u32 power_cap, bool ignore_dresp)
 {
 	int ret = -EINVAL;
 	const struct scmi_powercap_info *pc;
 
 	pc = scmi_powercap_dom_info_get(ph, domain_id);
-	if (!pc || !pc->powercap_cap_config)
+	if (!pc || !pc->cpli[cpl_id].cap_config)
 		return ret;
 
 	if (power_cap &&
-	    (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap))
+	    (power_cap < pc->cpli[cpl_id].min_power_cap ||
+	     power_cap > pc->cpli[cpl_id].max_power_cap))
 		return ret;
 
-	if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) {
-		struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP];
+	if (pc->cpli[cpl_id].fc_info &&
+	    pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].set_addr) {
+		struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP];
 
 		iowrite32(power_cap, fci->set_addr);
 		ph->hops->fastchannel_db_ring(fci->set_db);
@@ -428,7 +456,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
 				   domain_id, power_cap, 0);
 		ret = 0;
 	} else {
-		ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
+		ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
 						 ignore_dresp);
 	}
 
@@ -440,7 +468,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
 }
 
 static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
-				 u32 domain_id, u32 power_cap,
+				 u32 domain_id, u32 cpl_id, u32 power_cap,
 				 bool ignore_dresp)
 {
 	struct powercap_info *pi = ph->get_priv(ph);
@@ -459,7 +487,7 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
 		return 0;
 	}
 
-	return __scmi_powercap_cap_set(ph, pi, domain_id,
+	return __scmi_powercap_cap_set(ph, pi, domain_id, cpl_id,
 				       power_cap, ignore_dresp);
 }
 
@@ -485,7 +513,7 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
 }
 
 static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
-				 u32 domain_id, u32 *pai)
+				 u32 domain_id, u32 cpl_id, u32 *pai)
 {
 	struct scmi_powercap_info *dom;
 	struct powercap_info *pi = ph->get_priv(ph);
@@ -494,8 +522,11 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
 		return -EINVAL;
 
 	dom = pi->powercaps + domain_id;
-	if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) {
-		*pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr);
+	if (cpl_id >= dom->num_cpli)
+		return -EINVAL;
+
+	if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
+		*pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
 		trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
 				   domain_id, *pai, 0);
 		return 0;
@@ -504,6 +535,12 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
 	return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
 }
 
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+					  u32 domain_id, u32 cpl_id, u32 *val)
+{
+	return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+}
+
 static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
 				      u32 domain_id, u32 pai)
 {
@@ -528,17 +565,18 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
 }
 
 static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
-				 u32 domain_id, u32 pai)
+				 u32 domain_id, u32 cpl_id, u32 pai)
 {
 	const struct scmi_powercap_info *pc;
 
 	pc = scmi_powercap_dom_info_get(ph, domain_id);
-	if (!pc || !pc->powercap_pai_config || !pai ||
-	    pai < pc->min_pai || pai > pc->max_pai)
+	if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
+	    !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
+	    pai > pc->cpli[cpl_id].max_avg_ivl)
 		return -EINVAL;
 
-	if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) {
-		struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI];
+	if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
+		struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
 
 		trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
 				   domain_id, pai, 0);
@@ -550,6 +588,12 @@ static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
 	return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
 }
 
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+					  u32 domain_id, u32 cpl_id, u32 val)
+{
+	return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+}
+
 static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
 					  u32 domain_id, u32 *average_power,
 					  u32 *pai)
@@ -645,11 +689,11 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
 		if (!pi->states[domain_id].last_pcap)
 			return -EINVAL;
 
-		ret = __scmi_powercap_cap_set(ph, pi, domain_id,
+		ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0,
 					      pi->states[domain_id].last_pcap,
 					      true);
 	} else {
-		ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
+		ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0, 0, true);
 	}
 
 	if (ret)
@@ -660,7 +704,7 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
 	 * server could have ignored a disable request and kept enforcing some
 	 * powercap limit requested by other agents.
 	 */
-	ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+	ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
 	if (!ret)
 		pi->states[domain_id].enabled = !!power_cap;
 
@@ -682,7 +726,7 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
 	 * Report always real platform state; platform could have ignored
 	 * a previous disable request. Default true on any error.
 	 */
-	ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+	ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
 	if (!ret)
 		*enable = !!power_cap;
 
@@ -699,8 +743,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
 	.cap_set = scmi_powercap_cap_set,
 	.cap_enable_set = scmi_powercap_cap_enable_set,
 	.cap_enable_get = scmi_powercap_cap_enable_get,
-	.pai_get = scmi_powercap_pai_get,
-	.pai_set = scmi_powercap_pai_set,
+	.avg_interval_get = scmi_powercap_avg_interval_get,
+	.avg_interval_set = scmi_powercap_avg_interval_set,
 	.measurements_get = scmi_powercap_measurements_get,
 	.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
 	.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
@@ -991,18 +1035,18 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
 	 * formed and correlated by sane parent-child relationship (if any).
 	 */
 	for (domain = 0; domain < pinfo->num_domains; domain++) {
-		ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain);
+		ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
 		if (ret)
 			return ret;
 
 		if (pinfo->powercaps[domain].fastchannels)
 			scmi_powercap_domain_init_fc(ph, domain,
-						     &pinfo->powercaps[domain].fc_info);
+						     &pinfo->powercaps[domain].cpli[CPL0].fc_info);
 
 		/* Grab initial state when disable is supported. */
 		if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
 			ret = __scmi_powercap_cap_get(ph,
-						      &pinfo->powercaps[domain],
+						      &pinfo->powercaps[domain], CPL0,
 						      &pinfo->states[domain].last_pcap);
 			if (ret)
 				return ret;
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index ab66e9a3b1e2..be3007206a74 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -97,7 +97,7 @@ static const struct powercap_zone_ops zone_ops = {
 };
 
 static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
-					u64 power_limit_uw, u32 *norm)
+					u64 power_limit_uw, int cid, u32 *norm)
 {
 	bool scale_mw = spz->info->powercap_scale_mw;
 	u64 val;
@@ -108,9 +108,9 @@ static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
 	 * the range [min_power_cap, max_power_cap] whose bounds are assured to
 	 * be two unsigned 32bits quantities.
 	 */
-	*norm = clamp_t(u32, val, spz->info->min_power_cap,
-			spz->info->max_power_cap);
-	*norm = rounddown(*norm, spz->info->power_cap_step);
+	*norm = clamp_t(u32, val, spz->info->cpli[cid].min_power_cap,
+			spz->info->cpli[cid].max_power_cap);
+	*norm = rounddown(*norm, spz->info->cpli[cid].power_cap_step);
 
 	val = (scale_mw) ? *norm * 1000 : *norm;
 	if (power_limit_uw != val)
@@ -125,12 +125,12 @@ static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid,
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 	u32 norm_power;
 
-	if (!spz->info->powercap_cap_config)
+	if (!spz->info->cpli[cid].cap_config)
 		return -EINVAL;
 
-	scmi_powercap_normalize_cap(spz, power_uw, &norm_power);
+	scmi_powercap_normalize_cap(spz, power_uw, cid, &norm_power);
 
-	return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false);
+	return powercap_ops->cap_set(spz->ph, spz->info->id, cid, norm_power, false);
 }
 
 static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
@@ -140,7 +140,7 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
 	u32 power;
 	int ret;
 
-	ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power);
+	ret = powercap_ops->cap_get(spz->ph, spz->info->id, cid, &power);
 	if (ret)
 		return ret;
 
@@ -152,19 +152,20 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
 }
 
 static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz,
-					 u64 time_us, u32 *norm)
+					 u64 time_us, int cid, u32 *norm)
 {
 	/*
 	 * This cast is lossless since here @time_us is certain to be within the
-	 * range [min_pai, max_pai] whose bounds are assured to be two unsigned
-	 * 32bits quantities.
+	 * range [min_avg_ivl, max_avg_ivl] whose bounds are assured to be two
+	 * unsigned 32bits quantities.
 	 */
-	*norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai);
-	*norm = rounddown(*norm, spz->info->pai_step);
+	*norm = clamp_t(u32, time_us, spz->info->cpli[cid].min_avg_ivl,
+			spz->info->cpli[cid].max_avg_ivl);
+	*norm = rounddown(*norm, spz->info->cpli[cid].avg_ivl_step);
 
 	if (time_us != *norm)
 		dev_dbg(spz->dev,
-			"Normalized %s:PAI - requested:%llu - normalized:%u\n",
+			"Normalized %s:AVG_IVL - requested:%llu - normalized:%u\n",
 			spz->info->name, time_us, *norm);
 }
 
@@ -174,12 +175,13 @@ static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid,
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 	u32 norm_pai;
 
-	if (!spz->info->powercap_pai_config)
+	if (!spz->info->cpli[cid].avg_ivl_config)
 		return -EINVAL;
 
-	scmi_powercap_normalize_time(spz, time_window_us, &norm_pai);
+	scmi_powercap_normalize_time(spz, time_window_us, cid, &norm_pai);
 
-	return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai);
+	return powercap_ops->avg_interval_set(spz->ph, spz->info->id,
+					      cid, norm_pai);
 }
 
 static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
@@ -189,7 +191,7 @@ static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
 	int ret;
 	u32 pai;
 
-	ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai);
+	ret = powercap_ops->avg_interval_get(spz->ph, spz->info->id, cid, &pai);
 	if (ret)
 		return ret;
 
@@ -203,7 +205,7 @@ static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid,
 {
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 
-	*max_power_uw = spz->info->max_power_cap;
+	*max_power_uw = spz->info->cpli[cid].max_power_cap;
 	if (spz->info->powercap_scale_mw)
 		*max_power_uw *= 1000;
 
@@ -215,7 +217,7 @@ static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid,
 {
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 
-	*min_power_uw = spz->info->min_power_cap;
+	*min_power_uw = spz->info->cpli[cid].min_power_cap;
 	if (spz->info->powercap_scale_mw)
 		*min_power_uw *= 1000;
 
@@ -227,7 +229,7 @@ static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz,
 {
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 
-	*time_window_us = spz->info->max_pai;
+	*time_window_us = spz->info->cpli[cid].max_avg_ivl;
 
 	return 0;
 }
@@ -237,14 +239,16 @@ static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz,
 {
 	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
 
-	*time_window_us = (u64)spz->info->min_pai;
+	*time_window_us = (u64)spz->info->cpli[cid].min_avg_ivl;
 
 	return 0;
 }
 
 static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid)
 {
-	return "SCMI power-cap";
+	struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
+
+	return spz->info->cpli[cid].name;
 }
 
 static const struct powercap_zone_constraint_ops constraint_ops  = {
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..9918fb30100c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,7 +2,7 @@
 /*
  * SCMI Message Protocol driver header
  *
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
  */
 
 #ifndef _LINUX_SCMI_PROTOCOL_H
@@ -609,6 +609,35 @@ struct scmi_voltage_proto_ops {
 			 s32 *volt_uV);
 };
 
+/**
+ * struct scmi_powercap_cpl_info  - Describe one CPL - Concurrent Powercap Limit
+ *
+ * @id: CPL ID as advertised by the platform.
+ * @cap_config: CAP configuration support for this CPL.
+ * @min_power_cap: Minimum configurable CAP.
+ * @max_power_cap: Maximum configurable CAP.
+ * @power_cap_step: Step size between two consecutive CAP values.
+ * @avg_ivl_config: Powercap averaging interval configuration support.
+ * @min_avg_ivl: Minimum configurable powercap averaging interval.
+ * @max_avg_ivl: Maximum configurable powercap averaging interval.
+ * @avg_ivl_step: Step size between two consecutive averaging intervals.
+ * @name: name assigned to the Powercap Domain by platform.
+ * @fc_info: Reference to the FastChannels descriptors supported by this CPL
+ */
+struct scmi_powercap_cpl_info {
+	unsigned int id;
+	bool cap_config;
+	unsigned int min_power_cap;
+	unsigned int max_power_cap;
+	unsigned int power_cap_step;
+	bool avg_ivl_config;
+	unsigned int min_avg_ivl;
+	unsigned int max_avg_ivl;
+	unsigned int avg_ivl_step;
+	char name[SCMI_SHORT_NAME_MAX_SIZE];
+	struct scmi_fc_info *fc_info;
+};
+
 /**
  * struct scmi_powercap_info  - Describe one available Powercap domain
  *
@@ -617,21 +646,15 @@ struct scmi_voltage_proto_ops {
  * @notify_powercap_measurement_change: MEASUREMENTS change notifications
  *				       support.
  * @async_powercap_cap_set: Asynchronous CAP set support.
- * @powercap_cap_config: CAP configuration support.
  * @powercap_monitoring: Monitoring (measurements) support.
- * @powercap_pai_config: PAI configuration support.
  * @powercap_scale_mw: Domain reports power data in milliwatt units.
  * @powercap_scale_uw: Domain reports power data in microwatt units.
  *		       Note that, when both @powercap_scale_mw and
  *		       @powercap_scale_uw are set to false, the domain
  *		       reports power data on an abstract linear scale.
+ * @extended_names: Support for long names.
+ * @fastchannels: Support for at least one fastchannel,
  * @name: name assigned to the Powercap Domain by platform.
- * @min_pai: Minimum configurable PAI.
- * @max_pai: Maximum configurable PAI.
- * @pai_step: Step size between two consecutive PAI values.
- * @min_power_cap: Minimum configurable CAP.
- * @max_power_cap: Maximum configurable CAP.
- * @power_cap_step: Step size between two consecutive CAP values.
  * @sustainable_power: Maximum sustainable power consumption for this domain
  *		       under normal conditions.
  * @accuracy: The accuracy with which the power is measured and reported in
@@ -639,30 +662,25 @@ struct scmi_voltage_proto_ops {
  * @parent_id: Identifier of the containing parent power capping domain, or the
  *	       value 0xFFFFFFFF if this powercap domain is a root domain not
  *	       contained in any other domain.
+ * @num_cpli: Number of discovered CPLs.
+ * @cpli: Reference to an array holding descriptors to all the discovered CPLs.
  */
 struct scmi_powercap_info {
 	unsigned int id;
 	bool notify_powercap_cap_change;
 	bool notify_powercap_measurement_change;
 	bool async_powercap_cap_set;
-	bool powercap_cap_config;
 	bool powercap_monitoring;
-	bool powercap_pai_config;
 	bool powercap_scale_mw;
 	bool powercap_scale_uw;
 	bool fastchannels;
 	char name[SCMI_MAX_STR_SIZE];
-	unsigned int min_pai;
-	unsigned int max_pai;
-	unsigned int pai_step;
-	unsigned int min_power_cap;
-	unsigned int max_power_cap;
-	unsigned int power_cap_step;
 	unsigned int sustainable_power;
 	unsigned int accuracy;
 #define SCMI_POWERCAP_ROOT_ZONE_ID     0xFFFFFFFFUL
 	unsigned int parent_id;
-	struct scmi_fc_info *fc_info;
+	unsigned int num_cpli;
+	struct scmi_powercap_cpl_info *cpli;
 };
 
 /**
@@ -691,8 +709,12 @@ struct scmi_powercap_info {
  *		    on the system: for this reason @cap_get and @cap_enable_get
  *		    will always report the final platform view of the powercaps.
  * @cap_enable_get: get the current CAP enable status for the specified domain.
- * @pai_get: get the current PAI value for the specified domain.
- * @pai_set: set the PAI value for the specified domain to the provided value.
+ * @avg_interval_get: get the current averaging interval value for the specified
+ *		      domain. This will get the PAI or CAI depending on the used
+ *		      protocol version.
+ * @avg_interval_set: set the current averaging interval value for the specified
+ *		      domain. This will set the PAI or CAI depending on the used
+ *		      protocol version.
  * @measurements_get: retrieve the current average power measurements for the
  *		      specified domain and the related PAI upon which is
  *		      calculated.
@@ -716,17 +738,17 @@ struct scmi_powercap_proto_ops {
 	const struct scmi_powercap_info __must_check *(*info_get)
 		(const struct scmi_protocol_handle *ph, u32 domain_id);
 	int (*cap_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
-		       u32 *power_cap);
+		       u32 cpl_id, u32 *power_cap);
 	int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
-		       u32 power_cap, bool ignore_dresp);
+		       u32 cpl_id, u32 power_cap, bool ignore_dresp);
 	int (*cap_enable_set)(const struct scmi_protocol_handle *ph,
 			      u32 domain_id, bool enable);
 	int (*cap_enable_get)(const struct scmi_protocol_handle *ph,
 			      u32 domain_id, bool *enable);
-	int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
-		       u32 *pai);
-	int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
-		       u32 pai);
+	int (*avg_interval_get)(const struct scmi_protocol_handle *ph,
+				u32 domain_id, u32 cpl_id, u32 *val);
+	int (*avg_interval_set)(const struct scmi_protocol_handle *ph,
+				u32 domain_id, u32 cpl_id, u32 val);
 	int (*measurements_get)(const struct scmi_protocol_handle *ph,
 				u32 domain_id, u32 *average_power, u32 *pai);
 	int (*measurements_threshold_set)(const struct scmi_protocol_handle *ph,
-- 
2.47.3



^ permalink raw reply related

* [PATCH v4 01/11] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers
From: Philip Radford @ 2026-04-01  9:53 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, quic_sibis,
	dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
	Philip Radford
In-Reply-To: <20260401095349.2217840-1-philip.radford@arm.com>

From: Cristian Marussi <cristian.marussi@arm.com>

Starting from SCMIv4.0 the protocols DESCRIBE_FASTCHANNEL commands allow
to specify one additional per-protocol custom field in the outgoing message
request in order to, optionally, further narrow down the scope of the
fastchannel discovery request; the related message-reply format is instead
unchanged.

Add an optional custom protocol parameter to the common fastchannel helper
so as to enable the caller to choose the kind of message to send based on
the detected protocol version.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
 drivers/firmware/arm_scmi/driver.c    | 12 ++++++++++--
 drivers/firmware/arm_scmi/perf.c      |  8 ++++----
 drivers/firmware/arm_scmi/powercap.c  |  8 ++++----
 drivers/firmware/arm_scmi/protocols.h |  2 +-
 4 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 3e76a3204ba4..f7b71cf19d46 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1872,6 +1872,11 @@ static int scmi_iterator_run(void *iter)
 struct scmi_msg_get_fc_info {
 	__le32 domain;
 	__le32 message_id;
+	__le32 custom;
+#define MSG_FC_INFO_SZ_EXTENDED	\
+	(sizeof(struct scmi_msg_get_fc_info))
+#define MSG_FC_INFO_SZ		\
+	(sizeof(struct scmi_msg_get_fc_info) - sizeof(__le32))
 };
 
 struct scmi_msg_resp_desc_fc {
@@ -1900,7 +1905,7 @@ struct scmi_msg_resp_desc_fc {
 static void
 scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
 			     u8 describe_id, u32 message_id, u32 valid_size,
-			     u32 domain, void __iomem **p_addr,
+			     u32 domain, u32 *custom, void __iomem **p_addr,
 			     struct scmi_fc_db_info **p_db, u32 *rate_limit)
 {
 	int ret;
@@ -1931,13 +1936,16 @@ scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
 	}
 
 	ret = ph->xops->xfer_get_init(ph, describe_id,
-				      sizeof(*info), sizeof(*resp), &t);
+				      custom ? MSG_FC_INFO_SZ_EXTENDED :
+				      MSG_FC_INFO_SZ, sizeof(*resp), &t);
 	if (ret)
 		goto err_out;
 
 	info = t->tx.buf;
 	info->domain = cpu_to_le32(domain);
 	info->message_id = cpu_to_le32(message_id);
+	if (custom)
+		info->custom = cpu_to_le32(*custom);
 
 	/*
 	 * Bail out on error leaving fc_info addresses zeroed; this includes
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 4583d02bee1c..7f283f457e02 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -835,25 +835,25 @@ static void scmi_perf_domain_init_fc(const struct scmi_protocol_handle *ph,
 		return;
 
 	ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
-				   PERF_LEVEL_GET, 4, dom->id,
+				   PERF_LEVEL_GET, 4, dom->id, NULL,
 				   &fc[PERF_FC_LEVEL].get_addr, NULL,
 				   &fc[PERF_FC_LEVEL].rate_limit);
 
 	ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
-				   PERF_LIMITS_GET, 8, dom->id,
+				   PERF_LIMITS_GET, 8, dom->id, NULL,
 				   &fc[PERF_FC_LIMIT].get_addr, NULL,
 				   &fc[PERF_FC_LIMIT].rate_limit);
 
 	if (dom->info.set_perf)
 		ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
-					   PERF_LEVEL_SET, 4, dom->id,
+					   PERF_LEVEL_SET, 4, dom->id, NULL,
 					   &fc[PERF_FC_LEVEL].set_addr,
 					   &fc[PERF_FC_LEVEL].set_db,
 					   &fc[PERF_FC_LEVEL].rate_limit);
 
 	if (dom->set_limits)
 		ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
-					   PERF_LIMITS_SET, 8, dom->id,
+					   PERF_LIMITS_SET, 8, dom->id, NULL,
 					   &fc[PERF_FC_LIMIT].set_addr,
 					   &fc[PERF_FC_LIMIT].set_db,
 					   &fc[PERF_FC_LIMIT].rate_limit);
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index ab9733f4458b..22aff71c75e9 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -716,24 +716,24 @@ static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
 		return;
 
 	ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
-				   POWERCAP_CAP_SET, 4, domain,
+				   POWERCAP_CAP_SET, 4, domain, NULL,
 				   &fc[POWERCAP_FC_CAP].set_addr,
 				   &fc[POWERCAP_FC_CAP].set_db,
 				   &fc[POWERCAP_FC_CAP].rate_limit);
 
 	ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
-				   POWERCAP_CAP_GET, 4, domain,
+				   POWERCAP_CAP_GET, 4, domain, NULL,
 				   &fc[POWERCAP_FC_CAP].get_addr, NULL,
 				   &fc[POWERCAP_FC_CAP].rate_limit);
 
 	ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
-				   POWERCAP_PAI_SET, 4, domain,
+				   POWERCAP_PAI_SET, 4, domain, NULL,
 				   &fc[POWERCAP_FC_PAI].set_addr,
 				   &fc[POWERCAP_FC_PAI].set_db,
 				   &fc[POWERCAP_FC_PAI].rate_limit);
 
 	ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
-				   POWERCAP_PAI_GET, 4, domain,
+				   POWERCAP_PAI_GET, 4, domain, NULL,
 				   &fc[POWERCAP_FC_PAI].get_addr, NULL,
 				   &fc[POWERCAP_FC_PAI].rate_limit);
 
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 4c75970326e6..516bf7c77745 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -280,7 +280,7 @@ struct scmi_proto_helpers_ops {
 				  u32 message_id, u32 *attributes);
 	void (*fastchannel_init)(const struct scmi_protocol_handle *ph,
 				 u8 describe_id, u32 message_id,
-				 u32 valid_size, u32 domain,
+				 u32 valid_size, u32 domain, u32 *custom,
 				 void __iomem **p_addr,
 				 struct scmi_fc_db_info **p_db,
 				 u32 *rate_limit);
-- 
2.47.3



^ permalink raw reply related

* [PATCH v4 00/11] Add support for SCMIv4.0 Powercap Extensions
From: Philip Radford @ 2026-04-01  9:53 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, quic_sibis,
	dan.carpenter, d-gole, souvik.chakravarty, Philip Radford

Hi all,

I will be taking over this series from Cristian and in doing so I have
addressed a couple of issues raised by the first version and added four
additional patches since Cristian's original series:

[6/11] exposes the measurement averaging interval (MAI) value when the
agent has registered to receive power measurement change notifications
for the power capping domain.

[9/11] adds MAI get and set support for the powercap protocol.

[10/11] introduces a synthetic root zone to act as a common parent for all
top-level domains.

[11/11] adds get_power_uw to synthetic root zone, summing the per-zone
power of immediate child zones.

TODO:
- Add enable/disable functionality to synthetic root zone to enable/disable
  immediate children.

The original series was based on v6.17-rc1 whereas this version has been
based on v7.0-rc4.

The rest of Cristian's series is explained below;

SCMIv4.0 [1] introduces some new features and commands into the Powercap
protocol. In a nutshell, such protocol changes add support for:

 - setting multiple powercap limit/interval constraints for each SCMI
   powercap domain which supports multiple Concurrent Power Limit
 - enabling more Powercap commands to use Fastchannels mechanism
 - adding multiple constraints support to the existing notifications

After a bit of needed updates in the SCMI core this series adds:

- support for the idea of optional multiple Concurrent Power Limit (CPLs)
- support for the new FCs
- support for extended notifications
- enable usage of such multiple constraint in the ARM SCMI Powercap driver

Note that the public SCMIv4.0 spec at [1] is currently still BETA0, so
this series could anyway need some minor rework along the way and
definitely will need to wait for a final public release before being
possibly merged.

Tested on a single and multi-instance scenario on an emulated setup
implementing the new protocol extensions.

Based on v7.0-rc4.

Thanks,
Phil

---
V3->V4
- Rebased on v7.0-rc4
- Added sythentic parent node and functionality
- Moved fastchannel inits outside of loop
- Renamed arguments for consistency
V2->V3
- Added powercap MAI get/set support
V1->V2
- Rebased on sudeep/for-next/scmi/updates
- Amended Copyright to include 2026
- Added patch to extend powercap report to include MAI
- Removed creation of pi powercap_info struct due to legacy code change
- Amended references to pi->version and similar, which were based on
  legacy code
- Wrapped two variables in le32_to_cpu() to appease Sparse warnings
- Amended comparing operator value in response to feedback

Cristian Marussi (7):
  firmware: arm_scmi: Add an optional custom parameter to fastchannel
    helpers
  firmware: arm_scmi: Refactor powercap domain layout
  firmware: arm_scmi: Add SCMIv4.0 Powercap basic support
  firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
  firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support
  include: trace: Add new parameter to trace_scmi_fc_call
  powercap: arm_scmi: Enable multiple constraints support

Philip Radford (4):
  firmware: arm_scmi: Extend powercap report to include MAI
  firmware: arm_scmi: add Powercap MAI get/set support
  powercap: arm_scmi: Create synthetic parent node for multi-instance
  powercap: arm_scmi: Add get_power_uw to synthetic node

 drivers/firmware/arm_scmi/driver.c    |  12 +-
 drivers/firmware/arm_scmi/perf.c      |  16 +-
 drivers/firmware/arm_scmi/powercap.c  | 984 ++++++++++++++++++++------
 drivers/firmware/arm_scmi/protocols.h |   2 +-
 drivers/powercap/arm_scmi_powercap.c  | 146 +++-
 include/linux/scmi_protocol.h         |  87 ++-
 include/trace/events/scmi.h           |  12 +-
 7 files changed, 986 insertions(+), 273 deletions(-)

--
2.47.3



^ permalink raw reply

* Re: [PATCH v5 3/3] arm64: dts: rockchip: Add Orange Pi 5 Pro board support
From: Alexey Charkov @ 2026-04-01  9:51 UTC (permalink / raw)
  To: dennis
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	FUKAUMI Naoki, Hsun Lai, Jonas Karlman, Chaoyi Chen, John Clark,
	Michael Opdenacker, Quentin Schulz, Andrew Lunn, Chukun Pan,
	Peter Robinson, Michael Riesch, Mykola Kvach, Jimmy Hon,
	devicetree, linux-arm-kernel, linux-rockchip, linux-kernel
In-Reply-To: <20260401010707.2584962-4-dennis@ausil.us>

On Wed, Apr 1, 2026 at 5:07 AM <dennis@ausil.us> wrote:
>
> From: Dennis Gilmore <dennis@ausil.us>
>
> Add device tree for the Xunlong Orange Pi 5 Pro (RK3588S).
>
> - eMMC module, you can optionally solder a SPI NOR in place and turn
>  off the eMMC
> - PCIe-attached NIC (pcie2x1l1)
> - PCIe NVMe slot (pcie2x1l2)
> - AP6256 WiFi (BCM43456) via SDIO with mmc-pwrseq
> - BCM4345C5 Bluetooth
> - es8388 audio
> - USB 2.0 and USB 3.0
>
> Vendors description and links to schematics available:
> http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-5-Pro.html
>
> Signed-off-by: Dennis Gilmore <dennis@ausil.us>
> ---
>  arch/arm64/boot/dts/rockchip/Makefile         |   1 +
>  .../dts/rockchip/rk3588s-orangepi-5-pro.dts   | 320 ++++++++++++++++++
>  2 files changed, 321 insertions(+)
>  create mode 100644 arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts
>
> diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
> index 4d384f153c13..c99dca2ae9e7 100644
> --- a/arch/arm64/boot/dts/rockchip/Makefile
> +++ b/arch/arm64/boot/dts/rockchip/Makefile
> @@ -214,6 +214,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-nanopi-r6c.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-odroid-m2.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5b.dtb
> +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5-pro.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-cm5-base.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-radxa-cm5-io.dtb
>  dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-roc-pc.dtb
> diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts
> new file mode 100644
> index 000000000000..7ab68245d2c6
> --- /dev/null
> +++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts
> @@ -0,0 +1,320 @@
> +// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
> +
> +/dts-v1/;
> +
> +#include "rk3588s-orangepi-5.dtsi"
> +
> +/ {
> +       model = "Xunlong Orange Pi 5 Pro";
> +       compatible = "xunlong,orangepi-5-pro", "rockchip,rk3588s";
> +
> +       aliases {
> +               mmc0 = &sdhci;
> +               mmc1 = &sdmmc;
> +               mmc2 = &sdio;
> +       };
> +
> +       analog-sound {
> +               compatible = "simple-audio-card";
> +               pinctrl-names = "default";
> +               pinctrl-0 = <&hp_detect>;
> +               simple-audio-card,bitclock-master = <&masterdai>;
> +               simple-audio-card,format = "i2s";
> +               simple-audio-card,frame-master = <&masterdai>;
> +               simple-audio-card,hp-det-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
> +               simple-audio-card,mclk-fs = <256>;
> +               simple-audio-card,name = "rockchip,es8388";
> +               simple-audio-card,routing =
> +                       "Headphones", "LOUT1",
> +                       "Headphones", "ROUT1",
> +                       "LINPUT1", "Microphone Jack",
> +                       "RINPUT1", "Microphone Jack",
> +                       "LINPUT2", "Onboard Microphone",
> +                       "RINPUT2", "Onboard Microphone";
> +               simple-audio-card,widgets =
> +                       "Microphone", "Microphone Jack",
> +                       "Microphone", "Onboard Microphone",
> +                       "Headphone", "Headphones";
> +
> +               simple-audio-card,cpu {
> +                       sound-dai = <&i2s2_2ch>;
> +               };
> +
> +               masterdai: simple-audio-card,codec {
> +                       sound-dai = <&es8388>;
> +                       system-clock-frequency = <12288000>;
> +               };
> +       };
> +
> +       pwm-leds {
> +               compatible = "pwm-leds";
> +
> +               led-0 {
> +                       color = <LED_COLOR_ID_BLUE>;
> +                       function = LED_FUNCTION_STATUS;
> +                       linux,default-trigger = "heartbeat";
> +                       max-brightness = <255>;
> +                       pwms = <&pwm15 0 1000000 0>;
> +               };
> +
> +               led-1 {
> +                       color = <LED_COLOR_ID_GREEN>;
> +                       function = LED_FUNCTION_ACTIVITY;
> +                       linux,default-trigger = "heartbeat";
> +                       max-brightness = <255>;
> +                       pwms = <&pwm3 0 1000000 0>;
> +               };
> +       };
> +
> +       fan: pwm-fan {
> +               compatible = "pwm-fan";
> +               #cooling-cells = <2>;
> +               cooling-levels = <0 50 100 150 200 255>;
> +               fan-supply = <&vcc5v0_sys>;
> +               pwms = <&pwm2 0 20000000 0>;
> +       };
> +
> +       vcc3v3_phy1: regulator-vcc3v3-phy1 {
> +               compatible = "regulator-fixed";
> +               enable-active-high;
> +               gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
> +               regulator-boot-on;
> +               regulator-max-microvolt = <3300000>;
> +               regulator-min-microvolt = <3300000>;
> +               regulator-name = "vcc3v3_phy1";
> +               startup-delay-us = <50000>;
> +               vin-supply = <&vcc_3v3_s3>;
> +       };
> +
> +       vcc5v0_otg: regulator-vcc5v0-otg {
> +               compatible = "regulator-fixed";
> +               enable-active-high;
> +               gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>;
> +               pinctrl-names = "default";
> +               pinctrl-0 = <&vcc5v0_otg_en>;
> +               regulator-max-microvolt = <5000000>;
> +               regulator-min-microvolt = <5000000>;
> +               regulator-name = "vcc5v0_otg";
> +               vin-supply = <&vcc5v0_sys>;
> +       };
> +
> +       sdio_pwrseq: sdio-pwrseq {
> +               compatible = "mmc-pwrseq-simple";
> +               clocks = <&hym8563>;
> +               clock-names = "ext_clock";
> +               post-power-on-delay-ms = <200>;
> +               reset-gpios = <&gpio0 RK_PD0 GPIO_ACTIVE_LOW>;
> +       };
> +
> +       typea_con: usb-a-connector {
> +               compatible = "usb-a-connector";
> +               data-role = "host";
> +               label = "USB3 Type-A";
> +               power-role = "source";
> +               vbus-supply = <&vcc5v0_otg>;
> +
> +               ports {
> +                       #address-cells = <1>;
> +                       #size-cells = <0>;
> +
> +                       port@0 {
> +                               reg = <0>;
> +                               typea_con_hs: endpoint {
> +                               };
> +                       };

Hi Dennis,

You don't have to define ports if you aren't using them - i.e. here
the endpoints are not linked up to their remote counterparts, so they
don't do anything. If it works for you this way that probably means
you can drop the whole ports subnode altogether and add it later if a
need arises.

> +                       port@1 {
> +                               reg = <1>;
> +                               typea_con_ss: endpoint {
> +                               };
> +                       };
> +               };
> +       };
> +};
> +
> +&i2c1 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&i2c1m4_xfer>;
> +       status = "okay";
> +};
> +
> +&i2c3 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&i2c3m0_xfer>;
> +       status = "okay";
> +
> +       es8388: audio-codec@11 {
> +               compatible = "everest,es8388", "everest,es8328";
> +               reg = <0x11>;
> +               #sound-dai-cells = <0>;
> +               AVDD-supply = <&vcc_3v3_s0>;
> +               DVDD-supply = <&vcc_1v8_s0>;
> +               HPVDD-supply = <&vcc_3v3_s0>;
> +               PVDD-supply = <&vcc_3v3_s0>;
> +               assigned-clock-rates = <12288000>;
> +               assigned-clocks = <&cru I2S2_2CH_MCLKOUT>;
> +               clocks = <&cru I2S2_2CH_MCLKOUT>;

Your codec is the clock master, so it would be better for it to
request the pin config for the mclk pin, as it's the real user of the
master clock (not the i2s2 node). See also below

> +       };
> +};
> +
> +&i2c4 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&i2c4m3_xfer>;
> +       status = "okay";
> +};
> +
> +&i2s2_2ch {
> +       pinctrl-0 = <&i2s2m1_lrck &i2s2m1_mclk &i2s2m1_sclk

Looks like &i2s2m1_mclk belongs in &es8388 instead, not here.

> +                    &i2s2m1_sdi &i2s2m1_sdo>;
> +       status = "okay";
> +};
> +
> +&package_thermal {
> +       polling-delay = <1000>;
> +
> +       cooling-maps {
> +               map0 {
> +                       trip = <&package_fan0>;
> +                       cooling-device = <&fan THERMAL_NO_LIMIT 1>;
> +               };
> +
> +               map1 {
> +                       trip = <&package_fan1>;
> +                       cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
> +               };
> +       };
> +
> +       trips {
> +               package_fan0: package-fan0 {
> +                       hysteresis = <2000>;
> +                       temperature = <55000>;
> +                       type = "active";
> +               };
> +
> +               package_fan1: package-fan1 {
> +                       hysteresis = <2000>;
> +                       temperature = <65000>;
> +                       type = "active";
> +               };
> +       };
> +};
> +
> +/* NVMe */
> +&pcie2x1l1 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pcie30x1m1_1_clkreqn &pcie30x1m1_1_waken>;
> +       reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
> +       supports-clkreq;
> +       vpcie3v3-supply = <&vcc_3v3_s3>;
> +       status = "okay";
> +};
> +
> +/* NIC */
> +&pcie2x1l2 {
> +       reset-gpios = <&gpio3 RK_PD1 GPIO_ACTIVE_HIGH>;
> +       vpcie3v3-supply = <&vcc3v3_phy1>;
> +       status = "okay";
> +};
> +
> +&pinctrl {
> +       bluetooth {
> +               bt_wake_gpio: bt-wake-pin {
> +                       rockchip,pins = <0 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
> +               };
> +
> +               bt_wake_host_irq: bt-wake-host-irq {
> +                       rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_down>;
> +               };
> +       };
> +
> +       usb {
> +               vcc5v0_otg_en: vcc5v0-otg-en {
> +                       rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
> +               };
> +       };
> +
> +       wlan {
> +               wifi_host_wake_irq: wifi-host-wake-irq {
> +                       rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_down>;
> +               };
> +       };
> +};
> +
> +&pwm15 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pwm15m2_pins>;
> +       status = "okay";
> +};
> +
> +&pwm2 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pwm2m1_pins>;
> +       status = "okay";
> +};
> +
> +&pwm3 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pwm3m2_pins>;
> +       status = "okay";
> +};
> +
> +&sdhci {
> +       status = "okay";
> +};
> +
> +&sdio {
> +       #address-cells = <1>;
> +       #size-cells = <0>;
> +       bus-width = <4>;
> +       cap-sd-highspeed;
> +       cap-sdio-irq;
> +       keep-power-in-suspend;
> +       max-frequency = <150000000>;
> +       mmc-pwrseq = <&sdio_pwrseq>;
> +       no-mmc;
> +       no-sd;
> +       non-removable;
> +       sd-uhs-sdr104;
> +       status = "okay";
> +
> +       ap6256: wifi@1 {
> +               compatible = "brcm,bcm43456-fmac", "brcm,bcm4329-fmac";
> +               reg = <1>;
> +               interrupt-names = "host-wake";
> +               interrupt-parent = <&gpio0>;
> +               interrupts = <RK_PA0 IRQ_TYPE_LEVEL_HIGH>;
> +               pinctrl-names = "default";
> +               pinctrl-0 = <&wifi_host_wake_irq>;
> +       };
> +};
> +
> +&uart9 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&uart9m2_xfer &uart9m2_ctsn &uart9m2_rtsn>;
> +       uart-has-rtscts;
> +       status = "okay";
> +
> +       bluetooth {
> +               compatible = "brcm,bcm4345c5";
> +               clocks = <&hym8563>;
> +               clock-names = "lpo";
> +               device-wakeup-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
> +               interrupt-names = "host-wakeup";
> +               interrupt-parent = <&gpio0>;
> +               interrupts = <RK_PC5 IRQ_TYPE_LEVEL_HIGH>;
> +               max-speed = <1500000>;
> +               pinctrl-names = "default";
> +               pinctrl-0 = <&bt_wake_host_irq &bt_wake_gpio>;
> +               shutdown-gpios = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
> +               vbat-supply = <&vcc_3v3_s3>;
> +               vddio-supply = <&vcc_1v8_s3>;
> +       };
> +};
> +
> +&usb_host0_xhci {
> +       dr_mode = "host";
> +};
> +
> +&usbdp_phy0 {
> +       rockchip,dp-lane-mux = <0 1>;

You probably also need a definition for your DP to HDMI bridge and the
HDMI type A connector. Do you get image output on the HDMI port, and
does the hotplug work? I don't see you enabling the &dp0 node with its
pinctrl and ports.

It's okay to leave them out in the initial submission if you wish, but
then it's best mentioned in the commit description.

Best regards,
Alexey


^ permalink raw reply

* [PATCH] media: mtk-jpeg: cancel workqueue on release for supported platforms only
From: Louis-Alexis Eyraud @ 2026-04-01  9:44 UTC (permalink / raw)
  To: Bin Liu, Mauro Carvalho Chehab, Matthias Brugger,
	AngeloGioacchino Del Regno, Nicolas Dufresne, Hans Verkuil,
	Fan Wu
  Cc: kernel, linux-media, linux-kernel, linux-arm-kernel,
	linux-mediatek, Louis-Alexis Eyraud

Since a recent fix the mtk_jpeg_release function cancels any pending
or running work present in the driver workqueue using
cancel_work_sync function.
Currently, only the multicore based variants use this workqueue and they
have the jpeg_worker platform data field initialized with a workqueue
callback function. For the others, this field value remain NULL by
default.
The cancel_work_sync function is unconditionally called in
mtk_jpeg_release function, even for the variants that do not use the
workqueue. This call generates a WARN_ON print in __flush_work because
the workqueue callback function presence check fails in __flush_work
function (used by cancel_work_sync).

So, to avoid these warnings, call cancel_work_sync only if a workqueue
callback is defined in platform data.

Fixes: 34c519feef3e ("media: mtk-jpeg: fix use-after-free in release path due to uncancelled work")
Signed-off-by: Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
---
Patch to fix the following warning that occurs on boards such as
Mediatek Genio 510-EVK:
```
WARNING: kernel/workqueue.c:4291 at __flush_work+0x320/0x340, CPU#1:
  v4l_id/409
...
Call trace:
 __flush_work+0x320/0x340 (P)
 cancel_work_sync+0x6c/0x9c
 mtk_jpeg_release+0x2c/0x84 [mtk_jpeg]
 v4l2_release+0x8c/0xe8 [videodev]
 __fput+0xcc/0x2e0
 fput_close_sync+0x40/0x100
 __arm64_sys_close+0x38/0x7c
 invoke_syscall+0x54/0x10c
 el0_svc_common.constprop.0+0xc0/0xe0
 do_el0_svc+0x1c/0x34
 el0_svc+0x34/0x108
 el0t_64_sync_handler+0xa0/0xf0
 el0t_64_sync+0x198/0x19c
---[ end trace 0000000000000000 ]---
```

It is based on linux-next (tag next-20260331) and has been tested on
Mediatek Genio 510-EVK (mt8390) and 1200-EVK (mt8395) boards.
---
 drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
index 8c684756d5fc2524da3a67f67f0fdda894b676fc..d147ec48308110ae8520662e182dc0445447d8d0 100644
--- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
+++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
@@ -1202,7 +1202,8 @@ static int mtk_jpeg_release(struct file *file)
 	struct mtk_jpeg_dev *jpeg = video_drvdata(file);
 	struct mtk_jpeg_ctx *ctx = mtk_jpeg_file_to_ctx(file);
 
-	cancel_work_sync(&ctx->jpeg_work);
+	if (jpeg->variant->jpeg_worker)
+		cancel_work_sync(&ctx->jpeg_work);
 	mutex_lock(&jpeg->lock);
 	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
 	v4l2_ctrl_handler_free(&ctx->ctrl_hdl);

---
base-commit: e5da3eef8dadab4e98b228725ca8948edd9d601f
change-id: 20260401-mtk-jpeg-release-issue-d8921970a250

Best regards,
-- 
Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>



^ permalink raw reply related

* RE: Re: [PATCH 1/2] dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
From: Guangliu Ding @ 2026-04-01  9:43 UTC (permalink / raw)
  To: Daniel Baluta (OSS), Liviu Dudau
  Cc: Daniel Almeida, Alice Ryhl, Boris Brezillon, Steven Price,
	David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, imx@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, Jiyu Yang
In-Reply-To: <99a1da55-d6e5-4d11-abaa-8c85283ab5f2@oss.nxp.com>

Hi Daniel

> On 4/1/26 11:48, Guangliu Ding wrote:
> > [You don't often get email from guangliu.ding@nxp.com. Learn why this
> > is important at https://aka.ms/LearnAboutSenderIdentification ]
> >
> > Hi Liviu
> >
> > Thanks for your review. Please refer to my comments below:
> >
> >> On Tue, Mar 31, 2026 at 06:12:38PM +0800, Guangliu Ding wrote:
> >>> Add compatible string of Mali G310 GPU on i.MX952 board.
> >>>
> >>> Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
> >>> Reviewed-by: Jiyu Yang <jiyu.yang@nxp.com>
> >>> ---
> >>>  Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | 1
> >>> +
> >>>  1 file changed, 1 insertion(+)
> >>>
> >>> diff --git
> >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> >> b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> >>> index 8eccd4338a2b..6a10843a26e2 100644
> >>> ---
> >>> a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
> >>> +++ b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yam
> >>> +++ l
> >>> @@ -20,6 +20,7 @@ properties:
> >>>            - enum:
> >>>                - mediatek,mt8196-mali
> >>>                - nxp,imx95-mali            # G310
> >>> +              - nxp,imx952-mali           # G310
> >> Can you explain why this is needed? Can it not be covered by the
> >> existing compatible?
> > There are functional differences in GPU module (GPUMIX) between i.MX95
> > and i.MX952. So they cannot be fully covered by a single existing compatible.
> > On i.MX952, The GPU clock is controlled by hardware GPU auto
> > clock-gating mechanism, while the GPU clock is managed explicitly by the
> driver on i.MX95.
> > Because of these behavioral differences, separate compatible strings
> > "nxp,imx95-mali" and "nxp,imx952-mali" are needed to allow the driver
> > to handle the two variants independently and to keep room for future
> divergence.
> 
> 
> This information should be added in the commit message explaining why
> 
> the change is needed.
> 
> 
> But then where is the driver code taking care of these diferences?
> 

Yes. Currently the driver does not require "nxp,imx952-mali" string.
However, when GPU ipa_counters are enabled to calculate the GPU busy_time/idle_time for 
GPU DVFS feature, they will conflict with the hardware GPU auto clock‑gating mechanism, 
causing GPU clock to remain always on.
In such cases, ipa_counters need to be disabled so that the GPU auto clock‑gating 
mechanism can operate normally, using "nxp,imx952-mali" string.

^ permalink raw reply

* [PATCH v12 16/17] drm/bridge: analogix_dp: Remove bridge disabing and panel unpreparing in analogix_dp_unbind()
From: Damon Ding @ 2026-04-01  9:35 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401093547.25840-1-damon.ding@rock-chips.com>

The analogix_dp_unbind() should be balanced with analogix_dp_bind().
There are no bridge enabling and panel preparing in analogix_dp_bind(),
so it should be reasonable to remove the bridge disabing and panel
unpreparing in analogix_dp_unbind().

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

-----

Changes in v9:
- Add Tested-by tag.

Changes in v11:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index b66fd3062e41..8bb248e44c26 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1526,10 +1526,6 @@ EXPORT_SYMBOL_GPL(analogix_dp_bind);
 
 void analogix_dp_unbind(struct analogix_dp_device *dp)
 {
-	analogix_dp_bridge_disable(&dp->bridge);
-
-	drm_panel_unprepare(dp->plat_data->panel);
-
 	drm_dp_aux_unregister(&dp->aux);
 }
 EXPORT_SYMBOL_GPL(analogix_dp_unbind);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 17/17] drm/bridge: analogix_dp: Apply panel_bridge helper
From: Damon Ding @ 2026-04-01  9:35 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401093547.25840-1-damon.ding@rock-chips.com>

In order to unify the handling of the panel and bridge, apply
panel_bridge helpers for Analogix DP driver. With this patch, the
bridge support will also become available.

The following changes have ben made:
- Apply plane_bridge helper to wrap the panel as the bridge.
- Remove the explicit panel APIs calls, which can be replaced with
  the automic bridge APIs calls wrapped by the panel.
- Remove the unnecessary analogix_dp_bridge_get_modes().

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v4:
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge.

Changes in v5:
- Move panel_bridge addition a little forward.
- Move next_bridge attachment from Analogix side to Rockchip/Exynos
  side.

Changes in v6
- Remove the unnecessary analogix_dp_bridge_get_modes().
- Not to set DRM_BRIDGE_OP_MODES if the next is a panel.
- Squash [PATCH v5 15/17]drm/bridge: analogix_dp: Remove panel
  disabling and enabling in analogix_dp_set_bridge() into this
  commit.
- Fix the &drm_bridge->ops to DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT.

Changes in v9:
- Add Tested-by tag.
---
 .../drm/bridge/analogix/analogix_dp_core.c    | 41 +++++--------------
 1 file changed, 11 insertions(+), 30 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 8bb248e44c26..fe7158d9edde 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -749,9 +749,6 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
 {
 	int ret;
 
-	/* Keep the panel disabled while we configure video */
-	drm_panel_disable(dp->plat_data->panel);
-
 	ret = analogix_dp_train_link(dp);
 	if (ret) {
 		dev_err(dp->dev, "unable to do link train, ret=%d\n", ret);
@@ -771,9 +768,6 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
 		return ret;
 	}
 
-	/* Safe to enable the panel now */
-	drm_panel_enable(dp->plat_data->panel);
-
 	/* Check whether panel supports fast training */
 	ret = analogix_dp_fast_link_train_detection(dp);
 	if (ret)
@@ -858,17 +852,6 @@ static int analogix_dp_disable_psr(struct analogix_dp_device *dp)
 	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
 }
 
-static int analogix_dp_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector)
-{
-	struct analogix_dp_device *dp = to_dp(bridge);
-	int num_modes = 0;
-
-	if (dp->plat_data->panel)
-		num_modes += drm_panel_get_modes(dp->plat_data->panel, connector);
-
-	return num_modes;
-}
-
 static const struct drm_edid *analogix_dp_bridge_edid_read(struct drm_bridge *bridge,
 							   struct drm_connector *connector)
 {
@@ -909,7 +892,7 @@ analogix_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *conne
 	struct analogix_dp_device *dp = to_dp(bridge);
 	enum drm_connector_status status = connector_status_disconnected;
 
-	if (dp->plat_data->panel || dp->plat_data->next_bridge)
+	if (dp->plat_data->next_bridge)
 		return connector_status_connected;
 
 	if (!analogix_dp_detect_hpd(dp))
@@ -995,8 +978,6 @@ static void analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge,
 	/* Don't touch the panel if we're coming back from PSR */
 	if (old_crtc_state && old_crtc_state->self_refresh_active)
 		return;
-
-	drm_panel_prepare(dp->plat_data->panel);
 }
 
 static int analogix_dp_set_bridge(struct analogix_dp_device *dp)
@@ -1168,16 +1149,12 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
 	if (dp->dpms_mode != DRM_MODE_DPMS_ON)
 		return;
 
-	drm_panel_disable(dp->plat_data->panel);
-
 	disable_irq(dp->irq);
 
 	analogix_dp_set_analog_power_down(dp, POWER_ALL, 1);
 
 	pm_runtime_put_sync(dp->dev);
 
-	drm_panel_unprepare(dp->plat_data->panel);
-
 	dp->fast_train_enable = false;
 	dp->psr_supported = false;
 	dp->dpms_mode = DRM_MODE_DPMS_OFF;
@@ -1252,7 +1229,6 @@ static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
 	.atomic_post_disable = analogix_dp_bridge_atomic_post_disable,
 	.atomic_check = analogix_dp_bridge_atomic_check,
 	.attach = analogix_dp_bridge_attach,
-	.get_modes = analogix_dp_bridge_get_modes,
 	.edid_read = analogix_dp_bridge_edid_read,
 	.detect = analogix_dp_bridge_detect,
 };
@@ -1498,17 +1474,22 @@ int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
 		return ret;
 	}
 
-	if (dp->plat_data->panel)
-		bridge->ops = DRM_BRIDGE_OP_MODES | DRM_BRIDGE_OP_DETECT;
-	else
-		bridge->ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT;
-
+	bridge->ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT;
 	bridge->of_node = dp->dev->of_node;
 	bridge->type = DRM_MODE_CONNECTOR_eDP;
 	ret = devm_drm_bridge_add(dp->dev, &dp->bridge);
 	if (ret)
 		goto err_unregister_aux;
 
+	if (dp->plat_data->panel) {
+		dp->plat_data->next_bridge = devm_drm_panel_bridge_add(dp->dev,
+								       dp->plat_data->panel);
+		if (IS_ERR(dp->plat_data->next_bridge)) {
+			ret = PTR_ERR(bridge);
+			goto err_unregister_aux;
+		}
+	}
+
 	ret = drm_bridge_attach(dp->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
 	if (ret) {
 		DRM_ERROR("failed to create bridge (%d)\n", ret);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 15/17] drm/bridge: analogix_dp: Attach the next bridge in analogix_dp_bridge_attach()
From: Damon Ding @ 2026-04-01  9:35 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401093547.25840-1-damon.ding@rock-chips.com>

Uniformly, move the next bridge attachment to the Analogix side
rather than scattered on Rockchip and Exynos sides. It can also
help get rid of the callback &analogix_dp_plat_data.attach() and
make codes more concise.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

------

Changes in v6:
- Move the next bridge attachment to the Analogix side rather than
  scattered on Rockchip and Exynos sides.

Changes in v9:
- Add Tested-by tag.

Changes in v11:
- Add Reviewed-by tag.
---
 .../drm/bridge/analogix/analogix_dp_core.c    |  7 ++++---
 drivers/gpu/drm/exynos/exynos_dp.c            | 19 -------------------
 include/drm/bridge/analogix_dp.h              |  1 -
 3 files changed, 4 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index b112df52cabf..b66fd3062e41 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -930,10 +930,11 @@ static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
 		return -EINVAL;
 	}
 
-	if (dp->plat_data->attach) {
-		ret = dp->plat_data->attach(dp->plat_data, bridge);
+	if (dp->plat_data->next_bridge) {
+		ret = drm_bridge_attach(dp->encoder, dp->plat_data->next_bridge, bridge,
+					DRM_BRIDGE_ATTACH_NO_CONNECTOR);
 		if (ret) {
-			DRM_ERROR("Failed at platform attach func\n");
+			dev_err(dp->dev, "failed to attach following panel or bridge (%d)\n", ret);
 			return ret;
 		}
 	}
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index 9f3d4d4c7352..6884ea6d04eb 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -68,24 +68,6 @@ static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data)
 	return exynos_dp_crtc_clock_enable(plat_data, false);
 }
 
-static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
-				   struct drm_bridge *bridge)
-{
-	struct exynos_dp_device *dp = to_dp(plat_data);
-	enum drm_bridge_attach_flags flags = 0;
-	int ret;
-
-	/* Pre-empt DP connector creation if there's a bridge */
-	if (plat_data->next_bridge) {
-		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge,
-					flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
-}
-
 static void exynos_dp_mode_set(struct drm_encoder *encoder,
 			       struct drm_display_mode *mode,
 			       struct drm_display_mode *adjusted_mode)
@@ -196,7 +178,6 @@ static int exynos_dp_probe(struct platform_device *pdev)
 	dp->plat_data.dev_type = EXYNOS_DP;
 	dp->plat_data.power_on = exynos_dp_poweron;
 	dp->plat_data.power_off = exynos_dp_poweroff;
-	dp->plat_data.attach = exynos_dp_bridge_attach;
 	dp->plat_data.ops = &exynos_dp_ops;
 
 out:
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index bae969dec63a..854af692229b 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -34,7 +34,6 @@ struct analogix_dp_plat_data {
 
 	int (*power_on)(struct analogix_dp_plat_data *);
 	int (*power_off)(struct analogix_dp_plat_data *);
-	int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *);
 };
 
 int analogix_dp_resume(struct analogix_dp_device *dp);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 14/17] drm/exynos: exynos_dp: Apply analogix_dp_finish_probe()
From: Damon Ding @ 2026-04-01  9:35 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

Apply analogix_dp_finish_probe() in order to move the panel/bridge
parsing from Exynos side to the Analogix side.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v4:
- Rename analogix_dp_find_panel_or_bridge() to
  analogix_dp_finish_probe().

Changes in v7:
- Remove exynos_dp_legacy_bridge_init() and inline API
  devm_drm_of_display_mode_bridge().
- If the panel or the next_bridge is parsed from DT, use ptr validity
  to check whether to call component_add() directly at the end of
  probing.

-Changes in v9:
- Add Tested-by tag.
---
 drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b2597fafd73d..9f3d4d4c7352 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -158,9 +158,6 @@ static int exynos_dp_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct device_node *np;
 	struct exynos_dp_device *dp;
-	struct drm_panel *panel;
-	struct drm_bridge *bridge;
-	int ret;
 
 	dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device),
 			  GFP_KERNEL);
@@ -187,30 +184,30 @@ static int exynos_dp_probe(struct platform_device *pdev)
 		goto out;
 	}
 
-	ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, &bridge);
-	if (ret == -ENODEV) {
+	if (of_get_display_timings(dev->of_node)) {
 		dp->plat_data.next_bridge = devm_drm_of_display_mode_bridge(dp->dev,
 									dp->dev->of_node,
 									DRM_MODE_CONNECTOR_eDP);
-		ret = IS_ERR(dp->plat_data.next_bridge) ? PTR_ERR(dp->plat_data.next_bridge) : 0;
+		if (IS_ERR(dp->plat_data.next_bridge))
+			return PTR_ERR(dp->plat_data.next_bridge);
 	}
-	if (ret)
-		return ret;
 
 	/* The remote port can be either a panel or a bridge */
-	dp->plat_data.panel = panel;
-	dp->plat_data.next_bridge = bridge;
 	dp->plat_data.dev_type = EXYNOS_DP;
 	dp->plat_data.power_on = exynos_dp_poweron;
 	dp->plat_data.power_off = exynos_dp_poweroff;
 	dp->plat_data.attach = exynos_dp_bridge_attach;
+	dp->plat_data.ops = &exynos_dp_ops;
 
 out:
 	dp->adp = analogix_dp_probe(dev, &dp->plat_data);
 	if (IS_ERR(dp->adp))
 		return PTR_ERR(dp->adp);
 
-	return component_add(&pdev->dev, &exynos_dp_ops);
+	if (dp->plat_data.panel || dp->plat_data.next_bridge)
+		return component_add(&pdev->dev, &exynos_dp_ops);
+	else
+		return analogix_dp_finish_probe(dp->adp);
 }
 
 static void exynos_dp_remove(struct platform_device *pdev)
-- 
2.34.1



^ permalink raw reply related

* Re: [PATCH v2] iommu/rockchip: Drop global rk_ops in favor of per-device ops
From: Simon Xue @ 2026-04-01  9:26 UTC (permalink / raw)
  To: Jonas Karlman, Shawn Lin
  Cc: iommu, linux-arm-kernel, linux-rockchip, linux-kernel,
	Joerg Roedel, Will Deacon, Robin Murphy, Heiko Stuebner
In-Reply-To: <8ca0a8d3-aed9-4c2a-89dd-e9029afdd279@kwiboo.se>

Hi Jonas,

Thanks for the  review.

在 2026/4/1 16:25, Jonas Karlman 写道:
> Hi,
>
> On 4/1/2026 9:59 AM, Simon Xue wrote:
>> Hi all,
>>
>> A gentle ping on this patch.
>>
>> 在 2026/3/13 17:32, Shawn Lin 写道:
>>> 在 2026/03/10 星期二 18:53, Simon Xue 写道:
>>>> The driver currently uses a global rk_ops pointer, forcing all IOMMU
>>>> instances to share the same operations. This restricts the driver from
>>>> supporting SoCs that might integrate different versions of IOMMU
>>>> hardware.
>>>>
>>>> Since the IOMMU framework passes the master device information to
>>>> iommu_paging_domain_alloc(), the global variable is no longer needed.
>>>>
>>>> Fix this by moving rk_ops into struct rk_iommu and struct
>>>> rk_iommu_domain.
>>>> Initialize it per-device during probe via of_device_get_match_data(),
>>>> and replace all global references with the instance-specific pointers.
>>>>
>>> Thanks for the patch, Simon. I've tested it on the RK3576 EVB1 with
>>> PCIe1 + IOMMU. NVMe works fine on it, and I also verified the IOVA
>>> allocated in the NVMe driver, they look correct as I manually limited
>>> the memblock to under 2GB, so here it is:
>>>
>>> nvme 0001:21:00.0: cq_dma_addr: 0x00000000f7fc7000
>>>
>>> Tested-by: Shawn Lin <shawn.lin@rock-chips.com>
>>> Reviewed-by: Shawn Lin <shawn.lin@rock-chips.com>
>>>
>>>> Signed-off-by: Simon Xue <xxm@rock-chips.com>
>>>> ---
>>>> v2:
>>>>    - Remove the one-time-used 'ops' variable in rk_iommu_probe()
>>>>
>>>>    drivers/iommu/rockchip-iommu.c | 71 ++++++++++++++++------------------
>>>>    1 file changed, 33 insertions(+), 38 deletions(-)
>>>>
>>>> diff --git a/drivers/iommu/rockchip-iommu.c
>>>> b/drivers/iommu/rockchip-iommu.c
>>>> index 0013cf196c57..4da80136933c 100644
>>>> --- a/drivers/iommu/rockchip-iommu.c
>>>> +++ b/drivers/iommu/rockchip-iommu.c
>>>> @@ -82,6 +82,14 @@
>>>>      */
>>>>    #define RK_IOMMU_PGSIZE_BITMAP 0x007ff000
>>>>    +struct rk_iommu_ops {
>>>> +    phys_addr_t (*pt_address)(u32 dte);
>>>> +    u32 (*mk_dtentries)(dma_addr_t pt_dma);
>>>> +    u32 (*mk_ptentries)(phys_addr_t page, int prot);
>>>> +    u64 dma_bit_mask;
>>>> +    gfp_t gfp_flags;
>>>> +};
>>>> +
>>>>    struct rk_iommu_domain {
>>>>        struct list_head iommus;
>>>>        u32 *dt; /* page directory table */
>>>> @@ -89,6 +97,7 @@ struct rk_iommu_domain {
>>>>        spinlock_t iommus_lock; /* lock for iommus list */
>>>>        spinlock_t dt_lock; /* lock for modifying page directory table */
>>>>        struct device *dma_dev;
>>>> +    const struct rk_iommu_ops *rk_ops;
>>>>          struct iommu_domain domain;
>>>>    };
>>>> @@ -98,14 +107,6 @@ static const char * const rk_iommu_clocks[] = {
>>>>        "aclk", "iface",
>>>>    };
>>>>    -struct rk_iommu_ops {
>>>> -    phys_addr_t (*pt_address)(u32 dte);
>>>> -    u32 (*mk_dtentries)(dma_addr_t pt_dma);
>>>> -    u32 (*mk_ptentries)(phys_addr_t page, int prot);
>>>> -    u64 dma_bit_mask;
>>>> -    gfp_t gfp_flags;
>>>> -};
>>>> -
>>>>    struct rk_iommu {
>>>>        struct device *dev;
>>>>        void __iomem **bases;
>>>> @@ -117,6 +118,7 @@ struct rk_iommu {
>>>>        struct iommu_device iommu;
>>>>        struct list_head node; /* entry in rk_iommu_domain.iommus */
>>>>        struct iommu_domain *domain; /* domain to which iommu is
>>>> attached */
>>>> +    const struct rk_iommu_ops *rk_ops;
> Do we really need the rk_ops on both the rk_iommu and rk_iommu_domain?
>
>>>>    };
>>>>      struct rk_iommudata {
>>>> @@ -124,7 +126,6 @@ struct rk_iommudata {
>>>>        struct rk_iommu *iommu;
>>>>    };
>>>>    -static const struct rk_iommu_ops *rk_ops;
>>>>    static struct iommu_domain rk_identity_domain;
>>>>      static inline void rk_table_flush(struct rk_iommu_domain *dom,
>>>> dma_addr_t dma,
>>>> @@ -510,7 +511,7 @@ static int rk_iommu_force_reset(struct rk_iommu
>>>> *iommu)
>>>>         * and verifying that upper 5 (v1) or 7 (v2) nybbles are read
>>>> back.
>>>>         */
>>>>        for (i = 0; i < iommu->num_mmu; i++) {
>>>> -        dte_addr = rk_ops->pt_address(DTE_ADDR_DUMMY);
>>>> +        dte_addr = iommu->rk_ops->pt_address(DTE_ADDR_DUMMY);
> Maybe this patch it trying to do too much at once that makes it harder
> to review? To simplify review maybe one patch just drops the global
> rk_ops and a assigns a local version based on domain/iommu, and a second
> patch changes to use the iommu->rk_ops directly? Just a thought.

1. I think it's hard to split this patch safely.

Once the global rk_ops is removed, domain-only paths 
(map/unmap/iova_to_phys/domain_free)

still need SoC-specific helpers, while they only receive struct 
iommu_domain *.

2. Looking up ops from an attached IOMMU at call time may not reliable.

So keeping rk_ops in both rk_domain and rk_iommu is reasonable.

>
>>>>            rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR, dte_addr);
>>>>              if (dte_addr != rk_iommu_read(iommu->bases[i],
>>>> RK_MMU_DTE_ADDR)) {
>>>> @@ -551,7 +552,7 @@ static void log_iova(struct rk_iommu *iommu, int
>>>> index, dma_addr_t iova)
>>>>        page_offset = rk_iova_page_offset(iova);
>>>>          mmu_dte_addr = rk_iommu_read(base, RK_MMU_DTE_ADDR);
>>>> -    mmu_dte_addr_phys = rk_ops->pt_address(mmu_dte_addr);
>>>> +    mmu_dte_addr_phys = iommu->rk_ops->pt_address(mmu_dte_addr);
>>>>          dte_addr_phys = mmu_dte_addr_phys + (4 * dte_index);
>>>>        dte_addr = phys_to_virt(dte_addr_phys);
>>>> @@ -560,14 +561,14 @@ static void log_iova(struct rk_iommu *iommu,
>>>> int index, dma_addr_t iova)
>>>>        if (!rk_dte_is_pt_valid(dte))
>>>>            goto print_it;
>>>>    -    pte_addr_phys = rk_ops->pt_address(dte) + (pte_index * 4);
>>>> +    pte_addr_phys = iommu->rk_ops->pt_address(dte) + (pte_index * 4);
>>>>        pte_addr = phys_to_virt(pte_addr_phys);
>>>>        pte = *pte_addr;
>>>>          if (!rk_pte_is_page_valid(pte))
>>>>            goto print_it;
>>>>    -    page_addr_phys = rk_ops->pt_address(pte) + page_offset;
>>>> +    page_addr_phys = iommu->rk_ops->pt_address(pte) + page_offset;
>>>>        page_flags = pte & RK_PTE_PAGE_FLAGS_MASK;
>>>>      print_it:
>>>> @@ -663,13 +664,13 @@ static phys_addr_t rk_iommu_iova_to_phys(struct
>>>> iommu_domain *domain,
>>>>        if (!rk_dte_is_pt_valid(dte))
>>>>            goto out;
>>>>    -    pt_phys = rk_ops->pt_address(dte);
>>>> +    pt_phys = rk_domain->rk_ops->pt_address(dte);
>>>>        page_table = (u32 *)phys_to_virt(pt_phys);
>>>>        pte = page_table[rk_iova_pte_index(iova)];
>>>>        if (!rk_pte_is_page_valid(pte))
>>>>            goto out;
>>>>    -    phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova);
>>>> +    phys = rk_domain->rk_ops->pt_address(pte) +
>>>> rk_iova_page_offset(iova);
>>>>    out:
>>>>        spin_unlock_irqrestore(&rk_domain->dt_lock, flags);
>>>>    @@ -730,7 +731,7 @@ static u32 *rk_dte_get_page_table(struct
>>>> rk_iommu_domain *rk_domain,
>>>>        if (rk_dte_is_pt_valid(dte))
>>>>            goto done;
>>>>    -    page_table = iommu_alloc_pages_sz(GFP_ATOMIC | rk_ops->gfp_flags,
>>>> +    page_table = iommu_alloc_pages_sz(GFP_ATOMIC |
>>>> rk_domain->rk_ops->gfp_flags,
>>>>                          SPAGE_SIZE);
>>>>        if (!page_table)
>>>>            return ERR_PTR(-ENOMEM);
>>>> @@ -742,13 +743,13 @@ static u32 *rk_dte_get_page_table(struct
>>>> rk_iommu_domain *rk_domain,
>>>>            return ERR_PTR(-ENOMEM);
>>>>        }
>>>>    -    dte = rk_ops->mk_dtentries(pt_dma);
>>>> +    dte = rk_domain->rk_ops->mk_dtentries(pt_dma);
>>>>        *dte_addr = dte;
>>>>          rk_table_flush(rk_domain,
>>>>                   rk_domain->dt_dma + dte_index * sizeof(u32), 1);
>>>>    done:
>>>> -    pt_phys = rk_ops->pt_address(dte);
>>>> +    pt_phys = rk_domain->rk_ops->pt_address(dte);
>>>>        return (u32 *)phys_to_virt(pt_phys);
>>>>    }
>>>>    @@ -790,7 +791,7 @@ static int rk_iommu_map_iova(struct
>>>> rk_iommu_domain *rk_domain, u32 *pte_addr,
>>>>            if (rk_pte_is_page_valid(pte))
>>>>                goto unwind;
>>>>    -        pte_addr[pte_count] = rk_ops->mk_ptentries(paddr, prot);
>>>> +        pte_addr[pte_count] = rk_domain->rk_ops->mk_ptentries(paddr,
>>>> prot);
>>>>              paddr += SPAGE_SIZE;
>>>>        }
>>>> @@ -812,7 +813,7 @@ static int rk_iommu_map_iova(struct
>>>> rk_iommu_domain *rk_domain, u32 *pte_addr,
>>>>                    pte_count * SPAGE_SIZE);
>>>>          iova += pte_count * SPAGE_SIZE;
>>>> -    page_phys = rk_ops->pt_address(pte_addr[pte_count]);
>>>> +    page_phys = rk_domain->rk_ops->pt_address(pte_addr[pte_count]);
>>>>        pr_err("iova: %pad already mapped to %pa cannot remap to phys:
>>>> %pa prot: %#x\n",
>>>>               &iova, &page_phys, &paddr, prot);
>>>>    @@ -849,7 +850,7 @@ static int rk_iommu_map(struct iommu_domain
>>>> *domain, unsigned long _iova,
>>>>        pte_index = rk_iova_pte_index(iova);
>>>>        pte_addr = &page_table[pte_index];
>>>>    -    pte_dma = rk_ops->pt_address(dte_index) + pte_index *
>>>> sizeof(u32);
>>>> +    pte_dma = rk_domain->rk_ops->pt_address(dte_index) + pte_index *
>>>> sizeof(u32);
>>>>        ret = rk_iommu_map_iova(rk_domain, pte_addr, pte_dma, iova,
>>>>                    paddr, size, prot);
>>>>    @@ -887,7 +888,7 @@ static size_t rk_iommu_unmap(struct
>>>> iommu_domain *domain, unsigned long _iova,
>>>>            return 0;
>>>>        }
>>>>    -    pt_phys = rk_ops->pt_address(dte);
>>>> +    pt_phys = rk_domain->rk_ops->pt_address(dte);
>>>>        pte_addr = (u32 *)phys_to_virt(pt_phys) + rk_iova_pte_index(iova);
>>>>        pte_dma = pt_phys + rk_iova_pte_index(iova) * sizeof(u32);
>>>>        unmap_size = rk_iommu_unmap_iova(rk_domain, pte_addr, pte_dma,
>>>> size);
>>>> @@ -945,7 +946,7 @@ static int rk_iommu_enable(struct rk_iommu *iommu)
>>>>          for (i = 0; i < iommu->num_mmu; i++) {
>>>>            rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR,
>>>> - rk_ops->mk_dtentries(rk_domain->dt_dma));
>>>> + iommu->rk_ops->mk_dtentries(rk_domain->dt_dma));
>>>>            rk_iommu_base_command(iommu->bases[i], RK_MMU_CMD_ZAP_CACHE);
>>>>            rk_iommu_write(iommu->bases[i], RK_MMU_INT_MASK,
>>>> RK_MMU_IRQ_MASK);
>>>>        }
>>>> @@ -1068,17 +1069,19 @@ static struct iommu_domain
>>>> *rk_iommu_domain_alloc_paging(struct device *dev)
>>>>        if (!rk_domain)
>>>>            return NULL;
>>>>    +    iommu = rk_iommu_from_dev(dev);
>>>> +    rk_domain->rk_ops = iommu->rk_ops;
> As mentioned above, this seem strange and is possible just a shortcut
> due to current call-paths? Why do we assign "iommu ops" to the domain?
>
> Regards,
> Jonas
>
>>>> +
>>>>        /*
>>>>         * rk32xx iommus use a 2 level pagetable.
>>>>         * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries.
>>>>         * Allocate one 4 KiB page for each table.
>>>>         */
>>>> -    rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL |
>>>> rk_ops->gfp_flags,
>>>> +    rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL |
>>>> rk_domain->rk_ops->gfp_flags,
>>>>                             SPAGE_SIZE);
>>>>        if (!rk_domain->dt)
>>>>            goto err_free_domain;
>>>>    -    iommu = rk_iommu_from_dev(dev);
>>>>        rk_domain->dma_dev = iommu->dev;
>>>>        rk_domain->dt_dma = dma_map_single(rk_domain->dma_dev,
>>>> rk_domain->dt,
>>>>                           SPAGE_SIZE, DMA_TO_DEVICE);
>>>> @@ -1117,7 +1120,7 @@ static void rk_iommu_domain_free(struct
>>>> iommu_domain *domain)
>>>>        for (i = 0; i < NUM_DT_ENTRIES; i++) {
>>>>            u32 dte = rk_domain->dt[i];
>>>>            if (rk_dte_is_pt_valid(dte)) {
>>>> -            phys_addr_t pt_phys = rk_ops->pt_address(dte);
>>>> +            phys_addr_t pt_phys = rk_domain->rk_ops->pt_address(dte);
>>>>                u32 *page_table = phys_to_virt(pt_phys);
>>>>                dma_unmap_single(rk_domain->dma_dev, pt_phys,
>>>>                         SPAGE_SIZE, DMA_TO_DEVICE);
>>>> @@ -1197,7 +1200,6 @@ static int rk_iommu_probe(struct
>>>> platform_device *pdev)
>>>>        struct device *dev = &pdev->dev;
>>>>        struct rk_iommu *iommu;
>>>>        struct resource *res;
>>>> -    const struct rk_iommu_ops *ops;
>>>>        int num_res = pdev->num_resources;
>>>>        int err, i;
>>>>    @@ -1211,16 +1213,9 @@ static int rk_iommu_probe(struct
>>>> platform_device *pdev)
>>>>        iommu->dev = dev;
>>>>        iommu->num_mmu = 0;
>>>>    -    ops = of_device_get_match_data(dev);
>>>> -    if (!rk_ops)
>>>> -        rk_ops = ops;
>>>> -
>>>> -    /*
>>>> -     * That should not happen unless different versions of the
>>>> -     * hardware block are embedded the same SoC
>>>> -     */
>>>> -    if (WARN_ON(rk_ops != ops))
>>>> -        return -EINVAL;
>>>> +    iommu->rk_ops = of_device_get_match_data(dev);
>>>> +    if (!iommu->rk_ops)
>>>> +        return -ENOENT;
>>>>          iommu->bases = devm_kcalloc(dev, num_res, sizeof(*iommu->bases),
>>>>                        GFP_KERNEL);
>>>> @@ -1286,7 +1281,7 @@ static int rk_iommu_probe(struct
>>>> platform_device *pdev)
>>>>                goto err_pm_disable;
>>>>        }
>>>>    -    dma_set_mask_and_coherent(dev, rk_ops->dma_bit_mask);
>>>> +    dma_set_mask_and_coherent(dev, iommu->rk_ops->dma_bit_mask);
>>>>          err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL,
>>>> dev_name(dev));
>>>>        if (err)
>>>>
>> _______________________________________________
>> Linux-rockchip mailing list
>> Linux-rockchip@lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-rockchip
>


^ permalink raw reply

* [PATCH v12 10/17] drm/bridge: analogix_dp: Pass struct drm_atomic_state* for analogix_dp_bridge_mode_set()
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

To avoid using &analogix_dp_device.connector for compatibility
with the bridge connector framework, get &drm_connector from
&drm_atomic_state instead.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 3efd910ea463..7e42eb18313d 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1095,14 +1095,21 @@ static int analogix_dp_set_bridge(struct analogix_dp_device *dp)
 }
 
 static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
+					struct drm_atomic_state *state,
 					const struct drm_display_mode *mode)
 {
 	struct analogix_dp_device *dp = to_dp(bridge);
-	struct drm_display_info *display_info = &dp->connector.display_info;
 	struct video_info *video = &dp->video_info;
 	struct device_node *dp_node = dp->dev->of_node;
+	struct drm_connector *connector;
+	struct drm_display_info *display_info;
 	int vic;
 
+	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+	if (!connector)
+		return;
+	display_info = &connector->display_info;
+
 	/* Input video interlaces & hsync pol & vsync pol */
 	video->interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
 	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
@@ -1186,7 +1193,7 @@ static void analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge,
 	new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc);
 	if (!new_crtc_state)
 		return;
-	analogix_dp_bridge_mode_set(bridge, &new_crtc_state->adjusted_mode);
+	analogix_dp_bridge_mode_set(bridge, old_state, &new_crtc_state->adjusted_mode);
 
 	old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc);
 	/* Not a full enable, just disable PSR and continue */
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 05/17] drm/exynos: exynos_dp: Apply of-display-mode-bridge to parse the display-timings node
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

If there is neither a panel nor a bridge, the display timing can be
parsed from the display-timings node under the dp node.

In order to get rid of &analogix_dp_plat_data.get_modes() and make
the codes more consistent, apply DRM of-display-mode-bridge to parse
display timings.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

------

Changes in v6:
- Apply DRM legacy bridge to parse display timings instead of
  implementing the same codes only for Exynos DP.

Changes in v7:
- Use temporary flag &exynos_dp_device.has_of_bridge, which will be
  removed in the following patch, instead of applying API
  drm_bridge_is_legacy().
- Remove exynos_dp_legacy_bridge_init() and inline API
  devm_drm_of_display_mode_bridge().

Changes in v9:
- Add Tested-by tag.

Changes in v10:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/exynos/Kconfig     |  1 +
 drivers/gpu/drm/exynos/exynos_dp.c | 66 ++++++++----------------------
 2 files changed, 17 insertions(+), 50 deletions(-)

diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
index 0d13828e7d9e..380d9a8ce259 100644
--- a/drivers/gpu/drm/exynos/Kconfig
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -72,6 +72,7 @@ config DRM_EXYNOS_DP
 	select DRM_ANALOGIX_DP
 	select DRM_DISPLAY_DP_HELPER
 	default DRM_EXYNOS
+	select DRM_OF_DISPLAY_MODE_BRIDGE
 	select DRM_PANEL
 	help
 	  This enables support for DP device.
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index e20513164032..ac16138a22fe 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -19,6 +19,7 @@
 #include <video/videomode.h>
 
 #include <drm/bridge/analogix_dp.h>
+#include <drm/bridge/of-display-mode-bridge.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_crtc.h>
@@ -38,9 +39,10 @@ struct exynos_dp_device {
 	struct drm_device          *drm_dev;
 	struct device              *dev;
 
-	struct videomode           vm;
 	struct analogix_dp_device *adp;
 	struct analogix_dp_plat_data plat_data;
+
+	bool has_of_bridge;
 };
 
 static int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data,
@@ -67,44 +69,20 @@ static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data)
 	return exynos_dp_crtc_clock_enable(plat_data, false);
 }
 
-static int exynos_dp_get_modes(struct analogix_dp_plat_data *plat_data,
-			       struct drm_connector *connector)
-{
-	struct exynos_dp_device *dp = to_dp(plat_data);
-	struct drm_display_mode *mode;
-
-	if (dp->plat_data.panel)
-		return 0;
-
-	mode = drm_mode_create(connector->dev);
-	if (!mode) {
-		DRM_DEV_ERROR(dp->dev,
-			      "failed to create a new display mode.\n");
-		return 0;
-	}
-
-	drm_display_mode_from_videomode(&dp->vm, mode);
-	connector->display_info.width_mm = mode->width_mm;
-	connector->display_info.height_mm = mode->height_mm;
-
-	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
-	drm_mode_set_name(mode);
-	drm_mode_probed_add(connector, mode);
-
-	return 1;
-}
-
 static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
 				   struct drm_bridge *bridge,
 				   struct drm_connector *connector)
 {
 	struct exynos_dp_device *dp = to_dp(plat_data);
+	enum drm_bridge_attach_flags flags = 0;
 	int ret;
 
 	/* Pre-empt DP connector creation if there's a bridge */
 	if (plat_data->next_bridge) {
-		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge,
-					0);
+		if (dp->has_of_bridge)
+			flags = DRM_BRIDGE_ATTACH_NO_CONNECTOR;
+
+		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge, flags);
 		if (ret)
 			return ret;
 	}
@@ -129,19 +107,6 @@ static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = {
 	.disable = exynos_dp_nop,
 };
 
-static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp)
-{
-	int ret;
-
-	ret = of_get_videomode(dp->dev->of_node, &dp->vm, OF_USE_NATIVE_MODE);
-	if (ret) {
-		DRM_DEV_ERROR(dp->dev,
-			      "failed: of_get_videomode() : %d\n", ret);
-		return ret;
-	}
-	return 0;
-}
-
 static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
 {
 	struct exynos_dp_device *dp = dev_get_drvdata(dev);
@@ -151,12 +116,6 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
 
 	dp->drm_dev = drm_dev;
 
-	if (!dp->plat_data.panel && !dp->plat_data.next_bridge) {
-		ret = exynos_dp_dt_parse_panel(dp);
-		if (ret)
-			return ret;
-	}
-
 	drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
 
 	drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs);
@@ -223,6 +182,14 @@ static int exynos_dp_probe(struct platform_device *pdev)
 	}
 
 	ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, &bridge);
+	if (ret == -ENODEV) {
+		dp->plat_data.next_bridge = devm_drm_of_display_mode_bridge(dp->dev,
+									dp->dev->of_node,
+									DRM_MODE_CONNECTOR_eDP);
+		ret = IS_ERR(dp->plat_data.next_bridge) ? PTR_ERR(dp->plat_data.next_bridge) : 0;
+		if (!ret)
+			dp->has_of_bridge = true;
+	}
 	if (ret)
 		return ret;
 
@@ -233,7 +200,6 @@ static int exynos_dp_probe(struct platform_device *pdev)
 	dp->plat_data.power_on = exynos_dp_poweron;
 	dp->plat_data.power_off = exynos_dp_poweroff;
 	dp->plat_data.attach = exynos_dp_bridge_attach;
-	dp->plat_data.get_modes = exynos_dp_get_modes;
 	dp->plat_data.skip_connector = !!bridge;
 
 out:
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 06/17] drm/bridge: analogix_dp: Remove redundant &analogix_dp_plat_data.skip_connector
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

The &analogix_dp_plat_data.skip_connector related check can be replaced
by &analogix_dp_plat_data.bridge.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

------

Changes in v3:
- Squash the Exynos side commit and the Analogix side commit together.

Changes in v4:
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge.

Changes in v9:
- Add Tested-by tag.

Changes in v10:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +-
 drivers/gpu/drm/exynos/exynos_dp.c                 | 1 -
 include/drm/bridge/analogix_dp.h                   | 1 -
 3 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 3caa47d31649..4606ecc3f480 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -959,7 +959,7 @@ static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
 		return -EINVAL;
 	}
 
-	if (!dp->plat_data->skip_connector) {
+	if (!dp->plat_data->next_bridge) {
 		connector = &dp->connector;
 		connector->polled = DRM_CONNECTOR_POLL_HPD;
 
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index ac16138a22fe..1eeb0b15f99a 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -200,7 +200,6 @@ static int exynos_dp_probe(struct platform_device *pdev)
 	dp->plat_data.power_on = exynos_dp_poweron;
 	dp->plat_data.power_off = exynos_dp_poweroff;
 	dp->plat_data.attach = exynos_dp_bridge_attach;
-	dp->plat_data.skip_connector = !!bridge;
 
 out:
 	dp->adp = analogix_dp_probe(dev, &dp->plat_data);
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index 582357c20640..f06da105d8f2 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -30,7 +30,6 @@ struct analogix_dp_plat_data {
 	struct drm_bridge *next_bridge;
 	struct drm_encoder *encoder;
 	struct drm_connector *connector;
-	bool skip_connector;
 
 	int (*power_on)(struct analogix_dp_plat_data *);
 	int (*power_off)(struct analogix_dp_plat_data *);
-- 
2.34.1



^ permalink raw reply related

* Re: [EXT] Re: [PATCH 1/2] dt-bindings: gpu: mali-valhall-csf: Document i.MX952 support
From: Daniel Baluta @ 2026-04-01  9:19 UTC (permalink / raw)
  To: Guangliu Ding, Liviu Dudau
  Cc: Daniel Almeida, Alice Ryhl, Boris Brezillon, Steven Price,
	David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, imx@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, Jiyu Yang
In-Reply-To: <AM0PR04MB47073E9E8B5C704BCF5D9F72F350A@AM0PR04MB4707.eurprd04.prod.outlook.com>

On 4/1/26 11:48, Guangliu Ding wrote:
> [You don't often get email from guangliu.ding@nxp.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> Hi Liviu
>
> Thanks for your review. Please refer to my comments below:
>
>> On Tue, Mar 31, 2026 at 06:12:38PM +0800, Guangliu Ding wrote:
>>> Add compatible string of Mali G310 GPU on i.MX952 board.
>>>
>>> Signed-off-by: Guangliu Ding <guangliu.ding@nxp.com>
>>> Reviewed-by: Jiyu Yang <jiyu.yang@nxp.com>
>>> ---
>>>  Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml | 1 +
>>>  1 file changed, 1 insertion(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
>> b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
>>> index 8eccd4338a2b..6a10843a26e2 100644
>>> --- a/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
>>> +++ b/Documentation/devicetree/bindings/gpu/arm,mali-valhall-csf.yaml
>>> @@ -20,6 +20,7 @@ properties:
>>>            - enum:
>>>                - mediatek,mt8196-mali
>>>                - nxp,imx95-mali            # G310
>>> +              - nxp,imx952-mali           # G310
>> Can you explain why this is needed? Can it not be covered by the existing
>> compatible?
> There are functional differences in GPU module (GPUMIX) between i.MX95
> and i.MX952. So they cannot be fully covered by a single existing compatible.
> On i.MX952, The GPU clock is controlled by hardware GPU auto clock-gating
> mechanism, while the GPU clock is managed explicitly by the driver on i.MX95.
> Because of these behavioral differences, separate compatible strings
> "nxp,imx95-mali" and "nxp,imx952-mali" are needed to allow the driver to handle
> the two variants independently and to keep room for future divergence.


This information should be added in the commit message explaining why

the change is needed.


But then where is the driver code taking care of these diferences?




^ permalink raw reply

* [PATCH v12 12/17] drm/bridge: analogix_dp: Add new API analogix_dp_finish_probe()
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

Since the panel/bridge should logically be positioned behind the
Analogix bridge in the display pipeline, it makes sense to handle
the panel/bridge parsing on the Analogix side. Therefore, we add
a new API analogix_dp_finish_probe(), which combines the panel/bridge
parsing with component addition, to do it.

In order to process component binding right after the probe completes,
the &analogix_dp_plat_data.ops is newly added to pass &component_ops,
for which the &dp_aux_ep_device_with_data.done_probing() of DP AUX bus
only supports passing &drm_dp_aux.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v4:
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge.
- Remame API analogix_dp_find_panel_or_bridge() to
  analogix_dp_finish_probe().

Changes in v5:
- Select DRM_DISPLAY_DP_AUX_BUS for DRM_ANALOGIX_DP.

Changes in v9:
- Add Tested-by tag.

Changes in v10:
- Fix to use dev_err_probe() in analogix_dp_finish_probe().
- Expand the commit message.
---
 drivers/gpu/drm/bridge/analogix/Kconfig       |  1 +
 .../drm/bridge/analogix/analogix_dp_core.c    | 46 +++++++++++++++++++
 include/drm/bridge/analogix_dp.h              |  2 +
 3 files changed, 49 insertions(+)

diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig
index 4846b2e9be7c..964122b5bd39 100644
--- a/drivers/gpu/drm/bridge/analogix/Kconfig
+++ b/drivers/gpu/drm/bridge/analogix/Kconfig
@@ -29,6 +29,7 @@ config DRM_ANALOGIX_ANX78XX
 config DRM_ANALOGIX_DP
 	tristate
 	depends on DRM
+	select DRM_DISPLAY_DP_AUX_BUS
 
 config DRM_ANALOGIX_ANX7625
 	tristate "Analogix Anx7625 MIPI to DP interface support"
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 81c6e81dd352..b112df52cabf 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -20,12 +20,14 @@
 #include <linux/platform_device.h>
 
 #include <drm/bridge/analogix_dp.h>
+#include <drm/display/drm_dp_aux_bus.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_device.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_of.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
@@ -1581,6 +1583,50 @@ struct drm_dp_aux *analogix_dp_get_aux(struct analogix_dp_device *dp)
 }
 EXPORT_SYMBOL_GPL(analogix_dp_get_aux);
 
+static int analogix_dp_aux_done_probing(struct drm_dp_aux *aux)
+{
+	struct analogix_dp_device *dp = to_dp(aux);
+	struct analogix_dp_plat_data *plat_data = dp->plat_data;
+	int port = plat_data->dev_type == EXYNOS_DP ? 0 : 1;
+	int ret;
+
+	/*
+	 * If drm_of_find_panel_or_bridge() returns -ENODEV, there may be no valid panel
+	 * or bridge nodes. The driver should go on for the driver-free bridge or the DP
+	 * mode applications.
+	 */
+	ret = drm_of_find_panel_or_bridge(dp->dev->of_node, port, 0,
+					  &plat_data->panel, &plat_data->next_bridge);
+	if (ret && ret != -ENODEV)
+		return ret;
+
+	return component_add(dp->dev, plat_data->ops);
+}
+
+int analogix_dp_finish_probe(struct analogix_dp_device *dp)
+{
+	int ret;
+
+	ret = devm_of_dp_aux_populate_bus(&dp->aux, analogix_dp_aux_done_probing);
+	if (ret) {
+		/*
+		 * If devm_of_dp_aux_populate_bus() returns -ENODEV, the done_probing() will
+		 * not be called because there are no EP devices. Then the callback function
+		 * analogix_dp_aux_done_probing() will be called directly in order to support
+		 * the other valid DT configurations.
+		 *
+		 * NOTE: The devm_of_dp_aux_populate_bus() is allowed to return -EPROBE_DEFER.
+		 */
+		if (ret != -ENODEV)
+			return dev_err_probe(dp->dev, ret, "failed to populate aux bus\n");
+
+		return analogix_dp_aux_done_probing(&dp->aux);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(analogix_dp_finish_probe);
+
 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
 MODULE_DESCRIPTION("Analogix DP Core Driver");
 MODULE_LICENSE("GPL v2");
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index 3428ffff24c5..bae969dec63a 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -30,6 +30,7 @@ struct analogix_dp_plat_data {
 	struct drm_bridge *next_bridge;
 	struct drm_encoder *encoder;
 	struct drm_connector *connector;
+	const struct component_ops *ops;
 
 	int (*power_on)(struct analogix_dp_plat_data *);
 	int (*power_off)(struct analogix_dp_plat_data *);
@@ -49,5 +50,6 @@ int analogix_dp_stop_crc(struct drm_connector *connector);
 
 struct analogix_dp_plat_data *analogix_dp_aux_to_plat_data(struct drm_dp_aux *aux);
 struct drm_dp_aux *analogix_dp_get_aux(struct analogix_dp_device *dp);
+int analogix_dp_finish_probe(struct analogix_dp_device *dp);
 
 #endif /* _ANALOGIX_DP_H_ */
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 13/17] drm/rockchip: analogix_dp: Apply analogix_dp_finish_probe()
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

Apply analogix_dp_finish_probe() in order to move the panel/bridge
parsing from Rockchip side to the Analogix side.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v4:
- Rename analogix_dp_find_panel_or_bridge() to
  analogix_dp_finish_probe().

Changes in v5:
- Remove DRM_DISPLAY_DP_AUX_BUS for ROCKCHIP_ANALOGIX_DP

Changes in v9:
- Add Tested-by tag.
---
 .../gpu/drm/rockchip/analogix_dp-rockchip.c   | 38 +------------------
 1 file changed, 2 insertions(+), 36 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index 7fa17ba26c46..832e9766bef0 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -21,14 +21,12 @@
 #include <video/of_videomode.h>
 #include <video/videomode.h>
 
-#include <drm/display/drm_dp_aux_bus.h>
 #include <drm/display/drm_dp_helper.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge_connector.h>
 #include <drm/bridge/analogix_dp.h>
 #include <drm/drm_of.h>
-#include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
@@ -417,24 +415,6 @@ static const struct component_ops rockchip_dp_component_ops = {
 	.unbind = rockchip_dp_unbind,
 };
 
-static int rockchip_dp_link_panel(struct drm_dp_aux *aux)
-{
-	struct analogix_dp_plat_data *plat_data = analogix_dp_aux_to_plat_data(aux);
-	struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
-	int ret;
-
-	/*
-	 * If drm_of_find_panel_or_bridge() returns -ENODEV, there may be no valid panel
-	 * or bridge nodes. The driver should go on for the driver-free bridge or the DP
-	 * mode applications.
-	 */
-	ret = drm_of_find_panel_or_bridge(dp->dev->of_node, 1, 0, &plat_data->panel, NULL);
-	if (ret && ret != -ENODEV)
-		return ret;
-
-	return component_add(dp->dev, &rockchip_dp_component_ops);
-}
-
 static int rockchip_dp_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -473,6 +453,7 @@ static int rockchip_dp_probe(struct platform_device *pdev)
 	dp->plat_data.dev_type = dp->data->chip_type;
 	dp->plat_data.power_on = rockchip_dp_poweron;
 	dp->plat_data.power_off = rockchip_dp_powerdown;
+	dp->plat_data.ops = &rockchip_dp_component_ops;
 
 	ret = rockchip_dp_of_probe(dp);
 	if (ret < 0)
@@ -484,22 +465,7 @@ static int rockchip_dp_probe(struct platform_device *pdev)
 	if (IS_ERR(dp->adp))
 		return PTR_ERR(dp->adp);
 
-	ret = devm_of_dp_aux_populate_bus(analogix_dp_get_aux(dp->adp), rockchip_dp_link_panel);
-	if (ret) {
-		/*
-		 * If devm_of_dp_aux_populate_bus() returns -ENODEV, the done_probing() will not
-		 * be called because there are no EP devices. Then the rockchip_dp_link_panel()
-		 * will be called directly in order to support the other valid DT configurations.
-		 *
-		 * NOTE: The devm_of_dp_aux_populate_bus() is allowed to return -EPROBE_DEFER.
-		 */
-		if (ret != -ENODEV)
-			return dev_err_probe(dp->dev, ret, "failed to populate aux bus\n");
-
-		return rockchip_dp_link_panel(analogix_dp_get_aux(dp->adp));
-	}
-
-	return 0;
+	return analogix_dp_finish_probe(dp->adp);
 }
 
 static void rockchip_dp_remove(struct platform_device *pdev)
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 11/17] drm/bridge: analogix_dp: Apply drm_bridge_connector helper
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

Initialize bridge_connector for both Rockchip and Exynos encoder sides.
Then, make DRM_BRIDGE_ATTACH_NO_CONNECTOR mandatory for Analogix bridge
side, as the private &drm_connector is no longer created.

The previous &drm_connector_funcs and &drm_connector_helper_funcs APIs
are replaced by the corresponding &drm_bridge_funcs APIs:

analogix_dp_atomic_check() -> analogix_dp_bridge_atomic_check()
analogix_dp_detect()       -> analogix_dp_bridge_detect()
analogix_dp_get_modes()    -> analogix_dp_bridge_get_modes()
                              analogix_dp_bridge_edid_read()

Additionally, the compatibilities of Analogix DP bridge based on whether
the next bridge is a 'panel'. If it is, OP_MODES and OP_DETECT are
supported; If not (the next bridge is a 'monitor' or a bridge chip),
OP_EDID and OP_DETECT are supported.

The devm_drm_bridge_add() is placed in analogix_dp_bind() instead of
analogix_dp_probe(), because the type of next bridge (the panel, monitor
or bridge chip) can only be determined after the probe process has fully
completed.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

------

Changes in v2:
- For &drm_bridge.ops, remove DRM_BRIDGE_OP_HPD and add
  DRM_BRIDGE_OP_EDID.
- Add analogix_dp_bridge_edid_read().
- Move &analogix_dp_plat_data.skip_connector deletion to the previous
  patches.

Changes in v3:
- Rebase with the new devm_drm_bridge_alloc() related commit
  48f05c3b4b70 ("drm/bridge: analogix_dp: Use devm_drm_bridge_alloc()
  API").
- Expand the commit message.
- Call drm_bridge_get_modes() in analogix_dp_bridge_get_modes() if the
  bridge is available.
- Remove unnecessary parameter struct drm_connector* for callback
  &analogix_dp_plat_data.attach.
- In order to decouple the connector driver and the bridge driver, move
  the bridge connector initilization to the Rockchip and Exynos sides.

Changes in v4:
- Expand analogix_dp_bridge_detect() parameters to &drm_bridge and
  &drm_connector.
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge.

Changes in v5:
- Set the flag fo drm_bridge_attach() to DRM_BRIDGE_ATTACH_NO_CONNECTOR
  for next bridge attachment of Exynos side.
- Distinguish the &drm_bridge->ops of Analogix bridge based on whether
  the downstream device is a panel, a bridge or neither.
- Remove the calls to &analogix_dp_plat_data.get_modes().

Changes in v6:
- Select DRM_BRIDGE_CONNECTOR for both Rockchip and Exynos sides.
- Remove unnecessary drm_bridge_get_modes() in
  analogix_dp_bridge_get_modes().
- Simplify analogix_dp_bridge_edid_read().
- If the next is a bridge, set DRM_BRIDGE_OP_DETECT and return
  connector_status_connected in analogix_dp_bridge_detect().
- Set flag DRM_BRIDGE_ATTACH_NO_CONNECTOR for bridge attachment while
  binding. Meanwhile, make DRM_BRIDGE_ATTACH_NO_CONNECTOR unsuppported
  in analogix_dp_bridge_attach().
- Simplify the check of bridge capabilities.

Changes in v7:
- Remove temporary flag &exynos_dp_device.has_of_bridge.

Changes in v9
- Add Tested-by tag.

Changes in v10:
- Split this commit into serval smaller ones.
- Simplify the commit message.

Changes in v11:
- Move the removal of &analogix_dp_device.connector to this commit.
- In exynos_dp_bridge_attach(), set the bridge flag to 'flags |
  DRM_BRIDGE_ATTACH_NO_CONNECTOR' instead of fixed
  'DRM_BRIDGE_ATTACH_NO_CONNECTOR' for extensibility.

Changes in v12:
- Restore accidentally removed DRM_BRIDGE_CONNECTOR Kconfig in v10.
---
 .../drm/bridge/analogix/analogix_dp_core.c    | 135 +++++++-----------
 .../drm/bridge/analogix/analogix_dp_core.h    |   1 -
 drivers/gpu/drm/exynos/Kconfig                |   1 +
 drivers/gpu/drm/exynos/exynos_dp.c            |  25 ++--
 drivers/gpu/drm/rockchip/Kconfig              |   1 +
 .../gpu/drm/rockchip/analogix_dp-rockchip.c   |  11 +-
 6 files changed, 78 insertions(+), 96 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 7e42eb18313d..81c6e81dd352 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -856,44 +856,32 @@ static int analogix_dp_disable_psr(struct analogix_dp_device *dp)
 	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
 }
 
-static int analogix_dp_get_modes(struct drm_connector *connector)
+static int analogix_dp_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
-	const struct drm_edid *drm_edid;
+	struct analogix_dp_device *dp = to_dp(bridge);
 	int num_modes = 0;
 
-	if (dp->plat_data->panel) {
+	if (dp->plat_data->panel)
 		num_modes += drm_panel_get_modes(dp->plat_data->panel, connector);
-	} else {
-		drm_edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
-
-		drm_edid_connector_update(&dp->connector, drm_edid);
-
-		if (drm_edid) {
-			num_modes += drm_edid_connector_add_modes(&dp->connector);
-			drm_edid_free(drm_edid);
-		}
-	}
 
 	return num_modes;
 }
 
-static struct drm_encoder *
-analogix_dp_best_encoder(struct drm_connector *connector)
+static const struct drm_edid *analogix_dp_bridge_edid_read(struct drm_bridge *bridge,
+							   struct drm_connector *connector)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
+	struct analogix_dp_device *dp = to_dp(bridge);
 
-	return dp->encoder;
+	return drm_edid_read_ddc(connector, &dp->aux.ddc);
 }
 
-
-static int analogix_dp_atomic_check(struct drm_connector *connector,
-				    struct drm_atomic_state *state)
+static int analogix_dp_bridge_atomic_check(struct drm_bridge *bridge,
+					   struct drm_bridge_state *bridge_state,
+					   struct drm_crtc_state *crtc_state,
+					   struct drm_connector_state *conn_state)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
-	struct drm_display_info *di = &connector->display_info;
-	struct drm_connector_state *conn_state;
-	struct drm_crtc_state *crtc_state;
+	struct analogix_dp_device *dp = to_dp(bridge);
+	struct drm_display_info *di = &conn_state->connector->display_info;
 	u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422;
 
 	if (is_rockchip(dp->plat_data->dev_type)) {
@@ -905,38 +893,21 @@ static int analogix_dp_atomic_check(struct drm_connector *connector,
 		}
 	}
 
-	conn_state = drm_atomic_get_new_connector_state(state, connector);
-	if (WARN_ON(!conn_state))
-		return -ENODEV;
-
 	conn_state->self_refresh_aware = true;
 
-	if (!conn_state->crtc)
-		return 0;
-
-	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
-	if (!crtc_state)
-		return 0;
-
 	if (crtc_state->self_refresh_active && !dp->psr_supported)
 		return -EINVAL;
 
 	return 0;
 }
 
-static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = {
-	.get_modes = analogix_dp_get_modes,
-	.best_encoder = analogix_dp_best_encoder,
-	.atomic_check = analogix_dp_atomic_check,
-};
-
 static enum drm_connector_status
-analogix_dp_detect(struct drm_connector *connector, bool force)
+analogix_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
+	struct analogix_dp_device *dp = to_dp(bridge);
 	enum drm_connector_status status = connector_status_disconnected;
 
-	if (dp->plat_data->panel)
+	if (dp->plat_data->panel || dp->plat_data->next_bridge)
 		return connector_status_connected;
 
 	if (!analogix_dp_detect_hpd(dp))
@@ -945,51 +916,18 @@ analogix_dp_detect(struct drm_connector *connector, bool force)
 	return status;
 }
 
-static const struct drm_connector_funcs analogix_dp_connector_funcs = {
-	.fill_modes = drm_helper_probe_single_connector_modes,
-	.detect = analogix_dp_detect,
-	.destroy = drm_connector_cleanup,
-	.reset = drm_atomic_helper_connector_reset,
-	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
 static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
 				     struct drm_encoder *encoder,
 				     enum drm_bridge_attach_flags flags)
 {
 	struct analogix_dp_device *dp = to_dp(bridge);
-	struct drm_connector *connector = NULL;
 	int ret = 0;
 
-	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
-		DRM_ERROR("Fix bridge driver to make connector optional!");
+	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
+		DRM_ERROR("Unsupported connector creation\n");
 		return -EINVAL;
 	}
 
-	if (!dp->plat_data->next_bridge) {
-		connector = &dp->connector;
-		connector->polled = DRM_CONNECTOR_POLL_HPD;
-
-		ret = drm_connector_init(dp->drm_dev, connector,
-					 &analogix_dp_connector_funcs,
-					 DRM_MODE_CONNECTOR_eDP);
-		if (ret) {
-			DRM_ERROR("Failed to initialize connector with drm\n");
-			return ret;
-		}
-
-		drm_connector_helper_add(connector,
-					 &analogix_dp_connector_helper_funcs);
-		drm_connector_attach_encoder(connector, encoder);
-	}
-
-	/*
-	 * NOTE: the connector registration is implemented in analogix
-	 * platform driver, that to say connector would be exist after
-	 * plat_data->attch return, that's why we record the connector
-	 * point after plat attached.
-	 */
 	if (dp->plat_data->attach) {
 		ret = dp->plat_data->attach(dp->plat_data, bridge);
 		if (ret) {
@@ -1309,7 +1247,11 @@ static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
 	.atomic_enable = analogix_dp_bridge_atomic_enable,
 	.atomic_disable = analogix_dp_bridge_atomic_disable,
 	.atomic_post_disable = analogix_dp_bridge_atomic_post_disable,
+	.atomic_check = analogix_dp_bridge_atomic_check,
 	.attach = analogix_dp_bridge_attach,
+	.get_modes = analogix_dp_bridge_get_modes,
+	.edid_read = analogix_dp_bridge_edid_read,
+	.detect = analogix_dp_bridge_detect,
 };
 
 static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp)
@@ -1539,6 +1481,7 @@ EXPORT_SYMBOL_GPL(analogix_dp_resume);
 
 int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
 {
+	struct drm_bridge *bridge = &dp->bridge;
 	int ret;
 
 	dp->drm_dev = drm_dev;
@@ -1552,7 +1495,18 @@ int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
 		return ret;
 	}
 
-	ret = drm_bridge_attach(dp->encoder, &dp->bridge, NULL, 0);
+	if (dp->plat_data->panel)
+		bridge->ops = DRM_BRIDGE_OP_MODES | DRM_BRIDGE_OP_DETECT;
+	else
+		bridge->ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT;
+
+	bridge->of_node = dp->dev->of_node;
+	bridge->type = DRM_MODE_CONNECTOR_eDP;
+	ret = devm_drm_bridge_add(dp->dev, &dp->bridge);
+	if (ret)
+		goto err_unregister_aux;
+
+	ret = drm_bridge_attach(dp->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
 	if (ret) {
 		DRM_ERROR("failed to create bridge (%d)\n", ret);
 		goto err_unregister_aux;
@@ -1570,7 +1524,6 @@ EXPORT_SYMBOL_GPL(analogix_dp_bind);
 void analogix_dp_unbind(struct analogix_dp_device *dp)
 {
 	analogix_dp_bridge_disable(&dp->bridge);
-	dp->connector.funcs->destroy(&dp->connector);
 
 	drm_panel_unprepare(dp->plat_data->panel);
 
@@ -1580,7 +1533,8 @@ EXPORT_SYMBOL_GPL(analogix_dp_unbind);
 
 int analogix_dp_start_crc(struct drm_connector *connector)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
+	struct analogix_dp_device *dp;
+	struct drm_bridge *bridge;
 
 	if (!connector->state->crtc) {
 		DRM_ERROR("Connector %s doesn't currently have a CRTC.\n",
@@ -1588,13 +1542,26 @@ int analogix_dp_start_crc(struct drm_connector *connector)
 		return -EINVAL;
 	}
 
+	bridge = drm_bridge_chain_get_first_bridge(connector->encoder);
+	if (bridge->type != DRM_MODE_CONNECTOR_eDP)
+		return -EINVAL;
+
+	dp = to_dp(bridge);
+
 	return drm_dp_start_crc(&dp->aux, connector->state->crtc);
 }
 EXPORT_SYMBOL_GPL(analogix_dp_start_crc);
 
 int analogix_dp_stop_crc(struct drm_connector *connector)
 {
-	struct analogix_dp_device *dp = to_dp(connector);
+	struct analogix_dp_device *dp;
+	struct drm_bridge *bridge;
+
+	bridge = drm_bridge_chain_get_first_bridge(connector->encoder);
+	if (bridge->type != DRM_MODE_CONNECTOR_eDP)
+		return -EINVAL;
+
+	dp = to_dp(bridge);
 
 	return drm_dp_stop_crc(&dp->aux);
 }
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
index 91b215c6a0cf..17347448c6b0 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
@@ -154,7 +154,6 @@ struct analogix_dp_device {
 	struct drm_encoder	*encoder;
 	struct device		*dev;
 	struct drm_device	*drm_dev;
-	struct drm_connector	connector;
 	struct drm_bridge	bridge;
 	struct drm_dp_aux	aux;
 	struct clk		*clock;
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
index 380d9a8ce259..38bf070866f6 100644
--- a/drivers/gpu/drm/exynos/Kconfig
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -70,6 +70,7 @@ config DRM_EXYNOS_DP
 	bool "Exynos specific extensions for Analogix DP driver"
 	depends on DRM_EXYNOS_FIMD || DRM_EXYNOS7_DECON
 	select DRM_ANALOGIX_DP
+	select DRM_BRIDGE_CONNECTOR
 	select DRM_DISPLAY_DP_HELPER
 	default DRM_EXYNOS
 	select DRM_OF_DISPLAY_MODE_BRIDGE
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index 71a00ee97782..b2597fafd73d 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -22,6 +22,7 @@
 #include <drm/bridge/of-display-mode-bridge.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
@@ -41,8 +42,6 @@ struct exynos_dp_device {
 
 	struct analogix_dp_device *adp;
 	struct analogix_dp_plat_data plat_data;
-
-	bool has_of_bridge;
 };
 
 static int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data,
@@ -78,10 +77,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
 
 	/* Pre-empt DP connector creation if there's a bridge */
 	if (plat_data->next_bridge) {
-		if (dp->has_of_bridge)
-			flags = DRM_BRIDGE_ATTACH_NO_CONNECTOR;
-
-		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge, flags);
+		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge,
+					flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR);
 		if (ret)
 			return ret;
 	}
@@ -111,6 +108,7 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
 	struct exynos_dp_device *dp = dev_get_drvdata(dev);
 	struct drm_encoder *encoder = &dp->encoder;
 	struct drm_device *drm_dev = data;
+	struct drm_connector *connector;
 	int ret;
 
 	dp->drm_dev = drm_dev;
@@ -126,10 +124,19 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
 	dp->plat_data.encoder = encoder;
 
 	ret = analogix_dp_bind(dp->adp, dp->drm_dev);
-	if (ret)
+	if (ret) {
 		dp->encoder.funcs->destroy(&dp->encoder);
+		return ret;
+	}
+
+	connector = drm_bridge_connector_init(dp->drm_dev, dp->plat_data.encoder);
+	if (IS_ERR(connector)) {
+		ret = PTR_ERR(connector);
+		dev_err(dp->dev, "Failed to initialize bridge_connector\n");
+		return ret;
+	}
 
-	return ret;
+	return drm_connector_attach_encoder(connector, dp->plat_data.encoder);
 }
 
 static void exynos_dp_unbind(struct device *dev, struct device *master,
@@ -186,8 +193,6 @@ static int exynos_dp_probe(struct platform_device *pdev)
 									dp->dev->of_node,
 									DRM_MODE_CONNECTOR_eDP);
 		ret = IS_ERR(dp->plat_data.next_bridge) ? PTR_ERR(dp->plat_data.next_bridge) : 0;
-		if (!ret)
-			dp->has_of_bridge = true;
 	}
 	if (ret)
 		return ret;
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index 1479b8c4ed40..e7f49fe845ea 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -46,6 +46,7 @@ config ROCKCHIP_VOP2
 config ROCKCHIP_ANALOGIX_DP
 	bool "Rockchip specific extensions for Analogix DP driver"
 	depends on ROCKCHIP_VOP
+	select DRM_BRIDGE_CONNECTOR
 	select DRM_DISPLAY_HELPER
 	select DRM_DISPLAY_DP_HELPER
 	help
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index 70fe5ae69e2e..7fa17ba26c46 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -25,6 +25,7 @@
 #include <drm/display/drm_dp_helper.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge_connector.h>
 #include <drm/bridge/analogix_dp.h>
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
@@ -369,6 +370,7 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
 {
 	struct rockchip_dp_device *dp = dev_get_drvdata(dev);
 	struct drm_device *drm_dev = data;
+	struct drm_connector *connector;
 	int ret;
 
 	dp->drm_dev = drm_dev;
@@ -388,7 +390,14 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
 	if (ret)
 		goto err_cleanup_encoder;
 
-	return 0;
+	connector = drm_bridge_connector_init(dp->drm_dev, dp->plat_data.encoder);
+	if (IS_ERR(connector)) {
+		ret = PTR_ERR(connector);
+		dev_err(dp->dev, "Failed to initialize bridge_connector\n");
+		goto err_cleanup_encoder;
+	}
+
+	return drm_connector_attach_encoder(connector, dp->plat_data.encoder);
 err_cleanup_encoder:
 	dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
 	return ret;
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 09/17] drm/bridge: analogix_dp: Remove unused struct drm_connector* for &analogix_dp_plat_data.attach()
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

For both Rockchip and Exynos sides, the struct drm_connector* is
never used in callback &analogix_dp_plat_data.attach(). After
applying drm_bridge_connector helper, this parameter will no longer
be used at all.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>

-----

Changes in v11:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +-
 drivers/gpu/drm/exynos/exynos_dp.c                 | 3 +--
 include/drm/bridge/analogix_dp.h                   | 3 +--
 3 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index b0bc96693fdb..3efd910ea463 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -991,7 +991,7 @@ static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
 	 * point after plat attached.
 	 */
 	if (dp->plat_data->attach) {
-		ret = dp->plat_data->attach(dp->plat_data, bridge, connector);
+		ret = dp->plat_data->attach(dp->plat_data, bridge);
 		if (ret) {
 			DRM_ERROR("Failed at platform attach func\n");
 			return ret;
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index 1eeb0b15f99a..71a00ee97782 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -70,8 +70,7 @@ static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data)
 }
 
 static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
-				   struct drm_bridge *bridge,
-				   struct drm_connector *connector)
+				   struct drm_bridge *bridge)
 {
 	struct exynos_dp_device *dp = to_dp(plat_data);
 	enum drm_bridge_attach_flags flags = 0;
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index 3301392eda5f..3428ffff24c5 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -33,8 +33,7 @@ struct analogix_dp_plat_data {
 
 	int (*power_on)(struct analogix_dp_plat_data *);
 	int (*power_off)(struct analogix_dp_plat_data *);
-	int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *,
-		      struct drm_connector *);
+	int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *);
 };
 
 int analogix_dp_resume(struct analogix_dp_device *dp);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 08/17] drm/bridge: analogix_dp: Remove unused &analogix_dp_plat_data.get_modes()
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

The callback &analogix_dp_plat_data.get_modes() is not implemented
by either Rockchip side or Exynos side.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v9:
- Add Tested-by tag.

Changes in v10:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 3 ---
 include/drm/bridge/analogix_dp.h                   | 2 --
 2 files changed, 5 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 5bf41b364aba..b0bc96693fdb 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -875,9 +875,6 @@ static int analogix_dp_get_modes(struct drm_connector *connector)
 		}
 	}
 
-	if (dp->plat_data->get_modes)
-		num_modes += dp->plat_data->get_modes(dp->plat_data, connector);
-
 	return num_modes;
 }
 
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index f06da105d8f2..3301392eda5f 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -35,8 +35,6 @@ struct analogix_dp_plat_data {
 	int (*power_off)(struct analogix_dp_plat_data *);
 	int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *,
 		      struct drm_connector *);
-	int (*get_modes)(struct analogix_dp_plat_data *,
-			 struct drm_connector *);
 };
 
 int analogix_dp_resume(struct analogix_dp_device *dp);
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 07/17] drm/bridge: analogix_dp: Move the color format check to .atomic_check() for Rockchip platforms
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

For Rockchip platforms, the YUV color formats are currently unsupported.
This compatibility check was previously implemented in
&analogix_dp_plat_data.get_modes().

Moving color format check to &drm_connector_helper_funcs.atomic_check()
would get rid of &analogix_dp_plat_data.get_modes() and be more
reasonable than before.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v9:
- Add Tested-by tag.
---
 .../gpu/drm/bridge/analogix/analogix_dp_core.c | 11 +++++++++++
 .../gpu/drm/rockchip/analogix_dp-rockchip.c    | 18 ------------------
 2 files changed, 11 insertions(+), 18 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 4606ecc3f480..5bf41b364aba 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -894,8 +894,19 @@ static int analogix_dp_atomic_check(struct drm_connector *connector,
 				    struct drm_atomic_state *state)
 {
 	struct analogix_dp_device *dp = to_dp(connector);
+	struct drm_display_info *di = &connector->display_info;
 	struct drm_connector_state *conn_state;
 	struct drm_crtc_state *crtc_state;
+	u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422;
+
+	if (is_rockchip(dp->plat_data->dev_type)) {
+		if ((di->color_formats & mask)) {
+			DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
+			di->color_formats &= ~mask;
+			di->color_formats |= DRM_COLOR_FORMAT_RGB444;
+			di->bpc = 8;
+		}
+	}
 
 	conn_state = drm_atomic_get_new_connector_state(state, connector);
 	if (WARN_ON(!conn_state))
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index fdab71d51e2a..70fe5ae69e2e 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -166,23 +166,6 @@ static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
 	return 0;
 }
 
-static int rockchip_dp_get_modes(struct analogix_dp_plat_data *plat_data,
-				 struct drm_connector *connector)
-{
-	struct drm_display_info *di = &connector->display_info;
-	/* VOP couldn't output YUV video format for eDP rightly */
-	u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422;
-
-	if ((di->color_formats & mask)) {
-		DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
-		di->color_formats &= ~mask;
-		di->color_formats |= DRM_COLOR_FORMAT_RGB444;
-		di->bpc = 8;
-	}
-
-	return 0;
-}
-
 static bool
 rockchip_dp_drm_encoder_mode_fixup(struct drm_encoder *encoder,
 				   const struct drm_display_mode *mode,
@@ -481,7 +464,6 @@ static int rockchip_dp_probe(struct platform_device *pdev)
 	dp->plat_data.dev_type = dp->data->chip_type;
 	dp->plat_data.power_on = rockchip_dp_poweron;
 	dp->plat_data.power_off = rockchip_dp_powerdown;
-	dp->plat_data.get_modes = rockchip_dp_get_modes;
 
 	ret = rockchip_dp_of_probe(dp);
 	if (ret < 0)
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 00/17] Apply drm_bridge_connector and panel_bridge helper for the Analogix DP driver
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding

PATCH 1 is to add a new parameter to store the point of next bridge.
PATCH 2 is to make legacy bridge driver more universal.
PATCH 3-10 are preparations for apply drm_bridge_connector helper.
PATCH 11 is to apply the drm_bridge_connector helper.
PATCH 12-14 are to move the panel/bridge parsing to the Analogix side.
PATCH 15 is to attach the next bridge on Analogix side uniformly.
PATCH 16-17 are to apply the panel_bridge helper.

Damon Ding (17):
  drm/bridge: analogix_dp: Add &analogix_dp_plat_data.next_bridge
  drm/bridge: Move legacy bridge driver out of imx directory for
    multi-platform use
  drm/exynos: exynos_dp: Remove &exynos_dp_device.ptn_bridge
  drm/exynos: exynos_dp: Remove unused &exynos_dp_device.connector
  drm/exynos: exynos_dp: Apply of-display-mode-bridge to parse the
    display-timings node
  drm/bridge: analogix_dp: Remove redundant
    &analogix_dp_plat_data.skip_connector
  drm/bridge: analogix_dp: Move the color format check to
    .atomic_check() for Rockchip platforms
  drm/bridge: analogix_dp: Remove unused
    &analogix_dp_plat_data.get_modes()
  drm/bridge: analogix_dp: Remove unused struct drm_connector* for
    &analogix_dp_plat_data.attach()
  drm/bridge: analogix_dp: Pass struct drm_atomic_state* for
    analogix_dp_bridge_mode_set()
  drm/bridge: analogix_dp: Apply drm_bridge_connector helper
  drm/bridge: analogix_dp: Add new API analogix_dp_finish_probe()
  drm/rockchip: analogix_dp: Apply analogix_dp_finish_probe()
  drm/exynos: exynos_dp: Apply analogix_dp_finish_probe()
  drm/bridge: analogix_dp: Attach the next bridge in
    analogix_dp_bridge_attach()
  drm/bridge: analogix_dp: Remove bridge disabing and panel unpreparing
    in analogix_dp_unbind()
  drm/bridge: analogix_dp: Apply panel_bridge helper

 drivers/gpu/drm/bridge/Kconfig                |  10 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/analogix/Kconfig       |   1 +
 .../drm/bridge/analogix/analogix_dp_core.c    | 236 +++++++++---------
 .../drm/bridge/analogix/analogix_dp_core.h    |   1 -
 drivers/gpu/drm/bridge/imx/Kconfig            |  10 -
 drivers/gpu/drm/bridge/imx/Makefile           |   1 -
 .../gpu/drm/bridge/imx/imx-legacy-bridge.c    |  91 -------
 .../gpu/drm/bridge/of-display-mode-bridge.c   |  93 +++++++
 drivers/gpu/drm/exynos/Kconfig                |   2 +
 drivers/gpu/drm/exynos/exynos_dp.c            | 110 ++------
 drivers/gpu/drm/imx/ipuv3/Kconfig             |   4 +-
 drivers/gpu/drm/imx/ipuv3/imx-ldb.c           |   6 +-
 drivers/gpu/drm/imx/ipuv3/parallel-display.c  |   5 +-
 drivers/gpu/drm/rockchip/Kconfig              |   1 +
 .../gpu/drm/rockchip/analogix_dp-rockchip.c   |  67 +----
 include/drm/bridge/analogix_dp.h              |   8 +-
 include/drm/bridge/imx.h                      |  17 --
 include/drm/bridge/of-display-mode-bridge.h   |  17 ++
 19 files changed, 295 insertions(+), 386 deletions(-)
 delete mode 100644 drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c
 create mode 100644 drivers/gpu/drm/bridge/of-display-mode-bridge.c
 delete mode 100644 include/drm/bridge/imx.h
 create mode 100644 include/drm/bridge/of-display-mode-bridge.h

---

Changes in v2:
- Update Exynos DP driver synchronously.
- Move the panel/bridge parsing to the Analogix side.

Changes in v3:
- Rebase for the existing devm_drm_bridge_alloc() applying commit.
- Fix the typographical error of panel/bridge check in exynos_dp_bind().
- Squash all commits related to skip_connector deletion in both Exynos and
  Analogix code into one.
- Apply panel_bridge helper to make the codes more concise.
- Fix the handing of bridge in analogix_dp_bridge_get_modes().
- Remove unnecessary parameter struct drm_connector* for callback
  &analogix_dp_plat_data.attach().
- In order to decouple the connector driver and the bridge driver, move
  the bridge connector initilization to the Rockchip and Exynos sides.

Changes in v4:
- Rebase for the applied &drm_bridge_funcs.detect() modification commit.
- Rename analogix_dp_find_panel_or_bridge() to analogix_dp_finish_probe().
- Drop the drmm_encoder_init() modification commit.
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge.

Changes in v5:
- Add legacy bridge to parse the display-timings node under the dp node
  for Exynos side.
- Move color format check to &drm_connector_helper_funcs.atomic_check()
  in order to get rid of &analogix_dp_plat_data.get_modes().
- Remove unused callback &analogix_dp_plat_data.get_modes().
- Distinguish the &drm_bridge->ops of Analogix bridge based on whether
  the downstream device is a panel, a bridge or neither.
- Select DRM_DISPLAY_DP_AUX_BUS for DRM_ANALOGIX_DP, and remove it for
  ROCKCHIP_ANALOGIX_DP.
- Apply rockchip_dp_attach() to support the next bridge attachment for
  the Rockchip side.
- Move next_bridge attachment from Analogix side to Rockchip/Exynos sides.

Changes in v6:
- Move legacy bridge driver out of imx directory for multi-platform use.
- Apply DRM legacy bridge to parse display timings intead of implementing
  the same codes only for Exynos DP.
- Ensure last bridge determines EDID/modes detection capabilities in DRM
  bridge_connector driver.
- Remove unnecessary drm_bridge_get_modes() in
  analogix_dp_bridge_get_modes().
- Simplify analogix_dp_bridge_edid_read().
- If the next is a bridge, set DRM_BRIDGE_OP_DETECT and return
  connector_status_connected in analogix_dp_bridge_detect().
- Set flag DRM_BRIDGE_ATTACH_NO_CONNECTOR for bridge attachment while
  binding. Meanwhile, make DRM_BRIDGE_ATTACH_NO_CONNECTOR unsuppported
  in analogix_dp_bridge_attach().
- Move the next bridge attachment to the Analogix side rather than
  scattered on Rockchip and Exynos sides.
- Remove the unnecessary analogix_dp_bridge_get_modes().
- Squash [PATCH v5 15/17] into [PATCH v5 17/17].
- Fix the &drm_bridge->ops to DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT.

Changes in v7:
- As Luca suggested, simplify the code and related comment for bridge_connector
  modifications. Additionally, move the commit related to bridge_connector to
  the top of this patch series.
- Rename legacy-bridge driver to of-display-mode-bridge driver.
- Remove unnecessary API drm_bridge_is_legacy() and apply a temporary flag
  &exynos_dp_device.has_of_bridge instead, which will be removed finally.
- Remove exynos_dp_legacy_bridge_init() and inline API
  devm_drm_of_display_mode_bridge().

Changes in v8:
- Adapt the related modifications to the newest bridge_connector driver.

Changes in v9:
- Fix the Kconfig help text for CONFIG_DRM_OF_DISPLAY_MODE_BRIDGE.
- Add Tested-by tag from Heiko.

Changes in v10:
- Fix to use dev_err_probe() in newly added API analogix_dp_finish_probe().
- Expaned commit message for [PATCH v9 9/15] and [PATCH v9 10/15].
- Split [PATCH v9 9/15] into serval smaller commits.
- Add Reviewed-by tags from Luca.

Changes in v11:
- Merge [PATCH v10 12/18] into [PATCH v10 11/18].
- Fix the bridge flag to 'flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR' in
  [PATCH v10 11/18].
- Add Reviewed-by tags from Luca.

Changes in v12:
- Restore accidentally removed DRM_BRIDGE_CONNECTOR Kconfig in v10.

-- 
2.34.1



^ permalink raw reply

* [PATCH v12 02/17] drm/bridge: Move legacy bridge driver out of imx directory for multi-platform use
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

As suggested by Dmitry, the DRM legacy bridge driver can be pulled
out of imx/ subdir for multi-platform use. The driver is also renamed
to make it more generic and suitable for platforms other than i.MX.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v7:
- Rename legacy-bridge to of-display-mode-bridge.
- Remove unnecessary API drm_bridge_is_legacy().

Changes in v9:
- Fix the Kconfig help text.
- Add Tested-by tag.

Changes in v10:
- Add Reviewed-by tag.
---
 drivers/gpu/drm/bridge/Kconfig                | 10 ++
 drivers/gpu/drm/bridge/Makefile               |  1 +
 drivers/gpu/drm/bridge/imx/Kconfig            | 10 --
 drivers/gpu/drm/bridge/imx/Makefile           |  1 -
 .../gpu/drm/bridge/imx/imx-legacy-bridge.c    | 91 ------------------
 .../gpu/drm/bridge/of-display-mode-bridge.c   | 93 +++++++++++++++++++
 drivers/gpu/drm/imx/ipuv3/Kconfig             |  4 +-
 drivers/gpu/drm/imx/ipuv3/imx-ldb.c           |  6 +-
 drivers/gpu/drm/imx/ipuv3/parallel-display.c  |  5 +-
 include/drm/bridge/imx.h                      | 17 ----
 include/drm/bridge/of-display-mode-bridge.h   | 17 ++++
 11 files changed, 129 insertions(+), 126 deletions(-)
 delete mode 100644 drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c
 create mode 100644 drivers/gpu/drm/bridge/of-display-mode-bridge.c
 delete mode 100644 include/drm/bridge/imx.h
 create mode 100644 include/drm/bridge/of-display-mode-bridge.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 39385deafc68..6563d367cad8 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -251,6 +251,16 @@ config DRM_NXP_PTN3460
 	help
 	  NXP PTN3460 eDP-LVDS bridge chip driver.
 
+config DRM_OF_DISPLAY_MODE_BRIDGE
+	tristate
+	depends on DRM_BRIDGE && OF
+	help
+	  This is a DRM bridge implementation that uses of_get_drm_display_mode
+	  to acquire display mode.
+
+	  It exists for compatibility with legacy display mode parsing, in order
+	  to conform to the panel-bridge framework.
+
 config DRM_PARADE_PS8622
 	tristate "Parade eDP/LVDS bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 909c21cc3acd..c54018a014d3 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
 obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
 obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
+obj-$(CONFIG_DRM_OF_DISPLAY_MODE_BRIDGE) += of-display-mode-bridge.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
 obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o
diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig
index b9028a5e5a06..8877b9789868 100644
--- a/drivers/gpu/drm/bridge/imx/Kconfig
+++ b/drivers/gpu/drm/bridge/imx/Kconfig
@@ -3,16 +3,6 @@ if ARCH_MXC || COMPILE_TEST
 config DRM_IMX_LDB_HELPER
 	tristate
 
-config DRM_IMX_LEGACY_BRIDGE
-	tristate
-	depends on DRM_IMX
-	help
-	  This is a DRM bridge implementation for the DRM i.MX IPUv3 driver,
-	  that uses of_get_drm_display_mode to acquire display mode.
-
-	  Newer designs should not use this bridge and should use proper panel
-	  driver instead.
-
 config DRM_IMX8MP_DW_HDMI_BRIDGE
 	tristate "Freescale i.MX8MP HDMI-TX bridge support"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile
index 8d01fda25451..69d9f9abbe36 100644
--- a/drivers/gpu/drm/bridge/imx/Makefile
+++ b/drivers/gpu/drm/bridge/imx/Makefile
@@ -1,5 +1,4 @@
 obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o
-obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o
 obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o
 obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o
 obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o
diff --git a/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c b/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c
deleted file mode 100644
index 0e31d5000e7c..000000000000
--- a/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c
+++ /dev/null
@@ -1,91 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Freescale i.MX drm driver
- *
- * bridge driver for legacy DT bindings, utilizing display-timings node
- */
-
-#include <linux/export.h>
-
-#include <drm/drm_bridge.h>
-#include <drm/drm_modes.h>
-#include <drm/drm_probe_helper.h>
-#include <drm/bridge/imx.h>
-
-#include <video/of_display_timing.h>
-#include <video/of_videomode.h>
-
-struct imx_legacy_bridge {
-	struct drm_bridge base;
-
-	struct drm_display_mode mode;
-	u32 bus_flags;
-};
-
-#define to_imx_legacy_bridge(bridge)	container_of(bridge, struct imx_legacy_bridge, base)
-
-static int imx_legacy_bridge_attach(struct drm_bridge *bridge,
-				    struct drm_encoder *encoder,
-				    enum drm_bridge_attach_flags flags)
-{
-	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
-		return -EINVAL;
-
-	return 0;
-}
-
-static int imx_legacy_bridge_get_modes(struct drm_bridge *bridge,
-				       struct drm_connector *connector)
-{
-	struct imx_legacy_bridge *imx_bridge = to_imx_legacy_bridge(bridge);
-	int ret;
-
-	ret = drm_connector_helper_get_modes_fixed(connector, &imx_bridge->mode);
-	if (ret)
-		return ret;
-
-	connector->display_info.bus_flags = imx_bridge->bus_flags;
-
-	return 0;
-}
-
-struct drm_bridge_funcs imx_legacy_bridge_funcs = {
-	.attach = imx_legacy_bridge_attach,
-	.get_modes = imx_legacy_bridge_get_modes,
-};
-
-struct drm_bridge *devm_imx_drm_legacy_bridge(struct device *dev,
-					      struct device_node *np,
-					      int type)
-{
-	struct imx_legacy_bridge *imx_bridge;
-	int ret;
-
-	imx_bridge = devm_drm_bridge_alloc(dev, struct imx_legacy_bridge,
-					   base, &imx_legacy_bridge_funcs);
-	if (IS_ERR(imx_bridge))
-		return ERR_CAST(imx_bridge);
-
-	ret = of_get_drm_display_mode(np,
-				      &imx_bridge->mode,
-				      &imx_bridge->bus_flags,
-				      OF_USE_NATIVE_MODE);
-	if (ret)
-		return ERR_PTR(ret);
-
-	imx_bridge->mode.type |= DRM_MODE_TYPE_DRIVER;
-
-	imx_bridge->base.of_node = np;
-	imx_bridge->base.ops = DRM_BRIDGE_OP_MODES;
-	imx_bridge->base.type = type;
-
-	ret = devm_drm_bridge_add(dev, &imx_bridge->base);
-	if (ret)
-		return ERR_PTR(ret);
-
-	return &imx_bridge->base;
-}
-EXPORT_SYMBOL_GPL(devm_imx_drm_legacy_bridge);
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Freescale i.MX DRM bridge driver for legacy DT bindings");
diff --git a/drivers/gpu/drm/bridge/of-display-mode-bridge.c b/drivers/gpu/drm/bridge/of-display-mode-bridge.c
new file mode 100644
index 000000000000..cb15713f3a79
--- /dev/null
+++ b/drivers/gpu/drm/bridge/of-display-mode-bridge.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * bridge driver for legacy DT bindings, utilizing display-timings node
+ *
+ * Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+ */
+
+#include <linux/export.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/bridge/of-display-mode-bridge.h>
+
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+
+struct of_display_mode_bridge {
+	struct drm_bridge base;
+
+	struct drm_display_mode mode;
+	u32 bus_flags;
+};
+
+#define to_of_display_mode_bridge(bridge) container_of(bridge, struct of_display_mode_bridge, base)
+
+static int of_display_mode_bridge_attach(struct drm_bridge *bridge,
+					 struct drm_encoder *encoder,
+					 enum drm_bridge_attach_flags flags)
+{
+	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int of_display_mode_bridge_get_modes(struct drm_bridge *bridge,
+					    struct drm_connector *connector)
+{
+	struct of_display_mode_bridge *of_bridge = to_of_display_mode_bridge(bridge);
+	int ret;
+
+	ret = drm_connector_helper_get_modes_fixed(connector, &of_bridge->mode);
+	if (ret)
+		return ret;
+
+	connector->display_info.bus_flags = of_bridge->bus_flags;
+
+	return 0;
+}
+
+struct drm_bridge_funcs of_display_mode_bridge_funcs = {
+	.attach = of_display_mode_bridge_attach,
+	.get_modes = of_display_mode_bridge_get_modes,
+};
+
+struct drm_bridge *devm_drm_of_display_mode_bridge(struct device *dev,
+						   struct device_node *np,
+						   int type)
+{
+	struct of_display_mode_bridge *of_bridge;
+	int ret;
+
+	of_bridge = devm_drm_bridge_alloc(dev, struct of_display_mode_bridge,
+					  base, &of_display_mode_bridge_funcs);
+	if (IS_ERR(of_bridge))
+		return ERR_CAST(of_bridge);
+
+	ret = of_get_drm_display_mode(np,
+				      &of_bridge->mode,
+				      &of_bridge->bus_flags,
+				      OF_USE_NATIVE_MODE);
+	if (ret)
+		return ERR_PTR(ret);
+
+	of_bridge->mode.type |= DRM_MODE_TYPE_DRIVER;
+
+	of_bridge->base.of_node = np;
+	of_bridge->base.ops = DRM_BRIDGE_OP_MODES;
+	of_bridge->base.type = type;
+
+	ret = devm_drm_bridge_add(dev, &of_bridge->base);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &of_bridge->base;
+}
+EXPORT_SYMBOL_GPL(devm_drm_of_display_mode_bridge);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DRM bridge driver for legacy DT bindings");
diff --git a/drivers/gpu/drm/imx/ipuv3/Kconfig b/drivers/gpu/drm/imx/ipuv3/Kconfig
index acaf25089001..e98f8d35efaa 100644
--- a/drivers/gpu/drm/imx/ipuv3/Kconfig
+++ b/drivers/gpu/drm/imx/ipuv3/Kconfig
@@ -15,7 +15,7 @@ config DRM_IMX_PARALLEL_DISPLAY
 	depends on DRM_IMX
 	select DRM_BRIDGE
 	select DRM_BRIDGE_CONNECTOR
-	select DRM_IMX_LEGACY_BRIDGE
+	select DRM_OF_DISPLAY_MODE_BRIDGE
 	select DRM_PANEL_BRIDGE
 	select VIDEOMODE_HELPERS
 
@@ -36,7 +36,7 @@ config DRM_IMX_LDB
 	select DRM_BRIDGE
 	select DRM_BRIDGE_CONNECTOR
 	select DRM_PANEL_BRIDGE
-	select DRM_IMX_LEGACY_BRIDGE
+	select DRM_OF_DISPLAY_MODE_BRIDGE
 	help
 	  Choose this to enable the internal LVDS Display Bridge (LDB)
 	  found on i.MX53 and i.MX6 processors.
diff --git a/drivers/gpu/drm/imx/ipuv3/imx-ldb.c b/drivers/gpu/drm/imx/ipuv3/imx-ldb.c
index 626d410d9150..730caf883e83 100644
--- a/drivers/gpu/drm/imx/ipuv3/imx-ldb.c
+++ b/drivers/gpu/drm/imx/ipuv3/imx-ldb.c
@@ -28,7 +28,7 @@
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
-#include <drm/bridge/imx.h>
+#include <drm/bridge/of-display-mode-bridge.h>
 
 #include "imx-drm.h"
 
@@ -605,8 +605,8 @@ static int imx_ldb_probe(struct platform_device *pdev)
 		 * checking the bus_format property.
 		 */
 		if (!channel->bridge) {
-			channel->bridge = devm_imx_drm_legacy_bridge(dev, child,
-								     DRM_MODE_CONNECTOR_LVDS);
+			channel->bridge = devm_drm_of_display_mode_bridge(dev, child,
+									  DRM_MODE_CONNECTOR_LVDS);
 			if (IS_ERR(channel->bridge)) {
 				ret = PTR_ERR(channel->bridge);
 				goto free_child;
diff --git a/drivers/gpu/drm/imx/ipuv3/parallel-display.c b/drivers/gpu/drm/imx/ipuv3/parallel-display.c
index 6fbf505d2801..1109cb1badcb 100644
--- a/drivers/gpu/drm/imx/ipuv3/parallel-display.c
+++ b/drivers/gpu/drm/imx/ipuv3/parallel-display.c
@@ -19,7 +19,7 @@
 #include <drm/drm_of.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
-#include <drm/bridge/imx.h>
+#include <drm/bridge/of-display-mode-bridge.h>
 
 #include "imx-drm.h"
 
@@ -233,7 +233,8 @@ static int imx_pd_probe(struct platform_device *pdev)
 	/* port@1 is the output port */
 	imxpd->next_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
 	if (imxpd->next_bridge == ERR_PTR(-ENODEV))
-		imxpd->next_bridge = devm_imx_drm_legacy_bridge(dev, np, DRM_MODE_CONNECTOR_DPI);
+		imxpd->next_bridge = devm_drm_of_display_mode_bridge(dev, np,
+								     DRM_MODE_CONNECTOR_DPI);
 	if (IS_ERR(imxpd->next_bridge)) {
 		ret = PTR_ERR(imxpd->next_bridge);
 		return ret;
diff --git a/include/drm/bridge/imx.h b/include/drm/bridge/imx.h
deleted file mode 100644
index b93f719fe0e7..000000000000
--- a/include/drm/bridge/imx.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2012 Sascha Hauer, Pengutronix
- */
-
-#ifndef DRM_IMX_BRIDGE_H
-#define DRM_IMX_BRIDGE_H
-
-struct device;
-struct device_node;
-struct drm_bridge;
-
-struct drm_bridge *devm_imx_drm_legacy_bridge(struct device *dev,
-					      struct device_node *np,
-					      int type);
-
-#endif
diff --git a/include/drm/bridge/of-display-mode-bridge.h b/include/drm/bridge/of-display-mode-bridge.h
new file mode 100644
index 000000000000..89fcfedf68d8
--- /dev/null
+++ b/include/drm/bridge/of-display-mode-bridge.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ */
+
+#ifndef DRM_OF_DISPLAY_MODE_BRIDGE_H
+#define DRM_OF_DISPLAY_MODE_BRIDGE_H
+
+struct device;
+struct device_node;
+struct drm_bridge;
+
+struct drm_bridge *devm_drm_of_display_mode_bridge(struct device *dev,
+						   struct device_node *np,
+						   int type);
+
+#endif
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 04/17] drm/exynos: exynos_dp: Remove unused &exynos_dp_device.connector
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

The &exynos_dp_device.connector is assigned in exynos_dp_bridge_attach()
but never used. It should make sense to remove it.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v5:
- Fix the 'drm/bridge' to 'drm/exynos' in commit message.

Changes in v9:
- Add Reviewed-by and Tested-by tags.
---
 drivers/gpu/drm/exynos/exynos_dp.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index f469ac5b3c2a..e20513164032 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -35,7 +35,6 @@
 
 struct exynos_dp_device {
 	struct drm_encoder         encoder;
-	struct drm_connector       *connector;
 	struct drm_device          *drm_dev;
 	struct device              *dev;
 
@@ -102,8 +101,6 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
 	struct exynos_dp_device *dp = to_dp(plat_data);
 	int ret;
 
-	dp->connector = connector;
-
 	/* Pre-empt DP connector creation if there's a bridge */
 	if (plat_data->next_bridge) {
 		ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge,
-- 
2.34.1



^ permalink raw reply related

* [PATCH v12 01/17] drm/bridge: analogix_dp: Add &analogix_dp_plat_data.next_bridge
From: Damon Ding @ 2026-04-01  9:14 UTC (permalink / raw)
  To: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, victor.liu, shawnguo, s.hauer,
	inki.dae, sw0312.kim, kyungmin.park, krzk, jingoohan1, p.zabel,
	hjc, heiko, andy.yan
  Cc: Laurent.pinchart, jonas, jernej.skrabec, kernel, festevam,
	alim.akhtar, dmitry.baryshkov, luca.ceresoli, nicolas.frattaroli,
	dianders, m.szyprowski, linux-kernel, dri-devel, imx,
	linux-arm-kernel, linux-samsung-soc, linux-rockchip, Damon Ding
In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com>

In order to move the panel/bridge parsing and attachmenet to the
Analogix side, add component struct drm_bridge *next_bridge to
platform data struct analogix_dp_plat_data.

The movement makes sense because the panel/bridge should logically
be positioned behind the Analogix bridge in the display pipeline.

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de> (on rk3588)

---

Changes in v4:
- Rename the &analogix_dp_plat_data.bridge to
  &analogix_dp_plat_data.next_bridge

Changes in v9:
- Add Reviewed-by and Tested-by tags.
---
 include/drm/bridge/analogix_dp.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index cf17646c1310..582357c20640 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -27,6 +27,7 @@ static inline bool is_rockchip(enum analogix_dp_devtype type)
 struct analogix_dp_plat_data {
 	enum analogix_dp_devtype dev_type;
 	struct drm_panel *panel;
+	struct drm_bridge *next_bridge;
 	struct drm_encoder *encoder;
 	struct drm_connector *connector;
 	bool skip_connector;
-- 
2.34.1



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox