Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/37] drm/connector: split drmm_connector_hdmi_init() in 3 parts
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

In preparation for adding a dynamic variant of drmm_connector_hdmi_init(),
split reusable parts of the code to subfunctions.

drmm_connector_hdmi_init() currently has 3 sections:
 1. sanity checks
 2. call drmm_connector_init()
 3. initialize HDMI-specific fields not initialized at step 2

Split 1 and 3 to new functions, reusable independently.

No functional changes. Just moving code around.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/drm_connector.c | 105 +++++++++++++++++++++++++---------------
 1 file changed, 65 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 3fa4d2082cd7..37ed73300a18 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -542,44 +542,13 @@ int drmm_connector_init(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drmm_connector_init);
 
-/**
- * drmm_connector_hdmi_init - Init a preallocated HDMI connector
- * @dev: DRM device
- * @connector: A pointer to the HDMI connector to init
- * @vendor: HDMI Controller Vendor name
- * @product: HDMI Controller Product name
- * @funcs: callbacks for this connector
- * @hdmi_funcs: HDMI-related callbacks for this connector
- * @connector_type: user visible type of the connector
- * @ddc: optional pointer to the associated ddc adapter
- * @supported_formats: Bitmask of @drm_output_color_format listing supported output formats
- * @max_bpc: Maximum bits per char the HDMI connector supports
- *
- * Initialises a preallocated HDMI connector. Connectors can be
- * subclassed as part of driver connector objects.
- *
- * Cleanup is automatically handled with a call to
- * drm_connector_cleanup() in a DRM-managed action.
- *
- * The connector structure should be allocated with drmm_kzalloc().
- *
- * The @drm_connector_funcs.destroy hook must be NULL.
- *
- * Returns:
- * Zero on success, error code on failure.
- */
-int drmm_connector_hdmi_init(struct drm_device *dev,
-			     struct drm_connector *connector,
-			     const char *vendor, const char *product,
-			     const struct drm_connector_funcs *funcs,
-			     const struct drm_connector_hdmi_funcs *hdmi_funcs,
-			     int connector_type,
-			     struct i2c_adapter *ddc,
-			     unsigned long supported_formats,
-			     unsigned int max_bpc)
+static int drm_connector_hdmi_sanity_checks(struct drm_connector *connector,
+					    const char *vendor, const char *product,
+					    const struct drm_connector_hdmi_funcs *hdmi_funcs,
+					    int connector_type,
+					    unsigned long supported_formats,
+					    unsigned int max_bpc)
 {
-	int ret;
-
 	if (!vendor || !product)
 		return -EINVAL;
 
@@ -606,10 +575,15 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 	    !hdmi_funcs->hdmi.write_infoframe)
 		return -EINVAL;
 
-	ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
-	if (ret)
-		return ret;
+	return 0;
+}
 
+static void drm_connector_hdmi_init(struct drm_connector *connector,
+				    const char *vendor, const char *product,
+				    const struct drm_connector_hdmi_funcs *hdmi_funcs,
+				    unsigned long supported_formats,
+				    unsigned int max_bpc)
+{
 	connector->hdmi.supported_formats = supported_formats;
 	strtomem_pad(connector->hdmi.vendor, vendor, 0);
 	strtomem_pad(connector->hdmi.product, product, 0);
@@ -628,6 +602,57 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 		drm_connector_attach_hdr_output_metadata_property(connector);
 
 	connector->hdmi.funcs = hdmi_funcs;
+}
+
+/**
+ * drmm_connector_hdmi_init - Init a preallocated HDMI connector
+ * @dev: DRM device
+ * @connector: A pointer to the HDMI connector to init
+ * @vendor: HDMI Controller Vendor name
+ * @product: HDMI Controller Product name
+ * @funcs: callbacks for this connector
+ * @hdmi_funcs: HDMI-related callbacks for this connector
+ * @connector_type: user visible type of the connector
+ * @ddc: optional pointer to the associated ddc adapter
+ * @supported_formats: Bitmask of @drm_output_color_format listing supported output formats
+ * @max_bpc: Maximum bits per char the HDMI connector supports
+ *
+ * Initialises a preallocated HDMI connector. Connectors can be
+ * subclassed as part of driver connector objects.
+ *
+ * Cleanup is automatically handled with a call to
+ * drm_connector_cleanup() in a DRM-managed action.
+ *
+ * The connector structure should be allocated with drmm_kzalloc().
+ *
+ * The @drm_connector_funcs.destroy hook must be NULL.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drmm_connector_hdmi_init(struct drm_device *dev,
+			     struct drm_connector *connector,
+			     const char *vendor, const char *product,
+			     const struct drm_connector_funcs *funcs,
+			     const struct drm_connector_hdmi_funcs *hdmi_funcs,
+			     int connector_type,
+			     struct i2c_adapter *ddc,
+			     unsigned long supported_formats,
+			     unsigned int max_bpc)
+{
+	int ret;
+
+	ret = drm_connector_hdmi_sanity_checks(connector, vendor, product, hdmi_funcs,
+					       connector_type, supported_formats, max_bpc);
+	if (ret)
+		return ret;
+
+	ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
+	if (ret)
+		return ret;
+
+	drm_connector_hdmi_init(connector, vendor, product,
+				hdmi_funcs, supported_formats, max_bpc);
 
 	return 0;
 }

-- 
2.54.0



^ permalink raw reply related

* [PATCH 03/37] drm/display: bridge-connector: rename variable for consistency
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

All struct drm_bridge_connector pointers are called bridge_connector except
this one. Rename it for consistency.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 649969fca141..bf9b8a6bca78 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -153,10 +153,10 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
 	}
 }
 
-static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bridge_connector,
+static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *bridge_connector,
 					    enum drm_connector_status status)
 {
-	struct drm_connector *connector = &drm_bridge_connector->base;
+	struct drm_connector *connector = &bridge_connector->base;
 	struct drm_device *dev = connector->dev;
 
 	mutex_lock(&dev->mode_config.mutex);

-- 
2.54.0



^ permalink raw reply related

* [PATCH 02/37] drm/connector: add drm_connector_hdmi_dynamic_init()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

In preparation for adding hotpluggable bridges into the
drm_bridge_connector, we need connectors to be created dynamically, both
regular connectors and HDMI ones. drm_connector_init() already has a
dynamic variant, add one for HDMI connectors too.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/drm_connector.c | 50 +++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_connector.h     |  9 ++++++++
 2 files changed, 59 insertions(+)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 37ed73300a18..5e7a3a438e5d 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -658,6 +658,56 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drmm_connector_hdmi_init);
 
+/**
+ * drm_connector_hdmi_dynamic_init - Init a preallocated dynamic HDMI connector
+ * @dev: DRM device
+ * @connector: A pointer to the HDMI connector to init
+ * @vendor: HDMI Controller Vendor name
+ * @product: HDMI Controller Product name
+ * @funcs: callbacks for this connector
+ * @hdmi_funcs: HDMI-related callbacks for this connector
+ * @connector_type: user visible type of the connector
+ * @ddc: optional pointer to the associated ddc adapter
+ * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats
+ * @max_bpc: Maximum bits per char the HDMI connector supports
+ *
+ * Initialises a preallocated dynamic HDMI connector. Connectors can be
+ * subclassed as part of driver connector objects.
+ *
+ * See drm_connector_dynamic_init(), the same constraints apply here. This
+ * is just the HDMI version.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drm_connector_hdmi_dynamic_init(struct drm_device *dev,
+				    struct drm_connector *connector,
+				    const char *vendor, const char *product,
+				    const struct drm_connector_funcs *funcs,
+				    const struct drm_connector_hdmi_funcs *hdmi_funcs,
+				    int connector_type,
+				    struct i2c_adapter *ddc,
+				    unsigned long supported_formats,
+				    unsigned int max_bpc)
+{
+	int ret;
+
+	ret = drm_connector_hdmi_sanity_checks(connector, vendor, product, hdmi_funcs,
+					       connector_type, supported_formats, max_bpc);
+	if (ret)
+		return ret;
+
+	ret = drm_connector_dynamic_init(dev, connector, funcs, connector_type, ddc);
+	if (ret)
+		return ret;
+
+	drm_connector_hdmi_init(connector, vendor, product,
+				hdmi_funcs, supported_formats, max_bpc);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_hdmi_dynamic_init);
+
 /**
  * drm_connector_attach_edid_property - attach edid property.
  * @connector: the connector
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 5ad62c207d00..1f5dba947748 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -2442,6 +2442,15 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 			     struct i2c_adapter *ddc,
 			     unsigned long supported_formats,
 			     unsigned int max_bpc);
+int drm_connector_hdmi_dynamic_init(struct drm_device *dev,
+				    struct drm_connector *connector,
+				    const char *vendor, const char *product,
+				    const struct drm_connector_funcs *funcs,
+				    const struct drm_connector_hdmi_funcs *hdmi_funcs,
+				    int connector_type,
+				    struct i2c_adapter *ddc,
+				    unsigned long supported_formats,
+				    unsigned int max_bpc);
 void drm_connector_attach_edid_property(struct drm_connector *connector);
 int drm_connector_register(struct drm_connector *connector);
 int drm_connector_dynamic_register(struct drm_connector *connector);

-- 
2.54.0



^ permalink raw reply related

* [PATCH 04/37] drm/display: bridge-connector: store the drm_device pointer
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

Currently the struct drm_device pointer is only needed during the initial
drm_bridge_connector_init() and in drm_bridge_connector_handle_hpd() which
gets it from the struct drm_connector.

This will be insufficient when introducing bridge hotplugging, because:

 * some of the actions in drm_bridge_connector_init() will have to be
   performed later on, when a bridge is hot(un)plugged
 * the connector will be removed and re-added based on hotplug events,
   so the drm_connector might just not exist or its content be cleared

Store the drm_device pointer in struct drm_bridge_connector for any later
needs. Also convert drm_bridge_connector_handle_hpd() to use the newly
stored value.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index bf9b8a6bca78..62c4cb1e241f 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -60,6 +60,10 @@ struct drm_bridge_connector {
 	 * @base: The base DRM connector
 	 */
 	struct drm_connector base;
+	/**
+	 * @drm: The DRM device we belong to
+	 */
+	struct drm_device *drm;
 	/**
 	 * @encoder:
 	 *
@@ -157,7 +161,7 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *bridge_
 					    enum drm_connector_status status)
 {
 	struct drm_connector *connector = &bridge_connector->base;
-	struct drm_device *dev = connector->dev;
+	struct drm_device *dev = bridge_connector->drm;
 
 	mutex_lock(&dev->mode_config.mutex);
 	connector->status = status;
@@ -806,6 +810,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (ret)
 		return ERR_PTR(ret);
 
+	bridge_connector->drm = drm;
 	bridge_connector->encoder = encoder;
 
 	/*

-- 
2.54.0



^ permalink raw reply related

* [PATCH 05/37] drm/display: bridge-connector: split code creating the connector to a subfunction
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

In preparation to introduce bridge hotplug, split out from
drm_bridge_connector_init() the code adding the drm_connector into a
dedicated function. This will be needed to be able to add (and re-add) the
connector from different code paths.

No functional changes. Just moving code around.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 81 ++++++++++++++++----------
 1 file changed, 50 insertions(+), 31 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 62c4cb1e241f..6813c6008985 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -773,26 +773,19 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 }
 
 /**
- * drm_bridge_connector_init - Initialise a connector for a chain of bridges
- * @drm: the DRM device
- * @encoder: the encoder where the bridge chain starts
- *
- * Create a new &drm_bridge_connector for the @drm device. The connector is
- * allocated, initialised, registered with the @drm device and attached to
- * @encoder.
+ * drm_bridge_connector_add_connector - add the drm_connector
+ * @bridge_connector: drm_bridge_connector to add the drm_connector to
  *
- * The connector is associated with a chain of bridges that starts at
- * the @encoder. All bridges in the chain shall report bridge operation flags
- * (&drm_bridge->ops) and bridge output type (&drm_bridge->type), and none of
- * them may create a DRM connector directly.
+ * Preconditions: the drm_bridge_connector is initialized (the
+ * @bridge_connector->drm and @bridge_connector->encoder fields are
+ * assigned at least)
  *
- * Returns a pointer to the new connector on success, or a negative error
- * pointer otherwise.
+ * Returns a pointer to the new drm_connector on success, or a negative
+ * error pointer otherwise.
  */
-struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
-						struct drm_encoder *encoder)
+static struct drm_connector *
+drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
 {
-	struct drm_bridge_connector *bridge_connector;
 	struct drm_connector *connector;
 	struct i2c_adapter *ddc = NULL;
 	struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL;
@@ -802,17 +795,6 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	int connector_type;
 	int ret;
 
-	bridge_connector = drmm_kzalloc(drm, sizeof(*bridge_connector), GFP_KERNEL);
-	if (!bridge_connector)
-		return ERR_PTR(-ENOMEM);
-
-	ret = drmm_add_action(drm, drm_bridge_connector_put_bridges, bridge_connector);
-	if (ret)
-		return ERR_PTR(ret);
-
-	bridge_connector->drm = drm;
-	bridge_connector->encoder = encoder;
-
 	/*
 	 * TODO: Handle doublescan_allowed and stereo_allowed.
 	 */
@@ -828,7 +810,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	 * detection are available, we don't support hotplug detection at all.
 	 */
 	connector_type = DRM_MODE_CONNECTOR_Unknown;
-	drm_for_each_bridge_in_chain_scoped(encoder, bridge) {
+	drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) {
 		if (!bridge->interlace_allowed)
 			connector->interlace_allowed = false;
 		if (!bridge->ycbcr_420_allowed)
@@ -984,7 +966,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			bridge_connector->hdmi_funcs.spd =
 				drm_bridge_connector_hdmi_spd_infoframe;
 
-		ret = drmm_connector_hdmi_init(drm, connector,
+		ret = drmm_connector_hdmi_init(bridge_connector->drm, connector,
 					       bridge_connector->bridge_hdmi->vendor,
 					       bridge_connector->bridge_hdmi->product,
 					       &drm_bridge_connector_funcs,
@@ -995,7 +977,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 		if (ret)
 			return ERR_PTR(ret);
 	} else {
-		ret = drmm_connector_init(drm, connector,
+		ret = drmm_connector_init(bridge_connector->drm, connector,
 					  &drm_bridge_connector_funcs,
 					  connector_type, ddc);
 		if (ret)
@@ -1063,10 +1045,47 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	    IS_ENABLED(CONFIG_DRM_DISPLAY_HDCP_HELPER))
 		drm_connector_attach_content_protection_property(connector, true);
 
-	ret = drm_connector_attach_encoder(connector, encoder);
+	ret = drm_connector_attach_encoder(connector, bridge_connector->encoder);
 	if (ret)
 		return ERR_PTR(ret);
 
 	return connector;
 }
+
+/**
+ * drm_bridge_connector_init - Initialise a connector for a chain of bridges
+ * @drm: the DRM device
+ * @encoder: the encoder where the bridge chain starts
+ *
+ * Create a new &drm_bridge_connector for the @drm device. The connector is
+ * allocated, initialised, registered with the @drm device and attached to
+ * @encoder.
+ *
+ * The connector is associated with a chain of bridges that starts at
+ * the @encoder. All bridges in the chain shall report bridge operation flags
+ * (&drm_bridge->ops) and bridge output type (&drm_bridge->type), and none of
+ * them may create a DRM connector directly.
+ *
+ * Returns a pointer to the new connector on success, or a negative error
+ * pointer otherwise.
+ */
+struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
+						struct drm_encoder *encoder)
+{
+	struct drm_bridge_connector *bridge_connector;
+	int ret;
+
+	bridge_connector = drmm_kzalloc(drm, sizeof(*bridge_connector), GFP_KERNEL);
+	if (!bridge_connector)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drmm_add_action(drm, drm_bridge_connector_put_bridges, bridge_connector);
+	if (ret)
+		return ERR_PTR(ret);
+
+	bridge_connector->drm = drm;
+	bridge_connector->encoder = encoder;
+
+	return drm_bridge_connector_add_connector(bridge_connector);
+}
 EXPORT_SYMBOL_GPL(drm_bridge_connector_init);

-- 
2.54.0



^ permalink raw reply related

* [PATCH v4 0/3] pinctrl: aspeed: Add AST2700 SoC1 support
From: Billy Tsai @ 2026-05-19 10:38 UTC (permalink / raw)
  To: Linus Walleij, Tony Lindgren, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery, Bartosz Golaszewski,
	Lee Jones, Ryan Chen
  Cc: patrickw3, linux-gpio, devicetree, linux-kernel, linux-arm-kernel,
	linux-aspeed, BMC-SW, openbmc, Andrew Jeffery, linux-clk,
	Billy Tsai, Conor Dooley

Legacy ASPEED pin controllers have historically not had a coherent
register interface. Control fields often had no consistent mapping to
individual pins, and configuring a function frequently required
coordinating multiple control bits across several registers. As a
result, the existing ASPEED pinctrl drivers rely on complex macro
infrastructure to describe the dependencies between pins, functions,
and register fields.

The pin controller for SoC1 in the AST2700 breaks from this legacy
design.

For SoC1, each pin maps directly to a dedicated function field in the
SCU register space that determines the active mux function for that
pin. This results in a much more regular register layout compared to
previous generations.

While the behaviour is conceptually similar to pinctrl-single, the
register layout and configuration model differ enough that reusing
pinctrl-single directly is not practical. Therefore this driver is
implemented as a SoC-specific pinctrl driver using static data tables
to describe the register layout.

The binding reuses the standard pinmux and generic pin configuration
schemas and does not introduce any custom Devicetree properties.

Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
Changes in v4:
- Fix SGMII0 mux register: SGMII0's control bit is at SCU47C[0],
  outside the contiguous pin-indexed MUX register range. Handle it as
  a special case in set_mux (analogous to PCIERC2_PERST) using
  field.mask = 0x1 to avoid clobbering adjacent bits in SCU47C.
- Use devm_pinctrl_register_and_init() and pinctrl_enable(): populate
  all groups and functions before enabling the controller, closing the
  race window where a consumer could observe zero groups/functions.
- pin_config_get: return -EINVAL when BIAS_DISABLE is not active; return
  -ENOTSUPP for BIAS_PULL_UP and BIAS_PULL_DOWN because the hardware
  has a single 1-bit enable/disable field per pin and pull direction
  cannot be read back from the register.
- Probe: preserve const qualification in the pinctrl_generic_add_group()
  pin array cast.
- Binding: retain additionalProperties: false for the state-node schema.
  The state-node properties block re-declares the four pincfg properties
  the hardware supports (bias-disable, bias-pull-up, bias-pull-down,
  drive-strength) and intentionally rejects all others.
  unevaluatedProperties: false was suggested as an alternative, but that
  would permit the full set of pincfg-node.yaml properties
  (input-schmitt-enable, slew-rate, etc.) even though the hardware does
  not support them. additionalProperties: false is the correct mechanism
  when the goal is to restrict the accepted properties to a known subset.
- Link to v3: https://lore.kernel.org/r/20260515-pinctrl-single-bit-v3-0-e97da4312104@aspeedtech.com

Changes in v3:
- Added pin configuration group support for AST2700 SoC1 by
  implementing `pin_config_group_get()` and `pin_config_group_set()`.
- Restricted AST2700 SoC1 `drive-strength` settings to the supported
  4/8/12/16 mA values in the pinctrl driver.
- Convert kernel-doc comment for aspeed_g7_soc1_drv_map to a regular
  comment to avoid kernel-doc warning reported by kernel test robot.
- Update the AST2700 SoC1 pinctrl binding to describe the `reg`
  property and require it.
- Allow standard pinconf properties in pin state nodes.
- Add a binding example for the AST2700 SoC1 pinctrl node.
- Add state-node description, function+groups dependency constraint,
  and oneOf groups/pins constraint to the binding, matching the SoC0
  binding style.
- Add pins enum (212 entries) to the binding to cover all physical pins
  that support per-pin configuration.
- Add UART modem-line signals as independent functions/groups:
  NCTS0/1/5/6, NDCD0/1/5/6, NDSR0/1/5/6, NDTR0/1/5/6,
  NRI0/1/5/6, NRTS0/1/5/6; remove those pins from UART0/1/5/6 groups.
- Add LTPI_PS_I2C0/1/2/3 functions/groups for I2C-over-LTPI;
  extend I2C0/1/2/3 functions with the new LTPI groups.
- Fix typo: rename RMII0RCKO/RMII1RCKO to RMII0RCLKO/RMII1RCLKO.
- Fix wrong index: rename DSGPM1 to DSGPM0.
- Kconfig: use "Aspeed G7 SoC1 pin control" to match neighbouring entries.
- pin_config_get: fix BIAS_DISABLE readback (val=!val must be skipped
  for BIAS_DISABLE since hardware bit=1 means pull disabled).
- set_mux: remove dead null check on grp; propagate regmap_update_bits()
  return value.
- gpio_request_enable: propagate regmap_update_bits() return value.
- Link to v2: https://lore.kernel.org/r/20260306-pinctrl-single-bit-v2-0-79918cfab641@aspeedtech.com

Changes in v2:
- Updated the series title to focus on AST2700 SoC1 support.
- Reworked implementation to use static SoC-specific layout tables
  instead of a generic packed-field model.
- Dropped the generic "pinctrl-packed" driver approach.
- Removed custom Devicetree properties.
- Updated binding to reuse standard pinmux and generic pin
  configuration schemas.
- Link to v1: https://lore.kernel.org/r/20260213-pinctrl-single-bit-v1-0-c60f2fb80efb@aspeedtech.com

---
Billy Tsai (3):
      dt-bindings: mfd: aspeed,ast2x00-scu: Support AST2700 SoC1 pinctrl
      dt-bindings: pinctrl: Add aspeed,ast2700-soc1-pinctrl
      pinctrl: aspeed: Add AST2700 SoC1 support

 .../bindings/mfd/aspeed,ast2x00-scu.yaml           |    1 +
 .../pinctrl/aspeed,ast2700-soc1-pinctrl.yaml       |  760 +++++++++
 drivers/pinctrl/aspeed/Kconfig                     |   14 +
 drivers/pinctrl/aspeed/Makefile                    |    1 +
 drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c    | 1754 ++++++++++++++++++++
 5 files changed, 2530 insertions(+)
---
base-commit: e532a5a81d0db872acd2c0a92d2639580ca3da44
change-id: 20260211-pinctrl-single-bit-da213f282c95

Best regards,
-- 
Billy Tsai <billy_tsai@aspeedtech.com>



^ permalink raw reply

* [PATCH v4 1/3] dt-bindings: mfd: aspeed,ast2x00-scu: Support AST2700 SoC1 pinctrl
From: Billy Tsai @ 2026-05-19 10:38 UTC (permalink / raw)
  To: Linus Walleij, Tony Lindgren, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery, Bartosz Golaszewski,
	Lee Jones, Ryan Chen
  Cc: patrickw3, linux-gpio, devicetree, linux-kernel, linux-arm-kernel,
	linux-aspeed, BMC-SW, openbmc, Andrew Jeffery, linux-clk,
	Billy Tsai, Conor Dooley
In-Reply-To: <20260519-pinctrl-single-bit-v4-0-5fe568a8ffde@aspeedtech.com>

The AST2700 SoC integrates two interconnected SoC instances, each
managed by its own System Control Unit (SCU).

Allow the AST2700 SoC1 pin controller to be described as a child
node of the SCU by extending the compatible strings accepted by
the SCU binding.

There is no functional change to the SCU binding beyond permitting
the aspeed,ast2700-soc1-pinctrl compatible string.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
Note: The companion AST2700 SoC0 pinctrl series modifies the same
binding and has been applied to pinctrl/for-next. Linus Walleij noted
for the equivalent SoC0 MFD patch that it should be applied through the
MFD tree by Lee Jones, providing an Acked-by in the process [1]. The
same routing applies to this patch.

[1] https://lore.kernel.org/all/CAD++jL=3p9BvDgaot3=emM4Zn5jU-ZAUKtB4UwT1HzDiyzKq4Q@mail.gmail.com/
---
 Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml b/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml
index a87f31fce019..b81c561d2a8c 100644
--- a/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml
+++ b/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml
@@ -87,6 +87,7 @@ patternProperties:
             - aspeed,ast2400-pinctrl
             - aspeed,ast2500-pinctrl
             - aspeed,ast2600-pinctrl
+            - aspeed,ast2700-soc1-pinctrl
 
     required:
       - compatible

-- 
2.34.1



^ permalink raw reply related

* [PATCH 06/37] drm/display: bridge-connector: use a drm_bridge_connector internally, not a drm_connector
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

Currently drm_bridge_connector_init() always returns the added connector or
errors out. When adding bridge hotplug the bridge-connector can be
successfully initialized without creating a connector, which can be added
later when the pipeline will be complete.

For this the internal function drm_bridge_connector_add_connector() must be
able to return a valid drm_bridge_connector even without any drm_connector.

In preparation to support bridge hotplug, change its return value to be the
same drm_bridge_connector pointer it gets as input, or a PTR_ERR.

No functional changes, just changing an internal API.

Note the return value could now become an int (0 or negative error) because
returning the same value received as input does not carry any added
value. However this would be change a lot of lines, so leave such change as
a future cleanup.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 6813c6008985..eb1912611cd6 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -780,10 +780,10 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
  * @bridge_connector->drm and @bridge_connector->encoder fields are
  * assigned at least)
  *
- * Returns a pointer to the new drm_connector on success, or a negative
- * error pointer otherwise.
+ * Returns a pointer to @bridge_connector on success, or a negative error
+ * pointer otherwise.
  */
-static struct drm_connector *
+static struct drm_bridge_connector *
 drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
 {
 	struct drm_connector *connector;
@@ -1049,7 +1049,7 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 	if (ret)
 		return ERR_PTR(ret);
 
-	return connector;
+	return bridge_connector;
 }
 
 /**
@@ -1086,6 +1086,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	bridge_connector->drm = drm;
 	bridge_connector->encoder = encoder;
 
-	return drm_bridge_connector_add_connector(bridge_connector);
+	bridge_connector = drm_bridge_connector_add_connector(bridge_connector);
+	if (IS_ERR(bridge_connector))
+		return ERR_CAST(bridge_connector);
+
+	return &bridge_connector->base;
 }
 EXPORT_SYMBOL_GPL(drm_bridge_connector_init);

-- 
2.54.0



^ permalink raw reply related

* [PATCH 07/37] drm/display: bridge-connector: extract drm_bridge_connector_get_bridges()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

To support bridge hotplug we will need better control over the steps
involved in the crm_connector creation, especially in terms of error
management. This will be complicated by the length and amount of return
points in this function. In preparation, split the code to grab the various
bridge pointers out to a separate function.

No functional changes. Just moving code around.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 86 +++++++++++++++-----------
 1 file changed, 50 insertions(+), 36 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index eb1912611cd6..97d9a4c6d166 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -772,44 +772,24 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
 }
 
-/**
- * drm_bridge_connector_add_connector - add the drm_connector
- * @bridge_connector: drm_bridge_connector to add the drm_connector to
- *
- * Preconditions: the drm_bridge_connector is initialized (the
- * @bridge_connector->drm and @bridge_connector->encoder fields are
- * assigned at least)
- *
- * Returns a pointer to @bridge_connector on success, or a negative error
- * pointer otherwise.
- */
 static struct drm_bridge_connector *
-drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
+drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
+				 int *connector_type,
+				 unsigned int *supported_formats,
+				 unsigned int *max_bpc,
+				 struct i2c_adapter **ddc,
+				 struct drm_bridge **panel_bridge,
+				 bool *support_hdcp)
 {
-	struct drm_connector *connector;
-	struct i2c_adapter *ddc = NULL;
-	struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL;
-	unsigned int supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444);
-	unsigned int max_bpc = 8;
-	bool support_hdcp = false;
-	int connector_type;
-	int ret;
+	struct drm_connector *connector = &bridge_connector->base;
 
 	/*
 	 * TODO: Handle doublescan_allowed and stereo_allowed.
 	 */
-	connector = &bridge_connector->base;
 	connector->interlace_allowed = true;
 	connector->ycbcr_420_allowed = true;
+	*connector_type = DRM_MODE_CONNECTOR_Unknown;
 
-	/*
-	 * Initialise connector status handling. First locate the furthest
-	 * bridges in the pipeline that support HPD and output detection. Then
-	 * initialise the connector polling mode, using HPD if available and
-	 * falling back to polling if supported. If neither HPD nor output
-	 * detection are available, we don't support hotplug detection at all.
-	 */
-	connector_type = DRM_MODE_CONNECTOR_Unknown;
 	drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) {
 		if (!bridge->interlace_allowed)
 			connector->interlace_allowed = false;
@@ -865,9 +845,9 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 			bridge_connector->bridge_hdmi = drm_bridge_get(bridge);
 
 			if (bridge->supported_formats)
-				supported_formats = bridge->supported_formats;
+				*supported_formats = bridge->supported_formats;
 			if (bridge->max_bpc)
-				max_bpc = bridge->max_bpc;
+				*max_bpc = bridge->max_bpc;
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO) {
@@ -926,7 +906,7 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 		}
 
 		if (drm_bridge_is_last(bridge))
-			connector_type = bridge->type;
+			*connector_type = bridge->type;
 
 #ifdef CONFIG_OF
 		if (drm_bridge_is_last(bridge) && bridge->of_node)
@@ -934,20 +914,54 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 #endif
 
 		if (bridge->ddc)
-			ddc = bridge->ddc;
+			*ddc = bridge->ddc;
 
 		if (drm_bridge_is_panel(bridge)) {
-			drm_bridge_put(panel_bridge);
-			panel_bridge = drm_bridge_get(bridge);
+			drm_bridge_put(*panel_bridge);
+			*panel_bridge = drm_bridge_get(bridge);
 		}
 
 		if (bridge->support_hdcp)
-			support_hdcp = true;
+			*support_hdcp = true;
 	}
 
 	if (connector_type == DRM_MODE_CONNECTOR_Unknown)
 		return ERR_PTR(-EINVAL);
 
+	return bridge_connector;
+}
+
+/**
+ * drm_bridge_connector_add_connector - add the drm_connector
+ * @bridge_connector: drm_bridge_connector to add the drm_connector to
+ *
+ * Preconditions: the drm_bridge_connector is initialized (the
+ * @bridge_connector->drm and @bridge_connector->encoder fields are
+ * assigned at least)
+ *
+ * Returns a pointer to @bridge_connector on success, or a negative error
+ * pointer otherwise.
+ */
+static struct drm_bridge_connector *
+drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
+{
+	struct drm_connector *connector;
+	struct i2c_adapter *ddc = NULL;
+	struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL;
+	unsigned int supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444);
+	unsigned int max_bpc = 8;
+	bool support_hdcp = false;
+	int connector_type;
+	int ret;
+
+	connector = &bridge_connector->base;
+
+	bridge_connector = drm_bridge_connector_get_bridges(bridge_connector, &connector_type,
+							    &supported_formats, &max_bpc, &ddc,
+							    &panel_bridge, &support_hdcp);
+	if (IS_ERR(bridge_connector))
+		return bridge_connector;
+
 	if (bridge_connector->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)
 			supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);

-- 
2.54.0



^ permalink raw reply related

* [PATCH v4 2/3] dt-bindings: pinctrl: Add aspeed,ast2700-soc1-pinctrl
From: Billy Tsai @ 2026-05-19 10:38 UTC (permalink / raw)
  To: Linus Walleij, Tony Lindgren, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery, Bartosz Golaszewski,
	Lee Jones, Ryan Chen
  Cc: patrickw3, linux-gpio, devicetree, linux-kernel, linux-arm-kernel,
	linux-aspeed, BMC-SW, openbmc, Andrew Jeffery, linux-clk,
	Billy Tsai, Conor Dooley
In-Reply-To: <20260519-pinctrl-single-bit-v4-0-5fe568a8ffde@aspeedtech.com>

SoC1 in the AST2700 integrates its own pin controller responsible for
pin multiplexing and pin configuration.

The controller manages various peripheral functions such as eSPI, LPC,
VPI, SD, UART, I2C, I3C, PWM and others through SCU registers.

The binding reuses the standard pinmux and generic pin configuration
schemas and does not introduce custom Devicetree properties.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
 .../pinctrl/aspeed,ast2700-soc1-pinctrl.yaml       | 760 +++++++++++++++++++++
 1 file changed, 760 insertions(+)

diff --git a/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc1-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc1-pinctrl.yaml
new file mode 100644
index 000000000000..76944fd14e2c
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc1-pinctrl.yaml
@@ -0,0 +1,760 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/aspeed,ast2700-soc1-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED AST2700 SoC1 Pin Controller
+
+maintainers:
+  - Billy Tsai <billy_tsai@aspeedtech.com>
+
+description:
+  The AST2700 features a dual-SoC architecture with two interconnected SoCs,
+  each having its own System Control Unit (SCU) for independent pin control.
+  This pin controller manages the pin multiplexing for SoC1.
+
+  The SoC1 pin controller manages pin functions including eSPI, LPC and I2C,
+  among others.
+
+properties:
+  compatible:
+    const: aspeed,ast2700-soc1-pinctrl
+  reg:
+    maxItems: 1
+
+patternProperties:
+  '-state$':
+    description: |
+      Pin control state.
+
+      If `function` is present, the node describes a pinmux state and must
+      specify `groups`.
+
+      For pin configuration, exactly one of `groups` or `pins` must be
+      specified in each state node. Group-level configuration applies to all
+      pins in the group. Pin-level configuration may be supplied in a
+      separate state node for individual pins; when both group-level and
+      pin-level configuration apply to the same pin, the pin-level
+      configuration takes precedence.
+
+    type: object
+    allOf:
+      - $ref: pinmux-node.yaml#
+      - $ref: pincfg-node.yaml#
+      - if:
+          required:
+            - function
+        then:
+          required:
+            - groups
+      - oneOf:
+          - required:
+              - groups
+          - required:
+              - pins
+    additionalProperties: false
+
+    properties:
+      function:
+        enum:
+          - ADC0
+          - ADC1
+          - ADC10
+          - ADC11
+          - ADC12
+          - ADC13
+          - ADC14
+          - ADC15
+          - ADC2
+          - ADC3
+          - ADC4
+          - ADC5
+          - ADC6
+          - ADC7
+          - ADC8
+          - ADC9
+          - AUXPWRGOOD0
+          - AUXPWRGOOD1
+          - CANBUS
+          - ESPI0
+          - ESPI1
+          - FSI0
+          - FSI1
+          - FSI2
+          - FSI3
+          - FWQSPI
+          - FWSPIABR
+          - FWWPN
+          - HBLED
+          - I2C0
+          - I2C1
+          - I2C10
+          - I2C11
+          - I2C12
+          - I2C13
+          - I2C14
+          - I2C15
+          - I2C2
+          - I2C3
+          - I2C4
+          - I2C5
+          - I2C6
+          - I2C7
+          - I2C8
+          - I2C9
+          - I2CF0
+          - I2CF1
+          - I2CF2
+          - I3C0
+          - I3C1
+          - I3C10
+          - I3C11
+          - I3C12
+          - I3C13
+          - I3C14
+          - I3C15
+          - I3C2
+          - I3C3
+          - I3C4
+          - I3C5
+          - I3C6
+          - I3C7
+          - I3C8
+          - I3C9
+          - JTAGM1
+          - LPC0
+          - LPC1
+          - LTPI
+          - MACLINK0
+          - MACLINK1
+          - MACLINK2
+          - MDIO0
+          - MDIO1
+          - MDIO2
+          - NCTS0
+          - NCTS1
+          - NCTS5
+          - NCTS6
+          - NDCD0
+          - NDCD1
+          - NDCD5
+          - NDCD6
+          - NDSR0
+          - NDSR1
+          - NDSR5
+          - NDSR6
+          - NDTR0
+          - NDTR1
+          - NDTR5
+          - NDTR6
+          - NRI0
+          - NRI1
+          - NRI5
+          - NRI6
+          - NRTS0
+          - NRTS1
+          - NRTS5
+          - NRTS6
+          - OSCCLK
+          - PCIERC
+          - PWM0
+          - PWM1
+          - PWM10
+          - PWM11
+          - PWM12
+          - PWM13
+          - PWM14
+          - PWM15
+          - PWM2
+          - PWM3
+          - PWM4
+          - PWM5
+          - PWM6
+          - PWM7
+          - PWM8
+          - PWM9
+          - QSPI0
+          - QSPI1
+          - QSPI2
+          - RGMII0
+          - RGMII1
+          - RMII0
+          - RMII0RCLKO
+          - RMII1
+          - RMII1RCLKO
+          - SALT0
+          - SALT1
+          - SALT10
+          - SALT11
+          - SALT12
+          - SALT13
+          - SALT14
+          - SALT15
+          - SALT2
+          - SALT3
+          - SALT4
+          - SALT5
+          - SALT6
+          - SALT7
+          - SALT8
+          - SALT9
+          - SD
+          - SGMII
+          - SGPM0
+          - SGPM1
+          - SGPS
+          - SIOONCTRLN0
+          - SIOONCTRLN1
+          - SIOPBIN0
+          - SIOPBIN1
+          - SIOPBON0
+          - SIOPBON1
+          - SIOPWREQN0
+          - SIOPWREQN1
+          - SIOPWRGD1
+          - SIOS3N0
+          - SIOS3N1
+          - SIOS5N0
+          - SIOS5N1
+          - SIOSCIN0
+          - SIOSCIN1
+          - SMON0
+          - SMON1
+          - SPI0
+          - SPI0ABR
+          - SPI0CS1
+          - SPI0WPN
+          - SPI1
+          - SPI1ABR
+          - SPI1CS1
+          - SPI1WPN
+          - SPI2
+          - SPI2CS1
+          - TACH0
+          - TACH1
+          - TACH10
+          - TACH11
+          - TACH12
+          - TACH13
+          - TACH14
+          - TACH15
+          - TACH2
+          - TACH3
+          - TACH4
+          - TACH5
+          - TACH6
+          - TACH7
+          - TACH8
+          - TACH9
+          - THRU0
+          - THRU1
+          - THRU2
+          - THRU3
+          - UART0
+          - UART1
+          - UART10
+          - UART11
+          - UART2
+          - UART3
+          - UART5
+          - UART6
+          - UART7
+          - UART8
+          - UART9
+          - USB2C
+          - USB2D
+          - USBUART
+          - VGA
+          - VPI
+          - WDTRST0N
+          - WDTRST1N
+          - WDTRST2N
+          - WDTRST3N
+          - WDTRST4N
+          - WDTRST5N
+          - WDTRST6N
+          - WDTRST7N
+
+      groups:
+        enum:
+          - ADC0
+          - ADC1
+          - ADC10
+          - ADC11
+          - ADC12
+          - ADC13
+          - ADC14
+          - ADC15
+          - ADC2
+          - ADC3
+          - ADC4
+          - ADC5
+          - ADC6
+          - ADC7
+          - ADC8
+          - ADC9
+          - AUXPWRGOOD0
+          - AUXPWRGOOD1
+          - CANBUS
+          - DI2C0
+          - DI2C1
+          - DI2C10
+          - DI2C11
+          - DI2C12
+          - DI2C13
+          - DI2C14
+          - DI2C15
+          - DI2C2
+          - DI2C3
+          - DI2C8
+          - DI2C9
+          - DSGPM0
+          - ESPI0
+          - ESPI1
+          - FSI0
+          - FSI1
+          - FSI2
+          - FSI3
+          - FWQSPI
+          - FWSPIABR
+          - FWWPN
+          - HBLED
+          - HVI3C0
+          - HVI3C1
+          - HVI3C12
+          - HVI3C13
+          - HVI3C14
+          - HVI3C15
+          - HVI3C2
+          - HVI3C3
+          - I2C0
+          - I2C1
+          - I2C10
+          - I2C11
+          - I2C12
+          - I2C13
+          - I2C14
+          - I2C15
+          - I2C2
+          - I2C3
+          - I2C4
+          - I2C5
+          - I2C6
+          - I2C7
+          - I2C8
+          - I2C9
+          - I2CF0
+          - I2CF1
+          - I2CF2
+          - I3C10
+          - I3C11
+          - I3C4
+          - I3C5
+          - I3C6
+          - I3C7
+          - I3C8
+          - I3C9
+          - JTAGM1
+          - LPC0
+          - LPC1
+          - LTPI
+          - LTPI_PS_I2C0
+          - LTPI_PS_I2C1
+          - LTPI_PS_I2C2
+          - LTPI_PS_I2C3
+          - MACLINK0
+          - MACLINK1
+          - MACLINK2
+          - MDIO0
+          - MDIO1
+          - MDIO2
+          - NCTS0
+          - NCTS1
+          - NCTS5
+          - NCTS6
+          - NDCD0
+          - NDCD1
+          - NDCD5
+          - NDCD6
+          - NDSR0
+          - NDSR1
+          - NDSR5
+          - NDSR6
+          - NDTR0
+          - NDTR1
+          - NDTR5
+          - NDTR6
+          - NRI0
+          - NRI1
+          - NRI5
+          - NRI6
+          - NRTS0
+          - NRTS1
+          - NRTS5
+          - NRTS6
+          - OSCCLK
+          - PE2SGRSTN
+          - PWM0
+          - PWM1
+          - PWM10
+          - PWM11
+          - PWM12
+          - PWM13
+          - PWM14
+          - PWM15
+          - PWM2
+          - PWM3
+          - PWM4
+          - PWM5
+          - PWM6
+          - PWM7
+          - PWM8
+          - PWM9
+          - QSPI0
+          - QSPI1
+          - QSPI2
+          - RGMII0
+          - RGMII1
+          - RMII0
+          - RMII0RCLKO
+          - RMII1
+          - RMII1RCLKO
+          - SALT0
+          - SALT1
+          - SALT10
+          - SALT11
+          - SALT12
+          - SALT13
+          - SALT14
+          - SALT15
+          - SALT2
+          - SALT3
+          - SALT4
+          - SALT5
+          - SALT6
+          - SALT7
+          - SALT8
+          - SALT9
+          - SD
+          - SGMII
+          - SGPM0
+          - SGPM1
+          - SGPS
+          - SIOONCTRLN0
+          - SIOONCTRLN1
+          - SIOPBIN0
+          - SIOPBIN1
+          - SIOPBON0
+          - SIOPBON1
+          - SIOPWREQN0
+          - SIOPWREQN1
+          - SIOPWRGD1
+          - SIOS3N0
+          - SIOS3N1
+          - SIOS5N0
+          - SIOS5N1
+          - SIOSCIN0
+          - SIOSCIN1
+          - SMON0
+          - SMON1
+          - SPI0
+          - SPI0ABR
+          - SPI0CS1
+          - SPI0WPN
+          - SPI1
+          - SPI1ABR
+          - SPI1CS1
+          - SPI1WPN
+          - SPI2
+          - SPI2CS1
+          - TACH0
+          - TACH1
+          - TACH10
+          - TACH11
+          - TACH12
+          - TACH13
+          - TACH14
+          - TACH15
+          - TACH2
+          - TACH3
+          - TACH4
+          - TACH5
+          - TACH6
+          - TACH7
+          - TACH8
+          - TACH9
+          - THRU0
+          - THRU1
+          - THRU2
+          - THRU3
+          - UART0
+          - UART1
+          - UART10
+          - UART11
+          - UART2
+          - UART3
+          - UART5
+          - UART6
+          - UART7
+          - UART8
+          - UART9
+          - USB2CD
+          - USB2CH
+          - USB2CU
+          - USB2CUD
+          - USB2DD
+          - USB2DH
+          - USBUART
+          - VGA
+          - VPI
+          - WDTRST0N
+          - WDTRST1N
+          - WDTRST2N
+          - WDTRST3N
+          - WDTRST4N
+          - WDTRST5N
+          - WDTRST6N
+          - WDTRST7N
+
+      pins:
+        enum:
+          - A14
+          - A15
+          - A18
+          - A19
+          - A21
+          - A22
+          - A23
+          - A24
+          - A25
+          - A26
+          - A6
+          - A7
+          - A8
+          - AA12
+          - AA13
+          - AA14
+          - AA15
+          - AA16
+          - AA17
+          - AA18
+          - AA20
+          - AA21
+          - AA22
+          - AA23
+          - AA24
+          - AA25
+          - AA26
+          - AB15
+          - AB16
+          - AB17
+          - AB18
+          - AB19
+          - AB20
+          - AB21
+          - AB22
+          - AB23
+          - AB24
+          - AB25
+          - AB26
+          - AC15
+          - AC16
+          - AC17
+          - AC18
+          - AC19
+          - AC20
+          - AC22
+          - AC24
+          - AC25
+          - AC26
+          - AD15
+          - AD16
+          - AD17
+          - AD18
+          - AD19
+          - AD20
+          - AD22
+          - AD25
+          - AD26
+          - AE16
+          - AE17
+          - AE18
+          - AE19
+          - AE20
+          - AE21
+          - AE23
+          - AE25
+          - AE26
+          - AF16
+          - AF17
+          - AF18
+          - AF19
+          - AF20
+          - AF21
+          - AF23
+          - AF25
+          - AF26
+          - B10
+          - B11
+          - B12
+          - B13
+          - B14
+          - B15
+          - B16
+          - B18
+          - B19
+          - B21
+          - B22
+          - B23
+          - B24
+          - B25
+          - B26
+          - B6
+          - B7
+          - B8
+          - B9
+          - C10
+          - C11
+          - C12
+          - C13
+          - C14
+          - C15
+          - C16
+          - C17
+          - C18
+          - C19
+          - C20
+          - C23
+          - C26
+          - C6
+          - C7
+          - C8
+          - C9
+          - D10
+          - D12
+          - D14
+          - D15
+          - D19
+          - D20
+          - D24
+          - D26
+          - D7
+          - D8
+          - D9
+          - E10
+          - E11
+          - E12
+          - E13
+          - E14
+          - E26
+          - E7
+          - E8
+          - E9
+          - F10
+          - F11
+          - F12
+          - F13
+          - F14
+          - F26
+          - F7
+          - F8
+          - F9
+          - G10
+          - G11
+          - G7
+          - G8
+          - G9
+          - H10
+          - H11
+          - H7
+          - H8
+          - H9
+          - J10
+          - J11
+          - J12
+          - J13
+          - J9
+          - K12
+          - K13
+          - L12
+          - M13
+          - M14
+          - M15
+          - M16
+          - N13
+          - N14
+          - N15
+          - N25
+          - N26
+          - P13
+          - P14
+          - P25
+          - P26
+          - R14
+          - R25
+          - R26
+          - T23
+          - T24
+          - U21
+          - U22
+          - U25
+          - U26
+          - V14
+          - V16
+          - V17
+          - V18
+          - V19
+          - V20
+          - V21
+          - V22
+          - V23
+          - V24
+          - W14
+          - W16
+          - W17
+          - W18
+          - W20
+          - W21
+          - W22
+          - W25
+          - W26
+          - Y11
+          - Y15
+          - Y16
+          - Y17
+          - Y18
+          - Y20
+          - Y21
+          - Y22
+          - Y23
+          - Y24
+          - Y25
+          - Y26
+
+      drive-strength:
+        enum: [4, 8, 12, 16]
+
+      bias-disable: true
+      bias-pull-up: true
+      bias-pull-down: true
+
+required:
+  - compatible
+  - reg
+
+allOf:
+  - $ref: pinctrl.yaml#
+
+additionalProperties: false
+
+examples:
+  - |
+    pinctrl@400 {
+        compatible = "aspeed,ast2700-soc1-pinctrl";
+        reg = <0x400 0x2A0>;
+        sgpm0-state {
+            function = "SGPM0";
+            groups = "SGPM0";
+        };
+    };

-- 
2.34.1



^ permalink raw reply related

* [PATCH v4 3/3] pinctrl: aspeed: Add AST2700 SoC1 support
From: Billy Tsai @ 2026-05-19 10:38 UTC (permalink / raw)
  To: Linus Walleij, Tony Lindgren, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery, Bartosz Golaszewski,
	Lee Jones, Ryan Chen
  Cc: patrickw3, linux-gpio, devicetree, linux-kernel, linux-arm-kernel,
	linux-aspeed, BMC-SW, openbmc, Andrew Jeffery, linux-clk,
	Billy Tsai
In-Reply-To: <20260519-pinctrl-single-bit-v4-0-5fe568a8ffde@aspeedtech.com>

Implement pin multiplexing (and pin configuration where applicable)
for the AST2700 SoC1 SCU pinctrl block using static SoC data tables.

Unlike legacy ASPEED pin controllers, the SoC1 pin function control
fields are highly regular, which makes it practical to describe the
packed-field register layout directly in driver data rather than reuse
the existing Aspeed pinctrl macro infrastructure.

The driver uses the generic pinctrl, pinmux and pinconf frameworks.
The controller registers are accessed via regmap from the parent
syscon, allowing shared ownership of the SCU register block.

Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
 drivers/pinctrl/aspeed/Kconfig                  |   14 +
 drivers/pinctrl/aspeed/Makefile                 |    1 +
 drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c | 1754 +++++++++++++++++++++++
 3 files changed, 1769 insertions(+)

diff --git a/drivers/pinctrl/aspeed/Kconfig b/drivers/pinctrl/aspeed/Kconfig
index f9672cca891e..8e1d4da0891d 100644
--- a/drivers/pinctrl/aspeed/Kconfig
+++ b/drivers/pinctrl/aspeed/Kconfig
@@ -40,3 +40,17 @@ config PINCTRL_ASPEED_G7_SOC0
 	  Say Y here to enable pin controller support for the SoC0 instance
 	  of Aspeed's 7th generation SoCs. GPIO is provided by a separate
 	  GPIO driver.
+
+config PINCTRL_ASPEED_G7_SOC1
+	bool "Aspeed G7 SoC1 pin control"
+	depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+	select MFD_SYSCON
+	select PINMUX
+	select GENERIC_PINCTRL_GROUPS
+	select GENERIC_PINMUX_FUNCTIONS
+	select GENERIC_PINCONF
+	select REGMAP_MMIO
+	help
+	  Say Y here to enable pin controller support for the SoC1 instance
+	  of Aspeed's 7th generation SoCs. GPIO is provided by a separate
+	  GPIO driver.
diff --git a/drivers/pinctrl/aspeed/Makefile b/drivers/pinctrl/aspeed/Makefile
index 0de524ca2c72..7a41ca45c6ba 100644
--- a/drivers/pinctrl/aspeed/Makefile
+++ b/drivers/pinctrl/aspeed/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_PINCTRL_ASPEED_G4)	+= pinctrl-aspeed-g4.o
 obj-$(CONFIG_PINCTRL_ASPEED_G5)	+= pinctrl-aspeed-g5.o
 obj-$(CONFIG_PINCTRL_ASPEED_G6)	+= pinctrl-aspeed-g6.o
 obj-$(CONFIG_PINCTRL_ASPEED_G7_SOC0) += pinctrl-aspeed-g7-soc0.o
+obj-$(CONFIG_PINCTRL_ASPEED_G7_SOC1)	+= pinctrl-aspeed-g7-soc1.o
diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c
new file mode 100644
index 000000000000..400fdcf7b4d1
--- /dev/null
+++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c
@@ -0,0 +1,1754 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pinctrl driver for Aspeed G7 SoC1
+ *
+ * Copyright (C) 2026 Aspeed Technology Inc.
+ */
+
+#include <linux/errno.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "../core.h"
+#include "../pinconf.h"
+#include "../pinctrl-utils.h"
+#include "../pinmux.h"
+
+#define ASPEED_G7_SOC1_NR_PINS 220
+#define ASPEED_G7_SOC1_REG_WIDTH 32
+#define ASPEED_G7_SOC1_REG_STRIDE 4
+
+#define ASPEED_G7_SOC1_MUX_BASE          0x400
+#define ASPEED_G7_SOC1_BIAS_BASE         0x480
+#define ASPEED_G7_SOC1_DRV_BASE          0x4C0
+#define ASPEED_G7_SOC1_PCIE_REG          0x908
+#define ASPEED_G7_SOC1_USB_MODE_REG      0x3B0
+#define ASPEED_G7_SOC1_SGMII_REG         0x47C
+
+#define ASPEED_G7_SOC1_MUX_FUNC_MASK 0x7
+#define ASPEED_G7_SOC1_MUX_BITS_PER_PIN 4
+#define ASPEED_G7_SOC1_MUX_PINS_PER_REG \
+	(ASPEED_G7_SOC1_REG_WIDTH / ASPEED_G7_SOC1_MUX_BITS_PER_PIN)
+
+#define ASPEED_G7_SOC1_BIAS_FUNC_MASK 0x1
+#define ASPEED_G7_SOC1_BIAS_BITS_PER_PIN 1
+#define ASPEED_G7_SOC1_BIAS_PINS_PER_REG \
+	(ASPEED_G7_SOC1_REG_WIDTH / ASPEED_G7_SOC1_BIAS_BITS_PER_PIN)
+
+#define ASPEED_G7_SOC1_DRV_FUNC_MASK 0x3
+#define ASPEED_G7_SOC1_DRV_BITS_PER_PIN 2
+#define ASPEED_G7_SOC1_DRV_PINS_PER_REG \
+	(ASPEED_G7_SOC1_REG_WIDTH / ASPEED_G7_SOC1_DRV_BITS_PER_PIN)
+
+#define ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA 4
+#define ASPEED_G7_SOC1_DRV_STRENGTH_HW_BASE 1
+#define ASPEED_G7_SOC1_DRV_STRENGTH_MIN_MA \
+	(ASPEED_G7_SOC1_DRV_STRENGTH_HW_BASE * ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA)
+#define ASPEED_G7_SOC1_DRV_STRENGTH_MAX_MA \
+	((ASPEED_G7_SOC1_DRV_FUNC_MASK + ASPEED_G7_SOC1_DRV_STRENGTH_HW_BASE) * \
+	 ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA)
+
+/*
+ * NOTE: The numeric values of these enum entries are significant.
+ * They must match the SoC GPIO numbering / ball-to-GPIO ID mapping.
+ * Do not reorder alphabetically.
+ */
+enum {
+	C16,
+	C14,
+	C11,
+	D9,
+	F14,
+	D10,
+	C12,
+	C13,
+	AC26,
+	AA25,
+	AB23,
+	U22,
+	V21,
+	N26,
+	P25,
+	N25,
+	V23,
+	W22,
+	AB26,
+	AD26,
+	P26,
+	AE26,
+	AF26,
+	AF25,
+	AE25,
+	AD25,
+	AF23,
+	AF20,
+	AF21,
+	AE21,
+	AE23,
+	AD22,
+	AF17,
+	AA16,
+	Y16,
+	V17,
+	J13,
+	AB16,
+	AC16,
+	AF16,
+	AA15,
+	AB15,
+	AC15,
+	AD15,
+	Y15,
+	AA14,
+	W16,
+	V16,
+	AB18,
+	AC18,
+	K13,
+	AA17,
+	AB17,
+	AD16,
+	AC17,
+	AD17,
+	AE16,
+	AE17,
+	AB24,
+	W26,
+	HOLE0,
+	HOLE1,
+	HOLE2,
+	HOLE3,
+	W25,
+	Y23,
+	Y24,
+	W21,
+	AA23,
+	AC22,
+	AB22,
+	Y21,
+	AE20,
+	AF19,
+	Y22,
+	AA20,
+	AA22,
+	AB20,
+	AF18,
+	AE19,
+	AD20,
+	AC20,
+	AA21,
+	AB21,
+	AC19,
+	AE18,
+	AD19,
+	AD18,
+	U25,
+	U26,
+	Y26,
+	AA24,
+	R25,
+	AA26,
+	R26,
+	Y25,
+	B16,
+	D14,
+	B15,
+	B14,
+	C17,
+	B13,
+	E14,
+	C15,
+	D24,
+	B23,
+	B22,
+	C23,
+	B18,
+	B21,
+	M15,
+	B19,
+	B26,
+	A25,
+	A24,
+	B24,
+	E26,
+	A21,
+	A19,
+	A18,
+	D26,
+	C26,
+	A23,
+	A22,
+	B25,
+	F26,
+	A26,
+	A14,
+	E10,
+	E13,
+	D12,
+	F10,
+	E11,
+	F11,
+	F13,
+	N15,
+	C20,
+	C19,
+	A8,
+	R14,
+	A7,
+	P14,
+	D20,
+	A6,
+	B6,
+	N14,
+	B7,
+	B8,
+	B9,
+	M14,
+	J11,
+	E7,
+	D19,
+	B11,
+	D15,
+	B12,
+	B10,
+	P13,
+	C18,
+	C6,
+	C7,
+	D7,
+	N13,
+	C8,
+	C9,
+	C10,
+	M16,
+	A15,
+	G11,
+	H7,
+	H8,
+	H9,
+	H10,
+	H11,
+	J9,
+	J10,
+	E9,
+	F9,
+	F8,
+	M13,
+	F7,
+	D8,
+	E8,
+	L12,
+	F12,
+	E12,
+	J12,
+	G7,
+	G8,
+	G9,
+	G10,
+	K12,
+	W17,
+	V18,
+	W18,
+	Y17,
+	AA18,
+	AA13,
+	Y18,
+	AA12,
+	W20,
+	V20,
+	Y11,
+	V14,
+	V19,
+	W14,
+	Y20,
+	AB19,
+	U21,
+	T24,
+	V24,
+	V22,
+	T23,
+	AC25,
+	AB25,
+	AC24,
+	PCIERC2_PERST,
+	PORTC_MODE,
+	PORTD_MODE,
+	SGMII0,
+};
+
+struct aspeed_g7_soc1_pinctrl {
+	struct device *dev;
+	struct regmap *regmap;
+	struct pinctrl_dev *pctl;
+};
+
+struct aspeed_g7_field {
+	unsigned int reg;
+	unsigned int shift;
+	unsigned int mask;
+};
+
+static struct aspeed_g7_field
+aspeed_g7_soc1_pinmux_field_from_pin(unsigned int pin)
+{
+	return (struct aspeed_g7_field){
+		.reg = ASPEED_G7_SOC1_MUX_BASE +
+		       (pin / ASPEED_G7_SOC1_MUX_PINS_PER_REG) *
+			       ASPEED_G7_SOC1_REG_STRIDE,
+		.shift = (pin % ASPEED_G7_SOC1_MUX_PINS_PER_REG) *
+			 ASPEED_G7_SOC1_MUX_BITS_PER_PIN,
+		.mask = ASPEED_G7_SOC1_MUX_FUNC_MASK,
+	};
+}
+
+static struct aspeed_g7_field
+aspeed_g7_soc1_bias_field_from_pin(unsigned int pin)
+{
+	return (struct aspeed_g7_field){
+		.reg = ASPEED_G7_SOC1_BIAS_BASE +
+		       (pin / ASPEED_G7_SOC1_BIAS_PINS_PER_REG) *
+			       ASPEED_G7_SOC1_REG_STRIDE,
+		.shift = pin % ASPEED_G7_SOC1_BIAS_PINS_PER_REG,
+		.mask = ASPEED_G7_SOC1_BIAS_FUNC_MASK,
+	};
+}
+
+static struct aspeed_g7_field
+aspeed_g7_soc1_drv_field_from_idx(unsigned int idx)
+{
+	return (struct aspeed_g7_field){
+		.reg = ASPEED_G7_SOC1_DRV_BASE +
+		       (idx / ASPEED_G7_SOC1_DRV_PINS_PER_REG) *
+			       ASPEED_G7_SOC1_REG_STRIDE,
+		.shift = (idx % ASPEED_G7_SOC1_DRV_PINS_PER_REG) *
+			 ASPEED_G7_SOC1_DRV_BITS_PER_PIN,
+		.mask = ASPEED_G7_SOC1_DRV_FUNC_MASK,
+	};
+}
+
+#define PIN(n) PINCTRL_PIN(n, #n)
+
+static const struct pinctrl_pin_desc aspeed_g7_soc1_pins[] = {
+	PIN(C16),
+	PIN(C14),
+	PIN(C11),
+	PIN(D9),
+	PIN(F14),
+	PIN(D10),
+	PIN(C12),
+	PIN(C13),
+	PIN(AC26),
+	PIN(AA25),
+	PIN(AB23),
+	PIN(U22),
+	PIN(V21),
+	PIN(N26),
+	PIN(P25),
+	PIN(N25),
+	PIN(V23),
+	PIN(W22),
+	PIN(AB26),
+	PIN(AD26),
+	PIN(P26),
+	PIN(AE26),
+	PIN(AF26),
+	PIN(AF25),
+	PIN(AE25),
+	PIN(AD25),
+	PIN(AF23),
+	PIN(AF20),
+	PIN(AF21),
+	PIN(AE21),
+	PIN(AE23),
+	PIN(AD22),
+	PIN(AF17),
+	PIN(AA16),
+	PIN(Y16),
+	PIN(V17),
+	PIN(J13),
+	PIN(AB16),
+	PIN(AC16),
+	PIN(AF16),
+	PIN(AA15),
+	PIN(AB15),
+	PIN(AC15),
+	PIN(AD15),
+	PIN(Y15),
+	PIN(AA14),
+	PIN(W16),
+	PIN(V16),
+	PIN(AB18),
+	PIN(AC18),
+	PIN(K13),
+	PIN(AA17),
+	PIN(AB17),
+	PIN(AD16),
+	PIN(AC17),
+	PIN(AD17),
+	PIN(AE16),
+	PIN(AE17),
+	PIN(AB24),
+	PIN(W26),
+	PIN(HOLE0),
+	PIN(HOLE1),
+	PIN(HOLE2),
+	PIN(HOLE3),
+	PIN(W25),
+	PIN(Y23),
+	PIN(Y24),
+	PIN(W21),
+	PIN(AA23),
+	PIN(AC22),
+	PIN(AB22),
+	PIN(Y21),
+	PIN(AE20),
+	PIN(AF19),
+	PIN(Y22),
+	PIN(AA20),
+	PIN(AA22),
+	PIN(AB20),
+	PIN(AF18),
+	PIN(AE19),
+	PIN(AD20),
+	PIN(AC20),
+	PIN(AA21),
+	PIN(AB21),
+	PIN(AC19),
+	PIN(AE18),
+	PIN(AD19),
+	PIN(AD18),
+	PIN(U25),
+	PIN(U26),
+	PIN(Y26),
+	PIN(AA24),
+	PIN(R25),
+	PIN(AA26),
+	PIN(R26),
+	PIN(Y25),
+	PIN(B16),
+	PIN(D14),
+	PIN(B15),
+	PIN(B14),
+	PIN(C17),
+	PIN(B13),
+	PIN(E14),
+	PIN(C15),
+	PIN(D24),
+	PIN(B23),
+	PIN(B22),
+	PIN(C23),
+	PIN(B18),
+	PIN(B21),
+	PIN(M15),
+	PIN(B19),
+	PIN(B26),
+	PIN(A25),
+	PIN(A24),
+	PIN(B24),
+	PIN(E26),
+	PIN(A21),
+	PIN(A19),
+	PIN(A18),
+	PIN(D26),
+	PIN(C26),
+	PIN(A23),
+	PIN(A22),
+	PIN(B25),
+	PIN(F26),
+	PIN(A26),
+	PIN(A14),
+	PIN(E10),
+	PIN(E13),
+	PIN(D12),
+	PIN(F10),
+	PIN(E11),
+	PIN(F11),
+	PIN(F13),
+	PIN(N15),
+	PIN(C20),
+	PIN(C19),
+	PIN(A8),
+	PIN(R14),
+	PIN(A7),
+	PIN(P14),
+	PIN(D20),
+	PIN(A6),
+	PIN(B6),
+	PIN(N14),
+	PIN(B7),
+	PIN(B8),
+	PIN(B9),
+	PIN(M14),
+	PIN(J11),
+	PIN(E7),
+	PIN(D19),
+	PIN(B11),
+	PIN(D15),
+	PIN(B12),
+	PIN(B10),
+	PIN(P13),
+	PIN(C18),
+	PIN(C6),
+	PIN(C7),
+	PIN(D7),
+	PIN(N13),
+	PIN(C8),
+	PIN(C9),
+	PIN(C10),
+	PIN(M16),
+	PIN(A15),
+	PIN(G11),
+	PIN(H7),
+	PIN(H8),
+	PIN(H9),
+	PIN(H10),
+	PIN(H11),
+	PIN(J9),
+	PIN(J10),
+	PIN(E9),
+	PIN(F9),
+	PIN(F8),
+	PIN(M13),
+	PIN(F7),
+	PIN(D8),
+	PIN(E8),
+	PIN(L12),
+	PIN(F12),
+	PIN(E12),
+	PIN(J12),
+	PIN(G7),
+	PIN(G8),
+	PIN(G9),
+	PIN(G10),
+	PIN(K12),
+	PIN(W17),
+	PIN(V18),
+	PIN(W18),
+	PIN(Y17),
+	PIN(AA18),
+	PIN(AA13),
+	PIN(Y18),
+	PIN(AA12),
+	PIN(W20),
+	PIN(V20),
+	PIN(Y11),
+	PIN(V14),
+	PIN(V19),
+	PIN(W14),
+	PIN(Y20),
+	PIN(AB19),
+	PIN(U21),
+	PIN(T24),
+	PIN(V24),
+	PIN(V22),
+	PIN(T23),
+	PIN(AC25),
+	PIN(AB25),
+	PIN(AC24),
+	PIN(PCIERC2_PERST),
+	PIN(PORTC_MODE),
+	PIN(PORTD_MODE),
+	PIN(SGMII0),
+};
+
+static const struct pinctrl_ops aspeed_g7_soc1_pctl_ops = {
+	.get_groups_count = pinctrl_generic_get_group_count,
+	.get_group_name = pinctrl_generic_get_group_name,
+	.get_group_pins = pinctrl_generic_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinctrl_utils_free_map,
+};
+
+struct aspeed_g7_soc1_function {
+	struct pinfunction pinfunction;
+	const u8 *muxvals;
+};
+
+static int aspeed_g7_soc1_drive_strength_to_hw(u32 strength,
+					       unsigned int *val)
+{
+	if (strength < ASPEED_G7_SOC1_DRV_STRENGTH_MIN_MA ||
+	    strength > ASPEED_G7_SOC1_DRV_STRENGTH_MAX_MA ||
+	    strength % ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA)
+		return -EINVAL;
+
+	*val = (strength / ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA) -
+	       ASPEED_G7_SOC1_DRV_STRENGTH_HW_BASE;
+
+	return 0;
+}
+
+static int aspeed_g7_soc1_set_mux(struct pinctrl_dev *pctldev,
+				  unsigned int fselector, unsigned int group)
+{
+	struct aspeed_g7_soc1_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	const struct aspeed_g7_soc1_function *soc1_func;
+	const struct function_desc *fd;
+	const struct pinfunction *func;
+	const struct pingroup *grp;
+	struct group_desc *gd;
+	const char *gname;
+	int i, g_idx = -1, ret;
+
+	gd = pinctrl_generic_get_group(pctldev, group);
+	if (!gd)
+		return -EINVAL;
+
+	grp = &gd->grp;
+
+	fd = pinmux_generic_get_function(pctldev, fselector);
+	if (!fd)
+		return -EINVAL;
+
+	soc1_func = fd->data;
+	if (!soc1_func)
+		return -EINVAL;
+
+	func = &soc1_func->pinfunction;
+	gname = grp->name;
+
+	for (i = 0; i < func->ngroups; i++) {
+		if (!strcmp(gname, func->groups[i])) {
+			g_idx = i;
+			break;
+		}
+	}
+
+	if (g_idx < 0)
+		return -EINVAL;
+
+	for (i = 0; i < grp->npins; i++) {
+		unsigned int val = soc1_func->muxvals[g_idx];
+		unsigned int pin = grp->pins[i];
+		struct aspeed_g7_field field;
+
+		if (pin == PCIERC2_PERST) {
+			/*
+			 * PCIERC2_PERST is a special case: it is managed by a
+			 * dedicated control register (0x908) instead of the
+			 * standard 4-bit multi-function field.
+			 */
+			field.reg = ASPEED_G7_SOC1_PCIE_REG;
+			field.shift = 0;
+			field.mask = 0x1;
+			val = 1;
+		} else if (pin == PORTC_MODE || pin == PORTD_MODE) {
+			/*
+			 * PORTC_MODE and PORTD_MODE are virtual "pins" that
+			 * control the USB 2.0 controller mode settings.
+			 * These reside in a specific control register (0x3B0)
+			 * with non-standard bit widths.
+			 */
+			field.reg = ASPEED_G7_SOC1_USB_MODE_REG;
+			field.mask = 0x3;
+			field.shift = pin == PORTC_MODE ? 0 : 2;
+		} else if (pin == SGMII0) {
+			/*
+			 * SGMII0 is a virtual pin whose mux control resides at
+			 * SCU47C bit 0, outside the contiguous pin-indexed MUX
+			 * register range starting at MUX_BASE.  The field is
+			 * 1 bit wide; use a 1-bit mask to avoid clobbering
+			 * adjacent bits in SCU47C.
+			 */
+			field.reg = ASPEED_G7_SOC1_SGMII_REG;
+			field.shift = 0;
+			field.mask = 0x1;
+		} else {
+			/* Standard 4-bit-per-pin multi-function configuration */
+			field = aspeed_g7_soc1_pinmux_field_from_pin(pin);
+		}
+
+		dev_dbg(pctl->dev,
+			"Setting pin %u reg 0x%x shift %u to function %s (muxval=0x%x)\n",
+			pin, field.reg, field.shift, func->name, val);
+
+		ret = regmap_update_bits(pctl->regmap, field.reg,
+					 field.mask << field.shift,
+					 val << field.shift);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int aspeed_g7_soc1_gpio_request_enable(struct pinctrl_dev *pctldev,
+					      struct pinctrl_gpio_range *range,
+					      unsigned int pin)
+{
+	struct aspeed_g7_soc1_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	struct aspeed_g7_field field;
+	int ret = -ENOTSUPP;
+
+	if (pin <= AC24) {
+		field = aspeed_g7_soc1_pinmux_field_from_pin(pin);
+		ret = regmap_update_bits(pctl->regmap, field.reg,
+					 field.mask << field.shift, 0);
+	}
+
+	return ret;
+}
+
+static const struct pinmux_ops aspeed_g7_soc1_pmx_ops = {
+	.get_functions_count = pinmux_generic_get_function_count,
+	.get_function_name = pinmux_generic_get_function_name,
+	.get_function_groups = pinmux_generic_get_function_groups,
+	.set_mux = aspeed_g7_soc1_set_mux,
+	.gpio_request_enable = aspeed_g7_soc1_gpio_request_enable,
+	.strict = true,
+};
+
+/*
+ * aspeed_g7_soc1_drv_map - Mapping table for pin drive strength control.
+ *
+ * In AST2700 SOC1, drive strength configuration is architecturally decoupled
+ * from the main pin mux registers (0x400 range). It is managed by a separate
+ * set of registers starting at 0x4C0.
+ *
+ * This table is required because:
+ * 1. The mapping between physical pin IDs and drive strength control slots
+ *    is non-linear and sparse.
+ *    For example, W25 maps to field index 8 (stored as 9),
+ *    meaning it occupies bits [17:16] of the first 0x4C0 register.
+ * 2. Only a subset of physical pins supports drive strength configuration.
+ *
+ * The table stores (drive strength field index + 1).
+ * The field index refers to the 2-bit drive strength field position within the
+ * 0x4C0 register range. A value of 0 indicates that the pin does not support
+ * drive strength configuration (returning -ENOTSUPP).
+ * This +1 offset allows us to rely on C's default zero-initialization for
+ * unsupported pins while avoiding compiler warnings regarding overridden
+ * initializers.
+ */
+static const int aspeed_g7_soc1_drv_map[ASPEED_G7_SOC1_NR_PINS] = {
+	[C16] = 1,   [C14] = 2,	  [C11] = 3,   [D9] = 4,    [F14] = 5,	 [D10] = 6,   [C12] = 7,
+	[C13] = 8,   [W25] = 9,	  [Y23] = 10,  [Y24] = 11,  [W21] = 12,	 [AA23] = 13, [AC22] = 14,
+	[AB22] = 15, [Y21] = 16,  [AE20] = 17, [AF19] = 18, [Y22] = 19,	 [AA20] = 20, [AA22] = 21,
+	[AB20] = 22, [AF18] = 23, [AE19] = 24, [AD20] = 25, [AC20] = 26, [AA21] = 27, [AB21] = 28,
+	[AC19] = 29, [AE18] = 30, [AD19] = 31, [AD18] = 32, [U25] = 33,	 [U26] = 34,  [Y26] = 35,
+	[AA24] = 36, [R25] = 37,  [AA26] = 38, [R26] = 39,  [Y25] = 40,	 [B16] = 41,  [D14] = 42,
+	[B15] = 43,  [B14] = 44,  [C17] = 45,  [B13] = 46,  [E14] = 47,	 [C15] = 48,  [D24] = 49,
+	[B23] = 50,  [B22] = 51,  [C23] = 52,  [B18] = 53,  [B21] = 54,	 [M15] = 55,  [B19] = 56,
+	[B26] = 57,  [A25] = 58,  [A24] = 59,  [B24] = 60,  [E26] = 61,	 [A21] = 62,  [A19] = 63,
+	[A18] = 64,  [D26] = 65,  [C26] = 66,  [A23] = 67,  [A22] = 68,	 [B25] = 69,  [F26] = 70,
+	[A26] = 71,  [A14] = 72,  [E10] = 73,  [E13] = 74,  [D12] = 75,	 [F10] = 76,  [E11] = 77,
+	[F11] = 78,  [F13] = 79,  [N15] = 80,  [C20] = 81,  [C19] = 82,	 [A8] = 83,   [R14] = 84,
+	[A7] = 85,   [P14] = 86,  [D20] = 87,  [A6] = 88,   [B6] = 89,	 [N14] = 90,  [B7] = 91,
+	[B8] = 92,   [B9] = 93,	  [M14] = 94,  [J11] = 95,  [E7] = 96,	 [D19] = 97,  [B11] = 98,
+	[D15] = 99,  [B12] = 100, [B10] = 101, [P13] = 102, [C18] = 103, [C6] = 104,  [C7] = 105,
+	[D7] = 106,  [N13] = 107, [C8] = 108,  [C9] = 109,  [C10] = 110, [M16] = 111, [A15] = 112,
+	[E9] = 113,  [F9] = 114,  [F8] = 115,  [M13] = 116, [F7] = 117,	 [D8] = 118,  [E8] = 119,
+	[L12] = 120,
+};
+
+static int aspeed_g7_soc1_pin_config_get(struct pinctrl_dev *pctldev,
+					 unsigned int pin,
+					 unsigned long *config)
+{
+	struct aspeed_g7_soc1_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	struct aspeed_g7_field field;
+	unsigned int val, val_raw;
+	int ret, ds_idx;
+
+	if (pin > AC24)
+		return -EINVAL;
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+		field = aspeed_g7_soc1_bias_field_from_pin(pin);
+		break;
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		/*
+		 * The hardware has a single 1-bit enable/disable field per
+		 * pin; pull direction is fixed in silicon and cannot be read
+		 * back from the register.  Reject readback requests for a
+		 * specific pull direction.
+		 */
+		return -ENOTSUPP;
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		ds_idx = aspeed_g7_soc1_drv_map[pin];
+		if (!ds_idx)
+			return -ENOTSUPP;
+		ds_idx--; /* Adjust back to 0-based hardware index */
+		field = aspeed_g7_soc1_drv_field_from_idx(ds_idx);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	ret = regmap_read(pctl->regmap, field.reg, &val_raw);
+	if (ret)
+		return ret;
+
+	val = (val_raw & (field.mask << field.shift)) >> field.shift;
+	if (param == PIN_CONFIG_DRIVE_STRENGTH)
+		val = (val + ASPEED_G7_SOC1_DRV_STRENGTH_HW_BASE) *
+		      ASPEED_G7_SOC1_DRV_STRENGTH_STEP_MA;
+
+	if (!val)
+		return -EINVAL;
+
+	*config = pinconf_to_config_packed(param, val);
+
+	return 0;
+}
+
+static int aspeed_g7_soc1_pin_config_set(struct pinctrl_dev *pctldev,
+					 unsigned int pin,
+					 unsigned long *configs,
+					 unsigned int num_configs)
+{
+	struct aspeed_g7_soc1_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	struct aspeed_g7_field field;
+	enum pin_config_param param;
+	int i, ret, ds_idx;
+	unsigned int val;
+	u32 arg;
+
+	if (pin > AC24)
+		return -EINVAL;
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+
+		switch (param) {
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+		case PIN_CONFIG_BIAS_PULL_UP:
+		case PIN_CONFIG_BIAS_DISABLE:
+			field = aspeed_g7_soc1_bias_field_from_pin(pin);
+			val = (param == PIN_CONFIG_BIAS_DISABLE) ? 1 : 0;
+			break;
+		case PIN_CONFIG_DRIVE_STRENGTH:
+			ds_idx = aspeed_g7_soc1_drv_map[pin];
+			if (!ds_idx)
+				return -ENOTSUPP;
+			ds_idx--; /* Adjust back to 0-based hardware index */
+			field = aspeed_g7_soc1_drv_field_from_idx(ds_idx);
+			ret = aspeed_g7_soc1_drive_strength_to_hw(arg, &val);
+			if (ret)
+				return ret;
+			break;
+		default:
+			return -ENOTSUPP;
+		}
+
+		dev_dbg(pctl->dev,
+			"Configuring pin %u reg 0x%x shift %u param %d arg %u val 0x%x\n",
+			pin, field.reg, field.shift, param, arg, val);
+
+		ret = regmap_update_bits(pctl->regmap, field.reg,
+					 field.mask << field.shift,
+					 val << field.shift);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int aspeed_g7_soc1_get_group_pins(struct pinctrl_dev *pctldev,
+					 unsigned int selector,
+					 const unsigned int **pins,
+					 unsigned int *npins)
+{
+	struct group_desc *group;
+
+	group = pinctrl_generic_get_group(pctldev, selector);
+	if (!group)
+		return -EINVAL;
+
+	if (!group->grp.npins)
+		return -ENODEV;
+
+	*pins = group->grp.pins;
+	*npins = group->grp.npins;
+
+	return 0;
+}
+
+static int aspeed_g7_soc1_pin_config_group_get(struct pinctrl_dev *pctldev,
+					       unsigned int selector,
+					       unsigned long *config)
+{
+	const unsigned int *pins;
+	unsigned int npins;
+	int ret;
+
+	ret = aspeed_g7_soc1_get_group_pins(pctldev, selector, &pins, &npins);
+	if (ret)
+		return ret;
+
+	return aspeed_g7_soc1_pin_config_get(pctldev, pins[0], config);
+}
+
+static int aspeed_g7_soc1_pin_config_group_set(struct pinctrl_dev *pctldev,
+					       unsigned int selector,
+					       unsigned long *configs,
+					       unsigned int num_configs)
+{
+	const unsigned int *pins;
+	unsigned int npins;
+	int ret;
+	int i;
+
+	ret = aspeed_g7_soc1_get_group_pins(pctldev, selector, &pins, &npins);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < npins; i++) {
+		ret = aspeed_g7_soc1_pin_config_set(pctldev, pins[i], configs,
+						    num_configs);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct pinconf_ops aspeed_g7_soc1_conf_ops = {
+	.is_generic = true,
+	.pin_config_get = aspeed_g7_soc1_pin_config_get,
+	.pin_config_set = aspeed_g7_soc1_pin_config_set,
+	.pin_config_group_get = aspeed_g7_soc1_pin_config_group_get,
+	.pin_config_group_set = aspeed_g7_soc1_pin_config_group_set,
+	.pin_config_config_dbg_show = pinconf_generic_dump_config,
+};
+
+static const struct pinctrl_desc aspeed_g7_soc1_desc = {
+	.name = "aspeed-g7-soc1-pinctrl",
+	.pins = aspeed_g7_soc1_pins,
+	.npins = ARRAY_SIZE(aspeed_g7_soc1_pins),
+	.pctlops = &aspeed_g7_soc1_pctl_ops,
+	.pmxops = &aspeed_g7_soc1_pmx_ops,
+	.confops = &aspeed_g7_soc1_conf_ops,
+	.owner = THIS_MODULE,
+};
+
+ #define PIN_GROUP(name, ...) static const unsigned int name ## _pins[] = { __VA_ARGS__ }
+
+/* Pin Groups and Functions */
+PIN_GROUP(ADC0, W17);
+PIN_GROUP(ADC1, V18);
+PIN_GROUP(ADC10, Y11);
+PIN_GROUP(ADC11, V14);
+PIN_GROUP(ADC12, V19);
+PIN_GROUP(ADC13, W14);
+PIN_GROUP(ADC14, Y20);
+PIN_GROUP(ADC15, AB19);
+PIN_GROUP(ADC2, W18);
+PIN_GROUP(ADC3, Y17);
+PIN_GROUP(ADC4, AA18);
+PIN_GROUP(ADC5, AA13);
+PIN_GROUP(ADC6, Y18);
+PIN_GROUP(ADC7, AA12);
+PIN_GROUP(ADC8, W20);
+PIN_GROUP(ADC9, V20);
+PIN_GROUP(AUXPWRGOOD0, W14);
+PIN_GROUP(AUXPWRGOOD1, Y20);
+PIN_GROUP(CANBUS, G7, G8, G9);
+PIN_GROUP(DI2C0, C16, D9);
+PIN_GROUP(DI2C1, C14, F14);
+PIN_GROUP(DI2C10, R25, AA26);
+PIN_GROUP(DI2C11, R26, Y25);
+PIN_GROUP(DI2C12, W25, Y23);
+PIN_GROUP(DI2C13, Y24, W21);
+PIN_GROUP(DI2C14, AA23, AC22);
+PIN_GROUP(DI2C15, AB22, Y21);
+PIN_GROUP(DI2C2, D10, C12);
+PIN_GROUP(DI2C3, C11, C13);
+PIN_GROUP(DI2C8, U25, U26);
+PIN_GROUP(DI2C9, Y26, AA24);
+PIN_GROUP(DSGPM0, D19, B10, C7, D7);
+PIN_GROUP(ESPI0, B16, D14, B15, B14, C17, B13, E14, C15);
+PIN_GROUP(ESPI1, C16, C14, C11, D9, F14, D10, C12, C13);
+PIN_GROUP(FSI0, AD20, AC20);
+PIN_GROUP(FSI1, AA21, AB21);
+PIN_GROUP(FSI2, AC19, AE18);
+PIN_GROUP(FSI3, AD19, AD18);
+PIN_GROUP(FWQSPI, M16, A15);
+PIN_GROUP(FWSPIABR, A14);
+PIN_GROUP(FWWPN, N15);
+PIN_GROUP(HBLED, V24);
+PIN_GROUP(HVI3C0, U25, U26);
+PIN_GROUP(HVI3C1, Y26, AA24);
+PIN_GROUP(HVI3C12, W25, Y23);
+PIN_GROUP(HVI3C13, Y24, W21);
+PIN_GROUP(HVI3C14, AA23, AC22);
+PIN_GROUP(HVI3C15, AB22, Y21);
+PIN_GROUP(HVI3C2, R25, AA26);
+PIN_GROUP(HVI3C3, R26, Y25);
+PIN_GROUP(I2C0, G11, H7);
+PIN_GROUP(I2C1, H8, H9);
+PIN_GROUP(I2C10, G8, G9);
+PIN_GROUP(I2C11, G10, K12);
+PIN_GROUP(I2C12, AC18, AA17);
+PIN_GROUP(I2C13, AB17, AD16);
+PIN_GROUP(I2C14, AC17, AD17);
+PIN_GROUP(I2C15, AE16, AE17);
+PIN_GROUP(I2C2, H10, H11);
+PIN_GROUP(I2C3, J9, J10);
+PIN_GROUP(I2C4, E9, F9);
+PIN_GROUP(I2C5, F8, M13);
+PIN_GROUP(I2C6, F7, D8);
+PIN_GROUP(I2C7, E8, L12);
+PIN_GROUP(I2C8, F12, E12);
+PIN_GROUP(I2C9, J12, G7);
+PIN_GROUP(I2CF0, F12, E12, J12, G7);
+PIN_GROUP(I2CF1, E9, F9, F8, M13);
+PIN_GROUP(I2CF2, F7, D8, E8, L12);
+PIN_GROUP(I3C10, AC19, AE18);
+PIN_GROUP(I3C11, AD19, AD18);
+PIN_GROUP(I3C4, AE20, AF19);
+PIN_GROUP(I3C5, Y22, AA20);
+PIN_GROUP(I3C6, AA22, AB20);
+PIN_GROUP(I3C7, AF18, AE19);
+PIN_GROUP(I3C8, AD20, AC20);
+PIN_GROUP(I3C9, AA21, AB21);
+PIN_GROUP(JTAGM1, D12, F10, E11, F11, F13);
+PIN_GROUP(LPC0, AF26, AF25, B16, D14, B15, B14, C17, B13, E14, C15);
+PIN_GROUP(LPC1, C16, C14, C11, D9, F14, D10, C12, C13, AE16, AE17);
+PIN_GROUP(LTPI, U25, U26, Y26, AA24);
+PIN_GROUP(LTPI_PS_I2C0, G11, H7);
+PIN_GROUP(LTPI_PS_I2C1, H8, H9);
+PIN_GROUP(LTPI_PS_I2C2, H10, H11);
+PIN_GROUP(LTPI_PS_I2C3, J9, J10);
+PIN_GROUP(MACLINK0, U21);
+PIN_GROUP(MACLINK1, AC24);
+PIN_GROUP(MACLINK2, T24);
+PIN_GROUP(MDIO0, B9, M14);
+PIN_GROUP(MDIO1, C9, C10);
+PIN_GROUP(MDIO2, E10, E13);
+PIN_GROUP(NCTS0, AF17);
+PIN_GROUP(NCTS1, AA15);
+PIN_GROUP(NCTS5, V21);
+PIN_GROUP(NCTS6, AB26);
+PIN_GROUP(NDCD0, AA16);
+PIN_GROUP(NDCD1, AB15);
+PIN_GROUP(NDCD5, N26);
+PIN_GROUP(NDCD6, AD26);
+PIN_GROUP(NDSR0, Y16);
+PIN_GROUP(NDSR1, AC15);
+PIN_GROUP(NDSR5, P25);
+PIN_GROUP(NDSR6, P26);
+PIN_GROUP(NDTR0, J13);
+PIN_GROUP(NDTR1, Y15);
+PIN_GROUP(NDTR5, V23);
+PIN_GROUP(NDTR6, AF26);
+PIN_GROUP(NRI0, V17);
+PIN_GROUP(NRI1, AD15);
+PIN_GROUP(NRI5, N25);
+PIN_GROUP(NRI6, AE26);
+PIN_GROUP(NRTS0, AB16);
+PIN_GROUP(NRTS1, AA14);
+PIN_GROUP(NRTS5, W22);
+PIN_GROUP(NRTS6, AF25);
+PIN_GROUP(OSCCLK, C17);
+PIN_GROUP(PE2SGRSTN, E10, PCIERC2_PERST);
+PIN_GROUP(PWM0, AE25);
+PIN_GROUP(PWM1, AD25);
+PIN_GROUP(PWM10, AB17);
+PIN_GROUP(PWM11, AD16);
+PIN_GROUP(PWM12, AC17);
+PIN_GROUP(PWM13, AD17);
+PIN_GROUP(PWM14, AE16);
+PIN_GROUP(PWM15, AE17);
+PIN_GROUP(PWM2, AF23);
+PIN_GROUP(PWM3, AF20);
+PIN_GROUP(PWM4, AF21);
+PIN_GROUP(PWM5, AE21);
+PIN_GROUP(PWM6, AE23);
+PIN_GROUP(PWM7, AD22);
+PIN_GROUP(PWM8, K13);
+PIN_GROUP(PWM9, AA17);
+PIN_GROUP(QSPI0, C23, B18);
+PIN_GROUP(QSPI1, B24, E26);
+PIN_GROUP(QSPI2, B25, F26);
+PIN_GROUP(RGMII0, C20, C19, A8, R14, A7, P14, D20, A6, B6, N14, B7, B8);
+PIN_GROUP(RGMII1, D19, B11, D15, B12, B10, P13, C18, C6, C7, D7, N13, C8);
+PIN_GROUP(RMII0, C20, A8, R14, A7, P14, A6, B6, N14);
+PIN_GROUP(RMII0RCLKO, D20);
+PIN_GROUP(RMII1, D19, D15, B12, B10, P13, C6, C7, D7);
+PIN_GROUP(RMII1RCLKO, C18);
+PIN_GROUP(SALT0, AC17);
+PIN_GROUP(SALT1, AD17);
+PIN_GROUP(SALT10, Y18);
+PIN_GROUP(SALT11, AA12);
+PIN_GROUP(SALT12, AB26);
+PIN_GROUP(SALT13, AD26);
+PIN_GROUP(SALT14, P26);
+PIN_GROUP(SALT15, AE26);
+PIN_GROUP(SALT2, AC15);
+PIN_GROUP(SALT3, AD15);
+PIN_GROUP(SALT4, W17);
+PIN_GROUP(SALT5, V18);
+PIN_GROUP(SALT6, W18);
+PIN_GROUP(SALT7, Y17);
+PIN_GROUP(SALT8, AA18);
+PIN_GROUP(SALT9, AA13);
+PIN_GROUP(SD, C16, C14, C11, D9, F14, D10, C12, C13);
+PIN_GROUP(SGMII, SGMII0);
+PIN_GROUP(SGPM0, U21, T24, V22, T23);
+PIN_GROUP(SGPM1, AC25, AB25, AB24, W26);
+PIN_GROUP(SGPS, B11, C18, N13, C8);
+PIN_GROUP(SIOONCTRLN0, AE23);
+PIN_GROUP(SIOONCTRLN1, AA15);
+PIN_GROUP(SIOPBIN0, AD25);
+PIN_GROUP(SIOPBIN1, AA16);
+PIN_GROUP(SIOPBON0, AE25);
+PIN_GROUP(SIOPBON1, AF17);
+PIN_GROUP(SIOPWREQN0, AE21);
+PIN_GROUP(SIOPWREQN1, AB16);
+PIN_GROUP(SIOPWRGD1, AB15);
+PIN_GROUP(SIOS3N0, AF20);
+PIN_GROUP(SIOS3N1, V17);
+PIN_GROUP(SIOS5N0, AF21);
+PIN_GROUP(SIOS5N1, J13);
+PIN_GROUP(SIOSCIN0, AF23);
+PIN_GROUP(SIOSCIN1, Y16);
+PIN_GROUP(SMON0, U21, T24, V22, T23);
+PIN_GROUP(SMON1, W26, AC25, AB25);
+PIN_GROUP(SPI0, D24, B23, B22);
+PIN_GROUP(SPI0ABR, M15);
+PIN_GROUP(SPI0CS1, B21);
+PIN_GROUP(SPI0WPN, B19);
+PIN_GROUP(SPI1, B26, A25, A24);
+PIN_GROUP(SPI1ABR, A19);
+PIN_GROUP(SPI1CS1, A21);
+PIN_GROUP(SPI1WPN, A18);
+PIN_GROUP(SPI2, D26, C26, A23, A22);
+PIN_GROUP(SPI2CS1, A26);
+PIN_GROUP(TACH0, AC26);
+PIN_GROUP(TACH1, AA25);
+PIN_GROUP(TACH10, AB26);
+PIN_GROUP(TACH11, AD26);
+PIN_GROUP(TACH12, P26);
+PIN_GROUP(TACH13, AE26);
+PIN_GROUP(TACH14, AF26);
+PIN_GROUP(TACH15, AF25);
+PIN_GROUP(TACH2, AB23);
+PIN_GROUP(TACH3, U22);
+PIN_GROUP(TACH4, V21);
+PIN_GROUP(TACH5, N26);
+PIN_GROUP(TACH6, P25);
+PIN_GROUP(TACH7, N25);
+PIN_GROUP(TACH8, V23);
+PIN_GROUP(TACH9, W22);
+PIN_GROUP(THRU0, AC26, AA25);
+PIN_GROUP(THRU1, AB23, U22);
+PIN_GROUP(THRU2, A19, A18);
+PIN_GROUP(THRU3, B25, F26);
+PIN_GROUP(UART0, AC16, AF16);
+PIN_GROUP(UART1, W16, V16);
+PIN_GROUP(UART2, AB18, AC18);
+PIN_GROUP(UART3, K13, AA17);
+PIN_GROUP(UART5, AB17, AD16);
+PIN_GROUP(UART6, AC17, AD17);
+PIN_GROUP(UART7, AE16, AE17);
+PIN_GROUP(UART8, M15, B19);
+PIN_GROUP(UART9, B26, A25);
+PIN_GROUP(UART10, A24, B24);
+PIN_GROUP(UART11, E26, A21);
+PIN_GROUP(USB2CD, PORTC_MODE);
+PIN_GROUP(USB2CH, PORTC_MODE);
+PIN_GROUP(USB2CU, PORTC_MODE);
+PIN_GROUP(USB2CUD, PORTC_MODE);
+PIN_GROUP(USB2DD, PORTD_MODE);
+PIN_GROUP(USB2DH, PORTD_MODE);
+PIN_GROUP(USBUART, G10, K12);
+PIN_GROUP(VGA, J11, E7);
+PIN_GROUP(VPI, C16, C14, C11, D9, F14, D10, AC26, AA25, AB23, U22, V21, N26,
+	  P25, N25, V23, W22, AB26, AD26, P26, AE26, AF26, AF25, AE25, AD25,
+	  AF23, AF20, AF21, AE21);
+PIN_GROUP(WDTRST0N, K13);
+PIN_GROUP(WDTRST1N, AA17);
+PIN_GROUP(WDTRST2N, AB17);
+PIN_GROUP(WDTRST3N, AD16);
+PIN_GROUP(WDTRST4N, AC25);
+PIN_GROUP(WDTRST5N, AB25);
+PIN_GROUP(WDTRST6N, AC24);
+PIN_GROUP(WDTRST7N, AB24);
+
+#define GROUP(n) PINCTRL_PINGROUP(#n, n##_pins, ARRAY_SIZE(n##_pins))
+
+static const struct pingroup aspeed_g7_soc1_groups[] = {
+	GROUP(ADC0),
+	GROUP(ADC1),
+	GROUP(ADC10),
+	GROUP(ADC11),
+	GROUP(ADC12),
+	GROUP(ADC13),
+	GROUP(ADC14),
+	GROUP(ADC15),
+	GROUP(ADC2),
+	GROUP(ADC3),
+	GROUP(ADC4),
+	GROUP(ADC5),
+	GROUP(ADC6),
+	GROUP(ADC7),
+	GROUP(ADC8),
+	GROUP(ADC9),
+	GROUP(AUXPWRGOOD0),
+	GROUP(AUXPWRGOOD1),
+	GROUP(CANBUS),
+	GROUP(DI2C0),
+	GROUP(DI2C1),
+	GROUP(DI2C10),
+	GROUP(DI2C11),
+	GROUP(DI2C12),
+	GROUP(DI2C13),
+	GROUP(DI2C14),
+	GROUP(DI2C15),
+	GROUP(DI2C2),
+	GROUP(DI2C3),
+	GROUP(DI2C8),
+	GROUP(DI2C9),
+	GROUP(DSGPM0),
+	GROUP(ESPI0),
+	GROUP(ESPI1),
+	GROUP(FSI0),
+	GROUP(FSI1),
+	GROUP(FSI2),
+	GROUP(FSI3),
+	GROUP(FWQSPI),
+	GROUP(FWSPIABR),
+	GROUP(FWWPN),
+	GROUP(HBLED),
+	GROUP(HVI3C0),
+	GROUP(HVI3C1),
+	GROUP(HVI3C12),
+	GROUP(HVI3C13),
+	GROUP(HVI3C14),
+	GROUP(HVI3C15),
+	GROUP(HVI3C2),
+	GROUP(HVI3C3),
+	GROUP(I2C0),
+	GROUP(I2C1),
+	GROUP(I2C10),
+	GROUP(I2C11),
+	GROUP(I2C12),
+	GROUP(I2C13),
+	GROUP(I2C14),
+	GROUP(I2C15),
+	GROUP(I2C2),
+	GROUP(I2C3),
+	GROUP(I2C4),
+	GROUP(I2C5),
+	GROUP(I2C6),
+	GROUP(I2C7),
+	GROUP(I2C8),
+	GROUP(I2C9),
+	GROUP(I2CF0),
+	GROUP(I2CF1),
+	GROUP(I2CF2),
+	GROUP(I3C10),
+	GROUP(I3C11),
+	GROUP(I3C4),
+	GROUP(I3C5),
+	GROUP(I3C6),
+	GROUP(I3C7),
+	GROUP(I3C8),
+	GROUP(I3C9),
+	GROUP(JTAGM1),
+	GROUP(LPC0),
+	GROUP(LPC1),
+	GROUP(LTPI),
+	GROUP(LTPI_PS_I2C0),
+	GROUP(LTPI_PS_I2C1),
+	GROUP(LTPI_PS_I2C2),
+	GROUP(LTPI_PS_I2C3),
+	GROUP(MACLINK0),
+	GROUP(MACLINK1),
+	GROUP(MACLINK2),
+	GROUP(MDIO0),
+	GROUP(MDIO1),
+	GROUP(MDIO2),
+	GROUP(NCTS0),
+	GROUP(NCTS1),
+	GROUP(NCTS5),
+	GROUP(NCTS6),
+	GROUP(NDCD0),
+	GROUP(NDCD1),
+	GROUP(NDCD5),
+	GROUP(NDCD6),
+	GROUP(NDSR0),
+	GROUP(NDSR1),
+	GROUP(NDSR5),
+	GROUP(NDSR6),
+	GROUP(NDTR0),
+	GROUP(NDTR1),
+	GROUP(NDTR5),
+	GROUP(NDTR6),
+	GROUP(NRI0),
+	GROUP(NRI1),
+	GROUP(NRI5),
+	GROUP(NRI6),
+	GROUP(NRTS0),
+	GROUP(NRTS1),
+	GROUP(NRTS5),
+	GROUP(NRTS6),
+	GROUP(OSCCLK),
+	GROUP(PE2SGRSTN),
+	GROUP(PWM0),
+	GROUP(PWM1),
+	GROUP(PWM10),
+	GROUP(PWM11),
+	GROUP(PWM12),
+	GROUP(PWM13),
+	GROUP(PWM14),
+	GROUP(PWM15),
+	GROUP(PWM2),
+	GROUP(PWM3),
+	GROUP(PWM4),
+	GROUP(PWM5),
+	GROUP(PWM6),
+	GROUP(PWM7),
+	GROUP(PWM8),
+	GROUP(PWM9),
+	GROUP(QSPI0),
+	GROUP(QSPI1),
+	GROUP(QSPI2),
+	GROUP(RGMII0),
+	GROUP(RGMII1),
+	GROUP(RMII0),
+	GROUP(RMII0RCLKO),
+	GROUP(RMII1),
+	GROUP(RMII1RCLKO),
+	GROUP(SALT0),
+	GROUP(SALT1),
+	GROUP(SALT10),
+	GROUP(SALT11),
+	GROUP(SALT12),
+	GROUP(SALT13),
+	GROUP(SALT14),
+	GROUP(SALT15),
+	GROUP(SALT2),
+	GROUP(SALT3),
+	GROUP(SALT4),
+	GROUP(SALT5),
+	GROUP(SALT6),
+	GROUP(SALT7),
+	GROUP(SALT8),
+	GROUP(SALT9),
+	GROUP(SD),
+	GROUP(SGMII),
+	GROUP(SGPM0),
+	GROUP(SGPM1),
+	GROUP(SGPS),
+	GROUP(SIOONCTRLN0),
+	GROUP(SIOONCTRLN1),
+	GROUP(SIOPBIN0),
+	GROUP(SIOPBIN1),
+	GROUP(SIOPBON0),
+	GROUP(SIOPBON1),
+	GROUP(SIOPWREQN0),
+	GROUP(SIOPWREQN1),
+	GROUP(SIOPWRGD1),
+	GROUP(SIOS3N0),
+	GROUP(SIOS3N1),
+	GROUP(SIOS5N0),
+	GROUP(SIOS5N1),
+	GROUP(SIOSCIN0),
+	GROUP(SIOSCIN1),
+	GROUP(SMON0),
+	GROUP(SMON1),
+	GROUP(SPI0),
+	GROUP(SPI0ABR),
+	GROUP(SPI0CS1),
+	GROUP(SPI0WPN),
+	GROUP(SPI1),
+	GROUP(SPI1ABR),
+	GROUP(SPI1CS1),
+	GROUP(SPI1WPN),
+	GROUP(SPI2),
+	GROUP(SPI2CS1),
+	GROUP(TACH0),
+	GROUP(TACH1),
+	GROUP(TACH10),
+	GROUP(TACH11),
+	GROUP(TACH12),
+	GROUP(TACH13),
+	GROUP(TACH14),
+	GROUP(TACH15),
+	GROUP(TACH2),
+	GROUP(TACH3),
+	GROUP(TACH4),
+	GROUP(TACH5),
+	GROUP(TACH6),
+	GROUP(TACH7),
+	GROUP(TACH8),
+	GROUP(TACH9),
+	GROUP(THRU0),
+	GROUP(THRU1),
+	GROUP(THRU2),
+	GROUP(THRU3),
+	GROUP(UART0),
+	GROUP(UART1),
+	GROUP(UART10),
+	GROUP(UART11),
+	GROUP(UART2),
+	GROUP(UART3),
+	GROUP(UART5),
+	GROUP(UART6),
+	GROUP(UART7),
+	GROUP(UART8),
+	GROUP(UART9),
+	GROUP(USB2CD),
+	GROUP(USB2CH),
+	GROUP(USB2CU),
+	GROUP(USB2CUD),
+	GROUP(USB2DD),
+	GROUP(USB2DH),
+	GROUP(USBUART),
+	GROUP(VGA),
+	GROUP(VPI),
+	GROUP(WDTRST0N),
+	GROUP(WDTRST1N),
+	GROUP(WDTRST2N),
+	GROUP(WDTRST3N),
+	GROUP(WDTRST4N),
+	GROUP(WDTRST5N),
+	GROUP(WDTRST6N),
+	GROUP(WDTRST7N),
+};
+
+/**
+ * VM() - Helper macro to unwrap a parenthesized list of arguments.
+ * @...: The parenthesized list to be unwrapped.
+ *
+ * Since the C preprocessor treats commas inside braces {} as argument
+ * separators for macros, we wrap lists (like mux values) in parentheses ()
+ * to protect them during macro expansion. This macro strips those
+ * parentheses when the values are needed for array initialization.
+ */
+#define VM(...) __VA_ARGS__
+
+/**
+ * FUNC() - Macro to initialize an aspeed_g7_soc1_function entry.
+ * @n: Name of the pin function.
+ * @m: Parenthesized list of mux values, mapped 1:1 to the groups list.
+ * @...: Variable list of pin group names associated with this function.
+ *
+ * This macro solves complex static initialization by:
+ * 1. Creating anonymous arrays for both group names and mux values
+ *    using C99 Compound Literals.
+ * 2. Using VM(m) to unwrap mux values into the array initializer.
+ * 3. Calculating the number of groups via sizeof() division, which
+ *    bypasses the __must_be_array() check performed by ARRAY_SIZE()
+ *    that often fails on compound literals in the kernel environment.
+ *
+ * Example: FUNC(i2c0, (1, 4), "i2c0", "di2c0")
+ *          Maps "i2c0" group to mux value 1 and "di2c0" group to mux value 4.
+ */
+#define FUNC(n, m, ...)                                                                          \
+	{                                                                                        \
+		.pinfunction = {                                                                 \
+			.name = #n,                                                              \
+			.groups = (const char *const[]){ __VA_ARGS__ },                          \
+			.ngroups = sizeof((const char *const[]){ __VA_ARGS__ }) / sizeof(char *), \
+		},                                                                               \
+		.muxvals = (const u8[]){ VM m }                                                  \
+	}
+
+static const struct aspeed_g7_soc1_function aspeed_g7_soc1_functions[] = {
+	FUNC(ADC0, (0), "ADC0"),
+	FUNC(ADC1, (0), "ADC1"),
+	FUNC(ADC10, (0), "ADC10"),
+	FUNC(ADC11, (0), "ADC11"),
+	FUNC(ADC12, (0), "ADC12"),
+	FUNC(ADC13, (0), "ADC13"),
+	FUNC(ADC14, (0), "ADC14"),
+	FUNC(ADC15, (0), "ADC15"),
+	FUNC(ADC2, (0), "ADC2"),
+	FUNC(ADC3, (0), "ADC3"),
+	FUNC(ADC4, (0), "ADC4"),
+	FUNC(ADC5, (0), "ADC5"),
+	FUNC(ADC6, (0), "ADC6"),
+	FUNC(ADC7, (0), "ADC7"),
+	FUNC(ADC8, (0), "ADC8"),
+	FUNC(ADC9, (0), "ADC9"),
+	FUNC(AUXPWRGOOD0, (2), "AUXPWRGOOD0"),
+	FUNC(AUXPWRGOOD1, (2), "AUXPWRGOOD1"),
+	FUNC(CANBUS, (2), "CANBUS"),
+	FUNC(ESPI0, (1), "ESPI0"),
+	FUNC(ESPI1, (1), "ESPI1"),
+	FUNC(FSI0, (2), "FSI0"),
+	FUNC(FSI1, (2), "FSI1"),
+	FUNC(FSI2, (2), "FSI2"),
+	FUNC(FSI3, (2), "FSI3"),
+	FUNC(FWQSPI, (1), "FWQSPI"),
+	FUNC(FWSPIABR, (1), "FWSPIABR"),
+	FUNC(FWWPN, (1), "FWWPN"),
+	FUNC(HBLED, (2), "HBLED"),
+	FUNC(I2C0, (1, 2, 4), "I2C0", "LTPI_PS_I2C0", "DI2C0"),
+	FUNC(I2C1, (1, 2, 4), "I2C1", "LTPI_PS_I2C1", "DI2C1"),
+	FUNC(I2C10, (1, 2), "I2C10", "DI2C10"),
+	FUNC(I2C11, (1, 2), "I2C11", "DI2C11"),
+	FUNC(I2C12, (4, 2), "I2C12", "DI2C12"),
+	FUNC(I2C13, (4, 2), "I2C13", "DI2C13"),
+	FUNC(I2C14, (4, 2), "I2C14", "DI2C14"),
+	FUNC(I2C15, (2, 2), "I2C15", "DI2C15"),
+	FUNC(I2C2, (1, 2, 4), "I2C2", "LTPI_PS_I2C2", "DI2C2"),
+	FUNC(I2C3, (1, 2, 4), "I2C3", "LTPI_PS_I2C3", "DI2C3"),
+	FUNC(I2C4, (1), "I2C4"),
+	FUNC(I2C5, (1), "I2C5"),
+	FUNC(I2C6, (1), "I2C6"),
+	FUNC(I2C7, (1), "I2C7"),
+	FUNC(I2C8, (1, 2), "I2C8", "DI2C8"),
+	FUNC(I2C9, (1, 2), "I2C9", "DI2C9"),
+	FUNC(I2CF0, (5), "I2CF0"),
+	FUNC(I2CF1, (5), "I2CF1"),
+	FUNC(I2CF2, (5), "I2CF2"),
+	FUNC(I3C0, (1), "HVI3C0"),
+	FUNC(I3C1, (1), "HVI3C1"),
+	FUNC(I3C10, (1), "I3C10"),
+	FUNC(I3C11, (1), "I3C11"),
+	FUNC(I3C12, (1), "HVI3C12"),
+	FUNC(I3C13, (1), "HVI3C13"),
+	FUNC(I3C14, (1), "HVI3C14"),
+	FUNC(I3C15, (1), "HVI3C15"),
+	FUNC(I3C2, (1), "HVI3C2"),
+	FUNC(I3C3, (1), "HVI3C3"),
+	FUNC(I3C4, (1), "I3C4"),
+	FUNC(I3C5, (1), "I3C5"),
+	FUNC(I3C6, (1), "I3C6"),
+	FUNC(I3C7, (1), "I3C7"),
+	FUNC(I3C8, (1), "I3C8"),
+	FUNC(I3C9, (1), "I3C9"),
+	FUNC(JTAGM1, (1), "JTAGM1"),
+	FUNC(LPC0, (2), "LPC0"),
+	FUNC(LPC1, (2), "LPC1"),
+	FUNC(LTPI, (2), "LTPI"),
+	FUNC(MACLINK0, (4), "MACLINK0"),
+	FUNC(MACLINK1, (3), "MACLINK1"),
+	FUNC(MACLINK2, (4), "MACLINK2"),
+	FUNC(MDIO0, (1), "MDIO0"),
+	FUNC(MDIO1, (1), "MDIO1"),
+	FUNC(MDIO2, (1), "MDIO2"),
+	FUNC(NCTS0, (1), "NCTS0"),
+	FUNC(NCTS1, (1), "NCTS1"),
+	FUNC(NCTS5, (4), "NCTS5"),
+	FUNC(NCTS6, (4), "NCTS6"),
+	FUNC(NDCD0, (1), "NDCD0"),
+	FUNC(NDCD1, (1), "NDCD1"),
+	FUNC(NDCD5, (4), "NDCD5"),
+	FUNC(NDCD6, (4), "NDCD6"),
+	FUNC(NDSR0, (1), "NDSR0"),
+	FUNC(NDSR1, (1), "NDSR1"),
+	FUNC(NDSR5, (4), "NDSR5"),
+	FUNC(NDSR6, (4), "NDSR6"),
+	FUNC(NDTR0, (1), "NDTR0"),
+	FUNC(NDTR1, (1), "NDTR1"),
+	FUNC(NDTR5, (4), "NDTR5"),
+	FUNC(NDTR6, (4), "NDTR6"),
+	FUNC(NRI0, (1), "NRI0"),
+	FUNC(NRI1, (1), "NRI1"),
+	FUNC(NRI5, (4), "NRI5"),
+	FUNC(NRI6, (4), "NRI6"),
+	FUNC(NRTS0, (1), "NRTS0"),
+	FUNC(NRTS1, (1), "NRTS1"),
+	FUNC(NRTS5, (4), "NRTS5"),
+	FUNC(NRTS6, (4), "NRTS6"),
+	FUNC(OSCCLK, (3), "OSCCLK"),
+	FUNC(PCIERC, (2), "PE2SGRSTN"),
+	FUNC(PWM0, (1), "PWM0"),
+	FUNC(PWM1, (1), "PWM1"),
+	FUNC(PWM10, (3), "PWM10"),
+	FUNC(PWM11, (3), "PWM11"),
+	FUNC(PWM12, (3), "PWM12"),
+	FUNC(PWM13, (3), "PWM13"),
+	FUNC(PWM14, (3), "PWM14"),
+	FUNC(PWM15, (3), "PWM15"),
+	FUNC(PWM2, (1), "PWM2"),
+	FUNC(PWM3, (1), "PWM3"),
+	FUNC(PWM4, (1), "PWM4"),
+	FUNC(PWM5, (1), "PWM5"),
+	FUNC(PWM6, (1), "PWM6"),
+	FUNC(PWM7, (1), "PWM7"),
+	FUNC(PWM8, (3), "PWM8"),
+	FUNC(PWM9, (3), "PWM9"),
+	FUNC(QSPI0, (1), "QSPI0"),
+	FUNC(QSPI1, (1), "QSPI1"),
+	FUNC(QSPI2, (1), "QSPI2"),
+	FUNC(RGMII0, (1), "RGMII0"),
+	FUNC(RGMII1, (1), "RGMII1"),
+	FUNC(RMII0, (2), "RMII0"),
+	FUNC(RMII0RCLKO, (2), "RMII0RCLKO"),
+	FUNC(RMII1, (2), "RMII1"),
+	FUNC(RMII1RCLKO, (2), "RMII1RCLKO"),
+	FUNC(SALT0, (2), "SALT0"),
+	FUNC(SALT1, (2), "SALT1"),
+	FUNC(SALT10, (2), "SALT10"),
+	FUNC(SALT11, (2), "SALT11"),
+	FUNC(SALT12, (2), "SALT12"),
+	FUNC(SALT13, (2), "SALT13"),
+	FUNC(SALT14, (2), "SALT14"),
+	FUNC(SALT15, (2), "SALT15"),
+	FUNC(SALT2, (2), "SALT2"),
+	FUNC(SALT3, (2), "SALT3"),
+	FUNC(SALT4, (2), "SALT4"),
+	FUNC(SALT5, (2), "SALT5"),
+	FUNC(SALT6, (2), "SALT6"),
+	FUNC(SALT7, (2), "SALT7"),
+	FUNC(SALT8, (2), "SALT8"),
+	FUNC(SALT9, (2), "SALT9"),
+	FUNC(SD, (3), "SD"),
+	FUNC(SGMII, (1), "SGMII"),
+	FUNC(SGPM0, (1, 4), "SGPM0", "DSGPM0"),
+	FUNC(SGPM1, (1), "SGPM1"),
+	FUNC(SGPS, (5), "SGPS"),
+	FUNC(SIOONCTRLN0, (2), "SIOONCTRLN0"),
+	FUNC(SIOONCTRLN1, (2), "SIOONCTRLN1"),
+	FUNC(SIOPBIN0, (2), "SIOPBIN0"),
+	FUNC(SIOPBIN1, (2), "SIOPBIN1"),
+	FUNC(SIOPBON0, (2), "SIOPBON0"),
+	FUNC(SIOPBON1, (2), "SIOPBON1"),
+	FUNC(SIOPWREQN0, (2), "SIOPWREQN0"),
+	FUNC(SIOPWREQN1, (2), "SIOPWREQN1"),
+	FUNC(SIOPWRGD1, (2), "SIOPWRGD1"),
+	FUNC(SIOS3N0, (2), "SIOS3N0"),
+	FUNC(SIOS3N1, (2), "SIOS3N1"),
+	FUNC(SIOS5N0, (2), "SIOS5N0"),
+	FUNC(SIOS5N1, (2), "SIOS5N1"),
+	FUNC(SIOSCIN0, (2), "SIOSCIN0"),
+	FUNC(SIOSCIN1, (2), "SIOSCIN1"),
+	FUNC(SMON0, (2), "SMON0"),
+	FUNC(SMON1, (4), "SMON1"),
+	FUNC(SPI0, (1), "SPI0"),
+	FUNC(SPI0ABR, (1), "SPI0ABR"),
+	FUNC(SPI0CS1, (1), "SPI0CS1"),
+	FUNC(SPI0WPN, (1), "SPI0WPN"),
+	FUNC(SPI1, (1), "SPI1"),
+	FUNC(SPI1ABR, (1), "SPI1ABR"),
+	FUNC(SPI1CS1, (1), "SPI1CS1"),
+	FUNC(SPI1WPN, (1), "SPI1WPN"),
+	FUNC(SPI2, (1), "SPI2"),
+	FUNC(SPI2CS1, (1), "SPI2CS1"),
+	FUNC(TACH0, (1), "TACH0"),
+	FUNC(TACH1, (1), "TACH1"),
+	FUNC(TACH10, (1), "TACH10"),
+	FUNC(TACH11, (1), "TACH11"),
+	FUNC(TACH12, (1), "TACH12"),
+	FUNC(TACH13, (1), "TACH13"),
+	FUNC(TACH14, (1), "TACH14"),
+	FUNC(TACH15, (1), "TACH15"),
+	FUNC(TACH2, (1), "TACH2"),
+	FUNC(TACH3, (1), "TACH3"),
+	FUNC(TACH4, (1), "TACH4"),
+	FUNC(TACH5, (1), "TACH5"),
+	FUNC(TACH6, (1), "TACH6"),
+	FUNC(TACH7, (1), "TACH7"),
+	FUNC(TACH8, (1), "TACH8"),
+	FUNC(TACH9, (1), "TACH9"),
+	FUNC(THRU0, (2), "THRU0"),
+	FUNC(THRU1, (2), "THRU1"),
+	FUNC(THRU2, (4), "THRU2"),
+	FUNC(THRU3, (4), "THRU3"),
+	FUNC(UART0, (1), "UART0"),
+	FUNC(UART1, (1), "UART1"),
+	FUNC(UART10, (3), "UART10"),
+	FUNC(UART11, (3), "UART11"),
+	FUNC(UART2, (1), "UART2"),
+	FUNC(UART3, (1), "UART3"),
+	FUNC(UART5, (4), "UART5"),
+	FUNC(UART6, (4), "UART6"),
+	FUNC(UART7, (1), "UART7"),
+	FUNC(UART8, (3), "UART8"),
+	FUNC(UART9, (3), "UART9"),
+	FUNC(USB2C, (0, 1, 2, 3), "USB2CUD", "USB2CD", "USB2CH", "USB2CU"),
+	FUNC(USB2D, (1, 2), "USB2DD", "USB2DH"),
+	FUNC(USBUART, (2), "USBUART"),
+	FUNC(VGA, (1), "VGA"),
+	FUNC(VPI, (5), "VPI"),
+	FUNC(WDTRST0N, (2), "WDTRST0N"),
+	FUNC(WDTRST1N, (2), "WDTRST1N"),
+	FUNC(WDTRST2N, (2), "WDTRST2N"),
+	FUNC(WDTRST3N, (2), "WDTRST3N"),
+	FUNC(WDTRST4N, (2), "WDTRST4N"),
+	FUNC(WDTRST5N, (2), "WDTRST5N"),
+	FUNC(WDTRST6N, (2), "WDTRST6N"),
+	FUNC(WDTRST7N, (2), "WDTRST7N"),
+};
+
+static int aspeed_g7_soc1_pinctrl_probe(struct platform_device *pdev)
+{
+	struct aspeed_g7_soc1_pinctrl *pctl;
+	struct device *dev = &pdev->dev;
+	int i, ret;
+
+	pctl = devm_kzalloc(dev, sizeof(*pctl), GFP_KERNEL);
+	if (!pctl)
+		return -ENOMEM;
+
+	pctl->dev = dev;
+	pctl->regmap = syscon_node_to_regmap(dev->parent->of_node);
+	if (IS_ERR(pctl->regmap)) {
+		dev_err(dev, "Failed to get regmap from parent\n");
+		return PTR_ERR(pctl->regmap);
+	}
+
+	ret = devm_pinctrl_register_and_init(dev, &aspeed_g7_soc1_desc, pctl,
+					     &pctl->pctl);
+	if (ret) {
+		dev_err(dev, "Failed to register pinctrl\n");
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(aspeed_g7_soc1_groups); i++) {
+		const struct pingroup *grp = &aspeed_g7_soc1_groups[i];
+
+		ret = pinctrl_generic_add_group(pctl->pctl, grp->name,
+						(const unsigned int *)grp->pins,
+						grp->npins, pctl);
+		if (ret < 0) {
+			dev_err(dev, "Failed to add group %s\n", grp->name);
+			return ret;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(aspeed_g7_soc1_functions); i++) {
+		const struct aspeed_g7_soc1_function *func = &aspeed_g7_soc1_functions[i];
+
+		ret = pinmux_generic_add_function(pctl->pctl, func->pinfunction.name,
+						  func->pinfunction.groups,
+						  func->pinfunction.ngroups, (void *)func);
+		if (ret < 0) {
+			dev_err(dev, "Failed to add function %s\n", func->pinfunction.name);
+			return ret;
+		}
+	}
+
+	return pinctrl_enable(pctl->pctl);
+}
+
+static const struct of_device_id aspeed_g7_soc1_pinctrl_match[] = {
+	{ .compatible = "aspeed,ast2700-soc1-pinctrl" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, aspeed_g7_soc1_pinctrl_match);
+
+static struct platform_driver aspeed_g7_soc1_pinctrl_driver = {
+	.probe = aspeed_g7_soc1_pinctrl_probe,
+	.driver = {
+		.name = "aspeed-g7-soc1-pinctrl",
+		.of_match_table = aspeed_g7_soc1_pinctrl_match,
+		.suppress_bind_attrs = true,
+	},
+};
+
+static int __init aspeed_g7_soc1_pinctrl_init(void)
+{
+	return platform_driver_register(&aspeed_g7_soc1_pinctrl_driver);
+}
+arch_initcall(aspeed_g7_soc1_pinctrl_init);

-- 
2.34.1



^ permalink raw reply related

* [PATCH 09/37] drm/display: bridge-connector: extract drm_bridge_connector_init_hdmi_audio_cec()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

To support bridge hotplug we will need better control over the steps
involved in the crm_connector creation, especially in terms of error
management. This will be complicated by the length and amount of return
points in this function. In preparation, split the code to initialize HDMI
audio and CEC fields out to a separate function.

No functional changes. Just moving code around.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 102 ++++++++++++++-----------
 1 file changed, 57 insertions(+), 45 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index d8a033ed8f41..e6306e98a167 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -930,6 +930,60 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_
 	return 0;
 }
 
+static struct drm_bridge_connector *
+drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_connector)
+{
+	int ret;
+
+	if (bridge_connector->bridge_hdmi_audio ||
+	    bridge_connector->bridge_dp_audio) {
+		struct device *dev;
+		struct drm_bridge *bridge;
+
+		if (bridge_connector->bridge_hdmi_audio)
+			bridge = bridge_connector->bridge_hdmi_audio;
+		else
+			bridge = bridge_connector->bridge_dp_audio;
+
+		dev = bridge->hdmi_audio_dev;
+
+		ret = drm_connector_hdmi_audio_init(&bridge_connector->base, dev,
+						    &drm_bridge_connector_hdmi_audio_funcs,
+						    bridge->hdmi_audio_max_i2s_playback_channels,
+						    bridge->hdmi_audio_i2s_formats,
+						    bridge->hdmi_audio_spdif_playback,
+						    bridge->hdmi_audio_dai_port);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	if (bridge_connector->bridge_hdmi_cec &&
+	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
+		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
+
+		ret = drmm_connector_hdmi_cec_notifier_register(&bridge_connector->base,
+								NULL,
+								bridge->hdmi_cec_dev);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	if (bridge_connector->bridge_hdmi_cec &&
+	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
+		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
+
+		ret = drmm_connector_hdmi_cec_register(&bridge_connector->base,
+						       &drm_bridge_connector_hdmi_cec_funcs,
+						       bridge->hdmi_cec_adapter_name,
+						       bridge->hdmi_cec_available_las,
+						       bridge->hdmi_cec_dev);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	return bridge_connector;
+}
+
 /**
  * drm_bridge_connector_add_connector - add the drm_connector
  * @bridge_connector: drm_bridge_connector to add the drm_connector to
@@ -997,51 +1051,9 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 			return ERR_PTR(ret);
 	}
 
-	if (bridge_connector->bridge_hdmi_audio ||
-	    bridge_connector->bridge_dp_audio) {
-		struct device *dev;
-		struct drm_bridge *bridge;
-
-		if (bridge_connector->bridge_hdmi_audio)
-			bridge = bridge_connector->bridge_hdmi_audio;
-		else
-			bridge = bridge_connector->bridge_dp_audio;
-
-		dev = bridge->hdmi_audio_dev;
-
-		ret = drm_connector_hdmi_audio_init(connector, dev,
-						    &drm_bridge_connector_hdmi_audio_funcs,
-						    bridge->hdmi_audio_max_i2s_playback_channels,
-						    bridge->hdmi_audio_i2s_formats,
-						    bridge->hdmi_audio_spdif_playback,
-						    bridge->hdmi_audio_dai_port);
-		if (ret)
-			return ERR_PTR(ret);
-	}
-
-	if (bridge_connector->bridge_hdmi_cec &&
-	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
-		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
-
-		ret = drmm_connector_hdmi_cec_notifier_register(connector,
-								NULL,
-								bridge->hdmi_cec_dev);
-		if (ret)
-			return ERR_PTR(ret);
-	}
-
-	if (bridge_connector->bridge_hdmi_cec &&
-	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
-		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
-
-		ret = drmm_connector_hdmi_cec_register(connector,
-						       &drm_bridge_connector_hdmi_cec_funcs,
-						       bridge->hdmi_cec_adapter_name,
-						       bridge->hdmi_cec_available_las,
-						       bridge->hdmi_cec_dev);
-		if (ret)
-			return ERR_PTR(ret);
-	}
+	bridge_connector = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
+	if (IS_ERR(bridge_connector))
+		return bridge_connector;
 
 	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH 11/37] drm/display: bridge-connector: return int from drm_bridge_connector_add_connector()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

This function returns a struct drm_bridge_connector * because this was the
case in the origin of its code. In current code it does not make sense: it
can only return either the same pointer it received as input or a negative
ERR_PTR.

This forces users to an unnecessarily complex error management code.

Clean up error management by just returning an int (0 or negative error).

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 6066ca0c5624..982bc64d8361 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -991,11 +991,9 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
  * @bridge_connector->drm and @bridge_connector->encoder fields are
  * assigned at least)
  *
- * Returns a pointer to @bridge_connector on success, or a negative error
- * pointer otherwise.
+ * Returns 0 on success or a negative error otherwise.
  */
-static struct drm_bridge_connector *
-drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
+static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector)
 {
 	struct drm_connector *connector;
 	struct i2c_adapter *ddc = NULL;
@@ -1012,7 +1010,7 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 					       &supported_formats, &max_bpc, &ddc,
 					       &panel_bridge, &support_hdcp);
 	if (ret)
-		return ERR_PTR(ret);
+		return ret;
 
 	if (bridge_connector->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)
@@ -1041,18 +1039,18 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 					       supported_formats,
 					       max_bpc);
 		if (ret)
-			return ERR_PTR(ret);
+			return ret;
 	} else {
 		ret = drmm_connector_init(bridge_connector->drm, connector,
 					  &drm_bridge_connector_funcs,
 					  connector_type, ddc);
 		if (ret)
-			return ERR_PTR(ret);
+			return ret;
 	}
 
 	ret = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
 	if (ret)
-		return ERR_PTR(ret);
+		return ret;
 
 	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
 
@@ -1071,9 +1069,9 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 
 	ret = drm_connector_attach_encoder(connector, bridge_connector->encoder);
 	if (ret)
-		return ERR_PTR(ret);
+		return ret;
 
-	return bridge_connector;
+	return 0;
 }
 
 /**
@@ -1110,9 +1108,9 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	bridge_connector->drm = drm;
 	bridge_connector->encoder = encoder;
 
-	bridge_connector = drm_bridge_connector_add_connector(bridge_connector);
-	if (IS_ERR(bridge_connector))
-		return ERR_CAST(bridge_connector);
+	ret = drm_bridge_connector_add_connector(bridge_connector);
+	if (ret)
+		return ERR_PTR(ret);
 
 	return &bridge_connector->base;
 }

-- 
2.54.0



^ permalink raw reply related

* [PATCH 08/37] drm/display: bridge-connector: return int from drm_bridge_connector_get_bridges()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

This function returns a struct drm_bridge_connector * because this was the
case in the origin of its code. In current code it does not make sense
anymore: it can only return either the same pointer it received as input or
a negative ERR_PTR.

Simplify up error management by just returning an int (0 or negative
error).

No functional changes.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 61 +++++++++++++-------------
 1 file changed, 30 insertions(+), 31 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 97d9a4c6d166..d8a033ed8f41 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -772,14 +772,13 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
 }
 
-static struct drm_bridge_connector *
-drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
-				 int *connector_type,
-				 unsigned int *supported_formats,
-				 unsigned int *max_bpc,
-				 struct i2c_adapter **ddc,
-				 struct drm_bridge **panel_bridge,
-				 bool *support_hdcp)
+static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
+					    int *connector_type,
+					    unsigned int *supported_formats,
+					    unsigned int *max_bpc,
+					    struct i2c_adapter **ddc,
+					    struct drm_bridge **panel_bridge,
+					    bool *support_hdcp)
 {
 	struct drm_connector *connector = &bridge_connector->base;
 
@@ -820,27 +819,27 @@ drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
 		}
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
 			if (bridge_connector->bridge_hdmi)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 			if (!bridge->funcs->hdmi_write_avi_infoframe ||
 			    !bridge->funcs->hdmi_clear_avi_infoframe ||
 			    !bridge->funcs->hdmi_write_hdmi_infoframe ||
 			    !bridge->funcs->hdmi_clear_hdmi_infoframe)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO &&
 			    (!bridge->funcs->hdmi_write_audio_infoframe ||
 			     !bridge->funcs->hdmi_clear_audio_infoframe))
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			if (bridge->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME &&
 			    (!bridge->funcs->hdmi_write_hdr_drm_infoframe ||
 			     !bridge->funcs->hdmi_clear_hdr_drm_infoframe))
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME &&
 			    (!bridge->funcs->hdmi_write_spd_infoframe ||
 			     !bridge->funcs->hdmi_clear_spd_infoframe))
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			bridge_connector->bridge_hdmi = drm_bridge_get(bridge);
 
@@ -852,57 +851,57 @@ drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO) {
 			if (bridge_connector->bridge_hdmi_audio)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			if (bridge_connector->bridge_dp_audio)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			if (!bridge->hdmi_audio_max_i2s_playback_channels &&
 			    !bridge->hdmi_audio_spdif_playback)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			if (!bridge->funcs->hdmi_audio_prepare ||
 			    !bridge->funcs->hdmi_audio_shutdown)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			bridge_connector->bridge_hdmi_audio = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_DP_AUDIO) {
 			if (bridge_connector->bridge_dp_audio)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			if (bridge_connector->bridge_hdmi_audio)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			if (!bridge->hdmi_audio_max_i2s_playback_channels &&
 			    !bridge->hdmi_audio_spdif_playback)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			if (!bridge->funcs->dp_audio_prepare ||
 			    !bridge->funcs->dp_audio_shutdown)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 
 			bridge_connector->bridge_dp_audio = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
 			if (bridge_connector->bridge_hdmi_cec)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			bridge_connector->bridge_hdmi_cec = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
 			if (bridge_connector->bridge_hdmi_cec)
-				return ERR_PTR(-EBUSY);
+				return -EBUSY;
 
 			bridge_connector->bridge_hdmi_cec = drm_bridge_get(bridge);
 
 			if (!bridge->funcs->hdmi_cec_enable ||
 			    !bridge->funcs->hdmi_cec_log_addr ||
 			    !bridge->funcs->hdmi_cec_transmit)
-				return ERR_PTR(-EINVAL);
+				return -EINVAL;
 		}
 
 		if (drm_bridge_is_last(bridge))
@@ -926,9 +925,9 @@ drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
 	}
 
 	if (connector_type == DRM_MODE_CONNECTOR_Unknown)
-		return ERR_PTR(-EINVAL);
+		return -EINVAL;
 
-	return bridge_connector;
+	return 0;
 }
 
 /**
@@ -956,11 +955,11 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 
 	connector = &bridge_connector->base;
 
-	bridge_connector = drm_bridge_connector_get_bridges(bridge_connector, &connector_type,
-							    &supported_formats, &max_bpc, &ddc,
-							    &panel_bridge, &support_hdcp);
-	if (IS_ERR(bridge_connector))
-		return bridge_connector;
+	ret = drm_bridge_connector_get_bridges(bridge_connector, &connector_type,
+					       &supported_formats, &max_bpc, &ddc,
+					       &panel_bridge, &support_hdcp);
+	if (ret)
+		return ERR_PTR(ret);
 
 	if (bridge_connector->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)

-- 
2.54.0



^ permalink raw reply related

* [PATCH 10/37] drm/display: bridge-connector: return int from drm_bridge_connector_init_hdmi_audio_cec()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

This function returns a struct drm_bridge_connector * because this was the
case in the origin of its code. In current code it does not make sense
anymore: it can only return either the same pointer it received as input or
a negative ERR_PTR.

Simplify up error management by just returning an int (0 or negative
error).

No functional changes.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index e6306e98a167..6066ca0c5624 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -930,8 +930,7 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_
 	return 0;
 }
 
-static struct drm_bridge_connector *
-drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_connector)
+static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_connector)
 {
 	int ret;
 
@@ -954,7 +953,7 @@ drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_con
 						    bridge->hdmi_audio_spdif_playback,
 						    bridge->hdmi_audio_dai_port);
 		if (ret)
-			return ERR_PTR(ret);
+			return ret;
 	}
 
 	if (bridge_connector->bridge_hdmi_cec &&
@@ -965,7 +964,7 @@ drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_con
 								NULL,
 								bridge->hdmi_cec_dev);
 		if (ret)
-			return ERR_PTR(ret);
+			return ret;
 	}
 
 	if (bridge_connector->bridge_hdmi_cec &&
@@ -978,10 +977,10 @@ drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_con
 						       bridge->hdmi_cec_available_las,
 						       bridge->hdmi_cec_dev);
 		if (ret)
-			return ERR_PTR(ret);
+			return ret;
 	}
 
-	return bridge_connector;
+	return 0;
 }
 
 /**
@@ -1051,9 +1050,9 @@ drm_bridge_connector_add_connector(struct drm_bridge_connector *bridge_connector
 			return ERR_PTR(ret);
 	}
 
-	bridge_connector = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
-	if (IS_ERR(bridge_connector))
-		return bridge_connector;
+	ret = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
+	if (ret)
+		return ERR_PTR(ret);
 
 	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH 12/37] drm/display: bridge-connector: hoist error management to common code
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

In prepataion to add more error management code common to the HDMI and
non-HDMI branches, move error management to be common to both cases.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 982bc64d8361..defe29e339b0 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -1038,15 +1038,13 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 					       connector_type, ddc,
 					       supported_formats,
 					       max_bpc);
-		if (ret)
-			return ret;
 	} else {
 		ret = drmm_connector_init(bridge_connector->drm, connector,
 					  &drm_bridge_connector_funcs,
 					  connector_type, ddc);
-		if (ret)
-			return ret;
 	}
+	if (ret)
+		return ret;
 
 	ret = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
 	if (ret)

-- 
2.54.0



^ permalink raw reply related

* [PATCH 13/37] drm/display: bridge-connector: move drm_bridge_connector_put_bridges() definition eariler
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

drm_bridge_connector_put_bridges() will have to be called in other places
to support bridge hot-plugging.

In preparation for bridge hotplug support, move it near the top of the
file.

No functional changes. Just moving code around.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 28 +++++++++++++-------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index defe29e339b0..2e96af8efb55 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -140,6 +140,20 @@ struct drm_bridge_connector {
 #define to_drm_bridge_connector(x) \
 	container_of(x, struct drm_bridge_connector, base)
 
+static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
+{
+	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
+
+	drm_bridge_put(bridge_connector->bridge_edid);
+	drm_bridge_put(bridge_connector->bridge_hpd);
+	drm_bridge_put(bridge_connector->bridge_detect);
+	drm_bridge_put(bridge_connector->bridge_modes);
+	drm_bridge_put(bridge_connector->bridge_hdmi);
+	drm_bridge_put(bridge_connector->bridge_hdmi_audio);
+	drm_bridge_put(bridge_connector->bridge_dp_audio);
+	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+}
+
 /* -----------------------------------------------------------------------------
  * Bridge Connector Hot-Plug Handling
  */
@@ -758,20 +772,6 @@ static const struct drm_connector_hdmi_cec_funcs drm_bridge_connector_hdmi_cec_f
  * Bridge Connector Initialisation
  */
 
-static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
-{
-	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
-
-	drm_bridge_put(bridge_connector->bridge_edid);
-	drm_bridge_put(bridge_connector->bridge_hpd);
-	drm_bridge_put(bridge_connector->bridge_detect);
-	drm_bridge_put(bridge_connector->bridge_modes);
-	drm_bridge_put(bridge_connector->bridge_hdmi);
-	drm_bridge_put(bridge_connector->bridge_hdmi_audio);
-	drm_bridge_put(bridge_connector->bridge_dp_audio);
-	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
-}
-
 static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
 					    int *connector_type,
 					    unsigned int *supported_formats,

-- 
2.54.0



^ permalink raw reply related

* [PATCH 14/37] drm/display: bridge-connector: add non-drmm variant of drm_bridge_connector_put_bridges()
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

drm_bridge_connector_put_bridges() is currently meant as a drmm cleanup
action. In preparation for bridge hotplug add a non-drmm action, so it can
be called more simply in non-drmm context.

To do this split drm_bridge_connector_put_bridges() in two variants, both
doing the same actions and only differing in the name and arguments:

 * drm_bridge_connector_put_bridges() (same name as before) to be called
   directly by the drm_bridge_connector code with a drm_bridge_connector
   pointer as the only parameter
 * drmm_bridge_connector_put_bridges() (same name + drmm prefix) providing
   the same drmm calling convention as before

Update the only call site accordingly.

No functional changes.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 2e96af8efb55..1fd104f1647b 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -140,10 +140,8 @@ struct drm_bridge_connector {
 #define to_drm_bridge_connector(x) \
 	container_of(x, struct drm_bridge_connector, base)
 
-static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
+static void drm_bridge_connector_put_bridges(struct drm_bridge_connector *bridge_connector)
 {
-	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
-
 	drm_bridge_put(bridge_connector->bridge_edid);
 	drm_bridge_put(bridge_connector->bridge_hpd);
 	drm_bridge_put(bridge_connector->bridge_detect);
@@ -154,6 +152,13 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
 }
 
+static void drmm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
+{
+	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
+
+	drm_bridge_connector_put_bridges(bridge_connector);
+}
+
 /* -----------------------------------------------------------------------------
  * Bridge Connector Hot-Plug Handling
  */
@@ -1099,7 +1104,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (!bridge_connector)
 		return ERR_PTR(-ENOMEM);
 
-	ret = drmm_add_action(drm, drm_bridge_connector_put_bridges, bridge_connector);
+	ret = drmm_add_action(drm, drmm_bridge_connector_put_bridges, bridge_connector);
 	if (ret)
 		return ERR_PTR(ret);
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH 15/37] drm/display: bridge-connector: allocate the connector dynamically
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

Currently the drm_bridge_connector has an embedded drm_connector, so their
allocation lifetimes are tied to each other. This is insufficient to
support DRM bridge hotplugging, which requires the connector to be added
and removed dynamically at runtime multiple times based on hotplug/unplug
events while the drm_bridge_connector is persistent.

Moreover the drm_connector is exposed to user space and thus an ongoing
operation (e.g. an ioctl) might last for an arbitrarily long time even
after the hardware gets removed. This means a new connector might have to
be added when the previous one is still referenced by user space.

In preparation to handle hotplug, allocate the drm-connector dynamically,
to allow:

 * creating and destroying a connector multiple times during a single
   drm_bridge_connector lifetime
 * creating a new connector even though the previous one is still in use
   and thus still refcounted and not yet freed

This commit does not introduce the actions in the two bullets (it will
happen in a later commit), it only moves to dynamic APIs for connector
allocation and init.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 156 ++++++++++++++++++-------
 1 file changed, 114 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 1fd104f1647b..b4e2c8f1d32d 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ * Copyright (C) 2026 GE HealthCare
+ * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
  */
 
 #include <linux/export.h>
@@ -53,13 +55,24 @@
  */
 
 /**
- * struct drm_bridge_connector - A connector backed by a chain of bridges
+ * drm_bridge_connector_dynconn - wrapper struct for the dynamically
+ *                                created drm_connector
  */
-struct drm_bridge_connector {
+struct drm_bridge_connector_dynconn {
 	/**
-	 * @base: The base DRM connector
+	 * @bridge_connector: the owning bridge connector
 	 */
-	struct drm_connector base;
+	struct drm_bridge_connector *bridge_connector;
+	/**
+	 * @connector: the drm_connector exposed to user space
+	 */
+	struct drm_connector connector;
+};
+
+/**
+ * struct drm_bridge_connector - A connector backed by a chain of bridges
+ */
+struct drm_bridge_connector {
 	/**
 	 * @drm: The DRM device we belong to
 	 */
@@ -70,6 +83,10 @@ struct drm_bridge_connector {
 	 * The encoder at the start of the bridges chain.
 	 */
 	struct drm_encoder *encoder;
+	/**
+	 * @dynconn: The DRM connector added by the drm_bridge_connector
+	 */
+	struct drm_bridge_connector_dynconn *dynconn;
 	/**
 	 * @bridge_edid:
 	 *
@@ -137,8 +154,13 @@ struct drm_bridge_connector {
 	struct drm_connector_hdmi_funcs hdmi_funcs;
 };
 
-#define to_drm_bridge_connector(x) \
-	container_of(x, struct drm_bridge_connector, base)
+static struct drm_bridge_connector *to_drm_bridge_connector(const struct drm_connector *connector)
+{
+	const struct drm_bridge_connector_dynconn *dynconn =
+		container_of(connector, struct drm_bridge_connector_dynconn, connector);
+
+	return dynconn->bridge_connector;
+}
 
 static void drm_bridge_connector_put_bridges(struct drm_bridge_connector *bridge_connector)
 {
@@ -152,13 +174,6 @@ static void drm_bridge_connector_put_bridges(struct drm_bridge_connector *bridge
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
 }
 
-static void drmm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
-{
-	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)data;
-
-	drm_bridge_connector_put_bridges(bridge_connector);
-}
-
 /* -----------------------------------------------------------------------------
  * Bridge Connector Hot-Plug Handling
  */
@@ -179,7 +194,7 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
 static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *bridge_connector,
 					    enum drm_connector_status status)
 {
-	struct drm_connector *connector = &bridge_connector->base;
+	struct drm_connector *connector = &bridge_connector->dynconn->connector;
 	struct drm_device *dev = bridge_connector->drm;
 
 	mutex_lock(&dev->mode_config.mutex);
@@ -299,6 +314,16 @@ static void drm_bridge_connector_reset(struct drm_connector *connector)
 							 connector->state);
 }
 
+static void drm_bridge_connector_dynconn_destroy(struct drm_connector *connector)
+{
+	struct drm_bridge_connector_dynconn *dynconn =
+		container_of(connector, struct drm_bridge_connector_dynconn, connector);
+
+	drm_connector_cleanup(connector);
+	drm_bridge_connector_put_bridges(dynconn->bridge_connector);
+	kfree(dynconn);
+}
+
 static const struct drm_connector_funcs drm_bridge_connector_funcs = {
 	.reset = drm_bridge_connector_reset,
 	.detect = drm_bridge_connector_detect,
@@ -308,6 +333,7 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 	.debugfs_init = drm_bridge_connector_debugfs_init,
 	.oob_hotplug_event = drm_bridge_connector_oob_hotplug_event,
+	.destroy = drm_bridge_connector_dynconn_destroy,
 };
 
 /* -----------------------------------------------------------------------------
@@ -777,7 +803,17 @@ static const struct drm_connector_hdmi_cec_funcs drm_bridge_connector_hdmi_cec_f
  * Bridge Connector Initialisation
  */
 
-static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_connector,
+static void drm_bridge_connector_dynconn_release(struct drm_bridge_connector *bridge_connector)
+{
+	if (!bridge_connector->dynconn)
+		return;
+
+	drm_connector_unregister(&bridge_connector->dynconn->connector);
+	drm_connector_put(&bridge_connector->dynconn->connector);
+	bridge_connector->dynconn = NULL;
+}
+
+static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn *dynconn,
 					    int *connector_type,
 					    unsigned int *supported_formats,
 					    unsigned int *max_bpc,
@@ -785,7 +821,8 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_
 					    struct drm_bridge **panel_bridge,
 					    bool *support_hdcp)
 {
-	struct drm_connector *connector = &bridge_connector->base;
+	struct drm_bridge_connector *bridge_connector = dynconn->bridge_connector;
+	struct drm_connector *connector = &dynconn->connector;
 
 	/*
 	 * TODO: Handle doublescan_allowed and stereo_allowed.
@@ -937,6 +974,7 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector *bridge_
 
 static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_connector)
 {
+	struct drm_connector *connector = &bridge_connector->dynconn->connector;
 	int ret;
 
 	if (bridge_connector->bridge_hdmi_audio ||
@@ -951,7 +989,7 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
 
 		dev = bridge->hdmi_audio_dev;
 
-		ret = drm_connector_hdmi_audio_init(&bridge_connector->base, dev,
+		ret = drm_connector_hdmi_audio_init(connector, dev,
 						    &drm_bridge_connector_hdmi_audio_funcs,
 						    bridge->hdmi_audio_max_i2s_playback_channels,
 						    bridge->hdmi_audio_i2s_formats,
@@ -965,8 +1003,7 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
 	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
 		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
 
-		ret = drmm_connector_hdmi_cec_notifier_register(&bridge_connector->base,
-								NULL,
+		ret = drmm_connector_hdmi_cec_notifier_register(connector, NULL,
 								bridge->hdmi_cec_dev);
 		if (ret)
 			return ret;
@@ -976,7 +1013,7 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
 	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
 		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
 
-		ret = drmm_connector_hdmi_cec_register(&bridge_connector->base,
+		ret = drmm_connector_hdmi_cec_register(connector,
 						       &drm_bridge_connector_hdmi_cec_funcs,
 						       bridge->hdmi_cec_adapter_name,
 						       bridge->hdmi_cec_available_las,
@@ -1009,13 +1046,20 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 	int connector_type;
 	int ret;
 
-	connector = &bridge_connector->base;
+	struct drm_bridge_connector_dynconn *dynconn = kzalloc_obj(*dynconn);
+	if (!dynconn)
+		return -ENOMEM;
 
-	ret = drm_bridge_connector_get_bridges(bridge_connector, &connector_type,
+	dynconn->bridge_connector = bridge_connector;
+	connector = &dynconn->connector;
+
+	ret = drm_bridge_connector_get_bridges(dynconn, &connector_type,
 					       &supported_formats, &max_bpc, &ddc,
 					       &panel_bridge, &support_hdcp);
-	if (ret)
+	if (ret) {
+		kfree(dynconn);
 		return ret;
+	}
 
 	if (bridge_connector->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)
@@ -1035,25 +1079,34 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 			bridge_connector->hdmi_funcs.spd =
 				drm_bridge_connector_hdmi_spd_infoframe;
 
-		ret = drmm_connector_hdmi_init(bridge_connector->drm, connector,
-					       bridge_connector->bridge_hdmi->vendor,
-					       bridge_connector->bridge_hdmi->product,
-					       &drm_bridge_connector_funcs,
-					       &bridge_connector->hdmi_funcs,
-					       connector_type, ddc,
-					       supported_formats,
-					       max_bpc);
+		ret = drm_connector_hdmi_dynamic_init(bridge_connector->drm, connector,
+						      bridge_connector->bridge_hdmi->vendor,
+						      bridge_connector->bridge_hdmi->product,
+						      &drm_bridge_connector_funcs,
+						      &bridge_connector->hdmi_funcs,
+						      connector_type, ddc,
+						      supported_formats,
+						      max_bpc);
 	} else {
-		ret = drmm_connector_init(bridge_connector->drm, connector,
-					  &drm_bridge_connector_funcs,
-					  connector_type, ddc);
+		ret = drm_connector_dynamic_init(bridge_connector->drm, connector,
+						 &drm_bridge_connector_funcs,
+						 connector_type, ddc);
 	}
-	if (ret)
+	if (ret) {
+		drm_bridge_connector_put_bridges(dynconn->bridge_connector);
+		kfree(dynconn);
 		return ret;
+	}
+
+	/* From now on the connector is referenced and has to be put, not kfreed */
+
+	bridge_connector->dynconn = dynconn;
+
+	drm_bridge_connector_reset(connector);
 
 	ret = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
 	if (ret)
-		return ret;
+		goto err_put;
 
 	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
 
@@ -1072,9 +1125,25 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 
 	ret = drm_connector_attach_encoder(connector, bridge_connector->encoder);
 	if (ret)
-		return ret;
+		goto err_put;
+
+	ret = drm_connector_dynamic_register(connector);
+	if (ret)
+		goto err_put;
 
 	return 0;
+
+err_put:
+	drm_connector_put(connector);
+	bridge_connector->dynconn = NULL;
+	return ret;
+}
+
+static void drm_bridge_connector_fini(struct drm_device *dev, void *res)
+{
+	struct drm_bridge_connector *bridge_connector = (struct drm_bridge_connector *)res;
+
+	drm_bridge_connector_dynconn_release(bridge_connector);
 }
 
 /**
@@ -1104,10 +1173,6 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (!bridge_connector)
 		return ERR_PTR(-ENOMEM);
 
-	ret = drmm_add_action(drm, drmm_bridge_connector_put_bridges, bridge_connector);
-	if (ret)
-		return ERR_PTR(ret);
-
 	bridge_connector->drm = drm;
 	bridge_connector->encoder = encoder;
 
@@ -1115,6 +1180,13 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (ret)
 		return ERR_PTR(ret);
 
-	return &bridge_connector->base;
+	ret = drmm_add_action_or_reset(drm, drm_bridge_connector_fini, bridge_connector);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (!bridge_connector->dynconn)
+		return ERR_PTR(-EINVAL);
+
+	return &bridge_connector->dynconn->connector;
 }
 EXPORT_SYMBOL_GPL(drm_bridge_connector_init);

-- 
2.54.0



^ permalink raw reply related

* [PATCH] i2c: imx-lpi2c: mark I2C adapter when hardware is powered down
From: Carlos Song (OSS) @ 2026-05-19 10:41 UTC (permalink / raw)
  To: aisheng.dong, andi.shyti, Frank.Li, s.hauer, kernel, festevam
  Cc: carlos.song, linux-i2c, imx, linux-arm-kernel, linux-kernel,
	stable

From: Carlos Song <carlos.song@nxp.com>

Mark the I2C adapter as suspended during system suspend to block further
transfers, and resume it on system resume. This prevents potential hangs
when the hardware is powered down but clients still attempt I2C transfers.

Fixes: 1ee867e465c1 ("i2c: imx-lpi2c: add target mode support")
Cc: stable@vger.kernel.org
Signed-off-by: Carlos Song <carlos.song@nxp.com>
---
 drivers/i2c/busses/i2c-imx-lpi2c.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c
index a01c23696481..bf8c1ce1c7ff 100644
--- a/drivers/i2c/busses/i2c-imx-lpi2c.c
+++ b/drivers/i2c/busses/i2c-imx-lpi2c.c
@@ -1635,7 +1635,16 @@ static int __maybe_unused lpi2c_runtime_resume(struct device *dev)
 
 static int __maybe_unused lpi2c_suspend_noirq(struct device *dev)
 {
-	return pm_runtime_force_suspend(dev);
+	struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev);
+	int ret;
+
+	ret = pm_runtime_force_suspend(dev);
+	if (ret)
+		return ret;
+
+	i2c_mark_adapter_suspended(&lpi2c_imx->adapter);
+
+	return 0;
 }
 
 static int __maybe_unused lpi2c_resume_noirq(struct device *dev)
@@ -1655,6 +1664,8 @@ static int __maybe_unused lpi2c_resume_noirq(struct device *dev)
 	if (lpi2c_imx->target)
 		lpi2c_imx_target_init(lpi2c_imx);
 
+	i2c_mark_adapter_resumed(&lpi2c_imx->adapter);
+
 	return 0;
 }
 
-- 
2.43.0



^ permalink raw reply related

* [PATCH 17/37] drm/display: bridge-connector: protect dynconn creation and destruction with a mutex
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

Bridge hotplug will make the connector dynamically created and destroyed
based on hotplug events.

In preparation for that, add a mutex to mutually exclude connector creation
and destruction.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index bd421b6beb7c..b83da529cc7a 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -8,6 +8,7 @@
 #include <linux/export.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/of.h>
 #include <linux/property.h>
 #include <linux/slab.h>
@@ -152,6 +153,11 @@ struct drm_bridge_connector {
 	 * @dynconn: The DRM connector added by the drm_bridge_connector
 	 */
 	struct drm_bridge_connector_dynconn *dynconn;
+	/**
+	 * @dynconn_mutex: Protect @dynconn from concurrent creation and
+	 * destruction
+	 */
+	struct mutex dynconn_mutex;
 };
 
 static struct drm_bridge_connector_dynconn *
@@ -810,6 +816,8 @@ static const struct drm_connector_hdmi_cec_funcs drm_bridge_connector_hdmi_cec_f
 
 static void drm_bridge_connector_dynconn_release(struct drm_bridge_connector *bridge_connector)
 {
+	guard(mutex)(&bridge_connector->dynconn_mutex);
+
 	if (!bridge_connector->dynconn)
 		return;
 
@@ -1050,6 +1058,8 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 	int connector_type;
 	int ret;
 
+	guard(mutex)(&bridge_connector->dynconn_mutex);
+
 	struct drm_bridge_connector_dynconn *dynconn = kzalloc_obj(*dynconn);
 	if (!dynconn)
 		return -ENOMEM;
@@ -1177,6 +1187,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (!bridge_connector)
 		return ERR_PTR(-ENOMEM);
 
+	mutex_init(&bridge_connector->dynconn_mutex);
 	bridge_connector->drm = drm;
 	bridge_connector->encoder = encoder;
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH 18/37] drm/bridge: samsung-dsim: remove the panel_bridge on host_detach
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

In preparation for DRM bridge hot-plugging, we need to handle the dynamic
lifetime of the following bridge in case the samsung-dsim is always present
and the following bridge (next_bridge) is hot-unplugged.

Based on the 'if (!IS_ERR(panel))' check in samsung_dsim_host_attach(), the
next_bridge could be A) a panel bridge created by this driver via
devm_drm_panel_bridge_add() or B) a pre-existing bridge obtained via
of_drm_find_and_get_bridge().

For case B) we need to put that reference when the next_bridge is removed,
which is already handled by calling drm_bridge_clear_and_put() in
samsung_dsim_host_detach() and in the samsung_dsim_host_attach() error
management code.

In case A) we additionally have to remove the panel bridge. Currently it is
created by devm_drm_panel_bridge_add(), which adds two devm actions with
the refcounted panel bridge:

 - drm_bridge_put_void() via devm_drm_bridge_alloc() on panel->dev
 - devm_drm_panel_bridge_release() via devm_drm_panel_bridge_add_typed()
   on the consumer device (samsung-dsim)

The first action is OK: being tied to panel->dev it will happen when the
panel is unplugged.

The second action is bound to the consumer device, so the devm semantics is
not useful here when introducing hotplug. Indeed we need to drop the
next_bridge in samsung_dsim_host_detach() anyway, before the driver .remove
function, in order to support {add, {attach, detach} x N, remove} hotplug
event sequences.

Thus move to the non-devm drm_panel_bridge_add() along with the matching
drm_panel_bridge_remove(), so the lifetime of the panel-bridge is tied to
the host_attach/host_detach cycle and not the whole samsung-dsim device
lifetime.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>

---

In a previous discussion with Maxime he mentioned a plan to make every
drm_panel always have a wrapping bridge. With that done, all the code
handling the panel and adding the panel_bridge would become useless here
(and in many other places) and could be entirely removed. This patch is a
temporary solution until that happens. The best pointer I could find to
that discussion is [0], but there might be more recent material I could not
find at the moment.

[0] https://lore.kernel.org/lkml/20250218-faithful-white-magpie-da9ac9@houat/
---
 drivers/gpu/drm/bridge/samsung-dsim.c | 17 ++++++++++++++---
 include/drm/bridge/samsung-dsim.h     |  2 ++
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c
index 5b799619e07e..2af287221e22 100644
--- a/drivers/gpu/drm/bridge/samsung-dsim.c
+++ b/drivers/gpu/drm/bridge/samsung-dsim.c
@@ -1951,14 +1951,16 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
 	if (!remote)
 		return -ENODEV;
 
+	dsi->panel_bridge_added = false;
 	panel = of_drm_find_panel(remote);
 	if (!IS_ERR(panel)) {
-		next_bridge = devm_drm_panel_bridge_add(dev, panel);
+		next_bridge = drm_panel_bridge_add(panel);
 		if (IS_ERR(next_bridge)) {
 			ret = PTR_ERR(next_bridge);
 			next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR
 		} else {
 			drm_bridge_get(next_bridge);
+			dsi->panel_bridge_added = true;
 		}
 	} else {
 		next_bridge = of_drm_find_and_get_bridge(remote);
@@ -1989,7 +1991,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
 	if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
 		ret = samsung_dsim_register_te_irq(dsi, &device->dev);
 		if (ret)
-			goto err_remove_bridge;
+			goto err_remove_panel_bridge;
 	}
 
 	// The next bridge can be used by host_ops->attach
@@ -2011,8 +2013,12 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
 	drm_bridge_clear_and_put(&dsi->bridge.next_bridge);
 	if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO))
 		samsung_dsim_unregister_te_irq(dsi);
-err_remove_bridge:
+err_remove_panel_bridge:
 	drm_bridge_remove(&dsi->bridge);
+	if (dsi->panel_bridge_added) {
+		drm_panel_bridge_remove(next_bridge);
+		dsi->panel_bridge_added = false;
+	}
 	return ret;
 }
 
@@ -2022,6 +2028,11 @@ static int samsung_dsim_host_detach(struct mipi_dsi_host *host,
 	struct samsung_dsim *dsi = host_to_dsi(host);
 	const struct samsung_dsim_plat_data *pdata = dsi->plat_data;
 
+	if (dsi->panel_bridge_added) {
+		drm_panel_bridge_remove(dsi->bridge.next_bridge);
+		dsi->panel_bridge_added = false;
+	}
+
 	if (pdata->host_ops && pdata->host_ops->detach)
 		pdata->host_ops->detach(dsi, device);
 
diff --git a/include/drm/bridge/samsung-dsim.h b/include/drm/bridge/samsung-dsim.h
index 03005e474704..3620f7aa9394 100644
--- a/include/drm/bridge/samsung-dsim.h
+++ b/include/drm/bridge/samsung-dsim.h
@@ -131,6 +131,8 @@ struct samsung_dsim {
 	const struct samsung_dsim_plat_data *plat_data;
 
 	void *priv;
+
+	bool panel_bridge_added; /* true iff next_bridge is a panel_bridge we created */
 };
 
 extern int samsung_dsim_probe(struct platform_device *pdev);

-- 
2.54.0



^ permalink raw reply related

* [PATCH 16/37] drm/display: bridge-connector: move per-connector fields to the dynamic connector
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

The various bridge pointers and the hdmi_funcs pointer are per-connector
specific. In other words, when introducing bridge hotplug and the
dynamically-created connector, they will have to be set per each created
connector, possibly with different values from any previously created
connector.

Those pointers are currently in struct drm_bridge_connector, which will
persist across multiple connector creations and destructions. This means
when a dynamic connector is created before the previous one is destroyed,
these fields would be overwritten by the new connector but the old one will
still reference them. This would lead to possible put of incorrect briges,
resulting in use-after-free and leaks.

In preparation for bridge hotplug, move all those fields from struct
drm_bridge_connector to struct drm_bridge_connector_dynconn, so they have
the same allocation and lifetime of the connector they refer to.

Update the many locations where these fields are used.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 370 +++++++++++++------------
 1 file changed, 187 insertions(+), 183 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index b4e2c8f1d32d..bd421b6beb7c 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -67,26 +67,6 @@ struct drm_bridge_connector_dynconn {
 	 * @connector: the drm_connector exposed to user space
 	 */
 	struct drm_connector connector;
-};
-
-/**
- * struct drm_bridge_connector - A connector backed by a chain of bridges
- */
-struct drm_bridge_connector {
-	/**
-	 * @drm: The DRM device we belong to
-	 */
-	struct drm_device *drm;
-	/**
-	 * @encoder:
-	 *
-	 * The encoder at the start of the bridges chain.
-	 */
-	struct drm_encoder *encoder;
-	/**
-	 * @dynconn: The DRM connector added by the drm_bridge_connector
-	 */
-	struct drm_bridge_connector_dynconn *dynconn;
 	/**
 	 * @bridge_edid:
 	 *
@@ -154,24 +134,47 @@ struct drm_bridge_connector {
 	struct drm_connector_hdmi_funcs hdmi_funcs;
 };
 
-static struct drm_bridge_connector *to_drm_bridge_connector(const struct drm_connector *connector)
+/**
+ * struct drm_bridge_connector - A connector backed by a chain of bridges
+ */
+struct drm_bridge_connector {
+	/**
+	 * @drm: The DRM device we belong to
+	 */
+	struct drm_device *drm;
+	/**
+	 * @encoder:
+	 *
+	 * The encoder at the start of the bridges chain.
+	 */
+	struct drm_encoder *encoder;
+	/**
+	 * @dynconn: The DRM connector added by the drm_bridge_connector
+	 */
+	struct drm_bridge_connector_dynconn *dynconn;
+};
+
+static struct drm_bridge_connector_dynconn *
+to_drm_bridge_connector_dynconn(const struct drm_connector *connector)
 {
-	const struct drm_bridge_connector_dynconn *dynconn =
-		container_of(connector, struct drm_bridge_connector_dynconn, connector);
+	return container_of(connector, struct drm_bridge_connector_dynconn, connector);
+}
 
-	return dynconn->bridge_connector;
+static struct drm_bridge_connector *to_drm_bridge_connector(const struct drm_connector *connector)
+{
+	return to_drm_bridge_connector_dynconn(connector)->bridge_connector;
 }
 
-static void drm_bridge_connector_put_bridges(struct drm_bridge_connector *bridge_connector)
+static void drm_bridge_connector_put_bridges(struct drm_bridge_connector_dynconn *dynconn)
 {
-	drm_bridge_put(bridge_connector->bridge_edid);
-	drm_bridge_put(bridge_connector->bridge_hpd);
-	drm_bridge_put(bridge_connector->bridge_detect);
-	drm_bridge_put(bridge_connector->bridge_modes);
-	drm_bridge_put(bridge_connector->bridge_hdmi);
-	drm_bridge_put(bridge_connector->bridge_hdmi_audio);
-	drm_bridge_put(bridge_connector->bridge_dp_audio);
-	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+	drm_bridge_put(dynconn->bridge_edid);
+	drm_bridge_put(dynconn->bridge_hpd);
+	drm_bridge_put(dynconn->bridge_detect);
+	drm_bridge_put(dynconn->bridge_modes);
+	drm_bridge_put(dynconn->bridge_hdmi);
+	drm_bridge_put(dynconn->bridge_hdmi_audio);
+	drm_bridge_put(dynconn->bridge_dp_audio);
+	drm_bridge_put(dynconn->bridge_hdmi_cec);
 }
 
 /* -----------------------------------------------------------------------------
@@ -225,7 +228,9 @@ static void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
 {
 	struct drm_bridge_connector *bridge_connector =
 		to_drm_bridge_connector(connector);
-	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
+	struct drm_bridge *hpd = dynconn->bridge_hpd;
 
 	if (hpd)
 		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
@@ -234,9 +239,9 @@ static void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
 
 static void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
-	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
+	struct drm_bridge *hpd = dynconn->bridge_hpd;
 
 	if (hpd)
 		drm_bridge_hpd_disable(hpd);
@@ -249,10 +254,10 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
 static enum drm_connector_status
 drm_bridge_connector_detect(struct drm_connector *connector, bool force)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
-	struct drm_bridge *detect = bridge_connector->bridge_detect;
-	struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
+	struct drm_bridge *detect = dynconn->bridge_detect;
+	struct drm_bridge *hdmi = dynconn->bridge_hdmi;
 	enum drm_connector_status status;
 
 	if (detect) {
@@ -281,9 +286,9 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force)
 
 static void drm_bridge_connector_force(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
-	struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
+	struct drm_bridge *hdmi = dynconn->bridge_hdmi;
 
 	if (hdmi)
 		drm_atomic_helper_connector_hdmi_force(connector);
@@ -305,11 +310,11 @@ static void drm_bridge_connector_debugfs_init(struct drm_connector *connector,
 
 static void drm_bridge_connector_reset(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 
 	drm_atomic_helper_connector_reset(connector);
-	if (bridge_connector->bridge_hdmi)
+	if (dynconn->bridge_hdmi)
 		__drm_atomic_helper_connector_hdmi_reset(connector,
 							 connector->state);
 }
@@ -320,7 +325,7 @@ static void drm_bridge_connector_dynconn_destroy(struct drm_connector *connector
 		container_of(connector, struct drm_bridge_connector_dynconn, connector);
 
 	drm_connector_cleanup(connector);
-	drm_bridge_connector_put_bridges(dynconn->bridge_connector);
+	drm_bridge_connector_put_bridges(dynconn);
 	kfree(dynconn);
 }
 
@@ -370,15 +375,15 @@ static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
 
 static int drm_bridge_connector_get_modes(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
 	/*
 	 * If there is a HDMI bridge, EDID has been updated as a part of
 	 * the .detect(). Just update the modes here.
 	 */
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (bridge)
 		return drm_edid_connector_add_modes(connector);
 
@@ -386,7 +391,7 @@ static int drm_bridge_connector_get_modes(struct drm_connector *connector)
 	 * If display exposes EDID, then we parse that in the normal way to
 	 * build table of supported modes.
 	 */
-	bridge = bridge_connector->bridge_edid;
+	bridge = dynconn->bridge_edid;
 	if (bridge)
 		return drm_bridge_connector_get_modes_edid(connector, bridge);
 
@@ -394,7 +399,7 @@ static int drm_bridge_connector_get_modes(struct drm_connector *connector)
 	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
 	 * resolution panel or an analog TV output), query it.
 	 */
-	bridge = bridge_connector->bridge_modes;
+	bridge = dynconn->bridge_modes;
 	if (bridge)
 		return bridge->funcs->get_modes(bridge, connector);
 
@@ -410,10 +415,10 @@ static enum drm_mode_status
 drm_bridge_connector_mode_valid(struct drm_connector *connector,
 				const struct drm_display_mode *mode)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 
-	if (bridge_connector->bridge_hdmi)
+	if (dynconn->bridge_hdmi)
 		return drm_hdmi_connector_mode_valid(connector, mode);
 
 	return MODE_OK;
@@ -422,10 +427,10 @@ drm_bridge_connector_mode_valid(struct drm_connector *connector,
 static int drm_bridge_connector_atomic_check(struct drm_connector *connector,
 					     struct drm_atomic_commit *state)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 
-	if (bridge_connector->bridge_hdmi)
+	if (dynconn->bridge_hdmi)
 		return drm_atomic_helper_connector_hdmi_check(connector, state);
 
 	return 0;
@@ -444,11 +449,11 @@ drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector,
 					  const struct drm_display_mode *mode,
 					  unsigned long long tmds_rate)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return MODE_ERROR;
 
@@ -460,11 +465,11 @@ drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector,
 
 static int drm_bridge_connector_clear_avi_infoframe(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -474,11 +479,11 @@ static int drm_bridge_connector_clear_avi_infoframe(struct drm_connector *connec
 static int drm_bridge_connector_write_avi_infoframe(struct drm_connector *connector,
 						    const u8 *buffer, size_t len)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -487,11 +492,11 @@ static int drm_bridge_connector_write_avi_infoframe(struct drm_connector *connec
 
 static int drm_bridge_connector_clear_hdmi_infoframe(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -501,11 +506,11 @@ static int drm_bridge_connector_clear_hdmi_infoframe(struct drm_connector *conne
 static int drm_bridge_connector_write_hdmi_infoframe(struct drm_connector *connector,
 						     const u8 *buffer, size_t len)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -514,11 +519,11 @@ static int drm_bridge_connector_write_hdmi_infoframe(struct drm_connector *conne
 
 static int drm_bridge_connector_clear_audio_infoframe(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -528,11 +533,11 @@ static int drm_bridge_connector_clear_audio_infoframe(struct drm_connector *conn
 static int drm_bridge_connector_write_audio_infoframe(struct drm_connector *connector,
 						      const u8 *buffer, size_t len)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -541,11 +546,11 @@ static int drm_bridge_connector_write_audio_infoframe(struct drm_connector *conn
 
 static int drm_bridge_connector_clear_hdr_drm_infoframe(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -555,11 +560,11 @@ static int drm_bridge_connector_clear_hdr_drm_infoframe(struct drm_connector *co
 static int drm_bridge_connector_write_hdr_drm_infoframe(struct drm_connector *connector,
 							const u8 *buffer, size_t len)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -568,11 +573,11 @@ static int drm_bridge_connector_write_hdr_drm_infoframe(struct drm_connector *co
 
 static int drm_bridge_connector_clear_spd_infoframe(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -582,11 +587,11 @@ static int drm_bridge_connector_clear_spd_infoframe(struct drm_connector *connec
 static int drm_bridge_connector_write_spd_infoframe(struct drm_connector *connector,
 						    const u8 *buffer, size_t len)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi;
+	bridge = dynconn->bridge_hdmi;
 	if (!bridge)
 		return -EINVAL;
 
@@ -596,11 +601,11 @@ static int drm_bridge_connector_write_spd_infoframe(struct drm_connector *connec
 static const struct drm_edid *
 drm_bridge_connector_read_edid(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_edid;
+	bridge = dynconn->bridge_edid;
 	if (!bridge)
 		return NULL;
 
@@ -638,12 +643,12 @@ static const struct drm_connector_infoframe_funcs drm_bridge_connector_hdmi_spd_
 
 static int drm_bridge_connector_audio_startup(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	if (bridge_connector->bridge_hdmi_audio) {
-		bridge = bridge_connector->bridge_hdmi_audio;
+	if (dynconn->bridge_hdmi_audio) {
+		bridge = dynconn->bridge_hdmi_audio;
 
 		if (!bridge->funcs->hdmi_audio_startup)
 			return 0;
@@ -651,8 +656,8 @@ static int drm_bridge_connector_audio_startup(struct drm_connector *connector)
 		return bridge->funcs->hdmi_audio_startup(bridge, connector);
 	}
 
-	if (bridge_connector->bridge_dp_audio) {
-		bridge = bridge_connector->bridge_dp_audio;
+	if (dynconn->bridge_dp_audio) {
+		bridge = dynconn->bridge_dp_audio;
 
 		if (!bridge->funcs->dp_audio_startup)
 			return 0;
@@ -667,18 +672,18 @@ static int drm_bridge_connector_audio_prepare(struct drm_connector *connector,
 					      struct hdmi_codec_daifmt *fmt,
 					      struct hdmi_codec_params *hparms)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	if (bridge_connector->bridge_hdmi_audio) {
-		bridge = bridge_connector->bridge_hdmi_audio;
+	if (dynconn->bridge_hdmi_audio) {
+		bridge = dynconn->bridge_hdmi_audio;
 
 		return bridge->funcs->hdmi_audio_prepare(bridge, connector, fmt, hparms);
 	}
 
-	if (bridge_connector->bridge_dp_audio) {
-		bridge = bridge_connector->bridge_dp_audio;
+	if (dynconn->bridge_dp_audio) {
+		bridge = dynconn->bridge_dp_audio;
 
 		return bridge->funcs->dp_audio_prepare(bridge, connector, fmt, hparms);
 	}
@@ -688,17 +693,17 @@ static int drm_bridge_connector_audio_prepare(struct drm_connector *connector,
 
 static void drm_bridge_connector_audio_shutdown(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	if (bridge_connector->bridge_hdmi_audio) {
-		bridge = bridge_connector->bridge_hdmi_audio;
+	if (dynconn->bridge_hdmi_audio) {
+		bridge = dynconn->bridge_hdmi_audio;
 		bridge->funcs->hdmi_audio_shutdown(bridge, connector);
 	}
 
-	if (bridge_connector->bridge_dp_audio) {
-		bridge = bridge_connector->bridge_dp_audio;
+	if (dynconn->bridge_dp_audio) {
+		bridge = dynconn->bridge_dp_audio;
 		bridge->funcs->dp_audio_shutdown(bridge, connector);
 	}
 }
@@ -706,12 +711,12 @@ static void drm_bridge_connector_audio_shutdown(struct drm_connector *connector)
 static int drm_bridge_connector_audio_mute_stream(struct drm_connector *connector,
 						  bool enable, int direction)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	if (bridge_connector->bridge_hdmi_audio) {
-		bridge = bridge_connector->bridge_hdmi_audio;
+	if (dynconn->bridge_hdmi_audio) {
+		bridge = dynconn->bridge_hdmi_audio;
 
 		if (!bridge->funcs->hdmi_audio_mute_stream)
 			return -ENOTSUPP;
@@ -720,8 +725,8 @@ static int drm_bridge_connector_audio_mute_stream(struct drm_connector *connecto
 							     enable, direction);
 	}
 
-	if (bridge_connector->bridge_dp_audio) {
-		bridge = bridge_connector->bridge_dp_audio;
+	if (dynconn->bridge_dp_audio) {
+		bridge = dynconn->bridge_dp_audio;
 
 		if (!bridge->funcs->dp_audio_mute_stream)
 			return -ENOTSUPP;
@@ -742,22 +747,22 @@ static const struct drm_connector_hdmi_audio_funcs drm_bridge_connector_hdmi_aud
 
 static int drm_bridge_connector_hdmi_cec_enable(struct drm_connector *connector, bool enable)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi_cec;
+	bridge = dynconn->bridge_hdmi_cec;
 
 	return bridge->funcs->hdmi_cec_enable(bridge, enable);
 }
 
 static int drm_bridge_connector_hdmi_cec_log_addr(struct drm_connector *connector, u8 logical_addr)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi_cec;
+	bridge = dynconn->bridge_hdmi_cec;
 
 	return bridge->funcs->hdmi_cec_log_addr(bridge, logical_addr);
 }
@@ -767,11 +772,11 @@ static int drm_bridge_connector_hdmi_cec_transmit(struct drm_connector *connecto
 						  u32 signal_free_time,
 						  struct cec_msg *msg)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi_cec;
+	bridge = dynconn->bridge_hdmi_cec;
 
 	return bridge->funcs->hdmi_cec_transmit(bridge, attempts,
 						signal_free_time,
@@ -780,11 +785,11 @@ static int drm_bridge_connector_hdmi_cec_transmit(struct drm_connector *connecto
 
 static int drm_bridge_connector_hdmi_cec_init(struct drm_connector *connector)
 {
-	struct drm_bridge_connector *bridge_connector =
-		to_drm_bridge_connector(connector);
+	struct drm_bridge_connector_dynconn *dynconn =
+		to_drm_bridge_connector_dynconn(connector);
 	struct drm_bridge *bridge;
 
-	bridge = bridge_connector->bridge_hdmi_cec;
+	bridge = dynconn->bridge_hdmi_cec;
 
 	if (!bridge->funcs->hdmi_cec_init)
 		return 0;
@@ -821,7 +826,6 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 					    struct drm_bridge **panel_bridge,
 					    bool *support_hdcp)
 {
-	struct drm_bridge_connector *bridge_connector = dynconn->bridge_connector;
 	struct drm_connector *connector = &dynconn->connector;
 
 	/*
@@ -831,7 +835,7 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 	connector->ycbcr_420_allowed = true;
 	*connector_type = DRM_MODE_CONNECTOR_Unknown;
 
-	drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) {
+	drm_for_each_bridge_in_chain_scoped(dynconn->bridge_connector->encoder, bridge) {
 		if (!bridge->interlace_allowed)
 			connector->interlace_allowed = false;
 		if (!bridge->ycbcr_420_allowed)
@@ -841,26 +845,26 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 		 * Ensure the last bridge declares OP_EDID or OP_MODES or both.
 		 */
 		if (bridge->ops & DRM_BRIDGE_OP_EDID || bridge->ops & DRM_BRIDGE_OP_MODES) {
-			drm_bridge_put(bridge_connector->bridge_edid);
-			bridge_connector->bridge_edid = NULL;
-			drm_bridge_put(bridge_connector->bridge_modes);
-			bridge_connector->bridge_modes = NULL;
+			drm_bridge_put(dynconn->bridge_edid);
+			dynconn->bridge_edid = NULL;
+			drm_bridge_put(dynconn->bridge_modes);
+			dynconn->bridge_modes = NULL;
 
 			if (bridge->ops & DRM_BRIDGE_OP_EDID)
-				bridge_connector->bridge_edid = drm_bridge_get(bridge);
+				dynconn->bridge_edid = drm_bridge_get(bridge);
 			if (bridge->ops & DRM_BRIDGE_OP_MODES)
-				bridge_connector->bridge_modes = drm_bridge_get(bridge);
+				dynconn->bridge_modes = drm_bridge_get(bridge);
 		}
 		if (bridge->ops & DRM_BRIDGE_OP_HPD) {
-			drm_bridge_put(bridge_connector->bridge_hpd);
-			bridge_connector->bridge_hpd = drm_bridge_get(bridge);
+			drm_bridge_put(dynconn->bridge_hpd);
+			dynconn->bridge_hpd = drm_bridge_get(bridge);
 		}
 		if (bridge->ops & DRM_BRIDGE_OP_DETECT) {
-			drm_bridge_put(bridge_connector->bridge_detect);
-			bridge_connector->bridge_detect = drm_bridge_get(bridge);
+			drm_bridge_put(dynconn->bridge_detect);
+			dynconn->bridge_detect = drm_bridge_get(bridge);
 		}
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
-			if (bridge_connector->bridge_hdmi)
+			if (dynconn->bridge_hdmi)
 				return -EBUSY;
 			if (!bridge->funcs->hdmi_write_avi_infoframe ||
 			    !bridge->funcs->hdmi_clear_avi_infoframe ||
@@ -883,7 +887,7 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 			     !bridge->funcs->hdmi_clear_spd_infoframe))
 				return -EINVAL;
 
-			bridge_connector->bridge_hdmi = drm_bridge_get(bridge);
+			dynconn->bridge_hdmi = drm_bridge_get(bridge);
 
 			if (bridge->supported_formats)
 				*supported_formats = bridge->supported_formats;
@@ -892,10 +896,10 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO) {
-			if (bridge_connector->bridge_hdmi_audio)
+			if (dynconn->bridge_hdmi_audio)
 				return -EBUSY;
 
-			if (bridge_connector->bridge_dp_audio)
+			if (dynconn->bridge_dp_audio)
 				return -EBUSY;
 
 			if (!bridge->hdmi_audio_max_i2s_playback_channels &&
@@ -906,14 +910,14 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 			    !bridge->funcs->hdmi_audio_shutdown)
 				return -EINVAL;
 
-			bridge_connector->bridge_hdmi_audio = drm_bridge_get(bridge);
+			dynconn->bridge_hdmi_audio = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_DP_AUDIO) {
-			if (bridge_connector->bridge_dp_audio)
+			if (dynconn->bridge_dp_audio)
 				return -EBUSY;
 
-			if (bridge_connector->bridge_hdmi_audio)
+			if (dynconn->bridge_hdmi_audio)
 				return -EBUSY;
 
 			if (!bridge->hdmi_audio_max_i2s_playback_channels &&
@@ -924,21 +928,21 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 			    !bridge->funcs->dp_audio_shutdown)
 				return -EINVAL;
 
-			bridge_connector->bridge_dp_audio = drm_bridge_get(bridge);
+			dynconn->bridge_dp_audio = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
-			if (bridge_connector->bridge_hdmi_cec)
+			if (dynconn->bridge_hdmi_cec)
 				return -EBUSY;
 
-			bridge_connector->bridge_hdmi_cec = drm_bridge_get(bridge);
+			dynconn->bridge_hdmi_cec = drm_bridge_get(bridge);
 		}
 
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
-			if (bridge_connector->bridge_hdmi_cec)
+			if (dynconn->bridge_hdmi_cec)
 				return -EBUSY;
 
-			bridge_connector->bridge_hdmi_cec = drm_bridge_get(bridge);
+			dynconn->bridge_hdmi_cec = drm_bridge_get(bridge);
 
 			if (!bridge->funcs->hdmi_cec_enable ||
 			    !bridge->funcs->hdmi_cec_log_addr ||
@@ -972,20 +976,20 @@ static int drm_bridge_connector_get_bridges(struct drm_bridge_connector_dynconn
 	return 0;
 }
 
-static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector *bridge_connector)
+static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector_dynconn *dynconn)
 {
-	struct drm_connector *connector = &bridge_connector->dynconn->connector;
+	struct drm_connector *connector = &dynconn->connector;
 	int ret;
 
-	if (bridge_connector->bridge_hdmi_audio ||
-	    bridge_connector->bridge_dp_audio) {
+	if (dynconn->bridge_hdmi_audio ||
+	    dynconn->bridge_dp_audio) {
 		struct device *dev;
 		struct drm_bridge *bridge;
 
-		if (bridge_connector->bridge_hdmi_audio)
-			bridge = bridge_connector->bridge_hdmi_audio;
+		if (dynconn->bridge_hdmi_audio)
+			bridge = dynconn->bridge_hdmi_audio;
 		else
-			bridge = bridge_connector->bridge_dp_audio;
+			bridge = dynconn->bridge_dp_audio;
 
 		dev = bridge->hdmi_audio_dev;
 
@@ -999,9 +1003,9 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
 			return ret;
 	}
 
-	if (bridge_connector->bridge_hdmi_cec &&
-	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
-		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
+	if (dynconn->bridge_hdmi_cec &&
+	    dynconn->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
+		struct drm_bridge *bridge = dynconn->bridge_hdmi_cec;
 
 		ret = drmm_connector_hdmi_cec_notifier_register(connector, NULL,
 								bridge->hdmi_cec_dev);
@@ -1009,9 +1013,9 @@ static int drm_bridge_connector_init_hdmi_audio_cec(struct drm_bridge_connector
 			return ret;
 	}
 
-	if (bridge_connector->bridge_hdmi_cec &&
-	    bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
-		struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
+	if (dynconn->bridge_hdmi_cec &&
+	    dynconn->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
+		struct drm_bridge *bridge = dynconn->bridge_hdmi_cec;
 
 		ret = drmm_connector_hdmi_cec_register(connector,
 						       &drm_bridge_connector_hdmi_cec_funcs,
@@ -1061,29 +1065,29 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 		return ret;
 	}
 
-	if (bridge_connector->bridge_hdmi) {
+	if (dynconn->bridge_hdmi) {
 		if (!connector->ycbcr_420_allowed)
 			supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
 
-		bridge_connector->hdmi_funcs = drm_bridge_connector_hdmi_funcs;
+		dynconn->hdmi_funcs = drm_bridge_connector_hdmi_funcs;
 
-		if (bridge_connector->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_AUDIO)
-			bridge_connector->hdmi_funcs.audio =
+		if (dynconn->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_AUDIO)
+			dynconn->hdmi_funcs.audio =
 				drm_bridge_connector_hdmi_audio_infoframe;
 
-		if (bridge_connector->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME)
-			bridge_connector->hdmi_funcs.hdr_drm =
+		if (dynconn->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME)
+			dynconn->hdmi_funcs.hdr_drm =
 				drm_bridge_connector_hdmi_hdr_drm_infoframe;
 
-		if (bridge_connector->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME)
-			bridge_connector->hdmi_funcs.spd =
+		if (dynconn->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME)
+			dynconn->hdmi_funcs.spd =
 				drm_bridge_connector_hdmi_spd_infoframe;
 
 		ret = drm_connector_hdmi_dynamic_init(bridge_connector->drm, connector,
-						      bridge_connector->bridge_hdmi->vendor,
-						      bridge_connector->bridge_hdmi->product,
+						      dynconn->bridge_hdmi->vendor,
+						      dynconn->bridge_hdmi->product,
 						      &drm_bridge_connector_funcs,
-						      &bridge_connector->hdmi_funcs,
+						      &dynconn->hdmi_funcs,
 						      connector_type, ddc,
 						      supported_formats,
 						      max_bpc);
@@ -1093,7 +1097,7 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 						 connector_type, ddc);
 	}
 	if (ret) {
-		drm_bridge_connector_put_bridges(dynconn->bridge_connector);
+		drm_bridge_connector_put_bridges(dynconn);
 		kfree(dynconn);
 		return ret;
 	}
@@ -1104,15 +1108,15 @@ static int drm_bridge_connector_add_connector(struct drm_bridge_connector *bridg
 
 	drm_bridge_connector_reset(connector);
 
-	ret = drm_bridge_connector_init_hdmi_audio_cec(bridge_connector);
+	ret = drm_bridge_connector_init_hdmi_audio_cec(dynconn);
 	if (ret)
 		goto err_put;
 
 	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
 
-	if (bridge_connector->bridge_hpd)
+	if (dynconn->bridge_hpd)
 		connector->polled = DRM_CONNECTOR_POLL_HPD;
-	else if (bridge_connector->bridge_detect)
+	else if (dynconn->bridge_detect)
 		connector->polled = DRM_CONNECTOR_POLL_CONNECT
 				  | DRM_CONNECTOR_POLL_DISCONNECT;
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH 20/37] drm/bridge: samsung-dsim: attach: return -EPROBE_DEFER is next bridge not yet available
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

When samsung_dsim_attach() is called but the next_bridge is not (yet)
known, it calls drm_bridge_attach() unconditionally. In turn
drm_bridge_attach() will return -EINVAL because the bridge pointer in
NULL. -EINVAL is propagated transparently to the caller, resulting in the
whole attach operation to fail.

This is fine for current use cases, but not when introducing for bridge
hotplug, which implies the next bridge could be hot-plugged later on.

In preparation to support bridge hotplug, consider the absence of a
next_bridge an -EPROBE_DEFER, not a hard error.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/bridge/samsung-dsim.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c
index cefb20ec68ad..3c70a45c5dce 100644
--- a/drivers/gpu/drm/bridge/samsung-dsim.c
+++ b/drivers/gpu/drm/bridge/samsung-dsim.c
@@ -1828,6 +1828,9 @@ static int samsung_dsim_attach(struct drm_bridge *bridge,
 {
 	struct samsung_dsim *dsi = bridge_to_dsi(bridge);
 
+	if (!dsi->bridge.next_bridge)
+		return -EPROBE_DEFER;
+
 	return drm_bridge_attach(encoder, dsi->bridge.next_bridge, bridge,
 				 flags);
 }

-- 
2.54.0



^ permalink raw reply related

* [PATCH 19/37] drm/bridge: samsung-dsim: move drm_bridge_add() call to probe
From: Luca Ceresoli @ 2026-05-19 10:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Inki Dae,
	Jagan Teki, Marek Szyprowski, Marek Vasut, Stefan Agner, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, linux-kernel, imx,
	linux-arm-kernel, Luca Ceresoli
In-Reply-To: <20260519-drm-bridge-hotplug-v1-0-45e2bdb3dfb4@bootlin.com>

This bridge driver calls drm_bridge_add() in the DSI host .attach callback
instead of in the probe function.

This works for current use cases but is problematic for supporting hotplug
of DRM bridges. The problematic case is when this DSI host is always
present while its DSI device is hot-pluggable. In such case with the
current code the DRM card will not be populated until after the DSI device
attaches to the host, which could happen a very long time after booting, or
even not happen at all.

The reason is that the previous pipeline component (the encoder in this
case) when probing cannot find the samsung-dsim bridge. What happens is:

 [1 and 2 can happen in any order, same result]
 1) samsung-dsim probes (does not drm_bridge_add() itself)
 2) The lcdif starts probing multiple times, but
    lcdif_probe
    -> lcdif_load
       -> lcdif_attach_bridge
          -> devm_drm_of_get_bridge() returns -EPROBE_DEFER because
             the samsung-dsim is not in the global bridge_list
             (deferred probe pending: imx-lcdif: Cannot connect bridge)

The samsung-dsim will not drm_bridge_add() itself until a DSI device will
try to mipi_dsi_attach() to the DSI Host, which can happen arbitratily late
on hot-pluggable hardware.

As a preliminary step to supporting hotplug move drm_bridge_add() at probe
time, so that the samsung-dsim DSI host bridge is available during boot,
even without a connected DSI device. This results in:

 1) samsung-dsim probes (and adds to drm_bridge_add() itself)
 2) The lcdif starts probing multiple times, but
    lcdif_probe
    -> lcdif_load
       -> lcdif_attach_bridge
          -> devm_drm_of_get_bridge() --> OK, returns samsung-dsim ptr

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/bridge/samsung-dsim.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c
index 2af287221e22..cefb20ec68ad 100644
--- a/drivers/gpu/drm/bridge/samsung-dsim.c
+++ b/drivers/gpu/drm/bridge/samsung-dsim.c
@@ -1980,8 +1980,6 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
 		     mipi_dsi_pixel_format_to_bpp(device->format),
 		     device->mode_flags);
 
-	drm_bridge_add(&dsi->bridge);
-
 	/*
 	 * This is a temporary solution and should be made by more generic way.
 	 *
@@ -2014,7 +2012,6 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
 	if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO))
 		samsung_dsim_unregister_te_irq(dsi);
 err_remove_panel_bridge:
-	drm_bridge_remove(&dsi->bridge);
 	if (dsi->panel_bridge_added) {
 		drm_panel_bridge_remove(next_bridge);
 		dsi->panel_bridge_added = false;
@@ -2040,8 +2037,6 @@ static int samsung_dsim_host_detach(struct mipi_dsi_host *host,
 
 	samsung_dsim_unregister_te_irq(dsi);
 
-	drm_bridge_remove(&dsi->bridge);
-
 	return 0;
 }
 
@@ -2242,6 +2237,8 @@ int samsung_dsim_probe(struct platform_device *pdev)
 			goto err_disable_runtime;
 	}
 
+	drm_bridge_add(&dsi->bridge);
+
 	return 0;
 
 err_disable_runtime:
@@ -2255,6 +2252,8 @@ void samsung_dsim_remove(struct platform_device *pdev)
 {
 	struct samsung_dsim *dsi = platform_get_drvdata(pdev);
 
+	drm_bridge_remove(&dsi->bridge);
+
 	pm_runtime_disable(&pdev->dev);
 
 	if (dsi->plat_data->host_ops && dsi->plat_data->host_ops->unregister_host)

-- 
2.54.0



^ 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