From f7504385eecaa7e119cf487f952e653767a3096c Mon Sep 17 00:00:00 2001
From: Liu Ying <victor.liu@nxp.com>
Date: Fri, 2 Jun 2017 17:46:12 +0800
Subject: [PATCH] MLK-15001-21 drm/imx: ldb: Add i.MX8qm LDB support

This patch adds i.MX8qm LDB support.
Logics are added to make i.MX8qm LDB cope with Mixel LVDS PHY.
Also, logics are added to handle pixel link padding quirks for i.MX8qm LDB.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
(cherry picked from commit d8e089c7a45ce66c34db8682435bf31ad7be148a)
---
 .../devicetree/bindings/display/imx/ldb.txt   |  31 +-
 drivers/gpu/drm/imx/Kconfig                   |   2 +-
 drivers/gpu/drm/imx/imx-ldb.c                 | 405 +++++++++++++++---
 3 files changed, 372 insertions(+), 66 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt
index 38c637fa39ddf4..2c3a5ac5425039 100644
--- a/Documentation/devicetree/bindings/display/imx/ldb.txt
+++ b/Documentation/devicetree/bindings/display/imx/ldb.txt
@@ -9,10 +9,14 @@ nodes describing each of the two LVDS encoder channels of the bridge.
 Required properties:
  - #address-cells : should be <1>
  - #size-cells : should be <0>
- - compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb".
-                Both LDB versions are similar, but i.MX6 has an additional
-                multiplexer in the front to select any of the four IPU display
-                interfaces as input for each LVDS channel.
+ - compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb" or
+                "fsl,imx8qm-ldb".
+                All LDB versions are similar.
+                i.MX6q/dl has an additional multiplexer in the front to select
+                any of the two or four IPU display interfaces as input for each
+                LVDS channel.
+                i.MX8qm LDB supports 10bit RGB input and needs an additional
+                phy.
  - gpr : should be <&gpr> on i.MX53 and i.MX6q.
          The phandle points to the iomuxc-gpr region containing the LVDS
          control register.
@@ -29,14 +33,18 @@ Required properties:
         On i.MX6q the following additional clocks are needed:
                 "di2_sel" - IPU2 DI0 mux
                 "di3_sel" - IPU2 DI1 mux
+        The following clocks are expected on i.MX8qm:
+                "pixel" - pixel clock
+                "bypass" - bypass clock
         The needed clock numbers for each are documented in
         Documentation/devicetree/bindings/clock/imx5-clock.txt, and in
         Documentation/devicetree/bindings/clock/imx6q-clock.txt.
+- power-domains : phandle pointing to power domain, only required by i.MX8qm.
 
 Optional properties:
- - pinctrl-names : should be "default" on i.MX53, not used on i.MX6q
+ - pinctrl-names : should be "default" on i.MX53, not used on i.MX6q and i.MX8qm
  - pinctrl-0 : a phandle pointing to LVDS pin settings on i.MX53,
-               not used on i.MX6q
+               not used on i.MX6q and i.MX8qm
  - fsl,dual-channel : boolean. if it exists, only LVDS channel 0 should
    be configured - one input will be distributed on both outputs in dual
    channel mode
@@ -57,9 +65,13 @@ Required properties:
    (lvds-channel@[0,1], respectively).
    On i.MX6, there should be four input ports (port@[0-3]) that correspond
    to the four LVDS multiplexer inputs.
-   A single output port (port@2 on i.MX5, port@4 on i.MX6) must be connected
-   to a panel input port. Optionally, the output port can be left out if
-   display-timings are used instead.
+   On i.MX8qm, the two channels of LDB connect to one display interface of DPU.
+   A single output port (port@2 on i.MX5, port@4 on i.MX6, port@1 on i.MX8qm)
+   must be connected to a panel input port or a bridge input port.
+   Optionally, the output port can be left out if display-timings are used
+   instead.
+ - phys: the phandle for the LVDS PHY device. Valid only on i.MX8qm.
+ - phy-names: should be "ldb_phy". Valid only on i.MX8qm.
 
 Optional properties (required if display-timings are used):
  - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
@@ -69,6 +81,7 @@ Optional properties (required if display-timings are used):
                       This describes how the color bits are laid out in the
                       serialized LVDS signal.
  - fsl,data-width : should be <18> or <24>
+                    Additionally, <30> for i.MX8qm.
 
 example:
 
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index e4c18ef08d347d..b94628b524e59d 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -28,7 +28,7 @@ config DRM_IMX_LDB
 	select DRM_PANEL
 	help
 	  Choose this to enable the internal LVDS Display Bridge (LDB)
-	  found on i.MX53 and i.MX6 processors.
+	  found on i.MX53, i.MX6 and i.MX8 processors.
 
 config DRM_IMX_IPUV3
 	tristate
diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index b3c4b5f2b8b919..7d183b6e651bf4 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -29,6 +29,8 @@
 #include <linux/of_graph.h>
 #include <video/of_display_timing.h>
 #include <video/of_videomode.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mixel-lvds.h>
 #include <linux/regmap.h>
 #include <linux/videodev2.h>
 
@@ -50,6 +52,12 @@
 #define LDB_DI0_VS_POL_ACT_LOW		(1 << 9)
 #define LDB_DI1_VS_POL_ACT_LOW		(1 << 10)
 #define LDB_BGREF_RMODE_INT		(1 << 15)
+#define LDB_CH0_10BIT_EN		(1 << 22)
+#define LDB_CH1_10BIT_EN		(1 << 23)
+#define LDB_CH0_DATA_WIDTH_24BIT	(1 << 24)
+#define LDB_CH1_DATA_WIDTH_24BIT	(1 << 26)
+#define LDB_CH0_DATA_WIDTH_30BIT	(2 << 24)
+#define LDB_CH1_DATA_WIDTH_30BIT	(2 << 26)
 
 struct imx_ldb;
 
@@ -62,6 +70,9 @@ struct imx_ldb_channel {
 	struct drm_panel *panel;
 	struct drm_bridge *bridge;
 
+	struct phy *phy;
+	bool phy_is_on;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -89,6 +100,21 @@ struct bus_mux {
 	int mask;
 };
 
+struct devtype {
+	int ctrl_reg;
+	struct bus_mux *bus_mux;
+	bool capable_10bit;
+	bool visible_phy;
+	bool has_mux;
+	bool is_imx8;
+	bool use_mixel_phy;
+	bool padding_quirks;
+
+	/* pixel rate in KHz */
+	unsigned int max_prate_single_mode;
+	unsigned int max_prate_dual_mode;
+};
+
 struct imx_ldb {
 	struct regmap *regmap;
 	struct device *dev;
@@ -97,8 +123,21 @@ struct imx_ldb {
 	struct clk *clk_sel[4]; /* parent of display clock */
 	struct clk *clk_parent[4]; /* original parent of clk_sel */
 	struct clk *clk_pll[2]; /* upstream clock we can adjust */
+	struct clk *clk_pixel;
+	struct clk *clk_bypass;
+	u32 ldb_ctrl_reg;
 	u32 ldb_ctrl;
 	const struct bus_mux *lvds_mux;
+	bool capable_10bit;
+	bool visible_phy;
+	bool has_mux;
+	bool is_imx8;
+	bool use_mixel_phy;
+	bool padding_quirks;
+
+	/* pixel rate in KHz */
+	unsigned int max_prate_single_mode;
+	unsigned int max_prate_dual_mode;
 };
 
 static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch,
@@ -112,16 +151,38 @@ static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch,
 		break;
 	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
 		if (imx_ldb_ch->chno == 0 || dual)
-			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
+			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
+					 LDB_CH0_DATA_WIDTH_24BIT;
 		if (imx_ldb_ch->chno == 1 || dual)
-			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
+			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
+					 LDB_CH1_DATA_WIDTH_24BIT;
 		break;
 	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
 		if (imx_ldb_ch->chno == 0 || dual)
 			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
+					 LDB_CH0_DATA_WIDTH_24BIT |
 					 LDB_BIT_MAP_CH0_JEIDA;
 		if (imx_ldb_ch->chno == 1 || dual)
 			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
+					 LDB_CH1_DATA_WIDTH_24BIT |
+					 LDB_BIT_MAP_CH1_JEIDA;
+		break;
+	case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+		if (imx_ldb_ch->chno == 0 || dual)
+			ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+					 LDB_CH0_DATA_WIDTH_30BIT;
+		if (imx_ldb_ch->chno == 1 || dual)
+			ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+					 LDB_CH1_DATA_WIDTH_30BIT;
+		break;
+	case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+		if (imx_ldb_ch->chno == 0 || dual)
+			ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+					 LDB_CH0_DATA_WIDTH_30BIT |
+					 LDB_BIT_MAP_CH0_JEIDA;
+		if (imx_ldb_ch->chno == 1 || dual)
+			ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+					 LDB_CH1_DATA_WIDTH_30BIT |
 					 LDB_BIT_MAP_CH1_JEIDA;
 		break;
 	}
@@ -176,6 +237,12 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno,
 {
 	int ret;
 
+	if (ldb->is_imx8) {
+		clk_set_rate(ldb->clk_bypass, di_clk);
+		clk_set_rate(ldb->clk_pixel, di_clk);
+		return;
+	}
+
 	dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__,
 			clk_get_rate(ldb->clk_pll[chno]), serial_clk);
 	clk_set_rate(ldb->clk_pll[chno], serial_clk);
@@ -208,14 +275,22 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
 
 	drm_panel_prepare(imx_ldb_ch->panel);
 
-	if (dual) {
-		clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]);
-		clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]);
+	if (ldb->is_imx8) {
+		clk_prepare_enable(ldb->clk_pixel);
+		clk_prepare_enable(ldb->clk_bypass);
+	}
 
-		clk_prepare_enable(ldb->clk[0]);
-		clk_prepare_enable(ldb->clk[1]);
-	} else {
-		clk_set_parent(ldb->clk_sel[mux], ldb->clk[imx_ldb_ch->chno]);
+	if (ldb->has_mux) {
+		if (dual) {
+			clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]);
+			clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]);
+
+			clk_prepare_enable(ldb->clk[0]);
+			clk_prepare_enable(ldb->clk[1]);
+		} else {
+			clk_set_parent(ldb->clk_sel[mux],
+				       ldb->clk[imx_ldb_ch->chno]);
+		}
 	}
 
 	if (imx_ldb_ch == &ldb->channel[0] || dual) {
@@ -245,7 +320,19 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
 				   mux << lvds_mux->shift);
 	}
 
-	regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
+	regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl);
+
+	if (dual) {
+		phy_power_on(ldb->channel[0].phy);
+		phy_power_on(ldb->channel[1].phy);
+
+		ldb->channel[0].phy_is_on = true;
+		ldb->channel[1].phy_is_on = true;
+	} else {
+		phy_power_on(imx_ldb_ch->phy);
+
+		imx_ldb_ch->phy_is_on = true;
+	}
 
 	drm_panel_enable(imx_ldb_ch->panel);
 }
@@ -264,23 +351,35 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
 	int mux = drm_of_encoder_active_port_id(imx_ldb_ch->child, encoder);
 	u32 bus_format = imx_ldb_ch->bus_format;
 
-	if (mode->clock > 170000) {
+	if (mode->clock > ldb->max_prate_dual_mode) {
 		dev_warn(ldb->dev,
-			 "%s: mode exceeds 170 MHz pixel clock\n", __func__);
+			 "%s: mode exceeds %u MHz pixel clock\n", __func__,
+			 ldb->max_prate_dual_mode / 1000);
 	}
-	if (mode->clock > 85000 && !dual) {
+	if (mode->clock > ldb->max_prate_single_mode && !dual) {
 		dev_warn(ldb->dev,
-			 "%s: mode exceeds 85 MHz pixel clock\n", __func__);
+			 "%s: mode exceeds %u MHz pixel clock\n", __func__,
+			 ldb->max_prate_single_mode / 1000);
 	}
 
 	if (dual) {
 		serial_clk = 3500UL * mode->clock;
 		imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk);
 		imx_ldb_set_clock(ldb, mux, 1, serial_clk, di_clk);
+
+		if (ldb->use_mixel_phy) {
+			mixel_phy_lvds_set_phy_speed(ldb->channel[0].phy,
+						     di_clk / 2);
+			mixel_phy_lvds_set_phy_speed(ldb->channel[1].phy,
+						     di_clk / 2);
+		}
 	} else {
 		serial_clk = 7000UL * mode->clock;
 		imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk,
 				  di_clk);
+
+		if (ldb->use_mixel_phy)
+			mixel_phy_lvds_set_phy_speed(imx_ldb_ch->phy, di_clk);
 	}
 
 	/* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
@@ -297,6 +396,52 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
 			ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW;
 	}
 
+	if (dual) {
+		if (ldb->use_mixel_phy) {
+			/* VSYNC */
+			if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
+				mixel_phy_lvds_set_vsync_pol(
+					ldb->channel[0].phy, false);
+				mixel_phy_lvds_set_vsync_pol(
+					ldb->channel[1].phy, false);
+			} else if (mode->flags & DRM_MODE_FLAG_PVSYNC) {
+				mixel_phy_lvds_set_vsync_pol(
+					ldb->channel[0].phy, true);
+				mixel_phy_lvds_set_vsync_pol(
+					ldb->channel[1].phy, true);
+			}
+			/* HSYNC */
+			if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
+				mixel_phy_lvds_set_hsync_pol(
+					ldb->channel[0].phy, false);
+				mixel_phy_lvds_set_hsync_pol(
+					ldb->channel[1].phy, false);
+			} else if (mode->flags & DRM_MODE_FLAG_PHSYNC) {
+				mixel_phy_lvds_set_hsync_pol(
+					ldb->channel[0].phy, true);
+				mixel_phy_lvds_set_hsync_pol(
+					ldb->channel[1].phy, true);
+			}
+		}
+	} else {
+		if (ldb->use_mixel_phy) {
+			/* VSYNC */
+			if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+				mixel_phy_lvds_set_vsync_pol(imx_ldb_ch->phy,
+								false);
+			else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+				mixel_phy_lvds_set_vsync_pol(imx_ldb_ch->phy,
+								true);
+			/* HSYNC */
+			if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+				mixel_phy_lvds_set_hsync_pol(imx_ldb_ch->phy,
+								false);
+			else if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+				mixel_phy_lvds_set_hsync_pol(imx_ldb_ch->phy,
+								true);
+		}
+	}
+
 	if (!bus_format) {
 		struct drm_connector *connector = connector_state->connector;
 		struct drm_display_info *di = &connector->display_info;
@@ -311,22 +456,43 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
 {
 	struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
 	struct imx_ldb *ldb = imx_ldb_ch->ldb;
+	int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN;
 	int mux, ret;
 
 	drm_panel_disable(imx_ldb_ch->panel);
 
+	if (dual) {
+		phy_power_off(ldb->channel[0].phy);
+		phy_power_off(ldb->channel[1].phy);
+
+		ldb->channel[0].phy_is_on = false;
+		ldb->channel[1].phy_is_on = false;
+	} else {
+		phy_power_off(imx_ldb_ch->phy);
+
+		imx_ldb_ch->phy_is_on = false;
+	}
+
 	if (imx_ldb_ch == &ldb->channel[0])
 		ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
 	else if (imx_ldb_ch == &ldb->channel[1])
 		ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
 
-	regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
+	regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl);
 
-	if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) {
-		clk_disable_unprepare(ldb->clk[0]);
-		clk_disable_unprepare(ldb->clk[1]);
+	if (ldb->is_imx8) {
+		clk_disable_unprepare(ldb->clk_bypass);
+		clk_disable_unprepare(ldb->clk_pixel);
+	} else {
+		if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) {
+			clk_disable_unprepare(ldb->clk[0]);
+			clk_disable_unprepare(ldb->clk[1]);
+		}
 	}
 
+	if (!ldb->has_mux)
+		goto unprepare_panel;
+
 	if (ldb->lvds_mux) {
 		const struct bus_mux *lvds_mux = NULL;
 
@@ -349,6 +515,7 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
 			"unable to set di%d parent clock to original parent\n",
 			mux);
 
+unprepare_panel:
 	drm_panel_unprepare(imx_ldb_ch->panel);
 }
 
@@ -358,6 +525,7 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder,
 {
 	struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
 	struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
+	struct imx_ldb *ldb = imx_ldb_ch->ldb;
 	struct drm_display_info *di = &conn_state->connector->display_info;
 	u32 bus_format = imx_ldb_ch->bus_format;
 
@@ -371,11 +539,23 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder,
 	}
 	switch (bus_format) {
 	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
-		imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+		if (ldb->padding_quirks)
+			imx_crtc_state->bus_format =
+					MEDIA_BUS_FMT_RGB666_1X30_PADLO;
+		else
+			imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
 		break;
 	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
 	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
-		imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+		if (ldb->padding_quirks)
+			imx_crtc_state->bus_format =
+					MEDIA_BUS_FMT_RGB888_1X30_PADLO;
+		else
+			imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+	case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+	case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+		imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
 		break;
 	default:
 		return -EINVAL;
@@ -416,6 +596,9 @@ static int imx_ldb_get_clk(struct imx_ldb *ldb, int chno)
 {
 	char clkname[16];
 
+	if (ldb->is_imx8)
+		return 0;
+
 	snprintf(clkname, sizeof(clkname), "di%d", chno);
 	ldb->clk[chno] = devm_clk_get(ldb->dev, clkname);
 	if (IS_ERR(ldb->clk[chno]))
@@ -496,12 +679,15 @@ struct imx_ldb_bit_mapping {
 };
 
 static const struct imx_ldb_bit_mapping imx_ldb_bit_mappings[] = {
-	{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,  18, "spwg" },
-	{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,  24, "spwg" },
-	{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
+	{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,     18, "spwg" },
+	{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,     24, "spwg" },
+	{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,    24, "jeida" },
+	{ MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG,  30, "spwg" },
+	{ MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA, 30, "jeida" },
 };
 
-static u32 of_get_bus_format(struct device *dev, struct device_node *np)
+static u32 of_get_bus_format(struct device *dev, struct imx_ldb *ldb,
+			     struct device_node *np)
 {
 	const char *bm;
 	u32 datawidth = 0;
@@ -513,6 +699,11 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
 
 	of_property_read_u32(np, "fsl,data-width", &datawidth);
 
+	if (!ldb->capable_10bit && datawidth == 30) {
+		dev_err(dev, "invalid data width: %d-bit\n", datawidth);
+		return -ENOENT;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) {
 		if (!strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) &&
 		    datawidth == imx_ldb_bit_mappings[i].datawidth)
@@ -524,6 +715,16 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
 	return -ENOENT;
 }
 
+static struct devtype imx53_ldb_devtype = {
+	.ctrl_reg = IOMUXC_GPR2,
+	.bus_mux = NULL,
+	.capable_10bit = false,
+	.visible_phy = false,
+	.has_mux = true,
+	.max_prate_single_mode = 85000,
+	.max_prate_dual_mode = 150000,
+};
+
 static struct bus_mux imx6q_lvds_mux[2] = {
 	{
 		.reg = IOMUXC_GPR3,
@@ -536,15 +737,39 @@ static struct bus_mux imx6q_lvds_mux[2] = {
 	}
 };
 
+static struct devtype imx6q_ldb_devtype = {
+	.ctrl_reg = IOMUXC_GPR2,
+	.bus_mux = imx6q_lvds_mux,
+	.capable_10bit = false,
+	.visible_phy = false,
+	.has_mux = true,
+	.max_prate_single_mode = 85000,
+	.max_prate_dual_mode = 170000,
+};
+
+static struct devtype imx8qm_ldb_devtype = {
+	.ctrl_reg = 0x10e0,
+	.bus_mux = NULL,
+	.capable_10bit = true,
+	.visible_phy = true,
+	.is_imx8 = true,
+	.use_mixel_phy = true,
+	.padding_quirks = true,
+	.max_prate_single_mode = 150000,
+	.max_prate_dual_mode = 300000,
+};
+
 /*
- * For a device declaring compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb",
- * of_match_device will walk through this list and take the first entry
- * matching any of its compatible values. Therefore, the more generic
- * entries (in this case fsl,imx53-ldb) need to be ordered last.
+ * For a device declaring compatible = "fsl,imx8qm-ldb", "fsl,imx6q-ldb",
+ * "fsl,imx53-ldb", of_match_device will walk through this list and take the
+ * first entry matching any of its compatible values.
+ * Therefore, the more generic entries (in this case fsl,imx53-ldb) need
+ * to be ordered last.
  */
 static const struct of_device_id imx_ldb_dt_ids[] = {
-	{ .compatible = "fsl,imx6q-ldb", .data = imx6q_lvds_mux, },
-	{ .compatible = "fsl,imx53-ldb", .data = NULL, },
+	{ .compatible = "fsl,imx8qm-ldb", .data = &imx8qm_ldb_devtype, },
+	{ .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_devtype, },
+	{ .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_devtype, },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
@@ -595,6 +820,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	struct device_node *np = dev->of_node;
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
+	const struct devtype *devtype = of_id->data;
 	struct device_node *child;
 	struct imx_ldb *imx_ldb;
 	int dual;
@@ -612,40 +838,64 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	}
 
 	imx_ldb->dev = dev;
-
-	if (of_id)
-		imx_ldb->lvds_mux = of_id->data;
+	imx_ldb->ldb_ctrl_reg = devtype->ctrl_reg;
+	imx_ldb->lvds_mux = devtype->bus_mux;
+	imx_ldb->capable_10bit = devtype->capable_10bit;
+	imx_ldb->visible_phy = devtype->visible_phy;
+	imx_ldb->has_mux = devtype->has_mux;
+	imx_ldb->is_imx8 = devtype->is_imx8;
+	imx_ldb->use_mixel_phy = devtype->use_mixel_phy;
+	imx_ldb->padding_quirks = devtype->padding_quirks;
+	imx_ldb->max_prate_single_mode = devtype->max_prate_single_mode;
+	imx_ldb->max_prate_dual_mode = devtype->max_prate_dual_mode;
 
 	dual = of_property_read_bool(np, "fsl,dual-channel");
 	if (dual)
 		imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
 
-	/*
-	 * There are three different possible clock mux configurations:
-	 * i.MX53:  ipu1_di0_sel, ipu1_di1_sel
-	 * i.MX6q:  ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel, ipu2_di1_sel
-	 * i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel
-	 * Map them all to di0_sel...di3_sel.
-	 */
-	for (i = 0; i < 4; i++) {
-		char clkname[16];
-
-		sprintf(clkname, "di%d_sel", i);
-		imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev, clkname);
-		if (IS_ERR(imx_ldb->clk_sel[i])) {
-			ret = PTR_ERR(imx_ldb->clk_sel[i]);
-			imx_ldb->clk_sel[i] = NULL;
-			break;
-		}
+	if (imx_ldb->is_imx8) {
+		imx_ldb->clk_pixel = devm_clk_get(imx_ldb->dev, "pixel");
+		if (IS_ERR(imx_ldb->clk_pixel))
+			return PTR_ERR(imx_ldb->clk_pixel);
 
-		imx_ldb->clk_parent[i] = clk_get_parent(imx_ldb->clk_sel[i]);
+		imx_ldb->clk_bypass = devm_clk_get(imx_ldb->dev, "bypass");
+		if (IS_ERR(imx_ldb->clk_bypass))
+			return PTR_ERR(imx_ldb->clk_bypass);
+	}
+
+	if (imx_ldb->has_mux) {
+		/*
+		 * There are three different possible clock mux configurations:
+		 * i.MX53:  ipu1_di0_sel, ipu1_di1_sel
+		 * i.MX6q:  ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel,
+		 *          ipu2_di1_sel
+		 * i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel
+		 * Map them all to di0_sel...di3_sel.
+		 */
+		for (i = 0; i < 4; i++) {
+			char clkname[16];
+
+			sprintf(clkname, "di%d_sel", i);
+			imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev,
+								clkname);
+			if (IS_ERR(imx_ldb->clk_sel[i])) {
+				ret = PTR_ERR(imx_ldb->clk_sel[i]);
+				imx_ldb->clk_sel[i] = NULL;
+				break;
+			}
+
+			imx_ldb->clk_parent[i] =
+					clk_get_parent(imx_ldb->clk_sel[i]);
+		}
+		if (i == 0)
+			return ret;
 	}
-	if (i == 0)
-		return ret;
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
 		int bus_format;
+		int port_reg;
+		bool auxiliary_ch = false;
 
 		ret = of_property_read_u32(child, "reg", &i);
 		if (ret || i < 0 || i > 1) {
@@ -653,6 +903,12 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 			goto free_child;
 		}
 
+		if (dual && imx_ldb->use_mixel_phy && i > 0) {
+			auxiliary_ch = true;
+			channel = &imx_ldb->channel[i];
+			goto get_phy;
+		}
+
 		if (!of_device_is_available(child))
 			continue;
 
@@ -667,10 +923,15 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 		/*
 		 * The output port is port@4 with an external 4-port mux or
-		 * port@2 with the internal 2-port mux.
+		 * port@2 with the internal 2-port mux or port@1 without mux.
 		 */
+		if (imx_ldb->has_mux)
+			port_reg = imx_ldb->lvds_mux ? 4 : 2;
+		else
+			port_reg = 1;
+
 		ret = drm_of_find_panel_or_bridge(child,
-						  imx_ldb->lvds_mux ? 4 : 2, 0,
+						  port_reg, 0,
 						  &channel->panel, &channel->bridge);
 		if (ret && ret != -ENODEV)
 			goto free_child;
@@ -682,7 +943,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 				goto free_child;
 		}
 
-		bus_format = of_get_bus_format(dev, child);
+		bus_format = of_get_bus_format(dev, imx_ldb, child);
 		if (bus_format == -EINVAL) {
 			/*
 			 * If no bus format was specified in the device tree,
@@ -701,6 +962,33 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 		channel->bus_format = bus_format;
 		channel->child = child;
 
+get_phy:
+		if (imx_ldb->visible_phy) {
+			channel->phy = devm_of_phy_get(dev, child, "ldb_phy");
+			if (IS_ERR(channel->phy)) {
+				ret = PTR_ERR(channel->phy);
+				if (ret == -EPROBE_DEFER) {
+					return ret;
+				} else {
+					dev_err(dev,
+						"can't get channel%d phy: %d\n",
+							channel->chno, ret);
+					return ret;
+				}
+			}
+
+			ret = phy_init(channel->phy);
+			if (ret < 0) {
+				dev_err(dev,
+					"failed to initialize channel%d phy: %d\n",
+					channel->chno, ret);
+				return ret;
+			}
+
+			if (auxiliary_ch)
+				return 0;
+		}
+
 		ret = imx_ldb_register(drm, channel);
 		if (ret) {
 			channel->child = NULL;
@@ -726,6 +1014,11 @@ static void imx_ldb_unbind(struct device *dev, struct device *master,
 	for (i = 0; i < 2; i++) {
 		struct imx_ldb_channel *channel = &imx_ldb->channel[i];
 
+		if (channel->phy_is_on)
+			phy_power_off(channel->phy);
+
+		phy_exit(channel->phy);
+
 		if (channel->panel)
 			drm_panel_detach(channel->panel);
 
-- 
GitLab