From a17cb2216a34af3967ef30ec20b99ac65bfdcbe9 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Fri, 25 Jan 2019 11:23:39 +0800 Subject: [PATCH 01/11] drm/imx: Revert a patch which merges imx-drm-core and ipuv3-crtc in one module DPU CRTC found in i.MX8qm/qxp SoCs can be hooked into imx-drm. Thus, move ipuv3-crtc out of imx-drm-core. Revert "drm/imx: merge imx-drm-core and ipuv3-crtc in one module" This reverts commit 3d1df96ad46856ce850be5ac112eab919cbe1cab. Signed-off-by: Liu Ying [ Aisheng: fix conflicts ] Signed-off-by: Dong Aisheng --- drivers/gpu/drm/imx/Kconfig | 7 +++++++ drivers/gpu/drm/imx/Makefile | 4 +++- drivers/gpu/drm/imx/imx-drm-core.c | 18 +----------------- drivers/gpu/drm/imx/imx-drm.h | 2 -- drivers/gpu/drm/imx/ipuv3-crtc.c | 8 +++++++- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index 207bf7409dfb..458295fb92ed 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -33,6 +33,13 @@ config DRM_IMX_LDB Choose this to enable the internal LVDS Display Bridge (LDB) found on i.MX53 and i.MX6 processors. +config DRM_IMX_IPUV3 + tristate + depends on DRM_IMX + depends on IMX_IPUV3_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m + config DRM_IMX_HDMI tristate "Freescale i.MX DRM HDMI" select DRM_DW_HDMI diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile index 21cdcc2faabc..141e0eca3a4d 100644 --- a/drivers/gpu/drm/imx/Makefile +++ b/drivers/gpu/drm/imx/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -imxdrm-objs := imx-drm-core.o ipuv3-crtc.o ipuv3-plane.o +imxdrm-objs := imx-drm-core.o obj-$(CONFIG_DRM_IMX) += imxdrm.o @@ -8,4 +8,6 @@ obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o +imx-ipuv3-crtc-objs := ipuv3-crtc.o ipuv3-plane.o +obj-$(CONFIG_DRM_IMX_IPUV3) += imx-ipuv3-crtc.o obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index da87c70e413b..959874fc15bf 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -343,23 +343,7 @@ static struct platform_driver imx_drm_pdrv = { .of_match_table = imx_drm_dt_ids, }, }; - -static struct platform_driver * const drivers[] = { - &imx_drm_pdrv, - &ipu_drm_driver, -}; - -static int __init imx_drm_init(void) -{ - return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); -} -module_init(imx_drm_init); - -static void __exit imx_drm_exit(void) -{ - platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); -} -module_exit(imx_drm_exit); +module_platform_driver(imx_drm_pdrv); MODULE_AUTHOR("Sascha Hauer "); MODULE_DESCRIPTION("i.MX drm driver core"); diff --git a/drivers/gpu/drm/imx/imx-drm.h b/drivers/gpu/drm/imx/imx-drm.h index ab9c6f706eb3..c04f150d2e7c 100644 --- a/drivers/gpu/drm/imx/imx-drm.h +++ b/drivers/gpu/drm/imx/imx-drm.h @@ -28,8 +28,6 @@ int imx_drm_init_drm(struct platform_device *pdev, int preferred_bpp); int imx_drm_exit_drm(void); -extern struct platform_driver ipu_drm_driver; - void imx_drm_mode_config_init(struct drm_device *drm); struct drm_gem_cma_object *imx_drm_fb_get_obj(struct drm_framebuffer *fb); diff --git a/drivers/gpu/drm/imx/ipuv3-crtc.c b/drivers/gpu/drm/imx/ipuv3-crtc.c index 63c0284f8b3c..bf8c52e9dc9c 100644 --- a/drivers/gpu/drm/imx/ipuv3-crtc.c +++ b/drivers/gpu/drm/imx/ipuv3-crtc.c @@ -492,10 +492,16 @@ static int ipu_drm_remove(struct platform_device *pdev) return 0; } -struct platform_driver ipu_drm_driver = { +static struct platform_driver ipu_drm_driver = { .driver = { .name = "imx-ipuv3-crtc", }, .probe = ipu_drm_probe, .remove = ipu_drm_remove, }; +module_platform_driver(ipu_drm_driver); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-crtc"); From 427c82c5faea04a3e63017212b96c94daaf6f9a5 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Tue, 22 Jan 2019 17:08:01 +0800 Subject: [PATCH 02/11] gpu: Move ipu-v3 to imx folder The new imx folder may contain ipu-v3 and dpu common drivers. Signed-off-by: Liu Ying [ Aisheng: fix source path ] Signed-off-by: Dong Aisheng --- drivers/gpu/Makefile | 2 +- drivers/gpu/imx/Kconfig | 1 + drivers/gpu/imx/Makefile | 1 + drivers/gpu/{ => imx}/ipu-v3/Kconfig | 0 drivers/gpu/{ => imx}/ipu-v3/Makefile | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-common.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-cpmem.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-csi.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-dc.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-di.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-dmfc.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-dp.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-ic.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-image-convert.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-pre.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-prg.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-prv.h | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-smfc.c | 0 drivers/gpu/{ => imx}/ipu-v3/ipu-vdi.c | 0 drivers/video/Kconfig | 2 +- 20 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/imx/Kconfig create mode 100644 drivers/gpu/imx/Makefile rename drivers/gpu/{ => imx}/ipu-v3/Kconfig (100%) rename drivers/gpu/{ => imx}/ipu-v3/Makefile (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-common.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-cpmem.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-csi.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-dc.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-di.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-dmfc.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-dp.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-ic.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-image-convert.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-pre.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-prg.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-prv.h (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-smfc.c (100%) rename drivers/gpu/{ => imx}/ipu-v3/ipu-vdi.c (100%) diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index f17d01f076c7..a6ac74a9f4b1 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -3,5 +3,5 @@ # taken to initialize them in the correct order. Link order is the only way # to ensure this currently. obj-$(CONFIG_TEGRA_HOST1X) += host1x/ +obj-y += imx/ obj-y += drm/ vga/ -obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ diff --git a/drivers/gpu/imx/Kconfig b/drivers/gpu/imx/Kconfig new file mode 100644 index 000000000000..57277de697dd --- /dev/null +++ b/drivers/gpu/imx/Kconfig @@ -0,0 +1 @@ +source "drivers/gpu/imx/ipu-v3/Kconfig" diff --git a/drivers/gpu/imx/Makefile b/drivers/gpu/imx/Makefile new file mode 100644 index 000000000000..c3cb11425cf5 --- /dev/null +++ b/drivers/gpu/imx/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ diff --git a/drivers/gpu/ipu-v3/Kconfig b/drivers/gpu/imx/ipu-v3/Kconfig similarity index 100% rename from drivers/gpu/ipu-v3/Kconfig rename to drivers/gpu/imx/ipu-v3/Kconfig diff --git a/drivers/gpu/ipu-v3/Makefile b/drivers/gpu/imx/ipu-v3/Makefile similarity index 100% rename from drivers/gpu/ipu-v3/Makefile rename to drivers/gpu/imx/ipu-v3/Makefile diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/imx/ipu-v3/ipu-common.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-common.c rename to drivers/gpu/imx/ipu-v3/ipu-common.c diff --git a/drivers/gpu/ipu-v3/ipu-cpmem.c b/drivers/gpu/imx/ipu-v3/ipu-cpmem.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-cpmem.c rename to drivers/gpu/imx/ipu-v3/ipu-cpmem.c diff --git a/drivers/gpu/ipu-v3/ipu-csi.c b/drivers/gpu/imx/ipu-v3/ipu-csi.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-csi.c rename to drivers/gpu/imx/ipu-v3/ipu-csi.c diff --git a/drivers/gpu/ipu-v3/ipu-dc.c b/drivers/gpu/imx/ipu-v3/ipu-dc.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-dc.c rename to drivers/gpu/imx/ipu-v3/ipu-dc.c diff --git a/drivers/gpu/ipu-v3/ipu-di.c b/drivers/gpu/imx/ipu-v3/ipu-di.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-di.c rename to drivers/gpu/imx/ipu-v3/ipu-di.c diff --git a/drivers/gpu/ipu-v3/ipu-dmfc.c b/drivers/gpu/imx/ipu-v3/ipu-dmfc.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-dmfc.c rename to drivers/gpu/imx/ipu-v3/ipu-dmfc.c diff --git a/drivers/gpu/ipu-v3/ipu-dp.c b/drivers/gpu/imx/ipu-v3/ipu-dp.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-dp.c rename to drivers/gpu/imx/ipu-v3/ipu-dp.c diff --git a/drivers/gpu/ipu-v3/ipu-ic.c b/drivers/gpu/imx/ipu-v3/ipu-ic.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-ic.c rename to drivers/gpu/imx/ipu-v3/ipu-ic.c diff --git a/drivers/gpu/ipu-v3/ipu-image-convert.c b/drivers/gpu/imx/ipu-v3/ipu-image-convert.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-image-convert.c rename to drivers/gpu/imx/ipu-v3/ipu-image-convert.c diff --git a/drivers/gpu/ipu-v3/ipu-pre.c b/drivers/gpu/imx/ipu-v3/ipu-pre.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-pre.c rename to drivers/gpu/imx/ipu-v3/ipu-pre.c diff --git a/drivers/gpu/ipu-v3/ipu-prg.c b/drivers/gpu/imx/ipu-v3/ipu-prg.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-prg.c rename to drivers/gpu/imx/ipu-v3/ipu-prg.c diff --git a/drivers/gpu/ipu-v3/ipu-prv.h b/drivers/gpu/imx/ipu-v3/ipu-prv.h similarity index 100% rename from drivers/gpu/ipu-v3/ipu-prv.h rename to drivers/gpu/imx/ipu-v3/ipu-prv.h diff --git a/drivers/gpu/ipu-v3/ipu-smfc.c b/drivers/gpu/imx/ipu-v3/ipu-smfc.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-smfc.c rename to drivers/gpu/imx/ipu-v3/ipu-smfc.c diff --git a/drivers/gpu/ipu-v3/ipu-vdi.c b/drivers/gpu/imx/ipu-v3/ipu-vdi.c similarity index 100% rename from drivers/gpu/ipu-v3/ipu-vdi.c rename to drivers/gpu/imx/ipu-v3/ipu-vdi.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 427a993c7f57..df9bd4b452f9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -15,7 +15,7 @@ source "drivers/char/agp/Kconfig" source "drivers/gpu/vga/Kconfig" source "drivers/gpu/host1x/Kconfig" -source "drivers/gpu/ipu-v3/Kconfig" +source "drivers/gpu/imx/Kconfig" source "drivers/gpu/drm/Kconfig" From 0e81504511b72577a39c9b37860fed333bac8eca Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Tue, 19 Feb 2019 11:36:25 +0800 Subject: [PATCH 03/11] dt-bindings: display: imx: ldb: Add i.MXqm LDB compatible string and properties This patch adds device tree binding support for i.MXqm LDB, including compatible string and additional properties. Signed-off-by: Liu Ying --- .../devicetree/bindings/display/imx/ldb.txt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt index 38c637fa39dd..51a157a231a7 100644 --- a/Documentation/devicetree/bindings/display/imx/ldb.txt +++ b/Documentation/devicetree/bindings/display/imx/ldb.txt @@ -9,15 +9,20 @@ 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. - clocks, clock-names : phandles to the LDB divider and selector clocks and to - the display interface selector clocks, as described in + the display interface selector clocks or pixel and + bypass clocks as described in Documentation/devicetree/bindings/clock/clock-bindings.txt The following clocks are expected on i.MX53: "di0_pll" - LDB LVDS channel 0 mux @@ -29,14 +34,19 @@ 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. + Documentation/devicetree/bindings/clock/imx6q-clock.txt, and in + Documentation/devicetree/bindings/clock/imx8qm-lpcg.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 +67,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 +83,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: From 2e9d9adb1cf82c5b4609b13ef31c6f0aee7c2905 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Tue, 29 Jan 2019 15:15:04 +0800 Subject: [PATCH 04/11] 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 [ Aisheng: fix headfile include conflict during upgrade ] Signed-off-by: Dong Aisheng --- drivers/gpu/drm/imx/Kconfig | 3 +- drivers/gpu/drm/imx/imx-ldb.c | 487 +++++++++++++++++++++++++++++----- 2 files changed, 425 insertions(+), 65 deletions(-) diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index 458295fb92ed..877cb2bc40ca 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -29,9 +29,10 @@ config DRM_IMX_LDB tristate "Support for LVDS displays" depends on DRM_IMX && MFD_SYSCON select DRM_PANEL + select PHY_MIXEL_LVDS 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 695f307f36b2..e4cab40fc060 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -5,13 +5,17 @@ * Copyright (C) 2012 Sascha Hauer, Pengutronix */ +#include #include #include +#include #include #include #include #include #include +#include +#include #include #include @@ -44,6 +48,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; @@ -56,6 +66,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; @@ -83,6 +96,23 @@ 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 use_sc_misc; + bool has_padding_quirks; + bool has_pxlink_valid_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; @@ -91,8 +121,26 @@ 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; + struct imx_sc_ipc *handle; + bool capable_10bit; + bool visible_phy; + bool has_mux; + bool is_imx8; + bool use_mixel_phy; + bool use_sc_misc; + bool has_padding_quirks; + bool has_pxlink_valid_quirks; + + /* pixel rate in KHz */ + unsigned int max_prate_single_mode; + unsigned int max_prate_dual_mode; + + int id; }; static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch, @@ -106,16 +154,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; } @@ -167,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); @@ -190,6 +266,25 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, chno); } +static void imx_ldb_pxlink_set_mst_valid(struct imx_ldb *ldb, + int dc_id, int stream_id, bool enable) +{ + u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = stream_id ? + IMX_SC_C_PXL_LINK_MST2_VLD : IMX_SC_C_PXL_LINK_MST1_VLD; + + imx_sc_misc_set_control(ldb->handle, rsc, ctrl, enable); +} + +static void imx_ldb_pxlink_set_sync_ctrl(struct imx_ldb *ldb, + int dc_id, int stream_id, bool enable) +{ + u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = stream_id ? IMX_SC_C_SYNC_CTRL1 : IMX_SC_C_SYNC_CTRL0; + + imx_sc_misc_set_control(ldb->handle, rsc, ctrl, enable); +} + static void imx_ldb_encoder_enable(struct drm_encoder *encoder) { struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); @@ -199,29 +294,46 @@ 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) { ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; - if (mux == 0 || ldb->lvds_mux) + if (ldb->has_mux) { + if (mux == 0 || ldb->lvds_mux) + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + else if (mux == 1) + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI1; + } else { ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; - else if (mux == 1) - ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI1; + } } if (imx_ldb_ch == &ldb->channel[1] || dual) { ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; - if (mux == 1 || ldb->lvds_mux) - ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; - else if (mux == 0) - ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0; + if (ldb->has_mux) { + if (mux == 1 || ldb->lvds_mux) + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; + else if (mux == 0) + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0; + } else { + ldb->ldb_ctrl |= dual ? + LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1; + } } if (ldb->lvds_mux) { @@ -236,7 +348,26 @@ 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; + } + + if (ldb->has_pxlink_valid_quirks) { + if (ldb->use_mixel_phy) { + imx_ldb_pxlink_set_mst_valid(ldb, ldb->id, 1, true); + imx_ldb_pxlink_set_sync_ctrl(ldb, ldb->id, 1, true); + } + } drm_panel_enable(imx_ldb_ch->panel); } @@ -255,23 +386,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 */ @@ -288,6 +431,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; @@ -302,22 +491,50 @@ 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 (ldb->has_pxlink_valid_quirks) { + if (ldb->use_mixel_phy) { + imx_ldb_pxlink_set_mst_valid(ldb, ldb->id, 1, false); + imx_ldb_pxlink_set_sync_ctrl(ldb, ldb->id, 1, false); + } + } + + 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; @@ -340,6 +557,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); } @@ -349,6 +567,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; @@ -362,11 +581,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->has_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->has_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; @@ -407,6 +638,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])) @@ -488,12 +722,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; @@ -505,6 +742,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) @@ -516,6 +758,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, @@ -528,15 +780,41 @@ 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 = 0x0, + .bus_mux = NULL, + .capable_10bit = true, + .visible_phy = true, + .is_imx8 = true, + .use_mixel_phy = true, + .use_sc_misc = true, + .has_padding_quirks = true, + .has_pxlink_valid_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); @@ -587,6 +865,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; @@ -603,44 +882,81 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) return PTR_ERR(imx_ldb->regmap); } - /* disable LDB by resetting the control register to POR default */ - regmap_write(imx_ldb->regmap, IOMUXC_GPR2, 0); + 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->use_sc_misc = devtype->use_sc_misc; + imx_ldb->has_padding_quirks = devtype->has_padding_quirks; + imx_ldb->has_pxlink_valid_quirks = devtype->has_pxlink_valid_quirks; + imx_ldb->max_prate_single_mode = devtype->max_prate_single_mode; + imx_ldb->max_prate_dual_mode = devtype->max_prate_dual_mode; imx_ldb->dev = dev; - if (of_id) - imx_ldb->lvds_mux = of_id->data; + if (imx_ldb->use_sc_misc) { + ret = imx_scu_get_handle(&imx_ldb->handle); + if (ret) { + dev_err(dev, "failed to get scu ipc handle %d\n", ret); + return ret; + } + + imx_ldb->id = of_alias_get_id(np, "ldb"); + } + + /* disable LDB by resetting the control register to POR default */ + regmap_write(imx_ldb->regmap, imx_ldb->ldb_ctrl_reg , 0); 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]; + 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); - 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]); + 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) { @@ -648,6 +964,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; @@ -662,10 +984,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; @@ -677,7 +1004,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, @@ -696,6 +1023,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) + continue; + } + ret = imx_ldb_register(drm, channel); if (ret) { channel->child = NULL; @@ -721,6 +1075,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); From 15409f90e647e2898b5679a08cd642aa8a7beee0 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Tue, 19 Feb 2019 14:22:17 +0800 Subject: [PATCH 05/11] dt-bindings: display: imx: ldb: Add i.MXqxp LDB compatible string and properties This patch adds device tree binding support for i.MXqxp LDB, including compatible string and additional properties. Signed-off-by: Liu Ying --- .../devicetree/bindings/display/imx/ldb.txt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt index 51a157a231a7..f4ffeb4c5008 100644 --- a/Documentation/devicetree/bindings/display/imx/ldb.txt +++ b/Documentation/devicetree/bindings/display/imx/ldb.txt @@ -10,13 +10,15 @@ Required properties: - #address-cells : should be <1> - #size-cells : should be <0> - compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb" or - "fsl,imx8qm-ldb". + "fsl,imx8qm-ldb" or "fsl,imx8qxp-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. + i.MX8qxp LDB only supports one LVDS encoder channel(either + channel0 or channel1). - gpr : should be <&gpr> on i.MX53 and i.MX6q. The phandle points to the iomuxc-gpr region containing the LVDS control register. @@ -40,16 +42,20 @@ Required properties: 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, and in - Documentation/devicetree/bindings/clock/imx8qm-lpcg.txt. -- power-domains : phandle pointing to power domain, only required by i.MX8qm. + Documentation/devicetree/bindings/clock/imx8qm-lpcg.txt, and in + Documentation/devicetree/bindings/clock/imx8qxp-lpcg.txt. +- power-domains : phandle pointing to power domain, only required by i.MX8qm and + i.MX8qxp. Optional properties: - - pinctrl-names : should be "default" on i.MX53, not used on i.MX6q and i.MX8qm + - pinctrl-names : should be "default" on i.MX53, not used on i.MX6q, i.MX8qm + and i.MX8qxp - pinctrl-0 : a phandle pointing to LVDS pin settings on i.MX53, - not used on i.MX6q and i.MX8qm + not used on i.MX6q, i.MX8qm and i.MX8qxp - 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 + Currently, i.MX8qxp doesn't support dual channel mode. LVDS Channel ============ @@ -68,12 +74,13 @@ Required properties: On i.MX6, there should be four input ports (port@[0-3]) that correspond to the four LVDS multiplexer inputs. 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. + A single output port (port@2 on i.MX5, port@4 on i.MX6, port@1 on i.MX8qm + and i.MX8qxp) 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. + - phys: the phandle for the LVDS PHY device. Valid only on i.MX8qm and + i.MX8qxp. + - phy-names: should be "ldb_phy". Valid only on i.MX8qm and i.MX8qxp. Optional properties (required if display-timings are used): - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing From 654926dceb3b6a42f222c0221fb6d0be9e347623 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Tue, 29 Jan 2019 15:30:44 +0800 Subject: [PATCH 06/11] drm/imx: ldb: Add i.MX8qxp LDB support This patch adds i.MX8qxp LDB support. Logics are added to make i.MX8qxp LDB cope with Mixel LVDS combo PHY. Also, logics are added to handle pixel link quirks for i.MX8qxp LDB. Signed-off-by: Liu Ying --- drivers/gpu/drm/imx/Kconfig | 1 + drivers/gpu/drm/imx/imx-ldb.c | 100 ++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index 877cb2bc40ca..a7f31b4a32df 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -30,6 +30,7 @@ config DRM_IMX_LDB depends on DRM_IMX && MFD_SYSCON select DRM_PANEL select PHY_MIXEL_LVDS + select PHY_MIXEL_LVDS_COMBO help Choose this to enable the internal LVDS Display Bridge (LDB) found on i.MX53, i.MX6 and i.MX8 processors. diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index e4cab40fc060..7653a14502f2 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ #define LDB_CH1_DATA_WIDTH_24BIT (1 << 26) #define LDB_CH0_DATA_WIDTH_30BIT (2 << 24) #define LDB_CH1_DATA_WIDTH_30BIT (2 << 26) +#define LDB_CH_SEL (1 << 28) struct imx_ldb; @@ -102,8 +104,10 @@ struct devtype { bool capable_10bit; bool visible_phy; bool has_mux; + bool has_ch_sel; bool is_imx8; bool use_mixel_phy; + bool use_mixel_combo_phy; bool use_sc_misc; bool has_padding_quirks; bool has_pxlink_valid_quirks; @@ -130,8 +134,10 @@ struct imx_ldb { bool capable_10bit; bool visible_phy; bool has_mux; + bool has_ch_sel; bool is_imx8; bool use_mixel_phy; + bool use_mixel_combo_phy; bool use_sc_misc; bool has_padding_quirks; bool has_pxlink_valid_quirks; @@ -366,6 +372,9 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder) if (ldb->use_mixel_phy) { imx_ldb_pxlink_set_mst_valid(ldb, ldb->id, 1, true); imx_ldb_pxlink_set_sync_ctrl(ldb, ldb->id, 1, true); + } else if (ldb->use_mixel_combo_phy) { + imx_ldb_pxlink_set_mst_valid(ldb, 0, ldb->id, true); + imx_ldb_pxlink_set_sync_ctrl(ldb, 0, ldb->id, true); } } @@ -415,6 +424,16 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, if (ldb->use_mixel_phy) mixel_phy_lvds_set_phy_speed(imx_ldb_ch->phy, di_clk); + else if (ldb->use_mixel_combo_phy) + mixel_phy_combo_lvds_set_phy_speed(imx_ldb_ch->phy, + di_clk); + } + + if (ldb->has_ch_sel) { + if (imx_ldb_ch == &ldb->channel[0]) + ldb->ldb_ctrl &= ~LDB_CH_SEL; + if (imx_ldb_ch == &ldb->channel[1]) + ldb->ldb_ctrl |= LDB_CH_SEL; } /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ @@ -474,6 +493,25 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, else if (mode->flags & DRM_MODE_FLAG_PHSYNC) mixel_phy_lvds_set_hsync_pol(imx_ldb_ch->phy, true); + } else if (ldb->use_mixel_combo_phy) { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + mixel_phy_combo_lvds_set_vsync_pol( + imx_ldb_ch->phy, + false); + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + mixel_phy_combo_lvds_set_vsync_pol( + imx_ldb_ch->phy, + true); + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + mixel_phy_combo_lvds_set_hsync_pol( + imx_ldb_ch->phy, + false); + else if (mode->flags & DRM_MODE_FLAG_PHSYNC) + mixel_phy_combo_lvds_set_hsync_pol( + imx_ldb_ch->phy, + true); } } @@ -500,6 +538,9 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) if (ldb->use_mixel_phy) { imx_ldb_pxlink_set_mst_valid(ldb, ldb->id, 1, false); imx_ldb_pxlink_set_sync_ctrl(ldb, ldb->id, 1, false); + } else if (ldb->use_mixel_combo_phy) { + imx_ldb_pxlink_set_mst_valid(ldb, 0, ldb->id, false); + imx_ldb_pxlink_set_sync_ctrl(ldb, 0, ldb->id, false); } } @@ -804,14 +845,29 @@ static struct devtype imx8qm_ldb_devtype = { .max_prate_dual_mode = 300000, }; +static struct devtype imx8qxp_ldb_devtype = { + .ctrl_reg = 0x0, + .bus_mux = NULL, + .visible_phy = true, + .has_ch_sel = true, + .is_imx8 = true, + .use_mixel_combo_phy = true, + .use_sc_misc = true, + .has_padding_quirks = true, + .has_pxlink_valid_quirks = true, + .max_prate_single_mode = 150000, + .max_prate_dual_mode = 300000, +}; + /* - * 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. + * For a device declaring compatible = "fsl,imx8qxp-ldb", "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,imx8qxp-ldb", .data = &imx8qxp_ldb_devtype, }, { .compatible = "fsl,imx8qm-ldb", .data = &imx8qm_ldb_devtype, }, { .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_devtype, }, { .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_devtype, }, @@ -859,6 +915,21 @@ static int imx_ldb_panel_ddc(struct device *dev, return 0; } +static int imx_ldb_init_sc_misc(int ldb_id) +{ + struct imx_sc_ipc *handle; + u32 rsc = ldb_id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0; + int ret = 0; + + imx_scu_get_handle(&handle); + + ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_MODE, 1); + ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_DUAL_MODE, 0); + ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_PXL_LINK_SEL, 0); + + return ret; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -887,8 +958,10 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) imx_ldb->capable_10bit = devtype->capable_10bit; imx_ldb->visible_phy = devtype->visible_phy; imx_ldb->has_mux = devtype->has_mux; + imx_ldb->has_ch_sel = devtype->has_ch_sel; imx_ldb->is_imx8 = devtype->is_imx8; imx_ldb->use_mixel_phy = devtype->use_mixel_phy; + imx_ldb->use_mixel_combo_phy = devtype->use_mixel_combo_phy; imx_ldb->use_sc_misc = devtype->use_sc_misc; imx_ldb->has_padding_quirks = devtype->has_padding_quirks; imx_ldb->has_pxlink_valid_quirks = devtype->has_pxlink_valid_quirks; @@ -905,14 +978,29 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) } imx_ldb->id = of_alias_get_id(np, "ldb"); + + if (imx_ldb->use_mixel_combo_phy) { + ret = imx_ldb_init_sc_misc(imx_ldb->id); + if (ret) { + dev_err(dev, + "failed to initialize sc misc %d\n", + ret); + return ret; + } + } } /* disable LDB by resetting the control register to POR default */ regmap_write(imx_ldb->regmap, imx_ldb->ldb_ctrl_reg , 0); dual = of_property_read_bool(np, "fsl,dual-channel"); - if (dual) + if (dual) { + if (imx_ldb->has_ch_sel) { + dev_info(dev, "do not support dual channel mode\n"); + return -EINVAL; + } imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + } if (imx_ldb->is_imx8) { imx_ldb->clk_pixel = devm_clk_get(imx_ldb->dev, "pixel"); From d79348c30fef9e71ac0fc80e005a07c6ffa3e0aa Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Mon, 17 Jun 2019 18:10:19 +0800 Subject: [PATCH 07/11] MLK-21876-23 drm/imx: ldb: fix incorrect color displayed for mx8qxp The changed was introduced by Liu Ying from 4.19 kernel. But we observed it caused the latest kernel color display is wrong. So revert to the original version which worked before. Signed-off-by: Dong Aisheng --- drivers/gpu/drm/imx/imx-ldb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index 7653a14502f2..481ddc0210a4 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -832,7 +832,7 @@ static struct devtype imx6q_ldb_devtype = { }; static struct devtype imx8qm_ldb_devtype = { - .ctrl_reg = 0x0, + .ctrl_reg = 0x10e0, .bus_mux = NULL, .capable_10bit = true, .visible_phy = true, @@ -846,7 +846,7 @@ static struct devtype imx8qm_ldb_devtype = { }; static struct devtype imx8qxp_ldb_devtype = { - .ctrl_reg = 0x0, + .ctrl_reg = 0x10e0, .bus_mux = NULL, .visible_phy = true, .has_ch_sel = true, From 8a75807a3a9ffb1d5bdee80785871dca6c0a6425 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Fri, 13 Oct 2017 13:22:16 +0800 Subject: [PATCH 08/11] drm/imx: ldb: Add system power management support This patch adds system power management support for imx-ldb drm driver by proper PHY exit/init handling and pixel link re-initialization in the resume operation. The driver depends on the imx-drm core driver to handle ldb bridge power management operations. Signed-off-by: Liu Ying --- drivers/gpu/drm/imx/imx-ldb.c | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index 481ddc0210a4..d500db06173a 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -1174,6 +1174,8 @@ static void imx_ldb_unbind(struct device *dev, struct device *master, kfree(channel->edid); i2c_put_adapter(channel->ddc); } + + dev_set_drvdata(dev, NULL); } static const struct component_ops imx_ldb_ops = { @@ -1192,12 +1194,52 @@ static int imx_ldb_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int imx_ldb_suspend(struct device *dev) +{ + struct imx_ldb *imx_ldb = dev_get_drvdata(dev); + int i; + + if (imx_ldb == NULL) + return 0; + + if (imx_ldb->visible_phy) + for (i = 0; i < 2; i++) + phy_exit(imx_ldb->channel[i].phy); + + return 0; +} + +static int imx_ldb_resume(struct device *dev) +{ + struct imx_ldb *imx_ldb = dev_get_drvdata(dev); + int i; + + if (imx_ldb == NULL) + return 0; + + if (imx_ldb->visible_phy) + for (i = 0; i < 2; i++) + phy_init(imx_ldb->channel[i].phy); + + if (imx_ldb->use_mixel_combo_phy) + imx_ldb_init_sc_misc(imx_ldb->id); + + return 0; +} +#endif + +static const struct dev_pm_ops imx_ldb_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_ldb_suspend, imx_ldb_resume) +}; + static struct platform_driver imx_ldb_driver = { .probe = imx_ldb_probe, .remove = imx_ldb_remove, .driver = { .of_match_table = imx_ldb_dt_ids, .name = DRIVER_NAME, + .pm = &imx_ldb_pm_ops, }, }; From 0f332579595db4324fb29ec01c1289c160916bc2 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Fri, 15 Nov 2019 10:20:13 +0800 Subject: [PATCH 09/11] dt-bindings: display: imx: ldb: Correct pixel and bypass clock description Not only i.MX8qm LDB requires pixel and bypass clocks, but also i.MX8qxp LDB does. This patch corrects pixel and bypass clock description by explicitly saying that i.MX8qxp LDB requires the clocks. Signed-off-by: Liu Ying --- Documentation/devicetree/bindings/display/imx/ldb.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt index f4ffeb4c5008..3a2968315210 100644 --- a/Documentation/devicetree/bindings/display/imx/ldb.txt +++ b/Documentation/devicetree/bindings/display/imx/ldb.txt @@ -36,7 +36,7 @@ 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: + The following clocks are expected on i.MX8qm and i.MX8qxp: "pixel" - pixel clock "bypass" - bypass clock The needed clock numbers for each are documented in From 92ec3526fc248930e14d3860743264ff2ddfd6d3 Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Fri, 15 Nov 2019 10:35:04 +0800 Subject: [PATCH 10/11] dt-bindings: display: imx: ldb: Add i.MX8qxp LDB dual channel mode documentation i.MX8qxp LDB dual channel mode uses two LDB channels from two LDB instances, while all other LDB variants in other SoCs use two LDB channels from one LDB instance. This patch adds documentation for the special case of i.MX8qxp LDB dual channel mode. Signed-off-by: Liu Ying --- Documentation/devicetree/bindings/display/imx/ldb.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/imx/ldb.txt b/Documentation/devicetree/bindings/display/imx/ldb.txt index 3a2968315210..fb7ee4971d8b 100644 --- a/Documentation/devicetree/bindings/display/imx/ldb.txt +++ b/Documentation/devicetree/bindings/display/imx/ldb.txt @@ -22,6 +22,8 @@ Required properties: - gpr : should be <&gpr> on i.MX53 and i.MX6q. The phandle points to the iomuxc-gpr region containing the LVDS control register. + - fsl,auxldb : phandle to auxiliary LDB which is used in dual channel mode. + Only required by i.MX8qxp. - clocks, clock-names : phandles to the LDB divider and selector clocks and to the display interface selector clocks or pixel and bypass clocks as described in @@ -39,6 +41,9 @@ Required properties: The following clocks are expected on i.MX8qm and i.MX8qxp: "pixel" - pixel clock "bypass" - bypass clock + The following clocks are expected on i.MX8qxp: + "aux_pixel" - auxiliary pixel clock in dual channel mode + "aux_bypass" - auxiliary bypass clock in dual channel mode 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, and in @@ -55,7 +60,6 @@ Optional properties: - 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 - Currently, i.MX8qxp doesn't support dual channel mode. LVDS Channel ============ From 42797d088c006ea33718a750742192e91534743f Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Fri, 15 Nov 2019 13:52:25 +0800 Subject: [PATCH 11/11] drm/imx: ldb: Add dual channel mode support for i.MX8QXP i.MX8QXP uses two LDBs(one primary, one auxiliary) to support dual channel mode. This patch adds the dual channel mode support for i.MX8QXP. Note that the drivers contain specific sequence needed by this mode - LDB VSYNC polarity and channel selection settings should be configured into the register a bit earlier in ->atomic_mode_set instead of in ->enable, and DC subsystem pixel link enablement is moved from the DPU driver to the LDB driver to make sure it happens later than LDB clocks enablement in ->enable. Signed-off-by: Liu Ying --- drivers/gpu/drm/imx/imx-ldb.c | 271 ++++++++++++++++++++++++++++++---- 1 file changed, 242 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index d500db06173a..4a460d65b3e5 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -69,6 +69,7 @@ struct imx_ldb_channel { struct drm_bridge *bridge; struct phy *phy; + struct phy *aux_phy; bool phy_is_on; struct device_node *child; @@ -105,12 +106,14 @@ struct devtype { bool visible_phy; bool has_mux; bool has_ch_sel; + bool has_aux_ldb; bool is_imx8; bool use_mixel_phy; bool use_mixel_combo_phy; bool use_sc_misc; bool has_padding_quirks; bool has_pxlink_valid_quirks; + bool has_pxlink_enable_quirks; /* pixel rate in KHz */ unsigned int max_prate_single_mode; @@ -119,6 +122,7 @@ struct devtype { struct imx_ldb { struct regmap *regmap; + struct regmap *aux_regmap; struct device *dev; struct imx_ldb_channel channel[2]; struct clk *clk[2]; /* our own clock */ @@ -127,6 +131,8 @@ struct imx_ldb { struct clk *clk_pll[2]; /* upstream clock we can adjust */ struct clk *clk_pixel; struct clk *clk_bypass; + struct clk *clk_aux_pixel; + struct clk *clk_aux_bypass; u32 ldb_ctrl_reg; u32 ldb_ctrl; const struct bus_mux *lvds_mux; @@ -135,12 +141,14 @@ struct imx_ldb { bool visible_phy; bool has_mux; bool has_ch_sel; + bool has_aux_ldb; bool is_imx8; bool use_mixel_phy; bool use_mixel_combo_phy; bool use_sc_misc; bool has_padding_quirks; bool has_pxlink_valid_quirks; + bool has_pxlink_enable_quirks; /* pixel rate in KHz */ unsigned int max_prate_single_mode; @@ -241,11 +249,17 @@ static struct drm_encoder *imx_ldb_connector_best_encoder( static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, unsigned long serial_clk, unsigned long di_clk) { + int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; int ret; if (ldb->is_imx8) { clk_set_rate(ldb->clk_bypass, di_clk); clk_set_rate(ldb->clk_pixel, di_clk); + + if (dual && ldb->has_aux_ldb) { + clk_set_rate(ldb->clk_aux_bypass, di_clk); + clk_set_rate(ldb->clk_aux_pixel, di_clk); + } return; } @@ -272,6 +286,15 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, chno); } +static void imx_ldb_pxlink_enable(struct imx_ldb *ldb, + int stream_id, bool enable) +{ + u8 ctrl = stream_id ? + IMX_SC_C_PXL_LINK_MST2_ENB : IMX_SC_C_PXL_LINK_MST1_ENB; + + imx_sc_misc_set_control(ldb->handle, IMX_SC_R_DC_0, ctrl, enable); +} + static void imx_ldb_pxlink_set_mst_valid(struct imx_ldb *ldb, int dc_id, int stream_id, bool enable) { @@ -303,6 +326,11 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder) if (ldb->is_imx8) { clk_prepare_enable(ldb->clk_pixel); clk_prepare_enable(ldb->clk_bypass); + + if (dual && ldb->has_aux_ldb) { + clk_prepare_enable(ldb->clk_aux_pixel); + clk_prepare_enable(ldb->clk_aux_bypass); + } } if (ldb->has_mux) { @@ -318,6 +346,14 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder) } } + /* + * LDB frontend doesn't know if the auxiliary LDB is used or not. + * Enable pixel link after dual or single LDB clocks are enabled + * so that the dual LDBs are synchronized. + */ + if (ldb->has_pxlink_enable_quirks) + imx_ldb_pxlink_enable(ldb, ldb->id, true); + if (imx_ldb_ch == &ldb->channel[0] || dual) { ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; if (ldb->has_mux) { @@ -355,13 +391,20 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder) } regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl); + if (dual && ldb->has_aux_ldb) + regmap_write(ldb->aux_regmap, ldb->ldb_ctrl_reg, + ldb->ldb_ctrl | LDB_CH_SEL); if (dual) { phy_power_on(ldb->channel[0].phy); - phy_power_on(ldb->channel[1].phy); + if (ldb->has_aux_ldb) + phy_power_on(ldb->channel[0].aux_phy); + else + phy_power_on(ldb->channel[1].phy); ldb->channel[0].phy_is_on = true; - ldb->channel[1].phy_is_on = true; + if (!ldb->has_aux_ldb) + ldb->channel[1].phy_is_on = true; } else { phy_power_on(imx_ldb_ch->phy); @@ -416,6 +459,11 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, di_clk / 2); mixel_phy_lvds_set_phy_speed(ldb->channel[1].phy, di_clk / 2); + } else if (ldb->use_mixel_combo_phy) { + mixel_phy_combo_lvds_set_phy_speed(ldb->channel[0].phy, + di_clk / 2); + mixel_phy_combo_lvds_set_phy_speed(ldb->channel[0].aux_phy, + di_clk / 2); } } else { serial_clk = 7000UL * mode->clock; @@ -450,6 +498,13 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; } + /* settle vsync polarity and channel selection down early */ + if (dual && ldb->has_aux_ldb) { + regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl); + regmap_write(ldb->aux_regmap, ldb->ldb_ctrl_reg, + ldb->ldb_ctrl | LDB_CH_SEL); + } + if (dual) { if (ldb->use_mixel_phy) { /* VSYNC */ @@ -476,7 +531,32 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, mixel_phy_lvds_set_hsync_pol( ldb->channel[1].phy, true); } - } + } else if (ldb->use_mixel_combo_phy) { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) { + mixel_phy_combo_lvds_set_vsync_pol( + ldb->channel[0].phy, false); + mixel_phy_combo_lvds_set_vsync_pol( + ldb->channel[0].aux_phy, false); + } else { + mixel_phy_combo_lvds_set_vsync_pol( + ldb->channel[0].phy, true); + mixel_phy_combo_lvds_set_vsync_pol( + ldb->channel[0].aux_phy, true); + } + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + mixel_phy_combo_lvds_set_hsync_pol( + ldb->channel[0].phy, false); + mixel_phy_combo_lvds_set_hsync_pol( + ldb->channel[0].aux_phy, false); + } else { + mixel_phy_combo_lvds_set_hsync_pol( + ldb->channel[0].phy, true); + mixel_phy_combo_lvds_set_hsync_pol( + ldb->channel[0].aux_phy, true); + } + } } else { if (ldb->use_mixel_phy) { /* VSYNC */ @@ -546,10 +626,14 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) if (dual) { phy_power_off(ldb->channel[0].phy); - phy_power_off(ldb->channel[1].phy); + if (ldb->has_aux_ldb) + phy_power_off(ldb->channel[0].aux_phy); + else + phy_power_off(ldb->channel[1].phy); ldb->channel[0].phy_is_on = false; - ldb->channel[1].phy_is_on = false; + if (!ldb->has_aux_ldb) + ldb->channel[1].phy_is_on = false; } else { phy_power_off(imx_ldb_ch->phy); @@ -562,10 +646,17 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl); + if (dual && ldb->has_aux_ldb) + regmap_write(ldb->aux_regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl); if (ldb->is_imx8) { clk_disable_unprepare(ldb->clk_bypass); clk_disable_unprepare(ldb->clk_pixel); + + if (dual && ldb->has_aux_ldb) { + clk_disable_unprepare(ldb->clk_aux_bypass); + clk_disable_unprepare(ldb->clk_aux_pixel); + } } else { if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) { clk_disable_unprepare(ldb->clk[0]); @@ -573,6 +664,9 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) } } + if (ldb->has_pxlink_enable_quirks) + imx_ldb_pxlink_enable(ldb, ldb->id, false); + if (!ldb->has_mux) goto unprepare_panel; @@ -850,11 +944,13 @@ static struct devtype imx8qxp_ldb_devtype = { .bus_mux = NULL, .visible_phy = true, .has_ch_sel = true, + .has_aux_ldb = true, .is_imx8 = true, .use_mixel_combo_phy = true, .use_sc_misc = true, .has_padding_quirks = true, .has_pxlink_valid_quirks = true, + .has_pxlink_enable_quirks = true, .max_prate_single_mode = 150000, .max_prate_dual_mode = 300000, }; @@ -915,21 +1011,58 @@ static int imx_ldb_panel_ddc(struct device *dev, return 0; } -static int imx_ldb_init_sc_misc(int ldb_id) +static int imx_ldb_init_sc_misc(int ldb_id, bool dual) { struct imx_sc_ipc *handle; - u32 rsc = ldb_id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0; + u32 rsc; + bool is_aux = false; int ret = 0; imx_scu_get_handle(&handle); - ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_MODE, 1); - ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_DUAL_MODE, 0); - ret |= imx_sc_misc_set_control(handle, rsc, IMX_SC_C_PXL_LINK_SEL, 0); +again: + rsc = ldb_id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0; + + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_MODE, 1); + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_DUAL_MODE, is_aux); + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_PXL_LINK_SEL, is_aux); + + if (dual && !is_aux) { + ldb_id ^= 1; + is_aux = true; + goto again; + } return ret; } +static struct phy *imx_ldb_get_aux_phy(struct device_node *auxldb_np) +{ + struct device_node *child; + struct phy *phy = NULL; + int ret, i; + + for_each_child_of_node(auxldb_np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0) { + of_node_put(child); + return ERR_PTR(-ENODEV); + } + + if (i != 1) + continue; + + phy = of_phy_get(child, "ldb_phy"); + } + + of_node_put(child); + + return phy; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -937,7 +1070,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) 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 device_node *auxldb_np = NULL, *child; struct imx_ldb *imx_ldb; int dual; int ret; @@ -959,17 +1092,26 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) imx_ldb->visible_phy = devtype->visible_phy; imx_ldb->has_mux = devtype->has_mux; imx_ldb->has_ch_sel = devtype->has_ch_sel; + imx_ldb->has_aux_ldb = devtype->has_aux_ldb; imx_ldb->is_imx8 = devtype->is_imx8; imx_ldb->use_mixel_phy = devtype->use_mixel_phy; imx_ldb->use_mixel_combo_phy = devtype->use_mixel_combo_phy; imx_ldb->use_sc_misc = devtype->use_sc_misc; imx_ldb->has_padding_quirks = devtype->has_padding_quirks; imx_ldb->has_pxlink_valid_quirks = devtype->has_pxlink_valid_quirks; + imx_ldb->has_pxlink_enable_quirks = devtype->has_pxlink_enable_quirks; imx_ldb->max_prate_single_mode = devtype->max_prate_single_mode; imx_ldb->max_prate_dual_mode = devtype->max_prate_dual_mode; imx_ldb->dev = dev; + /* disable LDB by resetting the control register to POR default */ + regmap_write(imx_ldb->regmap, imx_ldb->ldb_ctrl_reg , 0); + + dual = of_property_read_bool(np, "fsl,dual-channel"); + if (dual) + imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + if (imx_ldb->use_sc_misc) { ret = imx_scu_get_handle(&imx_ldb->handle); if (ret) { @@ -980,7 +1122,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) imx_ldb->id = of_alias_get_id(np, "ldb"); if (imx_ldb->use_mixel_combo_phy) { - ret = imx_ldb_init_sc_misc(imx_ldb->id); + ret = imx_ldb_init_sc_misc(imx_ldb->id, dual); if (ret) { dev_err(dev, "failed to initialize sc misc %d\n", @@ -990,16 +1132,31 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) } } - /* disable LDB by resetting the control register to POR default */ - regmap_write(imx_ldb->regmap, imx_ldb->ldb_ctrl_reg , 0); - - dual = of_property_read_bool(np, "fsl,dual-channel"); - if (dual) { - if (imx_ldb->has_ch_sel) { - dev_info(dev, "do not support dual channel mode\n"); - return -EINVAL; + if (dual && imx_ldb->has_aux_ldb) { + auxldb_np = of_parse_phandle(np, "fsl,auxldb", 0); + if (!auxldb_np) { + dev_err(dev, + "failed to find aux LDB node in device tree\n"); + return -ENODEV; } - imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + + if (of_device_is_available(auxldb_np)) { + of_node_put(auxldb_np); + dev_err(dev, "aux LDB node is already in use\n"); + return -ENODEV; + } + + imx_ldb->aux_regmap = + syscon_regmap_lookup_by_phandle(auxldb_np, "gpr"); + of_node_put(auxldb_np); + if (IS_ERR(imx_ldb->aux_regmap)) { + ret = PTR_ERR(imx_ldb->aux_regmap); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get aux regmap\n"); + return ret; + } + + regmap_write(imx_ldb->aux_regmap, imx_ldb->ldb_ctrl_reg , 0); } if (imx_ldb->is_imx8) { @@ -1010,6 +1167,18 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) 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 (dual && imx_ldb->has_aux_ldb) { + imx_ldb->clk_aux_pixel = + devm_clk_get(imx_ldb->dev, "aux_pixel"); + if (IS_ERR(imx_ldb->clk_aux_pixel)) + return PTR_ERR(imx_ldb->clk_aux_pixel); + + imx_ldb->clk_aux_bypass = + devm_clk_get(imx_ldb->dev, "aux_bypass"); + if (IS_ERR(imx_ldb->clk_aux_bypass)) + return PTR_ERR(imx_ldb->clk_aux_bypass); + } } if (imx_ldb->has_mux) { @@ -1134,6 +1303,30 @@ get_phy: return ret; } + if (dual && imx_ldb->has_aux_ldb) { + channel->aux_phy = + imx_ldb_get_aux_phy(auxldb_np); + if (IS_ERR(channel->aux_phy)) { + ret = PTR_ERR(channel->aux_phy); + if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, + "can't get channel%d aux phy: %d\n", + channel->chno, ret); + return ret; + } + } + + ret = phy_init(channel->aux_phy); + if (ret < 0) { + dev_err(dev, + "failed to initialize channel%d aux phy: %d\n", + channel->chno, ret); + return ret; + } + } + if (auxiliary_ch) continue; } @@ -1158,15 +1351,21 @@ static void imx_ldb_unbind(struct device *dev, struct device *master, void *data) { struct imx_ldb *imx_ldb = dev_get_drvdata(dev); + int dual = imx_ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; int i; for (i = 0; i < 2; i++) { struct imx_ldb_channel *channel = &imx_ldb->channel[i]; - if (channel->phy_is_on) + if (channel->phy_is_on) { phy_power_off(channel->phy); + if (dual && imx_ldb->has_aux_ldb) + phy_power_off(channel->aux_phy); + } phy_exit(channel->phy); + if (dual && imx_ldb->has_aux_ldb && i == 0) + phy_exit(channel->aux_phy); if (channel->panel) drm_panel_detach(channel->panel); @@ -1198,32 +1397,46 @@ static int imx_ldb_remove(struct platform_device *pdev) static int imx_ldb_suspend(struct device *dev) { struct imx_ldb *imx_ldb = dev_get_drvdata(dev); - int i; + int i, dual; if (imx_ldb == NULL) return 0; - if (imx_ldb->visible_phy) - for (i = 0; i < 2; i++) + dual = imx_ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; + + if (imx_ldb->visible_phy) { + for (i = 0; i < 2; i++) { phy_exit(imx_ldb->channel[i].phy); + if (dual && imx_ldb->has_aux_ldb && i == 0) + phy_exit(imx_ldb->channel[i].aux_phy); + } + } + return 0; } static int imx_ldb_resume(struct device *dev) { struct imx_ldb *imx_ldb = dev_get_drvdata(dev); - int i; + int i, dual; if (imx_ldb == NULL) return 0; - if (imx_ldb->visible_phy) - for (i = 0; i < 2; i++) + dual = imx_ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; + + if (imx_ldb->visible_phy) { + for (i = 0; i < 2; i++) { phy_init(imx_ldb->channel[i].phy); + if (dual && imx_ldb->has_aux_ldb && i == 0) + phy_init(imx_ldb->channel[i].aux_phy); + } + } + if (imx_ldb->use_mixel_combo_phy) - imx_ldb_init_sc_misc(imx_ldb->id); + imx_ldb_init_sc_misc(imx_ldb->id, dual); return 0; }