diff mbox series

[v6,7/7] drm/rockchip: dsi: add dual mipi support

Message ID 20181001123845.11818-8-heiko@sntech.de (mailing list archive)
State New, archived
Headers show
Series drm/rockchip: migrate to common dw-mipi-dsi bridge and dual-dsi | expand

Commit Message

Heiko Stübner Oct. 1, 2018, 12:38 p.m. UTC
Add the Rockchip-sepcific dual-dsi setup and hook it into the VOP as well.
As described in the general dual-dsi devicetree binding, the panel should
define two input ports and point each of them to one of the used dsi-
controllers, as well as declare one of them as clock-master.
This is used to determine the dual-dsi state and get access to both
controller instances.

v6:
  handle master+slave component in dsi-attach
v5:
  use driver-internal mechanism to find dual dsi slave
v4:
  add component directly in probe when adding empty dsi slave controller

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
---
 .../gpu/drm/rockchip/dw-mipi-dsi-rockchip.c   | 136 ++++++++++++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_drv.h   |   1 +
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c   |   3 +
 drivers/gpu/drm/rockchip/rockchip_drm_vop.h   |   4 +
 drivers/gpu/drm/rockchip/rockchip_vop_reg.c   |   1 +
 5 files changed, 145 insertions(+)

Comments

Andrzej Hajda Oct. 29, 2018, 2:15 p.m. UTC | #1
On 01.10.2018 14:38, Heiko Stuebner wrote:
> Add the Rockchip-sepcific dual-dsi setup and hook it into the VOP as well.
> As described in the general dual-dsi devicetree binding, the panel should
> define two input ports and point each of them to one of the used dsi-
> controllers, as well as declare one of them as clock-master.
> This is used to determine the dual-dsi state and get access to both
> controller instances.
>
> v6:
>   handle master+slave component in dsi-attach
> v5:
>   use driver-internal mechanism to find dual dsi slave
> v4:
>   add component directly in probe when adding empty dsi slave controller
>
> Signed-off-by: Heiko Stuebner <heiko@sntech.de>

This is not what I really like (putting dual display functionality into
poor dsi driver), but it at least works.
So:
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

The patchset is quite long on the list so if there will be no objections
I will merge it tomorrow.

 --
Regards
Andrzej

> ---
>  .../gpu/drm/rockchip/dw-mipi-dsi-rockchip.c   | 136 ++++++++++++++++++
>  drivers/gpu/drm/rockchip/rockchip_drm_drv.h   |   1 +
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.c   |   3 +
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.h   |   4 +
>  drivers/gpu/drm/rockchip/rockchip_vop_reg.c   |   1 +
>  5 files changed, 145 insertions(+)
>
> diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> index b3aae8439aa3..63b028602d08 100644
> --- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> @@ -218,6 +218,10 @@ struct dw_mipi_dsi_rockchip {
>  	struct clk *grf_clk;
>  	struct clk *phy_cfg_clk;
>  
> +	/* dual-channel */
> +	bool is_slave;
> +	struct dw_mipi_dsi_rockchip *slave;
> +
>  	unsigned int lane_mbps; /* per lane */
>  	u16 input_div;
>  	u16 feedback_div;
> @@ -226,6 +230,7 @@ struct dw_mipi_dsi_rockchip {
>  	struct dw_mipi_dsi *dmd;
>  	const struct rockchip_dw_dsi_chip_data *cdata;
>  	struct dw_mipi_dsi_plat_data pdata;
> +	int devcnt;
>  };
>  
>  struct dphy_pll_parameter_map {
> @@ -602,6 +607,8 @@ dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder,
>  	}
>  
>  	s->output_type = DRM_MODE_CONNECTOR_DSI;
> +	if (dsi->slave)
> +		s->output_flags = ROCKCHIP_OUTPUT_DSI_DUAL;
>  
>  	return 0;
>  }
> @@ -617,6 +624,8 @@ static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
>  		return;
>  
>  	pm_runtime_get_sync(dsi->dev);
> +	if (dsi->slave)
> +		pm_runtime_get_sync(dsi->slave->dev);
>  
>  	/*
>  	 * For the RK3399, the clk of grf must be enabled before writing grf
> @@ -630,6 +639,8 @@ static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
>  	}
>  
>  	dw_mipi_dsi_rockchip_config(dsi, mux);
> +	if (dsi->slave)
> +		dw_mipi_dsi_rockchip_config(dsi->slave, mux);
>  
>  	clk_disable_unprepare(dsi->grf_clk);
>  }
> @@ -638,6 +649,8 @@ static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
>  
> +	if (dsi->slave)
> +		pm_runtime_put(dsi->slave->dev);
>  	pm_runtime_put(dsi->dev);
>  }
>  
> @@ -673,14 +686,113 @@ static int rockchip_dsi_drm_create_encoder(struct dw_mipi_dsi_rockchip *dsi,
>  	return 0;
>  }
>  
> +static struct device
> +*dw_mipi_dsi_rockchip_find_second(struct dw_mipi_dsi_rockchip *dsi)
> +{
> +	const struct of_device_id *match;
> +	struct device_node *node = NULL, *local;
> +
> +	match = of_match_device(dsi->dev->driver->of_match_table, dsi->dev);
> +
> +	local = of_graph_get_remote_node(dsi->dev->of_node, 1, 0);
> +	if (!local)
> +		return NULL;
> +
> +	while ((node = of_find_compatible_node(node, NULL,
> +					       match->compatible))) {
> +		struct device_node *remote;
> +
> +		/* found ourself */
> +		if (node == dsi->dev->of_node)
> +			continue;
> +
> +		remote = of_graph_get_remote_node(node, 1, 0);
> +		if (!remote)
> +			continue;
> +
> +		/* same display device in port1-ep0 for both */
> +		if (remote == local) {
> +			struct dw_mipi_dsi_rockchip *dsi2;
> +			struct platform_device *pdev;
> +
> +			pdev = of_find_device_by_node(node);
> +
> +			/*
> +			 * we have found the second, so will either return it
> +			 * or return with an error. In any case won't need the
> +			 * nodes anymore nor continue the loop.
> +			 */
> +			of_node_put(remote);
> +			of_node_put(node);
> +			of_node_put(local);
> +
> +			if (!pdev)
> +				return ERR_PTR(-EPROBE_DEFER);
> +
> +			dsi2 = platform_get_drvdata(pdev);
> +			if (!dsi2) {
> +				platform_device_put(pdev);
> +				return ERR_PTR(-EPROBE_DEFER);
> +			}
> +
> +			return &pdev->dev;
> +		}
> +
> +		of_node_put(remote);
> +	}
> +
> +	of_node_put(local);
> +
> +	return NULL;
> +}
> +
>  static int dw_mipi_dsi_rockchip_bind(struct device *dev,
>  				     struct device *master,
>  				     void *data)
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
>  	struct drm_device *drm_dev = data;
> +	struct device *second;
> +	bool master1, master2;
>  	int ret;
>  
> +	second = dw_mipi_dsi_rockchip_find_second(dsi);
> +	if (IS_ERR(second))
> +		return PTR_ERR(second);
> +
> +	if (second) {
> +		master1 = of_property_read_bool(dsi->dev->of_node,
> +						"clock-master");
> +		master2 = of_property_read_bool(second->of_node,
> +						"clock-master");
> +
> +		if (master1 && master2) {
> +			DRM_DEV_ERROR(dsi->dev, "only one clock-master allowed\n");
> +			return -EINVAL;
> +		}
> +
> +		if (!master1 && !master2) {
> +			DRM_DEV_ERROR(dsi->dev, "no clock-master defined\n");
> +			return -EINVAL;
> +		}
> +
> +		/* we are the slave in dual-DSI */
> +		if (!master1) {
> +			dsi->is_slave = true;
> +			return 0;
> +		}
> +
> +		dsi->slave = dev_get_drvdata(second);
> +		if (!dsi->slave) {
> +			DRM_DEV_ERROR(dev, "could not get slaves data\n");
> +			return -ENODEV;
> +		}
> +
> +		dsi->slave->is_slave = true;
> +		dw_mipi_dsi_set_slave(dsi->dmd, dsi->slave->dmd);
> +		put_device(second);
> +	}
> +
>  	ret = clk_prepare_enable(dsi->pllref_clk);
>  	if (ret) {
>  		DRM_DEV_ERROR(dev, "Failed to enable pllref_clk: %d\n", ret);
> @@ -708,6 +819,9 @@ static void dw_mipi_dsi_rockchip_unbind(struct device *dev,
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
>  
> +	if (dsi->is_slave)
> +		return;
> +
>  	dw_mipi_dsi_unbind(dsi->dmd);
>  
>  	clk_disable_unprepare(dsi->pllref_clk);
> @@ -722,6 +836,7 @@ static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
>  					    struct mipi_dsi_device *device)
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = priv_data;
> +	struct device *second;
>  	int ret;
>  
>  	ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_ops);
> @@ -731,6 +846,19 @@ static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
>  		return ret;
>  	}
>  
> +	second = dw_mipi_dsi_rockchip_find_second(dsi);
> +	if (IS_ERR(second))
> +		return PTR_ERR(second);
> +	if (second) {
> +		ret = component_add(second, &dw_mipi_dsi_rockchip_ops);
> +		if (ret) {
> +			DRM_DEV_ERROR(second,
> +				      "Failed to register component: %d\n",
> +				      ret);
> +			return ret;
> +		}
> +	}
> +
>  	return 0;
>  }
>  
> @@ -738,6 +866,11 @@ static int dw_mipi_dsi_rockchip_host_detach(void *priv_data,
>  					    struct mipi_dsi_device *device)
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = priv_data;
> +	struct device *second;
> +
> +	second = dw_mipi_dsi_rockchip_find_second(dsi);
> +	if (second && !IS_ERR(second))
> +		component_del(second, &dw_mipi_dsi_rockchip_ops);
>  
>  	component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
>  
> @@ -846,6 +979,9 @@ static int dw_mipi_dsi_rockchip_remove(struct platform_device *pdev)
>  {
>  	struct dw_mipi_dsi_rockchip *dsi = platform_get_drvdata(pdev);
>  
> +	if (dsi->devcnt == 0)
> +		component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
> +
>  	dw_mipi_dsi_remove(dsi->dmd);
>  
>  	return 0;
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> index 453fef6a55b9..ce48568ec8a0 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> @@ -37,6 +37,7 @@ struct rockchip_crtc_state {
>  	int output_type;
>  	int output_mode;
>  	int output_bpc;
> +	int output_flags;
>  };
>  #define to_rockchip_crtc_state(s) \
>  		container_of(s, struct rockchip_crtc_state, base)
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> index 0c35a88e33dd..fb70fb486fbf 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -916,6 +916,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
>  	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) ?
>  		   BIT(VSYNC_POSITIVE) : 0;
>  	VOP_REG_SET(vop, output, pin_pol, pin_pol);
> +	VOP_REG_SET(vop, output, mipi_dual_channel_en, 0);
>  
>  	switch (s->output_type) {
>  	case DRM_MODE_CONNECTOR_LVDS:
> @@ -933,6 +934,8 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
>  	case DRM_MODE_CONNECTOR_DSI:
>  		VOP_REG_SET(vop, output, mipi_pin_pol, pin_pol);
>  		VOP_REG_SET(vop, output, mipi_en, 1);
> +		VOP_REG_SET(vop, output, mipi_dual_channel_en,
> +			    !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL));
>  		break;
>  	case DRM_MODE_CONNECTOR_DisplayPort:
>  		pin_pol &= ~BIT(DCLK_INVERT);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> index fd5765dfd637..0fe40e1983d9 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> @@ -60,6 +60,7 @@ struct vop_output {
>  	struct vop_reg edp_en;
>  	struct vop_reg hdmi_en;
>  	struct vop_reg mipi_en;
> +	struct vop_reg mipi_dual_channel_en;
>  	struct vop_reg rgb_en;
>  };
>  
> @@ -214,6 +215,9 @@ struct vop_data {
>  /* for use special outface */
>  #define ROCKCHIP_OUT_MODE_AAAA	15
>  
> +/* output flags */
> +#define ROCKCHIP_OUTPUT_DSI_DUAL	BIT(0)
> +
>  enum alpha_mode {
>  	ALPHA_STRAIGHT,
>  	ALPHA_INVERSE,
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> index 047a8867af90..08fc40af52c8 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> @@ -634,6 +634,7 @@ static const struct vop_output rk3399_output = {
>  	.hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
>  	.edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),
>  	.mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),
> +	.mipi_dual_channel_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 3),
>  };
>  
>  static const struct vop_data rk3399_vop_big = {
diff mbox series

Patch

diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
index b3aae8439aa3..63b028602d08 100644
--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
@@ -218,6 +218,10 @@  struct dw_mipi_dsi_rockchip {
 	struct clk *grf_clk;
 	struct clk *phy_cfg_clk;
 
+	/* dual-channel */
+	bool is_slave;
+	struct dw_mipi_dsi_rockchip *slave;
+
 	unsigned int lane_mbps; /* per lane */
 	u16 input_div;
 	u16 feedback_div;
@@ -226,6 +230,7 @@  struct dw_mipi_dsi_rockchip {
 	struct dw_mipi_dsi *dmd;
 	const struct rockchip_dw_dsi_chip_data *cdata;
 	struct dw_mipi_dsi_plat_data pdata;
+	int devcnt;
 };
 
 struct dphy_pll_parameter_map {
@@ -602,6 +607,8 @@  dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder,
 	}
 
 	s->output_type = DRM_MODE_CONNECTOR_DSI;
+	if (dsi->slave)
+		s->output_flags = ROCKCHIP_OUTPUT_DSI_DUAL;
 
 	return 0;
 }
@@ -617,6 +624,8 @@  static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
 		return;
 
 	pm_runtime_get_sync(dsi->dev);
+	if (dsi->slave)
+		pm_runtime_get_sync(dsi->slave->dev);
 
 	/*
 	 * For the RK3399, the clk of grf must be enabled before writing grf
@@ -630,6 +639,8 @@  static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
 	}
 
 	dw_mipi_dsi_rockchip_config(dsi, mux);
+	if (dsi->slave)
+		dw_mipi_dsi_rockchip_config(dsi->slave, mux);
 
 	clk_disable_unprepare(dsi->grf_clk);
 }
@@ -638,6 +649,8 @@  static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
 {
 	struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
 
+	if (dsi->slave)
+		pm_runtime_put(dsi->slave->dev);
 	pm_runtime_put(dsi->dev);
 }
 
@@ -673,14 +686,113 @@  static int rockchip_dsi_drm_create_encoder(struct dw_mipi_dsi_rockchip *dsi,
 	return 0;
 }
 
+static struct device
+*dw_mipi_dsi_rockchip_find_second(struct dw_mipi_dsi_rockchip *dsi)
+{
+	const struct of_device_id *match;
+	struct device_node *node = NULL, *local;
+
+	match = of_match_device(dsi->dev->driver->of_match_table, dsi->dev);
+
+	local = of_graph_get_remote_node(dsi->dev->of_node, 1, 0);
+	if (!local)
+		return NULL;
+
+	while ((node = of_find_compatible_node(node, NULL,
+					       match->compatible))) {
+		struct device_node *remote;
+
+		/* found ourself */
+		if (node == dsi->dev->of_node)
+			continue;
+
+		remote = of_graph_get_remote_node(node, 1, 0);
+		if (!remote)
+			continue;
+
+		/* same display device in port1-ep0 for both */
+		if (remote == local) {
+			struct dw_mipi_dsi_rockchip *dsi2;
+			struct platform_device *pdev;
+
+			pdev = of_find_device_by_node(node);
+
+			/*
+			 * we have found the second, so will either return it
+			 * or return with an error. In any case won't need the
+			 * nodes anymore nor continue the loop.
+			 */
+			of_node_put(remote);
+			of_node_put(node);
+			of_node_put(local);
+
+			if (!pdev)
+				return ERR_PTR(-EPROBE_DEFER);
+
+			dsi2 = platform_get_drvdata(pdev);
+			if (!dsi2) {
+				platform_device_put(pdev);
+				return ERR_PTR(-EPROBE_DEFER);
+			}
+
+			return &pdev->dev;
+		}
+
+		of_node_put(remote);
+	}
+
+	of_node_put(local);
+
+	return NULL;
+}
+
 static int dw_mipi_dsi_rockchip_bind(struct device *dev,
 				     struct device *master,
 				     void *data)
 {
 	struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
 	struct drm_device *drm_dev = data;
+	struct device *second;
+	bool master1, master2;
 	int ret;
 
+	second = dw_mipi_dsi_rockchip_find_second(dsi);
+	if (IS_ERR(second))
+		return PTR_ERR(second);
+
+	if (second) {
+		master1 = of_property_read_bool(dsi->dev->of_node,
+						"clock-master");
+		master2 = of_property_read_bool(second->of_node,
+						"clock-master");
+
+		if (master1 && master2) {
+			DRM_DEV_ERROR(dsi->dev, "only one clock-master allowed\n");
+			return -EINVAL;
+		}
+
+		if (!master1 && !master2) {
+			DRM_DEV_ERROR(dsi->dev, "no clock-master defined\n");
+			return -EINVAL;
+		}
+
+		/* we are the slave in dual-DSI */
+		if (!master1) {
+			dsi->is_slave = true;
+			return 0;
+		}
+
+		dsi->slave = dev_get_drvdata(second);
+		if (!dsi->slave) {
+			DRM_DEV_ERROR(dev, "could not get slaves data\n");
+			return -ENODEV;
+		}
+
+		dsi->slave->is_slave = true;
+		dw_mipi_dsi_set_slave(dsi->dmd, dsi->slave->dmd);
+		put_device(second);
+	}
+
 	ret = clk_prepare_enable(dsi->pllref_clk);
 	if (ret) {
 		DRM_DEV_ERROR(dev, "Failed to enable pllref_clk: %d\n", ret);
@@ -708,6 +819,9 @@  static void dw_mipi_dsi_rockchip_unbind(struct device *dev,
 {
 	struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
 
+	if (dsi->is_slave)
+		return;
+
 	dw_mipi_dsi_unbind(dsi->dmd);
 
 	clk_disable_unprepare(dsi->pllref_clk);
@@ -722,6 +836,7 @@  static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
 					    struct mipi_dsi_device *device)
 {
 	struct dw_mipi_dsi_rockchip *dsi = priv_data;
+	struct device *second;
 	int ret;
 
 	ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_ops);
@@ -731,6 +846,19 @@  static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
 		return ret;
 	}
 
+	second = dw_mipi_dsi_rockchip_find_second(dsi);
+	if (IS_ERR(second))
+		return PTR_ERR(second);
+	if (second) {
+		ret = component_add(second, &dw_mipi_dsi_rockchip_ops);
+		if (ret) {
+			DRM_DEV_ERROR(second,
+				      "Failed to register component: %d\n",
+				      ret);
+			return ret;
+		}
+	}
+
 	return 0;
 }
 
@@ -738,6 +866,11 @@  static int dw_mipi_dsi_rockchip_host_detach(void *priv_data,
 					    struct mipi_dsi_device *device)
 {
 	struct dw_mipi_dsi_rockchip *dsi = priv_data;
+	struct device *second;
+
+	second = dw_mipi_dsi_rockchip_find_second(dsi);
+	if (second && !IS_ERR(second))
+		component_del(second, &dw_mipi_dsi_rockchip_ops);
 
 	component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
 
@@ -846,6 +979,9 @@  static int dw_mipi_dsi_rockchip_remove(struct platform_device *pdev)
 {
 	struct dw_mipi_dsi_rockchip *dsi = platform_get_drvdata(pdev);
 
+	if (dsi->devcnt == 0)
+		component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
+
 	dw_mipi_dsi_remove(dsi->dmd);
 
 	return 0;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index 453fef6a55b9..ce48568ec8a0 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -37,6 +37,7 @@  struct rockchip_crtc_state {
 	int output_type;
 	int output_mode;
 	int output_bpc;
+	int output_flags;
 };
 #define to_rockchip_crtc_state(s) \
 		container_of(s, struct rockchip_crtc_state, base)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 0c35a88e33dd..fb70fb486fbf 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -916,6 +916,7 @@  static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
 	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) ?
 		   BIT(VSYNC_POSITIVE) : 0;
 	VOP_REG_SET(vop, output, pin_pol, pin_pol);
+	VOP_REG_SET(vop, output, mipi_dual_channel_en, 0);
 
 	switch (s->output_type) {
 	case DRM_MODE_CONNECTOR_LVDS:
@@ -933,6 +934,8 @@  static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
 	case DRM_MODE_CONNECTOR_DSI:
 		VOP_REG_SET(vop, output, mipi_pin_pol, pin_pol);
 		VOP_REG_SET(vop, output, mipi_en, 1);
+		VOP_REG_SET(vop, output, mipi_dual_channel_en,
+			    !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL));
 		break;
 	case DRM_MODE_CONNECTOR_DisplayPort:
 		pin_pol &= ~BIT(DCLK_INVERT);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index fd5765dfd637..0fe40e1983d9 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -60,6 +60,7 @@  struct vop_output {
 	struct vop_reg edp_en;
 	struct vop_reg hdmi_en;
 	struct vop_reg mipi_en;
+	struct vop_reg mipi_dual_channel_en;
 	struct vop_reg rgb_en;
 };
 
@@ -214,6 +215,9 @@  struct vop_data {
 /* for use special outface */
 #define ROCKCHIP_OUT_MODE_AAAA	15
 
+/* output flags */
+#define ROCKCHIP_OUTPUT_DSI_DUAL	BIT(0)
+
 enum alpha_mode {
 	ALPHA_STRAIGHT,
 	ALPHA_INVERSE,
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index 047a8867af90..08fc40af52c8 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -634,6 +634,7 @@  static const struct vop_output rk3399_output = {
 	.hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
 	.edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),
 	.mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),
+	.mipi_dual_channel_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 3),
 };
 
 static const struct vop_data rk3399_vop_big = {