diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt index 38c637fa39ddf4efaef296a3878859dfb598e82c..2c3a5ac54250396dd0c19c02ddc362ecc2d9b013 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 e4c18ef08d347d5fb1416497fed84b4d51ef448f..b94628b524e59d4142b88ba71638e36158128a08 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 b3c4b5f2b8b9198885d878cb99d46dbaffcd6b11..7d183b6e651bf40a2e0c40a0c57f5dd2d5c17b1f 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);