Re: [PATCH 3/4] drm/mcde: Add new driver for ST-Ericsson MCDE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Feb 07, 2019 at 09:36:46AM +0100, Linus Walleij wrote:
> This adds a new DRM driver for the ST-Ericsson Multi Channel
> Display Engine, MCDE display controller.
> 
> This hardware has three independent DSI hosts and can composit
> and display several memory buffers onto an LCD display. It
> was developed for several years inside of ST-Ericsson and
> shipped with a few million mobile phones from Sony and Samsung,
> as well as with the Snowball community development board.
> 
> The driver is currently pretty rudimentary but supports a
> simple framebuffer so we can get penguins and graphics when
> using these SoCs.
> 
> Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx>

Scrolled through a bit, bunch of comments about latest drm evolution and
what would simplify the code (assuming I actually understand it) below.

Cheers, Daniel

> ---
>  Documentation/gpu/drivers.rst       |    1 +
>  Documentation/gpu/mcde.rst          |    8 +
>  MAINTAINERS                         |    7 +
>  drivers/gpu/drm/Kconfig             |    2 +
>  drivers/gpu/drm/Makefile            |    1 +
>  drivers/gpu/drm/mcde/Kconfig        |   18 +
>  drivers/gpu/drm/mcde/Makefile       |    3 +
>  drivers/gpu/drm/mcde/mcde_display.c | 1284 +++++++++++++++++++++++++
>  drivers/gpu/drm/mcde/mcde_drm.h     |   52 +
>  drivers/gpu/drm/mcde/mcde_drv.c     |  540 +++++++++++
>  drivers/gpu/drm/mcde/mcde_dsi.c     | 1374 +++++++++++++++++++++++++++
>  11 files changed, 3290 insertions(+)
>  create mode 100644 Documentation/gpu/mcde.rst
>  create mode 100644 drivers/gpu/drm/mcde/Kconfig
>  create mode 100644 drivers/gpu/drm/mcde/Makefile
>  create mode 100644 drivers/gpu/drm/mcde/mcde_display.c
>  create mode 100644 drivers/gpu/drm/mcde/mcde_drm.h
>  create mode 100644 drivers/gpu/drm/mcde/mcde_drv.c
>  create mode 100644 drivers/gpu/drm/mcde/mcde_dsi.c
> 
> diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
> index 7c1672118a73..4b9ec50601f2 100644
> --- a/Documentation/gpu/drivers.rst
> +++ b/Documentation/gpu/drivers.rst
> @@ -7,6 +7,7 @@ GPU Driver Documentation
>     amdgpu
>     amdgpu-dc
>     i915
> +   mcde
>     meson
>     pl111
>     tegra
> diff --git a/Documentation/gpu/mcde.rst b/Documentation/gpu/mcde.rst
> new file mode 100644
> index 000000000000..c69e977defda
> --- /dev/null
> +++ b/Documentation/gpu/mcde.rst
> @@ -0,0 +1,8 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=======================================================
> + drm/mcde ST-Ericsson MCDE Multi-channel display engine
> +=======================================================
> +
> +.. kernel-doc:: drivers/gpu/drm/mcde/mcde_drv.c
> +   :doc: ST-Ericsson MCDE DRM Driver
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 32d444476a90..3038c519340d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4905,6 +4905,13 @@ S:	Maintained
>  F:	drivers/gpu/drm/tinydrm/st7735r.c
>  F:	Documentation/devicetree/bindings/display/sitronix,st7735r.txt
>  
> +DRM DRIVER FOR ST-ERICSSON MCDE
> +M:	Linus Walleij <linus.walleij@xxxxxxxxxx>
> +T:	git git://anongit.freedesktop.org/drm/drm-misc
> +S:	Maintained
> +F:	drivers/gpu/drm/mcde/
> +F:	Documentation/devicetree/bindings/display/ste,mcde.txt
> +
>  DRM DRIVER FOR TDFX VIDEO CARDS
>  S:	Orphan / Obsolete
>  F:	drivers/gpu/drm/tdfx/
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 4385f00e1d05..8689ea46c3bc 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -333,6 +333,8 @@ source "drivers/gpu/drm/tve200/Kconfig"
>  
>  source "drivers/gpu/drm/xen/Kconfig"
>  
> +source "drivers/gpu/drm/mcde/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index ce8d1d384319..ebc44893b13b 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -109,3 +109,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>  obj-$(CONFIG_DRM_PL111) += pl111/
>  obj-$(CONFIG_DRM_TVE200) += tve200/
>  obj-$(CONFIG_DRM_XEN) += xen/
> +obj-$(CONFIG_DRM_MCDE) += mcde/
> diff --git a/drivers/gpu/drm/mcde/Kconfig b/drivers/gpu/drm/mcde/Kconfig
> new file mode 100644
> index 000000000000..b3990126562c
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/Kconfig
> @@ -0,0 +1,18 @@
> +config DRM_MCDE
> +	tristate "DRM Support for ST-Ericsson MCDE (Multichannel Display Engine)"
> +	depends on DRM
> +	depends on CMA
> +	depends on ARM || COMPILE_TEST
> +	depends on OF
> +	select MFD_SYSCON
> +	select DRM_MIPI_DSI
> +	select DRM_BRIDGE
> +	select DRM_PANEL_BRIDGE
> +	select DRM_KMS_HELPER
> +	select DRM_KMS_CMA_HELPER
> +	select DRM_GEM_CMA_HELPER
> +	select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
> +	help
> +	  Choose this option for DRM support for the ST-Ericsson MCDE
> +	  Multi-Channel Display Engine.
> +	  If M is selected the module will be called mcde_drm.
> diff --git a/drivers/gpu/drm/mcde/Makefile b/drivers/gpu/drm/mcde/Makefile
> new file mode 100644
> index 000000000000..fe28f4e0fe46
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/Makefile
> @@ -0,0 +1,3 @@
> +mcde_drm-y +=	mcde_drv.o mcde_dsi.o mcde_display.o
> +
> +obj-$(CONFIG_DRM_MCDE) += mcde_drm.o
> diff --git a/drivers/gpu/drm/mcde/mcde_display.c b/drivers/gpu/drm/mcde/mcde_display.c
> new file mode 100644
> index 000000000000..efa4c1c752a8
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/mcde_display.c
> @@ -0,0 +1,1284 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2018 Linus Walleij <linus.walleij@xxxxxxxxxx>
> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon
> + * (C) ST-Ericsson SA 2013
> + */
> +#include <linux/clk.h>
> +#include <linux/version.h>
> +#include <linux/dma-buf.h>
> +#include <linux/of_graph.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <video/mipi_display.h>
> +
> +#include "mcde_drm.h"
> +
> +/* PP (pixel processor) interrupts */
> +#define MCDE_IMSCPP 0x00000104
> +#define MCDE_RISPP 0x00000114
> +#define MCDE_MISPP 0x00000124
> +#define MCDE_SISPP 0x00000134
> +
> +#define MCDE_PP_VCMPA BIT(0)
> +#define MCDE_PP_VCMPB BIT(1)
> +#define MCDE_PP_VSCC0 BIT(2)
> +#define MCDE_PP_VSCC1 BIT(3)
> +#define MCDE_PP_VCMPC0 BIT(4)
> +#define MCDE_PP_VCMPC1 BIT(5)
> +#define MCDE_PP_ROTFD_A BIT(6)
> +#define MCDE_PP_ROTFD_B BIT(7)
> +
> +/* Overlay interrupts */
> +#define MCDE_IMSCOVL 0x00000108
> +#define MCDE_RISOVL 0x00000118
> +#define MCDE_MISOVL 0x00000128
> +#define MCDE_SISOVL 0x00000138
> +
> +/* Channel interrupts */
> +#define MCDE_IMSCCHNL 0x0000010C
> +#define MCDE_RISCHNL 0x0000011C
> +#define MCDE_MISCHNL 0x0000012C
> +#define MCDE_SISCHNL 0x0000013C
> +
> +/* X = 0..9 */
> +#define MCDE_EXTSRCXA0 0x00000200
> +#define MCDE_EXTSRCXA0_GROUPOFFSET 0x20
> +#define MCDE_EXTSRCXA0_BASEADDRESS0_SHIFT 3
> +#define MCDE_EXTSRCXA0_BASEADDRESS0_MASK 0xFFFFFFF8
> +
> +#define MCDE_EXTSRCXA1 0x00000204
> +#define MCDE_EXTSRCXA1_GROUPOFFSET 0x20
> +#define MCDE_EXTSRCXA1_BASEADDRESS1_SHIFT 3
> +#define MCDE_EXTSRCXA1_BASEADDRESS1_MASK 0xFFFFFFF8
> +
> +/* External sources 0..9 */
> +#define MCDE_EXTSRC0CONF 0x0000020C
> +#define MCDE_EXTSRC1CONF 0x0000022C
> +#define MCDE_EXTSRC2CONF 0x0000024C
> +#define MCDE_EXTSRC3CONF 0x0000026C
> +#define MCDE_EXTSRC4CONF 0x0000028C
> +#define MCDE_EXTSRC5CONF 0x000002AC
> +#define MCDE_EXTSRC6CONF 0x000002CC
> +#define MCDE_EXTSRC7CONF 0x000002EC
> +#define MCDE_EXTSRC8CONF 0x0000030C
> +#define MCDE_EXTSRC9CONF 0x0000032C
> +#define MCDE_EXTSRCXCONF_GROUPOFFSET 0x20
> +#define MCDE_EXTSRCXCONF_BUF_ID_SHIFT 0
> +#define MCDE_EXTSRCXCONF_BUF_ID_MASK 0x00000003
> +#define MCDE_EXTSRCXCONF_BUF_NB_SHIFT 2
> +#define MCDE_EXTSRCXCONF_BUF_NB_MASK 0x0000000C
> +#define MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT 4
> +#define MCDE_EXTSRCXCONF_PRI_OVLID_MASK 0x000000F0
> +#define MCDE_EXTSRCXCONF_BPP_SHIFT 8
> +#define MCDE_EXTSRCXCONF_BPP_MASK 0x00000F00
> +#define MCDE_EXTSRCXCONF_BPP_1BPP_PAL 0
> +#define MCDE_EXTSRCXCONF_BPP_2BPP_PAL 1
> +#define MCDE_EXTSRCXCONF_BPP_4BPP_PAL 2
> +#define MCDE_EXTSRCXCONF_BPP_8BPP_PAL 3
> +#define MCDE_EXTSRCXCONF_BPP_RGB444 4
> +#define MCDE_EXTSRCXCONF_BPP_ARGB4444 5
> +#define MCDE_EXTSRCXCONF_BPP_IRGB1555 6
> +#define MCDE_EXTSRCXCONF_BPP_RGB565 7
> +#define MCDE_EXTSRCXCONF_BPP_RGB888 8
> +#define MCDE_EXTSRCXCONF_BPP_XRGB8888 9
> +#define MCDE_EXTSRCXCONF_BPP_ARGB8888 10
> +#define MCDE_EXTSRCXCONF_BPP_YCBCR422 11
> +#define MCDE_EXTSRCXCONF_BGR BIT(12)
> +#define MCDE_EXTSRCXCONF_BEBO BIT(13)
> +#define MCDE_EXTSRCXCONF_BEPO BIT(14)
> +#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_SHIFT 16
> +#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_MASK 0x0FFF0000
> +
> +/* External sources 0..9 */
> +#define MCDE_EXTSRC0CR 0x00000210
> +#define MCDE_EXTSRC1CR 0x00000230
> +#define MCDE_EXTSRC2CR 0x00000250
> +#define MCDE_EXTSRC3CR 0x00000270
> +#define MCDE_EXTSRC4CR 0x00000290
> +#define MCDE_EXTSRC5CR 0x000002B0
> +#define MCDE_EXTSRC6CR 0x000002D0
> +#define MCDE_EXTSRC7CR 0x000002F0
> +#define MCDE_EXTSRC8CR 0x00000310
> +#define MCDE_EXTSRC9CR 0x00000330
> +#define MCDE_EXTSRC0CR_SEL_MOD_SHIFT 0
> +#define MCDE_EXTSRC0CR_SEL_MOD_MASK 0x00000003
> +#define MCDE_EXTSRC0CR_SEL_MOD_EXTERNAL_SEL 0
> +#define MCDE_EXTSRC0CR_SEL_MOD_AUTO_TOGGLE 1
> +#define MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL 2
> +#define MCDE_EXTSRC0CR_MULTIOVL_CTRL_PRIMARY BIT(2) /* 0 = all */
> +#define MCDE_EXTSRC0CR_FS_DIV_DISABLE BIT(3)
> +#define MCDE_EXTSRC0CR_FORCE_FS_DIV BIT(4)
> +
> +/* Only external source 6 has a second address register */
> +#define MCDE_EXTSRC6A2 0x000002C8
> +
> +/* 6 overlays */
> +#define MCDE_OVL0CR 0x00000400
> +#define MCDE_OVL1CR 0x00000420
> +#define MCDE_OVL2CR 0x00000440
> +#define MCDE_OVL3CR 0x00000460
> +#define MCDE_OVL4CR 0x00000480
> +#define MCDE_OVL5CR 0x000004A0
> +#define MCDE_OVLXCR_OVLEN BIT(0)
> +#define MCDE_OVLXCR_COLCCTRL_DISABLED 0
> +#define MCDE_OVLXCR_COLCCTRL_ENABLED_NO_SAT (1 << 1)
> +#define MCDE_OVLXCR_COLCCTRL_ENABLED_SAT (2 << 1)
> +#define MCDE_OVLXCR_CKEYGEN BIT(3)
> +#define MCDE_OVLXCR_ALPHAPMEN BIT(4)
> +#define MCDE_OVLXCR_OVLF BIT(5)
> +#define MCDE_OVLXCR_OVLR BIT(6)
> +#define MCDE_OVLXCR_OVLB BIT(7)
> +#define MCDE_OVLXCR_FETCH_ROPC_SHIFT 8
> +#define MCDE_OVLXCR_FETCH_ROPC_MASK 0x0000FF00
> +#define MCDE_OVLXCR_STBPRIO_SHIFT 16
> +#define MCDE_OVLXCR_STBPRIO_MASK 0x000F0000
> +#define MCDE_OVLXCR_BURSTSIZE_SHIFT 20
> +#define MCDE_OVLXCR_BURSTSIZE_MASK 0x00F00000
> +#define MCDE_OVLXCR_BURSTSIZE_1W 0
> +#define MCDE_OVLXCR_BURSTSIZE_2W 1
> +#define MCDE_OVLXCR_BURSTSIZE_4W 2
> +#define MCDE_OVLXCR_BURSTSIZE_8W 3
> +#define MCDE_OVLXCR_BURSTSIZE_16W 4
> +#define MCDE_OVLXCR_BURSTSIZE_HW_1W 8
> +#define MCDE_OVLXCR_BURSTSIZE_HW_2W 9
> +#define MCDE_OVLXCR_BURSTSIZE_HW_4W 10
> +#define MCDE_OVLXCR_BURSTSIZE_HW_8W 11
> +#define MCDE_OVLXCR_BURSTSIZE_HW_16W 12
> +#define MCDE_OVLXCR_MAXOUTSTANDING_SHIFT 24
> +#define MCDE_OVLXCR_MAXOUTSTANDING_MASK 0x0F000000
> +#define MCDE_OVLXCR_MAXOUTSTANDING_1_REQ 0
> +#define MCDE_OVLXCR_MAXOUTSTANDING_2_REQ 1
> +#define MCDE_OVLXCR_MAXOUTSTANDING_4_REQ 2
> +#define MCDE_OVLXCR_MAXOUTSTANDING_8_REQ 3
> +#define MCDE_OVLXCR_MAXOUTSTANDING_16_REQ 4
> +#define MCDE_OVLXCR_ROTBURSTSIZE_SHIFT 28
> +#define MCDE_OVLXCR_ROTBURSTSIZE_MASK 0xF0000000
> +#define MCDE_OVLXCR_ROTBURSTSIZE_1W 0
> +#define MCDE_OVLXCR_ROTBURSTSIZE_2W 1
> +#define MCDE_OVLXCR_ROTBURSTSIZE_4W 2
> +#define MCDE_OVLXCR_ROTBURSTSIZE_8W 3
> +#define MCDE_OVLXCR_ROTBURSTSIZE_16W 4
> +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_1W 8
> +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_2W 9
> +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_4W 10
> +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_8W 11
> +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_16W 12
> +
> +#define MCDE_OVL0CONF 0x00000404
> +#define MCDE_OVL1CONF 0x00000424
> +#define MCDE_OVL2CONF 0x00000444
> +#define MCDE_OVL3CONF 0x00000464
> +#define MCDE_OVL4CONF 0x00000484
> +#define MCDE_OVL5CONF 0x000004A4
> +#define MCDE_OVLXCONF_PPL_SHIFT 0
> +#define MCDE_OVLXCONF_PPL_MASK 0x000007FF
> +#define MCDE_OVLXCONF_EXTSRC_ID_SHIFT 11
> +#define MCDE_OVLXCONF_EXTSRC_ID_MASK 0x00007800
> +#define MCDE_OVLXCONF_LPF_SHIFT 16
> +#define MCDE_OVLXCONF_LPF_MASK 0x07FF0000
> +
> +#define MCDE_OVL0CONF2 0x00000408
> +#define MCDE_OVL1CONF2 0x00000428
> +#define MCDE_OVL2CONF2 0x00000448
> +#define MCDE_OVL3CONF2 0x00000468
> +#define MCDE_OVL4CONF2 0x00000488
> +#define MCDE_OVL5CONF2 0x000004A8
> +#define MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA 0
> +#define MCDE_OVLXCONF2_BP_CONSTANT_ALPHA BIT(0)
> +#define MCDE_OVLXCONF2_ALPHAVALUE_SHIFT 1
> +#define MCDE_OVLXCONF2_ALPHAVALUE_MASK 0x000001FE
> +#define MCDE_OVLXCONF2_OPQ BIT(9)
> +#define MCDE_OVLXCONF2_PIXOFF_SHIFT 10
> +#define MCDE_OVLXCONF2_PIXOFF_MASK 0x0000FC00
> +#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT 16
> +#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_MASK 0x1FFF0000
> +
> +#define MCDE_OVL0LJINC 0x0000040C
> +#define MCDE_OVL1LJINC 0x0000042C
> +#define MCDE_OVL2LJINC 0x0000044C
> +#define MCDE_OVL3LJINC 0x0000046C
> +#define MCDE_OVL4LJINC 0x0000048C
> +#define MCDE_OVL5LJINC 0x000004AC
> +
> +#define MCDE_OVL0CROP 0x00000410
> +#define MCDE_OVL1CROP 0x00000430
> +#define MCDE_OVL2CROP 0x00000450
> +#define MCDE_OVL3CROP 0x00000470
> +#define MCDE_OVL4CROP 0x00000490
> +#define MCDE_OVL5CROP 0x000004B0
> +#define MCDE_OVLXCROP_TMRGN_SHIFT 0
> +#define MCDE_OVLXCROP_TMRGN_MASK 0x003FFFFF
> +#define MCDE_OVLXCROP_LMRGN_SHIFT 22
> +#define MCDE_OVLXCROP_LMRGN_MASK 0xFFC00000
> +
> +#define MCDE_OVL0COMP 0x00000414
> +#define MCDE_OVL1COMP 0x00000434
> +#define MCDE_OVL2COMP 0x00000454
> +#define MCDE_OVL3COMP 0x00000474
> +#define MCDE_OVL4COMP 0x00000494
> +#define MCDE_OVL5COMP 0x000004B4
> +#define MCDE_OVLXCOMP_XPOS_SHIFT 0
> +#define MCDE_OVLXCOMP_XPOS_MASK 0x000007FF
> +#define MCDE_OVLXCOMP_CH_ID_SHIFT 11
> +#define MCDE_OVLXCOMP_CH_ID_MASK 0x00007800
> +#define MCDE_OVLXCOMP_YPOS_SHIFT 16
> +#define MCDE_OVLXCOMP_YPOS_MASK 0x07FF0000
> +#define MCDE_OVLXCOMP_Z_SHIFT 27
> +#define MCDE_OVLXCOMP_Z_MASK 0x78000000
> +
> +#define MCDE_CRC 0x00000C00
> +#define MCDE_CRC_C1EN BIT(2)
> +#define MCDE_CRC_C2EN BIT(3)
> +#define MCDE_CRC_SYCEN0 BIT(7)
> +#define MCDE_CRC_SYCEN1 BIT(8)
> +#define MCDE_CRC_SIZE1 BIT(9)
> +#define MCDE_CRC_SIZE2 BIT(10)
> +#define MCDE_CRC_YUVCONVC1EN BIT(15)
> +#define MCDE_CRC_CS1EN BIT(16)
> +#define MCDE_CRC_CS2EN BIT(17)
> +#define MCDE_CRC_CS1POL BIT(19)
> +#define MCDE_CRC_CS2POL BIT(20)
> +#define MCDE_CRC_CD1POL BIT(21)
> +#define MCDE_CRC_CD2POL BIT(22)
> +#define MCDE_CRC_WR1POL BIT(23)
> +#define MCDE_CRC_WR2POL BIT(24)
> +#define MCDE_CRC_RD1POL BIT(25)
> +#define MCDE_CRC_RD2POL BIT(26)
> +#define MCDE_CRC_SYNCCTRL_SHIFT 29
> +#define MCDE_CRC_SYNCCTRL_MASK 0x60000000
> +#define MCDE_CRC_SYNCCTRL_NO_SYNC 0
> +#define MCDE_CRC_SYNCCTRL_DBI0 1
> +#define MCDE_CRC_SYNCCTRL_DBI1 2
> +#define MCDE_CRC_SYNCCTRL_PING_PONG 3
> +#define MCDE_CRC_CLAMPC1EN BIT(31)
> +
> +#define MCDE_VSCRC0 0x00000C5C
> +#define MCDE_VSCRC1 0x00000C60
> +#define MCDE_VSCRC_VSPMIN_MASK 0x00000FFF
> +#define MCDE_VSCRC_VSPMAX_SHIFT 12
> +#define MCDE_VSCRC_VSPMAX_MASK 0x00FFF000
> +#define MCDE_VSCRC_VSPDIV_SHIFT 24
> +#define MCDE_VSCRC_VSPDIV_MASK 0x07000000
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_1 0
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_2 1
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_4 2
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_8 3
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_16 4
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_32 5
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_64 6
> +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_128 7
> +#define MCDE_VSCRC_VSPOL BIT(27) /* 0 active high, 1 active low */
> +#define MCDE_VSCRC_VSSEL BIT(28) /* 0 VSYNC0, 1 VSYNC1 */
> +#define MCDE_VSCRC_VSDBL BIT(29)
> +
> +/* Channel config 0..3 */
> +#define MCDE_CHNL0CONF 0x00000600
> +#define MCDE_CHNL1CONF 0x00000620
> +#define MCDE_CHNL2CONF 0x00000640
> +#define MCDE_CHNL3CONF 0x00000660
> +#define MCDE_CHNLXCONF_PPL_SHIFT 0
> +#define MCDE_CHNLXCONF_PPL_MASK 0x000007FF
> +#define MCDE_CHNLXCONF_LPF_SHIFT 16
> +#define MCDE_CHNLXCONF_LPF_MASK 0x07FF0000
> +#define MCDE_MAX_WIDTH 2048
> +
> +/* Channel status 0..3 */
> +#define MCDE_CHNL0STAT 0x00000604
> +#define MCDE_CHNL1STAT 0x00000624
> +#define MCDE_CHNL2STAT 0x00000644
> +#define MCDE_CHNL3STAT 0x00000664
> +#define MCDE_CHNLXSTAT_CHNLRD BIT(0)
> +#define MCDE_CHNLXSTAT_CHNLA BIT(1)
> +#define MCDE_CHNLXSTAT_CHNLBLBCKGND_EN BIT(16)
> +#define MCDE_CHNLXSTAT_PPLX2_V422 BIT(17)
> +#define MCDE_CHNLXSTAT_LPFX2_V422 BIT(18)
> +
> +/* Sync settings for channel 0..3 */
> +#define MCDE_CHNL0SYNCHMOD 0x00000608
> +#define MCDE_CHNL1SYNCHMOD 0x00000628
> +#define MCDE_CHNL2SYNCHMOD 0x00000648
> +#define MCDE_CHNL3SYNCHMOD 0x00000668
> +
> +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT 0
> +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_MASK 0x00000003
> +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE 0
> +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_NO_SYNCH 1
> +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE 2
> +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT 2
> +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_MASK 0x0000001C
> +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER 0
> +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 1
> +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE1 2
> +
> +/* Software sync triggers for channel 0..3 */
> +#define MCDE_CHNL0SYNCHSW 0x0000060C
> +#define MCDE_CHNL1SYNCHSW 0x0000062C
> +#define MCDE_CHNL2SYNCHSW 0x0000064C
> +#define MCDE_CHNL3SYNCHSW 0x0000066C
> +#define MCDE_CHNLXSYNCHSW_SW_TRIG BIT(0)
> +
> +#define MCDE_CHNL0BCKGNDCOL 0x00000610
> +#define MCDE_CHNL1BCKGNDCOL 0x00000630
> +#define MCDE_CHNL2BCKGNDCOL 0x00000650
> +#define MCDE_CHNL3BCKGNDCOL 0x00000670
> +#define MCDE_CHNLXBCKGNDCOL_B_SHIFT 0
> +#define MCDE_CHNLXBCKGNDCOL_B_MASK 0x000000FF
> +#define MCDE_CHNLXBCKGNDCOL_G_SHIFT 8
> +#define MCDE_CHNLXBCKGNDCOL_G_MASK 0x0000FF00
> +#define MCDE_CHNLXBCKGNDCOL_R_SHIFT 16
> +#define MCDE_CHNLXBCKGNDCOL_R_MASK 0x00FF0000
> +
> +#define MCDE_CHNL0MUXING 0x00000614
> +#define MCDE_CHNL1MUXING 0x00000634
> +#define MCDE_CHNL2MUXING 0x00000654
> +#define MCDE_CHNL3MUXING 0x00000674
> +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_A 0
> +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_B 1
> +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C0 2
> +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C1 3
> +
> +/* Pixel processing control registers for channel A B,  */
> +#define MCDE_CRA0 0x00000800
> +#define MCDE_CRB0 0x00000A00
> +#define MCDE_CRX0_FLOEN BIT(0)
> +#define MCDE_CRX0_POWEREN BIT(1)
> +#define MCDE_CRX0_BLENDEN BIT(2)
> +#define MCDE_CRX0_AFLICKEN BIT(3)
> +#define MCDE_CRX0_PALEN BIT(4)
> +#define MCDE_CRX0_DITHEN BIT(5)
> +#define MCDE_CRX0_GAMEN BIT(6)
> +#define MCDE_CRX0_KEYCTRL_SHIFT 7
> +#define MCDE_CRX0_KEYCTRL_MASK 0x00000380
> +#define MCDE_CRX0_KEYCTRL_OFF 0
> +#define MCDE_CRX0_KEYCTRL_ALPHA_RGB 1
> +#define MCDE_CRX0_KEYCTRL_RGB 2
> +#define MCDE_CRX0_KEYCTRL_FALPHA_FRGB 4
> +#define MCDE_CRX0_KEYCTRL_FRGB 5
> +#define MCDE_CRX0_BLENDCTRL BIT(10)
> +#define MCDE_CRX0_FLICKMODE_SHIFT 11
> +#define MCDE_CRX0_FLICKMODE_MASK 0x00001800
> +#define MCDE_CRX0_FLICKMODE_FORCE_FILTER_0 0
> +#define MCDE_CRX0_FLICKMODE_ADAPTIVE 1
> +#define MCDE_CRX0_FLICKMODE_TEST_MODE 2
> +#define MCDE_CRX0_FLOCKFORMAT_RGB BIT(13) /* 0 = YCVCR */
> +#define MCDE_CRX0_PALMODE_GAMMA BIT(14) /* 0 = palette */
> +#define MCDE_CRX0_OLEDEN BIT(15)
> +#define MCDE_CRX0_ALPHABLEND_SHIFT 16
> +#define MCDE_CRX0_ALPHABLEND_MASK 0x00FF0000
> +#define MCDE_CRX0_ROTEN BIT(24)
> +
> +#define MCDE_CRA1 0x00000804
> +#define MCDE_CRB1 0x00000A04
> +#define MCDE_CRX1_PCD_SHIFT 0
> +#define MCDE_CRX1_PCD_MASK 0x000003FF
> +#define MCDE_CRX1_CLKSEL_SHIFT 10
> +#define MCDE_CRX1_CLKSEL_MASK 0x00001C00
> +#define MCDE_CRX1_CLKSEL_CLKPLL72 0
> +#define MCDE_CRX1_CLKSEL_CLKPLL27 2
> +#define MCDE_CRX1_CLKSEL_TV1CLK 3
> +#define MCDE_CRX1_CLKSEL_TV2CLK 4
> +#define MCDE_CRX1_CLKSEL_MCDECLK 5
> +#define MCDE_CRX1_CDWIN_SHIFT 13
> +#define MCDE_CRX1_CDWIN_MASK 0x0001E000
> +#define MCDE_CRX1_CDWIN_8BPP_C1 0
> +#define MCDE_CRX1_CDWIN_12BPP_C1 1
> +#define MCDE_CRX1_CDWIN_12BPP_C2 2
> +#define MCDE_CRX1_CDWIN_16BPP_C1 3
> +#define MCDE_CRX1_CDWIN_16BPP_C2 4
> +#define MCDE_CRX1_CDWIN_16BPP_C3 5
> +#define MCDE_CRX1_CDWIN_18BPP_C1 6
> +#define MCDE_CRX1_CDWIN_18BPP_C2 7
> +#define MCDE_CRX1_CDWIN_24BPP 8
> +#define MCDE_CRX1_OUTBPP_SHIFT 25
> +#define MCDE_CRX1_OUTBPP_MASK 0x1E000000
> +#define MCDE_CRX1_OUTBPP_MONO1 0
> +#define MCDE_CRX1_OUTBPP_MONO2 1
> +#define MCDE_CRX1_OUTBPP_MONO4 2
> +#define MCDE_CRX1_OUTBPP_MONO8 3
> +#define MCDE_CRX1_OUTBPP_8BPP 4
> +#define MCDE_CRX1_OUTBPP_12BPP 5
> +#define MCDE_CRX1_OUTBPP_15BPP 6
> +#define MCDE_CRX1_OUTBPP_16BPP 7
> +#define MCDE_CRX1_OUTBPP_18BPP 8
> +#define MCDE_CRX1_OUTBPP_24BPP 9
> +#define MCDE_CRX1_BCD BIT(29)
> +#define MCDE_CRA1_CLKTYPE_TVXCLKSEL1 BIT(30) /* 0 = TVXCLKSEL1 */
> +
> +#define MCDE_COLKEYA 0x00000808
> +#define MCDE_COLKEYB 0x00000A08
> +
> +#define MCDE_FCOLKEYA 0x0000080C
> +#define MCDE_FCOLKEYB 0x00000A0C
> +
> +#define MCDE_RGBCONV1A 0x00000810
> +#define MCDE_RGBCONV1B 0x00000A10
> +
> +#define MCDE_RGBCONV2A 0x00000814
> +#define MCDE_RGBCONV2B 0x00000A14
> +
> +#define MCDE_RGBCONV3A 0x00000818
> +#define MCDE_RGBCONV3B 0x00000A18
> +
> +#define MCDE_RGBCONV4A 0x0000081C
> +#define MCDE_RGBCONV4B 0x00000A1C
> +
> +#define MCDE_RGBCONV5A 0x00000820
> +#define MCDE_RGBCONV5B 0x00000A20
> +
> +#define MCDE_RGBCONV6A 0x00000824
> +#define MCDE_RGBCONV6B 0x00000A24
> +
> +/* Rotation */
> +#define MCDE_ROTACONF 0x0000087C
> +#define MCDE_ROTBCONF 0x00000A7C
> +
> +#define MCDE_SYNCHCONFA 0x00000880
> +#define MCDE_SYNCHCONFB 0x00000A80
> +
> +/* Channel A+B control registers */
> +#define MCDE_CTRLA 0x00000884
> +#define MCDE_CTRLB 0x00000A84
> +#define MCDE_CTRLX_FIFOWTRMRK_SHIFT 0
> +#define MCDE_CTRLX_FIFOWTRMRK_MASK 0x000003FF
> +#define MCDE_CTRLX_FIFOEMPTY BIT(12)
> +#define MCDE_CTRLX_FIFOFULL BIT(13)
> +#define MCDE_CTRLX_FORMID_SHIFT 16
> +#define MCDE_CTRLX_FORMID_MASK 0x00070000
> +#define MCDE_CTRLX_FORMID_DSI0VID 0
> +#define MCDE_CTRLX_FORMID_DSI0CMD 1
> +#define MCDE_CTRLX_FORMID_DSI1VID 2
> +#define MCDE_CTRLX_FORMID_DSI1CMD 3
> +#define MCDE_CTRLX_FORMID_DSI2VID 4
> +#define MCDE_CTRLX_FORMID_DSI2CMD 5
> +#define MCDE_CTRLX_FORMID_DPIA 0
> +#define MCDE_CTRLX_FORMID_DPIB 1
> +#define MCDE_CTRLX_FORMTYPE_SHIFT 20
> +#define MCDE_CTRLX_FORMTYPE_MASK 0x00700000
> +#define MCDE_CTRLX_FORMTYPE_DPITV 0
> +#define MCDE_CTRLX_FORMTYPE_DBI 1
> +#define MCDE_CTRLX_FORMTYPE_DSI 2
> +
> +#define MCDE_DSIVID0CONF0 0x00000E00
> +#define MCDE_DSICMD0CONF0 0x00000E20
> +#define MCDE_DSIVID1CONF0 0x00000E40
> +#define MCDE_DSICMD1CONF0 0x00000E60
> +#define MCDE_DSIVID2CONF0 0x00000E80
> +#define MCDE_DSICMD2CONF0 0x00000EA0
> +#define MCDE_DSICONF0_BLANKING_SHIFT 0
> +#define MCDE_DSICONF0_BLANKING_MASK 0x000000FF
> +#define MCDE_DSICONF0_VID_MODE_CMD 0
> +#define MCDE_DSICONF0_VID_MODE_VID BIT(12)
> +#define MCDE_DSICONF0_CMD8 BIT(13)
> +#define MCDE_DSICONF0_BIT_SWAP BIT(16)
> +#define MCDE_DSICONF0_BYTE_SWAP BIT(17)
> +#define MCDE_DSICONF0_DCSVID_NOTGEN BIT(18)
> +#define MCDE_DSICONF0_PACKING_SHIFT 20
> +#define MCDE_DSICONF0_PACKING_MASK 0x00700000
> +#define MCDE_DSICONF0_PACKING_RGB565 0
> +#define MCDE_DSICONF0_PACKING_RGB666 1
> +#define MCDE_DSICONF0_PACKING_RGB666_PACKED 2
> +#define MCDE_DSICONF0_PACKING_RGB888 3
> +#define MCDE_DSICONF0_PACKING_HDTV 4
> +
> +#define MCDE_DSIVID0FRAME 0x00000E04
> +#define MCDE_DSICMD0FRAME 0x00000E24
> +#define MCDE_DSIVID1FRAME 0x00000E44
> +#define MCDE_DSICMD1FRAME 0x00000E64
> +#define MCDE_DSIVID2FRAME 0x00000E84
> +#define MCDE_DSICMD2FRAME 0x00000EA4
> +
> +#define MCDE_DSIVID0PKT 0x00000E08
> +#define MCDE_DSICMD0PKT 0x00000E28
> +#define MCDE_DSIVID1PKT 0x00000E48
> +#define MCDE_DSICMD1PKT 0x00000E68
> +#define MCDE_DSIVID2PKT 0x00000E88
> +#define MCDE_DSICMD2PKT 0x00000EA8
> +
> +#define MCDE_DSIVID0SYNC 0x00000E0C
> +#define MCDE_DSICMD0SYNC 0x00000E2C
> +#define MCDE_DSIVID1SYNC 0x00000E4C
> +#define MCDE_DSICMD1SYNC 0x00000E6C
> +#define MCDE_DSIVID2SYNC 0x00000E8C
> +#define MCDE_DSICMD2SYNC 0x00000EAC
> +
> +#define MCDE_DSIVID0CMDW 0x00000E10
> +#define MCDE_DSICMD0CMDW 0x00000E30
> +#define MCDE_DSIVID1CMDW 0x00000E50
> +#define MCDE_DSICMD1CMDW 0x00000E70
> +#define MCDE_DSIVID2CMDW 0x00000E90
> +#define MCDE_DSICMD2CMDW 0x00000EB0
> +#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT 0
> +#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_MASK 0x0000FFFF
> +#define MCDE_DSIVIDXCMDW_CMDW_START_SHIFT 16
> +#define MCDE_DSIVIDXCMDW_CMDW_START_MASK 0xFFFF0000
> +
> +#define MCDE_DSIVID0DELAY0 0x00000E14
> +#define MCDE_DSICMD0DELAY0 0x00000E34
> +#define MCDE_DSIVID1DELAY0 0x00000E54
> +#define MCDE_DSICMD1DELAY0 0x00000E74
> +#define MCDE_DSIVID2DELAY0 0x00000E94
> +#define MCDE_DSICMD2DELAY0 0x00000EB4
> +
> +#define MCDE_DSIVID0DELAY1 0x00000E18
> +#define MCDE_DSICMD0DELAY1 0x00000E38
> +#define MCDE_DSIVID1DELAY1 0x00000E58
> +#define MCDE_DSICMD1DELAY1 0x00000E78
> +#define MCDE_DSIVID2DELAY1 0x00000E98
> +#define MCDE_DSICMD2DELAY1 0x00000EB8
> +
> +void mcde_display_irq(struct mcde *mcde)
> +{
> +	u32 mispp, misovl, mischnl;
> +	bool vblank;
> +
> +	/* Handle display IRQs */
> +	mispp = readl(mcde->regs + MCDE_MISPP);
> +	misovl = readl(mcde->regs + MCDE_MISOVL);
> +	mischnl = readl(mcde->regs + MCDE_MISCHNL);
> +
> +	/*
> +	 * Handle IRQs from the DSI link. All IRQs from the DSI links
> +	 * are just latched onto the MCDE IRQ line, so we need to traverse
> +	 * any active DSI masters and check if an IRQ is originating from
> +	 * them.
> +	 *
> +	 * FIXME: Currently only one DSI link is supported.

If it's possible to have more you might be out of scope for the simple kms
helpers ... but if there's only ever a fixed routing, then it would still
fit, you can have a bunch of simple display pipes in on driver.

> +	 */
> +	if (mcde_dsi_irq(mcde->mdsi)) {
> +		u32 val;
> +
> +		/*
> +		 * In oneshot mode we do not send continuous updates
> +		 * to the display, instead we only push out updates when
> +		 * the update function is called, then we disable the
> +		 * flow on the channel once we get the TE IRQ.
> +		 */
> +		if (mcde->oneshot_mode) {
> +			spin_lock(&mcde->flow_lock);
> +			if (--mcde->flow_active == 0) {
> +				dev_dbg(mcde->dev, "TE0 IRQ\n");
> +				/* Disable FIFO A flow */
> +				val = readl(mcde->regs + MCDE_CRA0);
> +				val &= ~MCDE_CRX0_FLOEN;
> +				writel(val, mcde->regs + MCDE_CRA0);
> +			}
> +			spin_unlock(&mcde->flow_lock);
> +		}
> +	}
> +
> +	/* Vblank from one of the channels */
> +	if (mispp & MCDE_PP_VCMPA) {
> +		dev_dbg(mcde->dev, "chnl A vblank IRQ\n");
> +		vblank = true;
> +	}
> +	if (mispp & MCDE_PP_VCMPB) {
> +		dev_dbg(mcde->dev, "chnl B vblank IRQ\n");
> +		vblank = true;
> +	}
> +	if (mispp & MCDE_PP_VCMPC0)
> +		dev_dbg(mcde->dev, "chnl C0 vblank IRQ\n");
> +	if (mispp & MCDE_PP_VCMPC1)
> +		dev_dbg(mcde->dev, "chnl C1 vblank IRQ\n");
> +	if (mispp & MCDE_PP_VSCC0)
> +		dev_dbg(mcde->dev, "chnl C0 TE IRQ\n");
> +	if (mispp & MCDE_PP_VSCC1)
> +		dev_dbg(mcde->dev, "chnl C1 TE IRQ\n");
> +	writel(mispp, mcde->regs + MCDE_RISPP);
> +
> +	if (vblank)
> +		drm_crtc_handle_vblank(&mcde->pipe.crtc);
> +
> +	if (misovl)
> +		dev_info(mcde->dev, "some stray overlay IRQ %08x\n", misovl);
> +	writel(misovl, mcde->regs + MCDE_RISOVL);
> +
> +	if (mischnl)
> +		dev_info(mcde->dev, "some stray channel error IRQ %08x\n",
> +			 mischnl);
> +	writel(mischnl, mcde->regs + MCDE_RISCHNL);
> +}
> +
> +void mcde_display_disable_irqs(struct mcde *mcde)
> +{
> +	/* Disable all IRQs */
> +	writel(0, mcde->regs + MCDE_IMSCPP);
> +	writel(0, mcde->regs + MCDE_IMSCOVL);
> +	writel(0, mcde->regs + MCDE_IMSCCHNL);
> +
> +	/* Clear any pending IRQs */
> +	writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP);
> +	writel(0xFFFFFFFF, mcde->regs + MCDE_RISOVL);
> +	writel(0xFFFFFFFF, mcde->regs + MCDE_RISCHNL);
> +}
> +
> +static int mcde_display_check(struct drm_simple_display_pipe *pipe,
> +			       struct drm_plane_state *pstate,
> +			       struct drm_crtc_state *cstate)
> +{
> +	const struct drm_display_mode *mode = &cstate->mode;
> +	struct drm_framebuffer *old_fb = pipe->plane.state->fb;
> +	struct drm_framebuffer *fb = pstate->fb;
> +
> +	if (fb) {
> +		u32 offset = drm_fb_cma_get_gem_addr(fb, pstate, 0);
> +
> +		/* FB base address must be dword aligned. */
> +		if (offset & 3) {
> +			DRM_DEBUG_KMS("FB not 32-bit aligned\n");
> +			return -EINVAL;
> +		}
> +
> +		/*
> +		 * There's no pitch register, the mode's hdisplay
> +		 * controls this.
> +		 */
> +		if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) {
> +			DRM_DEBUG_KMS("can't handle pitches\n");
> +			return -EINVAL;
> +		}
> +
> +		/*
> +		 * We can't change the FB format in a flicker-free
> +		 * manner (and only update it during CRTC enable).
> +		 */
> +		if (old_fb && old_fb->format != fb->format)
> +			cstate->mode_changed = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static int mcde_dsi_get_pkt_div(int ppl, int fifo_size)
> +{
> +	/*
> +	 * DSI command mode line packets should be split into an even number of
> +	 * packets smaller than or equal to the fifo size.
> +	 */
> +	int div;
> +	const int max_div = DIV_ROUND_UP(MCDE_MAX_WIDTH, fifo_size);
> +
> +	for (div = 1; div < max_div; div++)
> +		if (ppl % div == 0 && ppl / div <= fifo_size)
> +			return div;
> +	return 1;
> +}
> +
> +static void mcde_display_enable(struct drm_simple_display_pipe *pipe,
> +				struct drm_crtc_state *cstate,
> +				struct drm_plane_state *plane_state)
> +{
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	struct drm_plane *plane = &pipe->plane;
> +	struct drm_device *drm = crtc->dev;
> +	struct mcde *mcde = drm->dev_private;
> +	const struct drm_display_mode *mode = &cstate->mode;
> +	struct drm_framebuffer *fb = plane->state->fb;
> +	u32 format = fb->format->format;
> +	u32 formatter_ppl = mode->hdisplay; /* pixels per line */
> +	u32 formatter_lpf = mode->vdisplay; /* lines per frame */
> +	int pkt_size, fifo_wtrmrk;
> +	int cpp = drm_format_plane_cpp(format, 0);
> +	int formatter_cpp;
> +	struct drm_format_name_buf tmp;
> +	u32 formatter_frame;
> +	u32 pkt_div;
> +	u32 val;
> +
> +	dev_info(drm->dev, "enable MCDE, %d x %d format %s\n",
> +		 mode->hdisplay, mode->vdisplay,
> +		 drm_get_format_name(format, &tmp));
> +	if (!mcde->mdsi) {
> +		/* TODO: deal with this for non-DSI output */
> +		dev_err(drm->dev, "no DSI master attached!\n");
> +		return;
> +	}
> +
> +	dev_info(drm->dev, "output in %s mode, format %dbpp\n",
> +		 (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ?
> +		 "VIDEO" : "CMD",
> +		 mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format));
> +	formatter_cpp =
> +		mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format) / 8;
> +	dev_info(drm->dev, "overlay CPP %d bytes, DSI CPP %d bytes\n",
> +		 cpp,
> +		 formatter_cpp);
> +
> +	/* Calculations from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() */
> +
> +	/*
> +	 * Set up FIFO A watermark level:
> +	 * 128 for LCD 32bpp video mode
> +	 * 48  for LCD 32bpp command mode
> +	 * 128 for LCD 16bpp video mode
> +	 * 64  for LCD 16bpp command mode
> +	 * 128 for HDMI 32bpp
> +	 * 192 for HDMI 16bpp
> +	 */
> +	fifo_wtrmrk = mode->hdisplay;
> +	if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
> +		fifo_wtrmrk = min(fifo_wtrmrk, 128);
> +		pkt_div = 1;
> +	} else {
> +		fifo_wtrmrk = min(fifo_wtrmrk, 48);
> +		/* The FIFO is 640 entries deep on this v3 hardware */
> +		pkt_div = mcde_dsi_get_pkt_div(mode->hdisplay, 640);
> +	}
> +	dev_dbg(drm->dev, "FIFO watermark after flooring: %d bytes\n",
> +		 fifo_wtrmrk);
> +	dev_dbg(drm->dev, "Packet divisor: %d bytes\n", pkt_div);
> +
> +
> +	/* NOTE: pkt_div is 1 for video mode */
> +	pkt_size = (formatter_ppl * formatter_cpp) / pkt_div;
> +	/* Commands CMD8 need one extra byte */
> +	if (!(mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO))
> +		pkt_size++;
> +
> +	dev_dbg(drm->dev, "DSI packet size: %d * %d bytes per line\n",
> +		pkt_size, pkt_div);
> +	dev_dbg(drm->dev, "Overlay frame size: %u bytes\n",
> +		mode->hdisplay * mode->vdisplay * cpp);
> +	mcde->stride = mode->hdisplay * cpp;
> +	dev_dbg(drm->dev, "Overlay line stride: %u bytes\n",
> +		mcde->stride);
> +	/* NOTE: pkt_div is 1 for video mode */
> +	formatter_frame = pkt_size * pkt_div * formatter_lpf;
> +	dev_dbg(drm->dev, "Formatter frame size: %u bytes\n", formatter_frame);
> +
> +	/* Check that the hardware on channel A is in a sane state */
> +	val = readl(mcde->regs + MCDE_CTRLA);
> +	if (!(val & MCDE_CTRLX_FIFOEMPTY)) {
> +		int timeout = 100;
> +
> +		dev_err(drm->dev, "Channel A FIFO not empty (handover)\n");
> +		/* Attempt to clear the FIFO */
> +		/* Enable FIFO A flow */
> +		val = readl(mcde->regs + MCDE_CRA0);
> +		val |= MCDE_CRX0_FLOEN;
> +		writel(val, mcde->regs + MCDE_CRA0);
> +		/* Trigger a software sync out on channel 0 */
> +		writel(MCDE_CHNLXSYNCHSW_SW_TRIG,
> +		       mcde->regs + MCDE_CHNL0SYNCHSW);
> +		/* Disable FIFO A flow again */
> +		val = readl(mcde->regs + MCDE_CRA0);
> +		val &= ~MCDE_CRX0_FLOEN;
> +		writel(val, mcde->regs + MCDE_CRA0);
> +		while (readl(mcde->regs + MCDE_CRA0) & MCDE_CRX0_FLOEN) {
> +			usleep_range(1000, 1500);
> +			if (!--timeout) {
> +				dev_err(drm->dev,
> +					"FIFO A timeout while clearing\n");
> +				break;
> +			}
> +		}
> +	}
> +
> +	/* Set up FIFO A and channel 0 (based on chnl_update_registers()) */
> +
> +	if (mcde->te_sync) {
> +		/*
> +		 * Turn on hardware TE0 synchronization
> +		 */
> +		val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE
> +			<< MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT;
> +		val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0
> +			<< MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT;
> +	} else {
> +		/*
> +		 * Set up sync source to software, out sync formatter
> +		 * Code mostly from mcde_hw.c chnl_update_registers()
> +		 */
> +		val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE
> +			<< MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT;
> +		val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER
> +			<< MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT;
> +	}
> +	writel(val, mcde->regs + MCDE_CHNL0SYNCHMOD);
> +
> +	/* Set up FIFO for channel A */
> +	val = fifo_wtrmrk << MCDE_CTRLX_FIFOWTRMRK_SHIFT;
> +	/* We only support DSI formatting for now */
> +	val |= MCDE_CTRLX_FORMTYPE_DSI <<
> +		MCDE_CTRLX_FORMTYPE_SHIFT;
> +	/* Use formatter 0 for FIFO A */
> +	val |= 0 << MCDE_CTRLX_FORMID_SHIFT;
> +	writel(val, mcde->regs + MCDE_CTRLA);
> +
> +	/* Set up muxing: connect channel 0 to FIFO A */
> +	writel(MCDE_CHNLXMUXING_FIFO_ID_FIFO_A,
> +	       mcde->regs + MCDE_CHNL0MUXING);
> +
> +	/* Pixel-per-line and line-per-frame set-up for the channel */
> +	val = (mode->hdisplay - 1) << MCDE_CHNLXCONF_PPL_SHIFT;
> +	val |= (mode->vdisplay - 1) << MCDE_CHNLXCONF_LPF_SHIFT;
> +	writel(val, mcde->regs + MCDE_CHNL0CONF);
> +
> +	/*
> +	 * Normalize color conversion:
> +	 * black background, OLED conversion disable on channel 0
> +	 * FIFO A, no rotation.
> +	 */
> +	val = MCDE_CHNLXSTAT_CHNLBLBCKGND_EN |
> +		MCDE_CHNLXSTAT_CHNLRD;
> +	writel(val, mcde->regs + MCDE_CHNL0STAT);
> +	writel(0, mcde->regs + MCDE_CHNL0BCKGNDCOL);
> +	/* Blend source with Alpha 0xff on FIFO A */
> +	val = MCDE_CRX0_BLENDEN |
> +		0xff << MCDE_CRX0_ALPHABLEND_SHIFT;
> +	writel(val, mcde->regs + MCDE_CRA0);
> +
> +	/*
> +	 * Configure external source 0 one buffer (buffer 0)
> +	 * primary overlay ID 0.
> +	 * From mcde_hw.c ovly_update_registers() in the vendor tree
> +	 */
> +	val = 0 << MCDE_EXTSRCXCONF_BUF_ID_SHIFT;
> +	val |= 1 << MCDE_EXTSRCXCONF_BUF_NB_SHIFT;
> +	val |= 0 << MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT;
> +	/*
> +	 * MCDE has inverse semantics from DRM on RBG/BGR which is why
> +	 * all the modes are inversed here.
> +	 */
> +	switch (format) {
> +	case DRM_FORMAT_ARGB8888:
> +		val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_ABGR8888:
> +		val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_XRGB8888:
> +		val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_XBGR8888:
> +		val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_RGB888:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_BGR888:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB888 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_ARGB4444:
> +		val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_ABGR4444:
> +		val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_XRGB4444:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB444 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_XBGR4444:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB444 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_XRGB1555:
> +		val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_XBGR1555:
> +		val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_RGB565:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB565 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		val |= MCDE_EXTSRCXCONF_BGR;
> +		break;
> +	case DRM_FORMAT_BGR565:
> +		val |= MCDE_EXTSRCXCONF_BPP_RGB565 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	case DRM_FORMAT_YUV422:
> +		val |= MCDE_EXTSRCXCONF_BPP_YCBCR422 <<
> +			MCDE_EXTSRCXCONF_BPP_SHIFT;
> +		break;
> +	default:
> +		dev_err(drm->dev, "Unknown pixel format 0x%08x\n",
> +			fb->format->format);
> +		break;
> +	}
> +	writel(val, mcde->regs + MCDE_EXTSRC0CONF);
> +	/* Software select, primary */
> +	val = MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL;
> +	val |= MCDE_EXTSRC0CR_MULTIOVL_CTRL_PRIMARY;
> +	writel(val, mcde->regs + MCDE_EXTSRC0CR);
> +
> +	/* Configure overlay 0 */

Is there more than overlay 0? Multiple planes on one output is definitely
out of scope for the simple helpers.

> +	val = mode->hdisplay << MCDE_OVLXCONF_PPL_SHIFT;
> +	val |= mode->vdisplay << MCDE_OVLXCONF_LPF_SHIFT;
> +	/* Use external source 0 that we just configured */
> +	val |= 0 << MCDE_OVLXCONF_EXTSRC_ID_SHIFT;
> +	writel(val, mcde->regs + MCDE_OVL0CONF);
> +
> +	val = MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA;
> +	val |= 0xff << MCDE_OVLXCONF2_ALPHAVALUE_SHIFT;
> +	/* OPQ: overlay is opaque */
> +	switch (format) {
> +	case DRM_FORMAT_ARGB8888:
> +	case DRM_FORMAT_ABGR8888:
> +	case DRM_FORMAT_ARGB4444:
> +	case DRM_FORMAT_ABGR4444:
> +	case DRM_FORMAT_XRGB1555:
> +	case DRM_FORMAT_XBGR1555:
> +		/* No OPQ */
> +		break;
> +	case DRM_FORMAT_XRGB8888:
> +	case DRM_FORMAT_XBGR8888:
> +	case DRM_FORMAT_RGB888:
> +	case DRM_FORMAT_BGR888:
> +	case DRM_FORMAT_RGB565:
> +	case DRM_FORMAT_BGR565:
> +	case DRM_FORMAT_YUV422:
> +		val |= MCDE_OVLXCONF2_OPQ;
> +		break;
> +	default:
> +		dev_err(drm->dev, "Unknown pixel format 0x%08x\n",
> +			fb->format->format);
> +		break;
> +	}
> +	/* The default watermark level for overlay 0 is 48 */
> +	val |= 48 << MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT;
> +	writel(val, mcde->regs + MCDE_OVL0CONF2);
> +
> +	/* Number of bytes to fetch per line */
> +	writel(mcde->stride, mcde->regs + MCDE_OVL0LJINC);
> +	/* No cropping */
> +	writel(0, mcde->regs + MCDE_OVL0CROP);
> +
> +	/* Set up overlay control register */
> +	val = MCDE_OVLXCR_OVLEN;
> +	val |= MCDE_OVLXCR_COLCCTRL_DISABLED;
> +	val |= MCDE_OVLXCR_BURSTSIZE_8W <<
> +		MCDE_OVLXCR_BURSTSIZE_SHIFT;
> +	val |= MCDE_OVLXCR_MAXOUTSTANDING_8_REQ <<
> +		MCDE_OVLXCR_MAXOUTSTANDING_SHIFT;
> +	/* Not using rotation but set it up anyways */
> +	val |= MCDE_OVLXCR_ROTBURSTSIZE_8W <<
> +		MCDE_OVLXCR_ROTBURSTSIZE_SHIFT;
> +	writel(val, mcde->regs + MCDE_OVL0CR);
> +
> +	/* Set-up from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() */
> +
> +	/* Channel formatter set-up for channel A */
> +	val = MCDE_CRX1_CLKSEL_MCDECLK << MCDE_CRX1_CLKSEL_SHIFT;
> +	/* FIXME: when adding DPI support add OUTBPP etc here */
> +	writel(val, mcde->regs + MCDE_CRA1);
> +
> +	/*
> +	 * Enable formatter
> +	 * 8 bit commands and DCS commands (notgen = not generic)
> +	 */
> +	val = MCDE_DSICONF0_CMD8 | MCDE_DSICONF0_DCSVID_NOTGEN;
> +	if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO)
> +		val |= MCDE_DSICONF0_VID_MODE_VID;
> +	switch (mcde->mdsi->format) {
> +	case MIPI_DSI_FMT_RGB888:
> +		val |= MCDE_DSICONF0_PACKING_RGB888 <<
> +			MCDE_DSICONF0_PACKING_SHIFT;
> +		break;
> +	case MIPI_DSI_FMT_RGB666:
> +		val |= MCDE_DSICONF0_PACKING_RGB666 <<
> +			MCDE_DSICONF0_PACKING_SHIFT;
> +		break;
> +	case MIPI_DSI_FMT_RGB666_PACKED:
> +		val |= MCDE_DSICONF0_PACKING_RGB666_PACKED <<
> +			MCDE_DSICONF0_PACKING_SHIFT;
> +		break;
> +	case MIPI_DSI_FMT_RGB565:
> +		val |= MCDE_DSICONF0_PACKING_RGB565 <<
> +			MCDE_DSICONF0_PACKING_SHIFT;
> +		break;
> +	default:
> +		dev_err(drm->dev, "unknown DSI format\n");
> +		return;
> +	}
> +	writel(val, mcde->regs + MCDE_DSIVID0CONF0);
> +
> +	writel(formatter_frame, mcde->regs + MCDE_DSIVID0FRAME);
> +	writel(pkt_size, mcde->regs + MCDE_DSIVID0PKT);
> +	writel(0, mcde->regs + MCDE_DSIVID0SYNC);
> +	/* Define the MIPI command: we want to write into display memory */
> +	val = MIPI_DCS_WRITE_MEMORY_CONTINUE <<
> +		MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT;
> +	val |= MIPI_DCS_WRITE_MEMORY_START <<
> +		MCDE_DSIVIDXCMDW_CMDW_START_SHIFT;
> +	writel(val, mcde->regs + MCDE_DSIVID0CMDW);
> +	/*
> +	 * FIXME: the vendor driver has some hack around this value in
> +	 * CMD mode with autotrig.
> +	 */
> +	writel(0, mcde->regs + MCDE_DSIVID0DELAY0);
> +	writel(0, mcde->regs + MCDE_DSIVID0DELAY1);
> +
> +	if (mcde->te_sync) {
> +		if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +			val = MCDE_VSCRC_VSPOL;
> +		else
> +			val = 0;
> +		writel(val, mcde->regs + MCDE_VSCRC0);
> +		/* Enable VSYNC capture on TE0 */
> +		val = readl(mcde->regs + MCDE_CRC);
> +		val |= MCDE_CRC_SYCEN0;
> +		writel(val, mcde->regs + MCDE_CRC);
> +		drm_crtc_vblank_on(crtc);
> +	}
> +
> +	dev_info(drm->dev, "MCDE display is enabled\n");
> +	mcde->enabled = true;

This kind of tracking shouldn't be needed, since you're essentially
fighting the helper, which should be helping, not making things worse.

If you use drm_atomic_helper_commit_tail_rpm as your
drm_mode_config_helper_funcs.atomic_commit_tail hook then I think there
shouldn't be a need for this. This should be compatible with the simple
kms helpers still.

> +}
> +
> +static void mcde_display_disable(struct drm_simple_display_pipe *pipe)
> +{
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	struct drm_device *drm = crtc->dev;
> +	struct mcde *mcde = drm->dev_private;
> +	int timeout = 100;
> +	u32 val;
> +
> +	/* Stops framebuffer updates */
> +	mcde->enabled = false;
> +
> +	if (mcde->te_sync)
> +		drm_crtc_vblank_off(crtc);
> +
> +	/* Disable FIFO A flow */
> +	val = readl(mcde->regs + MCDE_CRA0);
> +	val &= ~MCDE_CRX0_FLOEN;
> +	writel(val, mcde->regs + MCDE_CRA0);
> +	while (readl(mcde->regs + MCDE_CRA0) & MCDE_CRX0_FLOEN) {
> +		usleep_range(1000, 1500);
> +		if (!--timeout) {
> +			dev_err(drm->dev,
> +				"FIFO A timeout while stopping\n");
> +			break;
> +		}
> +	}
> +	spin_lock(&mcde->flow_lock);
> +	mcde->flow_active = 0;
> +	spin_unlock(&mcde->flow_lock);
> +
> +	dev_info(drm->dev, "MCDE display is disabled\n");
> +}
> +
> +static void mcde_display_send_one_frame(struct mcde *mcde)
> +{
> +	int timeout = 100;
> +	u32 val;
> +
> +	/* Request a TE ACK */
> +	if (mcde->te_sync)
> +		mcde_dsi_te_request(mcde->mdsi);
> +
> +	/* Enable FIFO A flow */
> +	spin_lock(&mcde->flow_lock);
> +	val = readl(mcde->regs + MCDE_CRA0);
> +	val |= MCDE_CRX0_FLOEN;
> +	writel(val, mcde->regs + MCDE_CRA0);
> +	mcde->flow_active++;
> +	spin_unlock(&mcde->flow_lock);
> +
> +	if (mcde->te_sync) {
> +		/*
> +		 * If oneshot mode is enabled, the flow will be disabled
> +		 * when the TE0 IRQ arrives in the interrupt handler. Otherwise
> +		 * updates are continuously streamed to the display after this
> +		 * point.
> +		 */
> +		dev_dbg(mcde->dev, "sent TE0 framebuffer update\n");
> +		return;
> +	}
> +
> +	/* Trigger a software sync out on channel 0 */
> +	writel(MCDE_CHNLXSYNCHSW_SW_TRIG,
> +	       mcde->regs + MCDE_CHNL0SYNCHSW);
> +	/* Disable FIFO A flow again */
> +	spin_lock(&mcde->flow_lock);
> +	val = readl(mcde->regs + MCDE_CRA0);
> +	val &= ~MCDE_CRX0_FLOEN;
> +	writel(val, mcde->regs + MCDE_CRA0);
> +	mcde->flow_active = 0;
> +	spin_unlock(&mcde->flow_lock);
> +
> +	/*
> +	 * At this point the DSI link should be running a
> +	 * frame update.
> +	 */
> +	while (readl(mcde->regs + MCDE_CRA0) & MCDE_CRX0_FLOEN) {
> +		usleep_range(1000, 1500);
> +		if (!--timeout) {
> +			dev_err(mcde->dev, "FIFO A timeout\n");
> +			break;
> +		}
> +	}
> +	dev_dbg(mcde->dev, "sent SW framebuffer update\n");
> +}
> +
> +static void mcde_display_update(struct drm_simple_display_pipe *pipe,
> +				 struct drm_plane_state *old_pstate)
> +{
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	struct drm_device *drm = crtc->dev;
> +	struct mcde *mcde = drm->dev_private;
> +	struct drm_pending_vblank_event *event = crtc->state->event;
> +	struct drm_plane *plane = &pipe->plane;
> +	struct drm_plane_state *pstate = plane->state;
> +	struct drm_framebuffer *fb = pstate->fb;
> +
> +	/*
> +	 * We do not start sending framebuffer updates before the
> +	 * display is enabled. Update events will however be dispatched
> +	 * from the DRM core before the display is enabled.
> +	 */
> +	if (fb && mcde->enabled) {

With the commit_tail_rpm change both of these checks shouldn't be needed.

> +		/* Write bitmap base address to register */
> +		/* Set to 0x100000 to display random junk */
> +		writel(drm_fb_cma_get_gem_addr(fb, pstate, 0),
> +		       mcde->regs + MCDE_EXTSRCXA0);
> +		/*
> +		 * Base address for next line this is probably only used
> +		 * in interlace modes.
> +		 */
> +		writel(drm_fb_cma_get_gem_addr(fb, pstate, 0) + mcde->stride,
> +		       mcde->regs + MCDE_EXTSRCXA1);
> +
> +		/* Set up overlay 0 compositor route to channel A */
> +		writel(0, mcde->regs + MCDE_OVL0COMP);
> +
> +		/* Send a single frame using software sync */
> +		mcde_display_send_one_frame(mcde);
> +	}
> +
> +	/* Handle any pending event */
> +	if (event) {
> +		crtc->state->event = NULL;
> +
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		/*
> +		 * Hardware must be on before we can arm any vblank event,
> +		 * this is not a scanout controller where there is always
> +		 * some periodic update going on, it is completely frozen
> +		 * until we get an update. If MCDE output isn't yet enabled,
> +		 * we just send a vblank dummy event back.
> +		 */
> +		if (mcde->enabled && crtc->state->active &&
> +		    drm_crtc_vblank_get(crtc) == 0 && !mcde->vblank_irq_on) {

->vblank_irq_on is guaranteed to be set if drm_crtc_vblank_get succeeds.
And I don't see any other reason why you need to keep track of the same
thing the drm_vblank code already keeps track of, best to remove?

> +			dev_dbg(mcde->dev, "arm vblank event\n");
> +			drm_crtc_arm_vblank_event(crtc, event);
> +		} else {
> +			dev_dbg(mcde->dev, "insert fake vblank event\n");
> +			drm_crtc_send_vblank_event(crtc, event);
> +		}
> +
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +	}
> +}
> +
> +static int mcde_display_enable_vblank(struct drm_simple_display_pipe *pipe)
> +{
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	struct drm_device *drm = crtc->dev;
> +	struct mcde *mcde = drm->dev_private;
> +	u32 val;
> +
> +	/* Enable all VBLANK IRQs */
> +	val = MCDE_PP_VCMPA |
> +		MCDE_PP_VCMPB |
> +		MCDE_PP_VSCC0 |
> +		MCDE_PP_VSCC1 |
> +		MCDE_PP_VCMPC0 |
> +		MCDE_PP_VCMPC1;
> +	writel(val, mcde->regs + MCDE_IMSCPP);
> +	mcde->vblank_irq_on = true;
> +
> +	return 0;
> +}

Would be interesting to run kms_flip from igt on this and see who much the
testcase is unhappy about your vblank interrupts. Compositors do expect
interrupts to work if you advertise them (through drm_vblank_init), but
most are totally fine if they're inaccurate.

> +
> +static void mcde_display_disable_vblank(struct drm_simple_display_pipe *pipe)
> +{
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	struct drm_device *drm = crtc->dev;
> +	struct mcde *mcde = drm->dev_private;
> +
> +	/* Disable all VBLANK IRQs */
> +	writel(0, mcde->regs + MCDE_IMSCPP);
> +	/* Clear any pending IRQs */
> +	writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP);
> +	mcde->vblank_irq_on = false;
> +}
> +
> +static int mcde_display_prepare_fb(struct drm_simple_display_pipe *pipe,
> +				    struct drm_plane_state *plane_state)
> +{
> +	return drm_gem_fb_prepare_fb(&pipe->plane, plane_state);
> +}

drm_gem_fb_simple_display_pipe_prepare_fb()

> +
> +static struct drm_simple_display_pipe_funcs mcde_display_funcs = {
> +	.check = mcde_display_check,
> +	.enable = mcde_display_enable,
> +	.disable = mcde_display_disable,
> +	.update = mcde_display_update,
> +	.prepare_fb = mcde_display_prepare_fb,
> +};
> +
> +int mcde_display_init(struct drm_device *drm)
> +{
> +	struct mcde *mcde = drm->dev_private;
> +	int ret;
> +	static const u32 formats[] = {
> +		DRM_FORMAT_ARGB8888,
> +		DRM_FORMAT_ABGR8888,
> +		DRM_FORMAT_XRGB8888,
> +		DRM_FORMAT_XBGR8888,
> +		DRM_FORMAT_RGB888,
> +		DRM_FORMAT_BGR888,
> +		DRM_FORMAT_ARGB4444,
> +		DRM_FORMAT_ABGR4444,
> +		DRM_FORMAT_XRGB4444,
> +		DRM_FORMAT_XBGR4444,
> +		/* These are actually IRGB1555 so intensity bit is lost */
> +		DRM_FORMAT_XRGB1555,
> +		DRM_FORMAT_XBGR1555,
> +		DRM_FORMAT_RGB565,
> +		DRM_FORMAT_BGR565,
> +		DRM_FORMAT_YUV422,
> +	};
> +
> +	/* Provide vblank only when we have TE enabled */
> +	if (mcde->te_sync) {
> +		mcde_display_funcs.enable_vblank = mcde_display_enable_vblank;
> +		mcde_display_funcs.disable_vblank = mcde_display_disable_vblank;
> +	}
> +
> +	ret = drm_simple_display_pipe_init(drm, &mcde->pipe,
> +					   &mcde_display_funcs,
> +					   formats, ARRAY_SIZE(formats),
> +					   NULL,
> +					   mcde->connector);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(mcde_display_init);
> +
> diff --git a/drivers/gpu/drm/mcde/mcde_drm.h b/drivers/gpu/drm/mcde/mcde_drm.h
> new file mode 100644
> index 000000000000..eea6dc23436a
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/mcde_drm.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 Linus Walleij <linus.walleij@xxxxxxxxxx>
> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon
> + * (C) ST-Ericsson SA 2013
> + */
> +#include <drm/drm_simple_kms_helper.h>
> +
> +#ifndef _MCDE_DRM_H_
> +#define _MCDE_DRM_H_
> +
> +struct mcde {
> +	struct device *dev;

The cool thing nowadays is embedding and drm_dev_init().

> +
> +	struct drm_device *drm;
> +	struct drm_panel *panel;
> +	struct drm_bridge *bridge;

Bridges can be stacked, see drm_bridge.next and drm_bridge_attach. I'm a
bit lost on why you need all the pointers here, I'd somehow assume with
all these of pointers and driver_private it should be possible to get at
all the right bits.

> +	struct drm_connector *connector;
> +	struct drm_simple_display_pipe pipe;
> +	struct mipi_dsi_device *mdsi;
> +	s16 stride;
> +	bool enabled;
> +	bool vblank_irq_on;
> +	bool te_sync;
> +	bool oneshot_mode;
> +	unsigned int flow_active;
> +	spinlock_t flow_lock; /* Locks the channel flow control */
> +
> +	void __iomem *regs;
> +
> +	struct clk *mcde_clk;
> +	struct clk *lcd_clk;
> +	struct clk *hdmi_clk;
> +	struct clk *dsi0_clk;
> +	struct clk *dsi1_clk;
> +	struct clk *dsi0es_clk;
> +	struct clk *dsi1es_clk;
> +	struct clk *dsi2es_clk;
> +
> +	struct regulator *epod;
> +	struct regulator *vana;
> +};
> +
> +bool mcde_dsi_irq(struct mipi_dsi_device *mdsi);
> +void mcde_dsi_te_request(struct mipi_dsi_device *mdsi);
> +extern struct platform_driver mcde_dsi_driver;
> +
> +void mcde_display_irq(struct mcde *mcde);
> +void mcde_display_disable_irqs(struct mcde *mcde);
> +int mcde_display_init(struct drm_device *drm);
> +
> +#endif /* _MCDE_DRM_H_ */
> diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c
> new file mode 100644
> index 000000000000..cb65609ac812
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/mcde_drv.c
> @@ -0,0 +1,540 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2018 Linus Walleij <linus.walleij@xxxxxxxxxx>
> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon
> + * (C) ST-Ericsson SA 2013
> + */
> +
> +/**
> + * DOC: ST-Ericsson MCDE Driver
> + *
> + * The MCDE (short for Multi-channel display engine) is a graphics
> + * controller found in the Ux500 chipsets, such as NovaThor U8500.
> + * It was initially conceptualized by ST Microelectronics for the
> + * successor of the Nomadik line, STn8500 but productified in the
> + * ST-Ericsson U8500 where is was used for mass-market deployments
> + * in Android phones from Samsung and Sony Ericsson.
> + *
> + * It can do 1080p30 on SDTV CCIR656, DPI-2, DBI-2 or DSI for
> + * panels with or without frame buffering and can convert most
> + * input formats including most variants of RGB and YUV.
> + *
> + * The hardware has four display pipes, and the layout is a little
> + * bit like this:
> + *
> + * Memory     -> 6 channels -> 5 formatters -> DSI/DPI -> LCD/HDMI
> + * 10 sources    (overlays)                    3 x DSI
> + *
> + * The memory has 5 input channels (memory ports):
> + * 2 channel A (LCD/TV)
> + * 2 channel B (LCD/TV)
> + * 1 channel CO/C1 (Panel with embedded buffer)
> + *
> + * 3 of the formatters are for DSI
> + * 2 of the formatters are for DPI
> + *
> + * Behind the formatters are the DSI or DPI ports, that route to
> + * the external pins of the chip. As there are 3 DSI ports and one
> + * DPI port, it is possible to configure up to 4 display pipelines.

Yeah this'll be outgrowing simple pipe helper real fast I think. If you
have any intentions of supporting all this stuff at least.

> + */
> +
> +#include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/dma-buf.h>
> +#include <linux/irq.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +#include <linux/component.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_bridge.h>
> +
> +#include "mcde_drm.h"
> +
> +#define DRIVER_DESC	"DRM module for MCDE"
> +
> +#define MCDE_CR 0x00000000
> +#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_SHIFT 0
> +#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_MASK 0x0000003F
> +#define MCDE_CR_IFIFOCTRLEN BIT(15)
> +#define MCDE_CR_UFRECOVERY_MODE_V422 BIT(16)
> +#define MCDE_CR_WRAP_MODE_V422_SHIFT BIT(17)
> +#define MCDE_CR_AUTOCLKG_EN BIT(30)
> +#define MCDE_CR_MCDEEN BIT(31)
> +
> +#define MCDE_CONF0 0x00000004
> +#define MCDE_CONF0_SYNCMUX0 BIT(0)
> +#define MCDE_CONF0_SYNCMUX1 BIT(1)
> +#define MCDE_CONF0_SYNCMUX2 BIT(2)
> +#define MCDE_CONF0_SYNCMUX3 BIT(3)
> +#define MCDE_CONF0_SYNCMUX4 BIT(4)
> +#define MCDE_CONF0_SYNCMUX5 BIT(5)
> +#define MCDE_CONF0_SYNCMUX6 BIT(6)
> +#define MCDE_CONF0_SYNCMUX7 BIT(7)
> +#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT 12
> +#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_MASK 0x00007000
> +#define MCDE_CONF0_OUTMUX0_SHIFT 16
> +#define MCDE_CONF0_OUTMUX0_MASK 0x00070000
> +#define MCDE_CONF0_OUTMUX1_SHIFT 19
> +#define MCDE_CONF0_OUTMUX1_MASK 0x00380000
> +#define MCDE_CONF0_OUTMUX2_SHIFT 22
> +#define MCDE_CONF0_OUTMUX2_MASK 0x01C00000
> +#define MCDE_CONF0_OUTMUX3_SHIFT 25
> +#define MCDE_CONF0_OUTMUX3_MASK 0x0E000000
> +#define MCDE_CONF0_OUTMUX4_SHIFT 28
> +#define MCDE_CONF0_OUTMUX4_MASK 0x70000000
> +
> +#define MCDE_SSP 0x00000008
> +#define MCDE_AIS 0x00000100
> +#define MCDE_IMSCERR 0x00000110
> +#define MCDE_RISERR 0x00000120
> +#define MCDE_MISERR 0x00000130
> +#define MCDE_SISERR 0x00000140
> +
> +#define MCDE_PID 0x000001FC
> +#define MCDE_PID_METALFIX_VERSION_SHIFT 0
> +#define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF
> +#define MCDE_PID_DEVELOPMENT_VERSION_SHIFT 8
> +#define MCDE_PID_DEVELOPMENT_VERSION_MASK 0x0000FF00
> +#define MCDE_PID_MINOR_VERSION_SHIFT 16
> +#define MCDE_PID_MINOR_VERSION_MASK 0x00FF0000
> +#define MCDE_PID_MAJOR_VERSION_SHIFT 24
> +#define MCDE_PID_MAJOR_VERSION_MASK 0xFF000000
> +
> +static const struct drm_mode_config_funcs mode_config_funcs = {
> +	.fb_create = drm_gem_fb_create,

You need drm_gem_fb_create_with_dirty here because you have a manual
upload screen. Also might head the advice from the kerneldoc and check
your buffer alignment constraints here instead of in the check callback.
But I guess either works, best would be if dumb_create would align stuff
correctly (which it should), but since this is super theoretical (usually
modes are even enough), who cares :-)

> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static irqreturn_t mcde_irq(int irq, void *data)
> +{
> +	struct mcde *mcde = data;
> +	u32 val;
> +
> +	val = readl(mcde->regs + MCDE_MISERR);
> +
> +	mcde_display_irq(mcde);
> +
> +	if (val)
> +		dev_info(mcde->dev, "some error IRQ\n");
> +	writel(val, mcde->regs + MCDE_RISERR);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int mcde_modeset_init(struct drm_device *drm)
> +{
> +	struct drm_mode_config *mode_config;
> +	struct mcde *mcde = drm->dev_private;
> +	int ret;
> +
> +	mode_config = &drm->mode_config;
> +	mode_config->funcs = &mode_config_funcs;
> +	/* This hardware can do 1080p */
> +	mode_config->min_width = 1;
> +	mode_config->max_width = 1920;
> +	mode_config->min_height = 1;
> +	mode_config->max_height = 1080;
> +
> +	if (mcde->te_sync) {
> +		ret = drm_vblank_init(drm, 1);
> +		if (ret) {
> +			dev_err(drm->dev, "failed to init vblank\n");
> +			goto out_config;
> +		}
> +	}
> +
> +	ret = mcde_display_init(drm);
> +	if (ret) {
> +		dev_err(drm->dev, "failed to init display\n");
> +		goto out_config;
> +	}
> +
> +	drm_mode_config_reset(drm);
> +	drm_fb_cma_fbdev_init(drm, 32, 0);

This is gone, I think Noralf's fancy replacement is
drm_fbdev_generic_setup().

> +	drm_kms_helper_poll_init(drm);
> +
> +	return 0;
> +
> +out_config:
> +	drm_mode_config_cleanup(drm);
> +	return ret;
> +}
> +
> +DEFINE_DRM_GEM_CMA_FOPS(drm_fops);
> +
> +static struct drm_driver mcde_drm_driver = {
> +	.driver_features =
> +		DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC,
> +	.lastclose = drm_fb_helper_lastclose,
> +	.ioctls = NULL,
> +	.fops = &drm_fops,
> +	.name = "mcde",
> +	.desc = DRIVER_DESC,
> +	.date = "20180529",
> +	.major = 1,
> +	.minor = 0,
> +	.patchlevel = 0,
> +	.dumb_create = drm_gem_cma_dumb_create,
> +	.gem_free_object_unlocked = drm_gem_cma_free_object,
> +	.gem_vm_ops = &drm_gem_cma_vm_ops,
> +
> +	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> +	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> +	.gem_prime_import = drm_gem_prime_import,
> +	.gem_prime_export = drm_gem_prime_export,
> +	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
> +	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
> +	.gem_prime_vmap = drm_gem_cma_prime_vmap,
> +	.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
> +	.gem_prime_mmap = drm_gem_cma_prime_mmap,
> +};
> +
> +static int mcde_drm_bind(struct device *dev)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	int ret;
> +
> +	drm_mode_config_init(drm);
> +
> +	ret = component_bind_all(drm->dev, drm);
> +	if (ret) {
> +		dev_err(dev, "can't bind component devices\n");
> +		return ret;
> +	}
> +
> +	ret = mcde_modeset_init(drm);
> +	if (ret)
> +		goto unbind;
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret < 0)
> +		goto unbind;
> +
> +	return 0;
> +
> +unbind:
> +	component_unbind_all(drm->dev, drm);
> +	return ret;
> +}
> +
> +static void mcde_drm_unbind(struct device *dev)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +
> +	drm_dev_unregister(drm);
> +	component_unbind_all(drm->dev, drm);

Probably want an drm_atomic_helper_shutdown here too somewhere.

> +	drm_mode_config_cleanup(drm);
> +}
> +
> +static const struct component_master_ops mcde_drm_comp_ops = {
> +	.bind = mcde_drm_bind,
> +	.unbind = mcde_drm_unbind,
> +};
> +
> +static struct platform_driver *const mcde_component_drivers[] = {
> +	&mcde_dsi_driver,
> +};
> +
> +static int mcde_compare_dev(struct device *dev, void *data)
> +{
> +	return dev == data;
> +}
> +
> +static int mcde_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct drm_device *drm;
> +	struct mcde *mcde;
> +	struct component_match *match;
> +	struct resource *res;
> +	u32 pid;
> +	u32 val;
> +	int irq;
> +	int ret;
> +	int i;
> +
> +	mcde = devm_kzalloc(dev, sizeof(*mcde), GFP_KERNEL);
> +	if (!mcde)
> +		return -ENOMEM;
> +	mcde->dev = dev;
> +
> +	drm = drm_dev_alloc(&mcde_drm_driver, dev);
> +	if (IS_ERR(drm))
> +		return PTR_ERR(drm);
> +	platform_set_drvdata(pdev, drm);
> +	mcde->drm = drm;
> +	/* Enable use of the TE signal and interrupt */
> +	mcde->te_sync = true;
> +	/* Enable continuous updates: this is what Linux' framebuffer expects */
> +	mcde->oneshot_mode = false;
> +	drm->dev_private = mcde;
> +
> +	/* First obtain and turn on the main power */
> +	mcde->epod = devm_regulator_get(dev, "epod");
> +	if (IS_ERR(mcde->epod)) {
> +		ret = PTR_ERR(mcde->epod);
> +		dev_err(dev, "can't get EPOD regulator\n");
> +		goto dev_unref;
> +	}
> +	ret = regulator_enable(mcde->epod);
> +	if (ret) {
> +		dev_err(dev, "can't enable EPOD regulator\n");
> +		goto dev_unref;
> +	}
> +	mcde->vana = devm_regulator_get(dev, "vana");
> +	if (IS_ERR(mcde->vana)) {
> +		ret = PTR_ERR(mcde->vana);
> +		dev_err(dev, "can't get VANA regulator\n");
> +		goto regulator_epod_off;
> +	}
> +	ret = regulator_enable(mcde->vana);
> +	if (ret) {
> +		dev_err(dev, "can't enable VANA regulator\n");
> +		goto regulator_epod_off;
> +	}
> +	/* Vendor code uses v-esram34 but we don't, yet */
> +
> +	/* Clock the silicon so we can access the registers */
> +	mcde->mcde_clk = devm_clk_get(dev, "mcde");
> +	if (IS_ERR(mcde->mcde_clk)) {
> +		dev_err(dev, "unable to get MCDE main clock\n");
> +		ret = PTR_ERR(mcde->mcde_clk);
> +		goto regulator_off;
> +	}
> +	ret = clk_prepare_enable(mcde->mcde_clk);
> +	if (ret) {
> +		dev_err(dev, "failed to enable MCDE main clock\n");
> +		goto regulator_off;
> +	}
> +	dev_info(dev, "MCDE clk rate %lu Hz\n", clk_get_rate(mcde->mcde_clk));
> +
> +	/* Also retrieve the additional clocks */
> +	mcde->dsi0_clk = devm_clk_get(dev, "dsi0");
> +	if (IS_ERR(mcde->dsi0_clk)) {
> +		dev_err(dev, "unable to get DSI0 clock\n");
> +		ret = PTR_ERR(mcde->dsi0_clk);
> +		goto clk_disable;
> +	}
> +	mcde->dsi1_clk = devm_clk_get(dev, "dsi1");
> +	if (IS_ERR(mcde->dsi1_clk)) {
> +		dev_err(dev, "unable to get DSI1 clock\n");
> +		ret = PTR_ERR(mcde->dsi1_clk);
> +		goto clk_disable;
> +	}
> +	/*
> +	 * ES = Energy Save, or LP = Low Power clocks
> +	 * These clocks are also used for TV out.
> +	 */
> +	mcde->dsi0es_clk = devm_clk_get(dev, "dsi0es");
> +	if (IS_ERR(mcde->dsi0es_clk)) {
> +		dev_err(dev, "unable to get DSI0ES clock\n");
> +		ret = PTR_ERR(mcde->dsi0es_clk);
> +		goto clk_disable;
> +	}
> +	mcde->dsi1es_clk = devm_clk_get(dev, "dsi1es");
> +	if (IS_ERR(mcde->dsi1es_clk)) {
> +		dev_err(dev, "unable to get DSI1ES clock\n");
> +		ret = PTR_ERR(mcde->dsi1es_clk);
> +		goto clk_disable;
> +	}
> +	mcde->dsi2es_clk = devm_clk_get(dev, "dsi2es");
> +	if (IS_ERR(mcde->dsi2es_clk)) {
> +		dev_err(dev, "unable to get DSI2ES clock\n");
> +		ret = PTR_ERR(mcde->dsi2es_clk);
> +		goto clk_disable;
> +	}
> +	mcde->lcd_clk = devm_clk_get(dev, "lcd");
> +	if (IS_ERR(mcde->lcd_clk)) {
> +		dev_err(dev, "unable to get LCD clock\n");
> +		ret = PTR_ERR(mcde->lcd_clk);
> +		goto clk_disable;
> +	}
> +	mcde->hdmi_clk = devm_clk_get(dev, "hdmi");
> +	if (IS_ERR(mcde->hdmi_clk)) {
> +		dev_err(dev, "unable to get HDMI clock\n");
> +		ret = PTR_ERR(mcde->hdmi_clk);
> +		goto clk_disable;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	mcde->regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(mcde->regs)) {
> +		dev_err(dev, "no MCDE regs\n");
> +		ret = -EINVAL;
> +		goto clk_disable;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (!irq) {
> +		ret = -EINVAL;
> +		goto clk_disable;
> +	}
> +
> +	ret = devm_request_irq(dev, irq, mcde_irq, 0, "mcde", mcde);
> +	if (ret) {
> +		dev_err(dev, "failed to request irq %d\n", ret);
> +		goto clk_disable;
> +	}
> +
> +	/*
> +	 * Check hardware revision, we only support U8500v2 version
> +	 * as this was the only version used for mass market deployment,
> +	 * but surely you can add more versions if you have them and
> +	 * need them.
> +	 */
> +	pid = readl(mcde->regs + MCDE_PID);
> +	dev_info(dev, "found MCDE HW revision %d.%d (dev %d, metal fix %d)\n",
> +		 (pid & MCDE_PID_MAJOR_VERSION_MASK)
> +		 >> MCDE_PID_MAJOR_VERSION_SHIFT,
> +		 (pid & MCDE_PID_MINOR_VERSION_MASK)
> +		 >> MCDE_PID_MINOR_VERSION_SHIFT,
> +		 (pid & MCDE_PID_DEVELOPMENT_VERSION_MASK)
> +		 >> MCDE_PID_DEVELOPMENT_VERSION_SHIFT,
> +		 (pid & MCDE_PID_METALFIX_VERSION_MASK)
> +		 >> MCDE_PID_METALFIX_VERSION_SHIFT);
> +	if (pid != 0x03000800) {
> +		dev_err(dev, "unsupported hardware revision\n");
> +		ret = -ENODEV;
> +		goto clk_disable;
> +	}
> +
> +	/* Set up the main control, watermark level at 7 */
> +	val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT;
> +	/* 24 bits DPI: connect LSB Ch B to D[0:7] */
> +	val |= 3 << MCDE_CONF0_OUTMUX0_SHIFT;
> +	/* TV out: connect LSB Ch B to D[8:15] */
> +	val |= 3 << MCDE_CONF0_OUTMUX1_SHIFT;
> +	/* Don't care about this muxing */
> +	val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT;
> +	/* 24 bits DPI: connect MID Ch B to D[24:31] */
> +	val |= 4 << MCDE_CONF0_OUTMUX3_SHIFT;
> +	/* 5: 24 bits DPI: connect MSB Ch B to D[32:39] */
> +	val |= 5 << MCDE_CONF0_OUTMUX4_SHIFT;
> +	/* Syncmux bits zero: DPI channel A and B on output pins A and B resp */
> +	writel(val, mcde->regs + MCDE_CONF0);
> +
> +	/* Enable automatic clock gating */
> +	val = readl(mcde->regs + MCDE_CR);
> +	val |= MCDE_CR_MCDEEN | MCDE_CR_AUTOCLKG_EN;
> +	writel(val, mcde->regs + MCDE_CR);
> +
> +	/* Clear any pending interrupts */
> +	mcde_display_disable_irqs(mcde);
> +	writel(0, mcde->regs + MCDE_IMSCERR);
> +	writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR);
> +
> +	/* Spawn child devices for the DSI ports */
> +	devm_of_platform_populate(dev);
> +
> +	/* Create something that will match the subdrivers when we bind */
> +	for (i = 0; i < ARRAY_SIZE(mcde_component_drivers); i++) {
> +		struct device_driver *drv = &mcde_component_drivers[i]->driver;
> +		struct device *p = NULL, *d;
> +
> +		while ((d = bus_find_device(&platform_bus_type, p, drv,
> +					    (void *)platform_bus_type.match))) {
> +			put_device(p);
> +			component_match_add(dev, &match, mcde_compare_dev, d);
> +			p = d;
> +		}
> +		put_device(p);
> +	}
> +	if (IS_ERR(match)) {
> +		dev_err(dev, "could not create component match\n");
> +		ret = PTR_ERR(match);
> +		goto clk_disable;
> +	}
> +	ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops,
> +					      match);
> +	if (ret) {
> +		dev_err(dev, "faule to add component master\n");
> +		goto clk_disable;
> +	}
> +	return 0;
> +
> +clk_disable:
> +	clk_disable_unprepare(mcde->mcde_clk);
> +regulator_off:
> +	regulator_disable(mcde->vana);
> +regulator_epod_off:
> +	regulator_disable(mcde->epod);
> +dev_unref:
> +	drm_dev_put(drm);
> +	return ret;
> +
> +}
> +
> +static int mcde_remove(struct platform_device *pdev)
> +{
> +	struct drm_device *drm = platform_get_drvdata(pdev);
> +	struct mcde *mcde = drm->dev_private;
> +
> +	component_master_del(&pdev->dev, &mcde_drm_comp_ops);
> +	clk_disable_unprepare(mcde->mcde_clk);
> +	regulator_disable(mcde->vana);
> +	regulator_disable(mcde->epod);
> +	drm_dev_put(drm);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id mcde_of_match[] = {
> +	{
> +		.compatible = "ste,mcde",
> +	},
> +	{},
> +};
> +
> +static struct platform_driver mcde_driver = {
> +	.driver = {
> +		.name           = "mcde",
> +		.of_match_table = of_match_ptr(mcde_of_match),
> +	},
> +	.probe = mcde_probe,
> +	.remove = mcde_remove,
> +};
> +
> +static struct platform_driver *const component_drivers[] = {
> +	&mcde_dsi_driver,
> +};
> +
> +static int __init mcde_drm_register(void)
> +{
> +	int ret;
> +
> +	ret = platform_register_drivers(component_drivers,
> +					ARRAY_SIZE(component_drivers));
> +	if (ret)
> +		return ret;
> +
> +	return platform_driver_register(&mcde_driver);
> +}
> +
> +static void __exit mcde_drm_unregister(void)
> +{
> +	platform_unregister_drivers(component_drivers,
> +				    ARRAY_SIZE(component_drivers));
> +	platform_driver_unregister(&mcde_driver);
> +}
> +
> +module_init(mcde_drm_register);
> +module_exit(mcde_drm_unregister);
> +
> +MODULE_ALIAS("platform:mcde-drm");
> +MODULE_DESCRIPTION(DRIVER_DESC);
> +MODULE_AUTHOR("Linus Walleij <linus.walleij@xxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
> new file mode 100644
> index 000000000000..de62e9e97298
> --- /dev/null
> +++ b/drivers/gpu/drm/mcde/mcde_dsi.c
> @@ -0,0 +1,1374 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +#include <linux/component.h>
> +#include <linux/of.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/regmap.h>
> +#include <video/mipi_display.h>
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_of.h>
> +
> +#include "mcde_drm.h"
> +
> +#define DSI_DEFAULT_LP_FREQ_HZ	19200000
> +#define DSI_DEFAULT_HS_FREQ_HZ	420160000
> +
> +#define DSI_MCTL_INTEGRATION_MODE 0x00000000
> +
> +#define DSI_MCTL_MAIN_DATA_CTL 0x00000004
> +#define DSI_MCTL_MAIN_DATA_CTL_LINK_EN BIT(0)
> +#define DSI_MCTL_MAIN_DATA_CTL_IF1_MODE BIT(1)
> +#define DSI_MCTL_MAIN_DATA_CTL_VID_EN BIT(2)
> +#define DSI_MCTL_MAIN_DATA_CTL_TVG_SEL BIT(3)
> +#define DSI_MCTL_MAIN_DATA_CTL_TBG_SEL BIT(4)
> +#define DSI_MCTL_MAIN_DATA_CTL_IF1_TE_EN BIT(5)
> +#define DSI_MCTL_MAIN_DATA_CTL_IF2_TE_EN BIT(6)
> +#define DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN BIT(7)
> +#define DSI_MCTL_MAIN_DATA_CTL_READ_EN BIT(8)
> +#define DSI_MCTL_MAIN_DATA_CTL_BTA_EN BIT(9)
> +#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_ECC BIT(10)
> +#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_CHECKSUM BIT(11)
> +#define DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN BIT(12)
> +#define DSI_MCTL_MAIN_DATA_CTL_DISP_EOT_GEN BIT(13)
> +#define DSI_MCTL_MAIN_DATA_CTL_DLX_REMAP_EN BIT(14)
> +#define DSI_MCTL_MAIN_DATA_CTL_TE_POLLING_EN BIT(15)
> +
> +#define DSI_MCTL_MAIN_PHY_CTL 0x00000008
> +#define DSI_MCTL_MAIN_PHY_CTL_LANE2_EN BIT(0)
> +#define DSI_MCTL_MAIN_PHY_CTL_FORCE_STOP_MODE BIT(1)
> +#define DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS BIT(2)
> +#define DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN BIT(3)
> +#define DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN BIT(4)
> +#define DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN BIT(5)
> +#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT 6
> +#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_MASK 0x000003C0
> +#define DSI_MCTL_MAIN_PHY_CTL_CLOCK_FORCE_STOP_MODE BIT(10)
> +
> +#define DSI_MCTL_PLL_CTL 0x0000000C
> +#define DSI_MCTL_LANE_STS 0x00000010
> +
> +#define DSI_MCTL_DPHY_TIMEOUT 0x00000014
> +#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT 0
> +#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_MASK 0x0000000F
> +#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT 4
> +#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_MASK 0x0003FFF0
> +#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT 18
> +#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_MASK 0xFFFC0000
> +
> +#define DSI_MCTL_ULPOUT_TIME 0x00000018
> +#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT 0
> +#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_MASK 0x000001FF
> +#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT 9
> +#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_MASK 0x0003FE00
> +
> +#define DSI_MCTL_DPHY_STATIC 0x0000001C
> +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_CLK BIT(0)
> +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_CLK BIT(1)
> +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT1 BIT(2)
> +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT1 BIT(3)
> +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT2 BIT(4)
> +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT2 BIT(5)
> +#define DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT 6
> +#define DSI_MCTL_DPHY_STATIC_UI_X4_MASK 0x00000FC0
> +
> +#define DSI_MCTL_MAIN_EN 0x00000020
> +#define DSI_MCTL_MAIN_EN_PLL_START BIT(0)
> +#define DSI_MCTL_MAIN_EN_CKLANE_EN BIT(3)
> +#define DSI_MCTL_MAIN_EN_DAT1_EN BIT(4)
> +#define DSI_MCTL_MAIN_EN_DAT2_EN BIT(5)
> +#define DSI_MCTL_MAIN_EN_CLKLANE_ULPM_REQ BIT(6)
> +#define DSI_MCTL_MAIN_EN_DAT1_ULPM_REQ BIT(7)
> +#define DSI_MCTL_MAIN_EN_DAT2_ULPM_REQ BIT(8)
> +#define DSI_MCTL_MAIN_EN_IF1_EN BIT(9)
> +#define DSI_MCTL_MAIN_EN_IF2_EN BIT(10)
> +
> +#define DSI_MCTL_MAIN_STS 0x00000024
> +#define DSI_MCTL_MAIN_STS_PLL_LOCK BIT(0)
> +#define DSI_MCTL_MAIN_STS_CLKLANE_READY BIT(1)
> +#define DSI_MCTL_MAIN_STS_DAT1_READY BIT(2)
> +#define DSI_MCTL_MAIN_STS_DAT2_READY BIT(3)
> +#define DSI_MCTL_MAIN_STS_HSTX_TO_ERR BIT(4)
> +#define DSI_MCTL_MAIN_STS_LPRX_TO_ERR BIT(5)
> +#define DSI_MCTL_MAIN_STS_CRS_UNTERM_PCK BIT(6)
> +#define DSI_MCTL_MAIN_STS_VRS_UNTERM_PCK BIT(7)
> +
> +#define DSI_MCTL_DPHY_ERR 0x00000028
> +#define DSI_INT_VID_RDDATA 0x00000030
> +#define DSI_INT_VID_GNT 0x00000034
> +#define DSI_INT_CMD_RDDATA 0x00000038
> +#define DSI_INT_CMD_GNT 0x0000003C
> +#define DSI_INT_INTERRUPT_CTL 0x00000040
> +
> +#define DSI_CMD_MODE_CTL 0x00000050
> +#define DSI_CMD_MODE_CTL_IF1_ID_SHIFT 0
> +#define DSI_CMD_MODE_CTL_IF1_ID_MASK 0x00000003
> +#define DSI_CMD_MODE_CTL_IF2_ID_SHIFT 2
> +#define DSI_CMD_MODE_CTL_IF2_ID_MASK 0x0000000C
> +#define DSI_CMD_MODE_CTL_IF1_LP_EN BIT(4)
> +#define DSI_CMD_MODE_CTL_IF2_LP_EN BIT(5)
> +#define DSI_CMD_MODE_CTL_ARB_MODE BIT(6)
> +#define DSI_CMD_MODE_CTL_ARB_PRI BIT(7)
> +#define DSI_CMD_MODE_CTL_FIL_VALUE_SHIFT 8
> +#define DSI_CMD_MODE_CTL_FIL_VALUE_MASK 0x0000FF00
> +#define DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT 16
> +#define DSI_CMD_MODE_CTL_TE_TIMEOUT_MASK 0x03FF0000
> +
> +#define DSI_CMD_MODE_STS 0x00000054
> +#define DSI_CMD_MODE_STS_ERR_NO_TE BIT(0)
> +#define DSI_CMD_MODE_STS_ERR_TE_MISS BIT(1)
> +#define DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN BIT(2)
> +#define DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN BIT(3)
> +#define DSI_CMD_MODE_STS_ERR_UNWANTED_RD BIT(4)
> +#define DSI_CMD_MODE_STS_CSM_RUNNING BIT(5)
> +
> +#define DSI_DIRECT_CMD_SEND 0x00000060
> +
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS 0x00000064
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_SHIFT 0
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_MASK 0x00000007
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE 0
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ 1
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ 4
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TRIG_REQ 5
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_BTA_REQ 6
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT BIT(3)
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT 8
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_MASK 0x00003F00
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_TURN_ON_PERIPHERAL 50
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHUT_DOWN_PERIPHERAL 34
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_0 3
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_1 19
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_2 35
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_LONG_WRITE 41
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_0 5
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 21
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_LONG_WRITE 57
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_READ 6
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SET_MAX_PKT_SIZE 55
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT 14
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT 16
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN BIT(21)
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_SHIFT 24
> +#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_MASK 0x0F000000
> +
> +#define DSI_DIRECT_CMD_STS 0x00000068
> +#define DSI_DIRECT_CMD_STS_CMD_TRANSMISSION BIT(0)
> +#define DSI_DIRECT_CMD_STS_WRITE_COMPLETED BIT(1)
> +#define DSI_DIRECT_CMD_STS_TRIGGER_COMPLETED BIT(2)
> +#define DSI_DIRECT_CMD_STS_READ_COMPLETED BIT(3)
> +#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_RECEIVED_SHIFT BIT(4)
> +#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED BIT(5)
> +#define DSI_DIRECT_CMD_STS_TRIGGER_RECEIVED BIT(6)
> +#define DSI_DIRECT_CMD_STS_TE_RECEIVED BIT(7)
> +#define DSI_DIRECT_CMD_STS_BTA_COMPLETED BIT(8)
> +#define DSI_DIRECT_CMD_STS_BTA_FINISHED BIT(9)
> +#define DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR BIT(10)
> +#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_MASK 0x00007800
> +#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_SHIFT 11
> +#define DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT 16
> +#define DSI_DIRECT_CMD_STS_ACK_VAL_MASK 0xFFFF0000
> +
> +#define DSI_DIRECT_CMD_RD_INIT 0x0000006C
> +#define DSI_DIRECT_CMD_RD_INIT_RESET_SHIFT 0
> +#define DSI_DIRECT_CMD_RD_INIT_RESET_MASK 0xFFFFFFFF
> +
> +#define DSI_DIRECT_CMD_WRDAT0 0x00000070
> +#define DSI_DIRECT_CMD_WRDAT1 0x00000074
> +#define DSI_DIRECT_CMD_WRDAT2 0x00000078
> +#define DSI_DIRECT_CMD_WRDAT3 0x0000007C
> +
> +#define DSI_DIRECT_CMD_RDDAT 0x00000080
> +
> +#define DSI_DIRECT_CMD_RD_PROPERTY 0x00000084
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_SHIFT 0
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK 0x0000FFFF
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_SHIFT 16
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_MASK 0x00030000
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_SHIFT 18
> +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_MASK 0x00040000
> +
> +#define DSI_DIRECT_CMD_RD_STS 0x00000088
> +
> +#define DSI_VID_MAIN_CTL 0x00000090
> +#define DSI_VID_MAIN_CTL_START_MODE_SHIFT 0
> +#define DSI_VID_MAIN_CTL_START_MODE_MASK 0x00000003
> +#define DSI_VID_MAIN_CTL_STOP_MODE_SHIFT 2
> +#define DSI_VID_MAIN_CTL_STOP_MODE_MASK 0x0000000C
> +#define DSI_VID_MAIN_CTL_VID_ID_SHIFT 4
> +#define DSI_VID_MAIN_CTL_VID_ID_MASK 0x00000030
> +#define DSI_VID_MAIN_CTL_HEADER_SHIFT 6
> +#define DSI_VID_MAIN_CTL_HEADER_MASK 0x00000FC0
> +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS 0
> +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS BIT(12)
> +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE BIT(13)
> +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS (BIT(12) | BIT(13))
> +#define DSI_VID_MAIN_CTL_BURST_MODE BIT(14)
> +#define DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE BIT(15)
> +#define DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL BIT(16)
> +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_NULL 0
> +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING BIT(17)
> +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0 BIT(18)
> +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_1 (BIT(17) | BIT(18))
> +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_NULL 0
> +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_BLANKING BIT(19)
> +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 BIT(20)
> +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_1 (BIT(19) | BIT(20))
> +#define DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT 21
> +#define DSI_VID_MAIN_CTL_RECOVERY_MODE_MASK 0x00600000
> +
> +#define DSI_VID_VSIZE 0x00000094
> +#define DSI_VID_VSIZE_VSA_LENGTH_SHIFT 0
> +#define DSI_VID_VSIZE_VSA_LENGTH_MASK 0x0000003F
> +#define DSI_VID_VSIZE_VBP_LENGTH_SHIFT 6
> +#define DSI_VID_VSIZE_VBP_LENGTH_MASK 0x00000FC0
> +#define DSI_VID_VSIZE_VFP_LENGTH_SHIFT 12
> +#define DSI_VID_VSIZE_VFP_LENGTH_MASK 0x000FF000
> +#define DSI_VID_VSIZE_VACT_LENGTH_SHIFT 20
> +#define DSI_VID_VSIZE_VACT_LENGTH_MASK 0x7FF00000
> +
> +#define DSI_VID_HSIZE1 0x00000098
> +#define DSI_VID_HSIZE1_HSA_LENGTH_SHIFT 0
> +#define DSI_VID_HSIZE1_HSA_LENGTH_MASK 0x000003FF
> +#define DSI_VID_HSIZE1_HBP_LENGTH_SHIFT 10
> +#define DSI_VID_HSIZE1_HBP_LENGTH_MASK 0x000FFC00
> +#define DSI_VID_HSIZE1_HFP_LENGTH_SHIFT 20
> +#define DSI_VID_HSIZE1_HFP_LENGTH_MASK 0x7FF00000
> +
> +#define DSI_VID_HSIZE2 0x0000009C
> +#define DSI_VID_HSIZE2_RGB_SIZE_SHIFT 0
> +#define DSI_VID_HSIZE2_RGB_SIZE_MASK 0x00001FFF
> +
> +#define DSI_VID_BLKSIZE1 0x000000A0
> +#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT 0
> +#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK 0x00001FFF
> +#define DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT 13
> +#define DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK 0x03FFE000
> +
> +#define DSI_VID_BLKSIZE2 0x000000A4
> +#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT 0
> +#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_MASK 0x00001FFF
> +
> +#define DSI_VID_PCK_TIME 0x000000A8
> +#define DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT 0
> +
> +#define DSI_VID_DPHY_TIME 0x000000AC
> +#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT 0
> +#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_MASK 0x00001FFF
> +#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT 13
> +#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_MASK 0x00FFE000
> +
> +#define DSI_VID_MODE_STS 0x000000BC
> +#define DSI_VID_MODE_STS_VSG_RUNNING BIT(0)
> +
> +#define DSI_VID_VCA_SETTING1 0x000000C0
> +#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT 0
> +#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK 0x0000FFFF
> +#define DSI_VID_VCA_SETTING1_BURST_LP BIT(16)
> +
> +#define DSI_VID_VCA_SETTING2 0x000000C4
> +#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT 0
> +#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK 0x0000FFFF
> +#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT 16
> +#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK 0xFFFF0000
> +
> +#define DSI_CMD_MODE_STS_CTL 0x000000F4
> +#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN BIT(0)
> +#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN BIT(1)
> +#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EN BIT(2)
> +#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EN BIT(3)
> +#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EN BIT(4)
> +#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EN BIT(5)
> +#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EDGE BIT(16)
> +#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EDGE BIT(17)
> +#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EDGE BIT(18)
> +#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EDGE BIT(19)
> +#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EDGE BIT(20)
> +#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EDGE BIT(21)
> +
> +#define DSI_DIRECT_CMD_STS_CTL 0x000000F8
> +#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EN BIT(0)
> +#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EN BIT(1)
> +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EN BIT(2)
> +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EN BIT(3)
> +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EN BIT(4)
> +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN BIT(5)
> +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EN BIT(6)
> +#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN BIT(7)
> +#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EN BIT(8)
> +#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EN BIT(9)
> +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EN BIT(10)
> +#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EDGE BIT(16)
> +#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EDGE BIT(17)
> +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EDGE BIT(18)
> +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EDGE BIT(19)
> +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EDGE BIT(20)
> +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EDGE BIT(21)
> +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EDGE BIT(22)
> +#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EDGE BIT(23)
> +#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EDGE BIT(24)
> +#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EDGE BIT(25)
> +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EDGE BIT(26)
> +
> +#define DSI_VID_MODE_STS_CTL 0x00000100
> +#define DSI_VID_MODE_STS_CTL_VSG_RUNNING BIT(0)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA BIT(1)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC BIT(2)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC BIT(3)
> +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH BIT(4)
> +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT BIT(5)
> +#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE BIT(6)
> +#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE BIT(7)
> +#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD BIT(8)
> +#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH BIT(9)
> +#define DSI_VID_MODE_STS_CTL_VSG_RUNNING_EDGE BIT(16)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA_EDGE BIT(17)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC_EDGE BIT(18)
> +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC_EDGE BIT(19)
> +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH_EDGE BIT(20)
> +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT_EDGE BIT(21)
> +#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE_EDGE BIT(22)
> +#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE_EDGE BIT(23)
> +#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD_EDGE BIT(24)
> +#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH_EDGE BIT(25)
> +#define DSI_VID_MODE_STS_CTL_VSG_RECOVERY_EDGE BIT(26)
> +
> +#define DSI_TG_STS_CTL 0x00000104
> +#define DSI_MCTL_DHPY_ERR_CTL 0x00000108
> +#define DSI_MCTL_MAIN_STS_CLR 0x00000110
> +
> +#define DSI_CMD_MODE_STS_CLR 0x00000114
> +#define DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR BIT(0)
> +#define DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR BIT(1)
> +#define DSI_CMD_MODE_STS_CLR_ERR_SDI1_UNDERRUN_CLR BIT(2)
> +#define DSI_CMD_MODE_STS_CLR_ERR_SDI2_UNDERRUN_CLR BIT(3)
> +#define DSI_CMD_MODE_STS_CLR_ERR_UNWANTED_RD_CLR BIT(4)
> +#define DSI_CMD_MODE_STS_CLR_CSM_RUNNING_CLR BIT(5)
> +
> +#define DSI_DIRECT_CMD_STS_CLR 0x00000118
> +#define DSI_DIRECT_CMD_STS_CLR_CMD_TRANSMISSION_CLR BIT(0)
> +#define DSI_DIRECT_CMD_STS_CLR_WRITE_COMPLETED_CLR BIT(1)
> +#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_COMPLETED_CLR BIT(2)
> +#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_CLR BIT(3)
> +#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_RECEIVED_CLR BIT(4)
> +#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR BIT(5)
> +#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_RECEIVED_CLR BIT(6)
> +#define DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR BIT(7)
> +#define DSI_DIRECT_CMD_STS_CLR_BTA_COMPLETED_CLR BIT(8)
> +#define DSI_DIRECT_CMD_STS_CLR_BTA_FINISHED_CLR BIT(9)
> +#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_WITH_ERR_CLR BIT(10)
> +
> +#define DSI_DIRECT_CMD_RD_STS_CLR 0x0000011C
> +#define DSI_VID_MODE_STS_CLR 0x00000120
> +#define DSI_TG_STS_CLR 0x00000124
> +#define DSI_MCTL_DPHY_ERR_CLR 0x00000128
> +#define DSI_MCTL_MAIN_STS_FLAG 0x00000130
> +#define DSI_CMD_MODE_STS_FLAG 0x00000134
> +#define DSI_DIRECT_CMD_STS_FLAG 0x00000138
> +#define DSI_DIRECT_CMD_RD_STS_FLAG 0x0000013C
> +#define DSI_VID_MODE_STS_FLAG 0x00000140
> +#define DSI_TG_STS_FLAG 0x00000144
> +
> +#define DSI_DPHY_LANES_TRIM 0x00000150
> +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_SHIFT 0
> +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_MASK 0x00000003
> +#define DSI_DPHY_LANES_TRIM_DPHY_CD_OFF_DAT1 BIT(2)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT1 BIT(3)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT1 BIT(4)
> +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT1 BIT(5)
> +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_SHIFT 6
> +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_MASK 0x000000C0
> +#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_SHIFT 8
> +#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_MASK 0x00000300
> +#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_SHIFT 10
> +#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_MASK 0x00000C00
> +#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_81 0
> +#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90 BIT(12)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_CLK BIT(13)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_CLK BIT(14)
> +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_CLK BIT(15)
> +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT2 BIT(16)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT2 BIT(18)
> +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT2 BIT(19)
> +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT2 BIT(20)
> +
> +#define DSI_ID_REG	0x00000FF0
> +
> +/* PRCMU DSI reset registers */
> +#define PRCM_DSI_SW_RESET 0x324
> +#define PRCM_DSI_SW_RESET_DSI0_SW_RESETN BIT(0)
> +#define PRCM_DSI_SW_RESET_DSI1_SW_RESETN BIT(1)
> +#define PRCM_DSI_SW_RESET_DSI2_SW_RESETN BIT(2)
> +
> +struct mcde_dsi {
> +	struct device *dev;
> +	struct mcde *mcde;
> +	struct drm_encoder encoder;
> +	struct drm_connector connector;
> +	struct drm_panel *panel;
> +	struct drm_bridge *bridge;
> +	struct mipi_dsi_host dsi_host;
> +	struct mipi_dsi_device *mdsi;
> +	unsigned long hs_freq;
> +	unsigned long lp_freq;
> +	bool unused;
> +
> +	void __iomem *regs;
> +	struct regmap *prcmu;
> +};
> +
> +static inline struct mcde_dsi *encoder_to_mcde_dsi(struct drm_encoder *e)
> +{
> +	return container_of(e, struct mcde_dsi, encoder);
> +}
> +
> +static inline struct mcde_dsi *host_to_mcde_dsi(struct mipi_dsi_host *h)
> +{
> +	return container_of(h, struct mcde_dsi, dsi_host);
> +}
> +
> +static inline struct mcde_dsi *connector_to_mcde_dsi(struct drm_connector *c)
> +{
> +	return container_of(c, struct mcde_dsi, connector);
> +}
> +
> +bool mcde_dsi_irq(struct mipi_dsi_device *mdsi)
> +{
> +	struct mcde_dsi *d;
> +	u32 val;
> +	bool te_received = false;
> +
> +	d = host_to_mcde_dsi(mdsi->host);
> +
> +	dev_dbg(d->dev, "%s called\n", __func__);
> +
> +	val = readl(d->regs + DSI_DIRECT_CMD_STS_FLAG);
> +	if (val)
> +		dev_dbg(d->dev, "DSI_DIRECT_CMD_STS_FLAG = %08x\n", val);
> +	if (val & DSI_DIRECT_CMD_STS_WRITE_COMPLETED)
> +		dev_dbg(d->dev, "direct command write completed\n");
> +	if (val & DSI_DIRECT_CMD_STS_TE_RECEIVED) {
> +		te_received = true;
> +		dev_dbg(d->dev, "direct command TE received\n");
> +	}
> +	if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED)
> +		dev_err(d->dev, "direct command ACK ERR received\n");
> +	if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR)
> +		dev_err(d->dev, "direct command read ERR received\n");
> +	/* Mask off the ACK value and clear status */
> +	writel(val, d->regs + DSI_DIRECT_CMD_STS_CLR);
> +
> +	val = readl(d->regs + DSI_CMD_MODE_STS_FLAG);
> +	if (val)
> +		dev_dbg(d->dev, "DSI_CMD_MODE_STS_FLAG = %08x\n", val);
> +	if (val & DSI_CMD_MODE_STS_ERR_NO_TE)
> +		/* This happens all the time (safe to ignore) */
> +		dev_dbg(d->dev, "CMD mode no TE\n");
> +	if (val & DSI_CMD_MODE_STS_ERR_TE_MISS)
> +		/* This happens all the time (safe to ignore) */
> +		dev_dbg(d->dev, "CMD mode TE miss\n");
> +	if (val & DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN)
> +		dev_err(d->dev, "CMD mode SD1 underrun\n");
> +	if (val & DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN)
> +		dev_err(d->dev, "CMD mode SD2 underrun\n");
> +	if (val & DSI_CMD_MODE_STS_ERR_UNWANTED_RD)
> +		dev_err(d->dev, "CMD mode unwanted RD\n");
> +	writel(val, d->regs + DSI_CMD_MODE_STS_CLR);
> +
> +	val = readl(d->regs + DSI_DIRECT_CMD_RD_STS_FLAG);
> +	if (val)
> +		dev_dbg(d->dev, "DSI_DIRECT_CMD_RD_STS_FLAG = %08x\n", val);
> +	writel(val, d->regs + DSI_DIRECT_CMD_RD_STS_CLR);
> +
> +	val = readl(d->regs + DSI_TG_STS_FLAG);
> +	if (val)
> +		dev_dbg(d->dev, "DSI_TG_STS_FLAG = %08x\n", val);
> +	writel(val, d->regs + DSI_TG_STS_CLR);
> +
> +	val = readl(d->regs + DSI_VID_MODE_STS_FLAG);
> +	if (val)
> +		dev_err(d->dev, "some video mode error status\n");
> +	writel(val, d->regs + DSI_VID_MODE_STS_CLR);
> +
> +	return te_received;
> +}
> +
> +static int mcde_dsi_host_attach(struct mipi_dsi_host *host,
> +				struct mipi_dsi_device *mdsi)
> +{
> +	struct mcde_dsi *d = host_to_mcde_dsi(host);
> +
> +	if (mdsi->lanes < 1 || mdsi->lanes > 2) {
> +		DRM_ERROR("dsi device params invalid, 1 or 2 lanes supported\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_info(d->dev, "attached DSI device with %d lanes\n", mdsi->lanes);
> +	/* MIPI_DSI_FMT_RGB88 etc */
> +	dev_info(d->dev, "format %08x, %dbpp\n", mdsi->format,
> +		 mipi_dsi_pixel_format_to_bpp(mdsi->format));
> +	dev_info(d->dev, "mode flags: %08lx\n", mdsi->mode_flags);
> +
> +	d->mdsi = mdsi;
> +	if (d->mcde)
> +		d->mcde->mdsi = mdsi;
> +
> +	return 0;
> +}
> +
> +static int mcde_dsi_host_detach(struct mipi_dsi_host *host,
> +				struct mipi_dsi_device *mdsi)
> +{
> +	struct mcde_dsi *d = host_to_mcde_dsi(host);
> +
> +	d->mdsi = NULL;
> +	if (d->mcde)
> +		d->mcde->mdsi = NULL;
> +
> +	return 0;
> +}
> +
> +#define MCDE_DSI_HOST_IS_READ(type)			    \
> +	((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \
> +	 (type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \
> +	 (type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \
> +	 (type == MIPI_DSI_DCS_READ))
> +
> +static ssize_t mcde_dsi_host_transfer(struct mipi_dsi_host *host,
> +				      const struct mipi_dsi_msg *msg)
> +{
> +	struct mcde_dsi *d = host_to_mcde_dsi(host);
> +	const u32 loop_delay_us = 10; /* us */
> +	const u8 *tx = msg->tx_buf;
> +	u32 loop_counter;
> +	size_t txlen;
> +	u32 val;
> +	int ret;
> +	int i;
> +
> +	txlen = msg->tx_len;
> +	if (txlen > 12) {
> +		dev_err(d->dev,
> +			"dunno how to write more than 12 bytes yet\n");
> +		return -EIO;
> +	}
> +
> +	dev_dbg(d->dev,
> +		"message to channel %d, %u bytes",
> +		msg->channel,
> +		txlen);
> +
> +	/* Command "nature" */
> +	if (MCDE_DSI_HOST_IS_READ(msg->type))
> +		/* MCTL_MAIN_DATA_CTL already set up */
> +		val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ;
> +	else
> +		val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE;
> +	/*
> +	 * More than 2 bytes will not fit in a single packet, so it's
> +	 * time to set the "long not short" bit. One byte is used by
> +	 * the MIPI DCS command leaving just one byte for the payload
> +	 * in a short package.
> +	 */
> +	if (mipi_dsi_packet_format_is_long(msg->type))
> +		val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT;
> +	val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT;
> +	/* Add one to the length for the MIPI DCS command */
> +	val |= txlen
> +		<< DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT;
> +	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN;
> +	val |= msg->type << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT;
> +	writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS);
> +
> +	/* MIPI DCS command is part of the data */
> +	if (txlen > 0) {
> +		val = 0;
> +		for (i = 0; i < 4 && i < txlen; i++)
> +			val |= tx[i] << (i & 3) * 8;
> +	}
> +	writel(val, d->regs + DSI_DIRECT_CMD_WRDAT0);
> +	if (txlen > 4) {
> +		val = 0;
> +		for (i = 0; i < 4 && (i + 4) < txlen; i++)
> +			val |= tx[i + 4] << (i & 3) * 8;
> +		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT1);
> +	}
> +	if (txlen > 8) {
> +		val = 0;
> +		for (i = 0; i < 4 && (i + 8) < txlen; i++)
> +			val |= tx[i + 8] << (i & 3) * 8;
> +		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT2);
> +	}
> +	if (txlen > 12) {
> +		val = 0;
> +		for (i = 0; i < 4 && (i + 12) < txlen; i++)
> +			val |= tx[i + 12] << (i & 3) * 8;
> +		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT3);
> +	}
> +
> +	writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR);
> +	writel(~0, d->regs + DSI_CMD_MODE_STS_CLR);
> +	/* Send command */
> +	writel(1, d->regs + DSI_DIRECT_CMD_SEND);
> +
> +	loop_counter = 1000 * 1000 / loop_delay_us;
> +	while (!(readl(d->regs + DSI_DIRECT_CMD_STS) &
> +		 DSI_DIRECT_CMD_STS_WRITE_COMPLETED)
> +	       && --loop_counter)
> +		usleep_range(loop_delay_us, (loop_delay_us * 3) / 2);
> +
> +	if (!loop_counter) {
> +		dev_err(d->dev, "DSI write timeout!\n");
> +		return -ETIME;
> +	}
> +
> +	val = readl(d->regs + DSI_DIRECT_CMD_STS);
> +	if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) {
> +		val >>= DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT;
> +		dev_err(d->dev, "error during transmission: %04x\n",
> +			val);
> +		return -EIO;
> +	}
> +
> +	if (!MCDE_DSI_HOST_IS_READ(msg->type)) {
> +		/* Return number of bytes written */
> +		if (mipi_dsi_packet_format_is_long(msg->type))
> +			ret = 4 + txlen;
> +		else
> +			ret = 4;
> +	} else {
> +		/* OK this is a read command, get the response */
> +		u32 rdsz;
> +		u32 rddat;
> +		u8 *rx = msg->rx_buf;
> +
> +		rdsz = readl(d->regs + DSI_DIRECT_CMD_RD_PROPERTY);
> +		rdsz &= DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK;
> +		rddat = readl(d->regs + DSI_DIRECT_CMD_RDDAT);
> +		for (i = 0; i < 4 && i < rdsz; i++)
> +			rx[i] = (rddat >> (i * 8)) & 0xff;
> +		ret = rdsz;
> +	}
> +
> +	writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR);
> +	writel(~0, d->regs + DSI_CMD_MODE_STS_CLR);
> +
> +	return ret;
> +}
> +
> +static const struct mipi_dsi_host_ops mcde_dsi_host_ops = {
> +	.attach = mcde_dsi_host_attach,
> +	.detach = mcde_dsi_host_detach,
> +	.transfer = mcde_dsi_host_transfer,
> +};
> +
> +/* This sends a direct (short) command to request TE */
> +void mcde_dsi_te_request(struct mipi_dsi_device *mdsi)
> +{
> +	struct mcde_dsi *d;
> +	u32 val;
> +
> +	d = host_to_mcde_dsi(mdsi->host);
> +
> +	/* Command "nature" TE request */
> +	val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ;
> +	val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT;
> +	val |= 2 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT;
> +	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN;
> +	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 <<
> +		DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT;
> +	writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS);
> +
> +	/* Clear TE reveived and error status bits and enables them */
> +	writel(DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR |
> +	       DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR,
> +	       d->regs + DSI_DIRECT_CMD_STS_CLR);
> +	val = readl(d->regs + DSI_DIRECT_CMD_STS_CTL);
> +	val |= DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN;
> +	val |= DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN;
> +	writel(val, d->regs + DSI_DIRECT_CMD_STS_CTL);
> +
> +	/* Clear and enable no TE or TE missing status */
> +	writel(DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR |
> +	       DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR,
> +	       d->regs + DSI_CMD_MODE_STS_CLR);
> +	val = readl(d->regs + DSI_CMD_MODE_STS_CTL);
> +	val |= DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN;
> +	val |= DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN;
> +	writel(val, d->regs + DSI_CMD_MODE_STS_CTL);
> +
> +	/* Send this TE request command */
> +	writel(1, d->regs + DSI_DIRECT_CMD_SEND);
> +}
> +
> +static void mcde_dsi_setup_video_mode(struct mcde_dsi *d,
> +				      struct drm_display_mode *mode)
> +{
> +	u8 bpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format);
> +	u64 bpl;
> +	u32 hfp;
> +	u32 hbp;
> +	u32 hsa;
> +	u32 blkline_pck, line_duration;
> +	u32 blkeol_pck, blkeol_duration;
> +	u32 val;
> +
> +	val = 0;
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
> +		val |= DSI_VID_MAIN_CTL_BURST_MODE;
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
> +		val |= DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE;
> +		val |= DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL;
> +	}
> +	/* RGB header and pixel mode */
> +	switch (d->mdsi->format) {
> +	case MIPI_DSI_FMT_RGB565:
> +		val |= MIPI_DSI_PACKED_PIXEL_STREAM_16 <<
> +			DSI_VID_MAIN_CTL_HEADER_SHIFT;
> +		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS;
> +		break;
> +	case MIPI_DSI_FMT_RGB666_PACKED:
> +		val |= MIPI_DSI_PACKED_PIXEL_STREAM_18 <<
> +			DSI_VID_MAIN_CTL_HEADER_SHIFT;
> +		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS;
> +		break;
> +	case MIPI_DSI_FMT_RGB666:
> +		val |= MIPI_DSI_PIXEL_STREAM_3BYTE_18
> +			<< DSI_VID_MAIN_CTL_HEADER_SHIFT;
> +		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE;
> +		break;
> +	case MIPI_DSI_FMT_RGB888:
> +		val |= MIPI_DSI_PACKED_PIXEL_STREAM_24 <<
> +			DSI_VID_MAIN_CTL_HEADER_SHIFT;
> +		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS;
> +		break;
> +	default:
> +		dev_err(d->dev, "unknown pixel mode\n");
> +		return;
> +	}
> +
> +	/* FIXME: TVG could be enabled here */
> +
> +	/* Send blanking packet */
> +	val |= DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0;
> +	/* Send EOL packet */
> +	val |= DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0;
> +	/* Recovery mode 1 */
> +	val |= 1 << DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT;
> +	/* All other fields zero */
> +	writel(val, d->regs + DSI_VID_MAIN_CTL);
> +
> +	/* Vertical frame parameters are pretty straight-forward */
> +	val = mode->vdisplay << DSI_VID_VSIZE_VSA_LENGTH_SHIFT;
> +	/* vertical front porch */
> +	val |= (mode->vsync_start - mode->vdisplay)
> +		<< DSI_VID_VSIZE_VFP_LENGTH_SHIFT;
> +	/* vertical sync active */
> +	val |= (mode->vsync_end - mode->vsync_start)
> +		<< DSI_VID_VSIZE_VACT_LENGTH_SHIFT;
> +	/* vertical back porch */
> +	val |= (mode->vtotal - mode->vsync_end)
> +		<< DSI_VID_VSIZE_VBP_LENGTH_SHIFT;
> +	writel(val, d->regs + DSI_VID_VSIZE);
> +
> +	/*
> +	 * Horizontal frame parameters:
> +	 * horizontal resolution is given in pixels and must be re-calculated
> +	 * into bytes since this is what the hardware expects.
> +	 *
> +	 * 6 + 2 is HFP header + checksum
> +	 */
> +	hfp = (mode->hsync_start - mode->hdisplay) * bpp - 6 - 2;
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
> +		/*
> +		 * 6 is HBP header + checksum
> +		 * 4 is RGB header + checksum
> +		 */
> +		hbp = (mode->htotal - mode->hsync_end) * bpp - 4 - 6;
> +		/*
> +		 * 6 is HBP header + checksum
> +		 * 4 is HSW packet bytes
> +		 * 4 is RGB header + checksum
> +		 */
> +		hsa = (mode->hsync_end - mode->hsync_start) * bpp - 4 - 4 - 6;
> +	} else {
> +		/*
> +		 * HBP includes both back porch and sync
> +		 * 6 is HBP header + checksum
> +		 * 4 is HSW packet bytes
> +		 * 4 is RGB header + checksum
> +		 */
> +		hbp = (mode->htotal - mode->hsync_start) * bpp - 4 - 4 - 6;
> +		/* HSA is not considered in this mode and set to 0 */
> +		hsa = 0;
> +	}
> +	dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u\n",
> +		hfp, hbp, hsa);
> +
> +	/* Frame parameters: horizontal sync active */
> +	val = hsa << DSI_VID_HSIZE1_HSA_LENGTH_SHIFT;
> +	/* horizontal back porch */
> +	val |= hbp << DSI_VID_HSIZE1_HBP_LENGTH_SHIFT;
> +	/* horizontal front porch */
> +	val |= hfp << DSI_VID_HSIZE1_HFP_LENGTH_SHIFT;
> +	writel(val, d->regs + DSI_VID_HSIZE1);
> +
> +	/* RGB data length (bytes on one scanline) */
> +	val = mode->hdisplay * (bpp / 8);
> +	writel(val, d->regs + DSI_VID_HSIZE2);
> +
> +	/* TODO: further adjustments for TVG mode here */
> +
> +	/*
> +	 * EOL packet length from bits per line calculations: pixel clock
> +	 * is given in kHz, calculate the time between two pixels in
> +	 * picoseconds.
> +	 */
> +	bpl = mode->clock * mode->htotal;
> +	bpl *= (d->hs_freq / 8);
> +	do_div(bpl, 1000000); /* microseconds */
> +	do_div(bpl, 1000000); /* seconds */
> +	bpl *= d->mdsi->lanes;
> +	dev_dbg(d->dev, "calculated bytes per line: %llu\n", bpl);
> +	/*
> +	 * 6 is header + checksum, header = 4 bytes, checksum = 2 bytes
> +	 * 4 is short packet for vsync/hsync
> +	 */
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
> +		/* Fixme: isn't the hsync width in pixels? */
> +		blkline_pck = bpl - (mode->hsync_end - mode->hsync_start) - 6;
> +		val = blkline_pck << DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT;
> +		writel(val, d->regs + DSI_VID_BLKSIZE2);
> +	} else {
> +		blkline_pck = bpl - 4 - 6;
> +		val = blkline_pck << DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT;
> +		writel(val, d->regs + DSI_VID_BLKSIZE1);
> +	}
> +
> +	line_duration = (blkline_pck + 6) / d->mdsi->lanes;
> +	dev_dbg(d->dev, "line duration %u\n", line_duration);
> +	val = line_duration << DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT;
> +	/*
> +	 * This is the time to perform LP->HS on D-PHY
> +	 * FIXME: nowhere to get this from: DT property on the DSI?
> +	 */
> +	val |= 0 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT;
> +	writel(val, d->regs + DSI_VID_DPHY_TIME);
> +
> +	/* Calculate block end of line */
> +	blkeol_pck = bpl - mode->hdisplay * bpp - 6;
> +	blkeol_duration = (blkeol_pck + 6) / d->mdsi->lanes;
> +	dev_dbg(d->dev, "blkeol pck: %u, duration: %u\n",
> +		 blkeol_pck, blkeol_duration);
> +
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) {
> +		/* Set up EOL clock for burst mode */
> +		val = readl(d->regs + DSI_VID_BLKSIZE1);
> +		val |= blkeol_pck << DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT;
> +		writel(val, d->regs + DSI_VID_BLKSIZE1);
> +		writel(blkeol_pck, d->regs + DSI_VID_VCA_SETTING2);
> +
> +		writel(blkeol_duration, d->regs + DSI_VID_PCK_TIME);
> +		writel(blkeol_duration - 6, d->regs + DSI_VID_VCA_SETTING1);
> +	}
> +
> +	/* Maximum line limit */
> +	val = readl(d->regs + DSI_VID_VCA_SETTING2);
> +	val |= blkline_pck <<
> +		DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT;
> +	writel(val, d->regs + DSI_VID_VCA_SETTING2);
> +
> +	/* Put IF1 into video mode */
> +	val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +	val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE;
> +	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +
> +	/* Disable command mode on IF1 */
> +	val = readl(d->regs + DSI_CMD_MODE_CTL);
> +	val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN;
> +	writel(val, d->regs + DSI_CMD_MODE_CTL);
> +
> +	/* Enable some error interrupts */
> +	val = readl(d->regs + DSI_VID_MODE_STS_CTL);
> +	val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC;
> +	val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA;
> +	writel(val, d->regs + DSI_VID_MODE_STS_CTL);
> +
> +	/* Enable video mode */
> +	val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +	val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN;
> +	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +}
> +
> +static void mcde_dsi_start(struct mcde_dsi *d)
> +{
> +	unsigned long hs_freq;
> +	u32 val;
> +	int i;
> +
> +	/* No integration mode */
> +	writel(0, d->regs + DSI_MCTL_INTEGRATION_MODE);
> +
> +	/* Enable the DSI port, from drivers/video/mcde/dsilink_v2.c */
> +	val = DSI_MCTL_MAIN_DATA_CTL_LINK_EN |
> +		DSI_MCTL_MAIN_DATA_CTL_BTA_EN |
> +		DSI_MCTL_MAIN_DATA_CTL_READ_EN |
> +		DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN;
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
> +		val |= DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN;
> +	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +
> +	/* Set a high command timeout, clear other fields */
> +	val = 0x3ff << DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT;
> +	writel(val, d->regs + DSI_CMD_MODE_CTL);
> +
> +	/*
> +	 * UI_X4 is described as "unit interval times four"
> +	 * I guess since DSI packets are 4 bytes wide, one unit
> +	 * is one byte.
> +	 */
> +	hs_freq = clk_get_rate(d->mcde->dsi0_clk);
> +	hs_freq /= 1000000; /* MHz */
> +	val = 4000 / hs_freq;
> +	dev_dbg(d->dev, "UI value: %d\n", val);
> +	val <<= DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT;
> +	val &= DSI_MCTL_DPHY_STATIC_UI_X4_MASK;
> +	writel(val, d->regs + DSI_MCTL_DPHY_STATIC);
> +
> +	/*
> +	 * Enable clocking: 0x0f (something?) between each burst,
> +	 * enable the second lane if needed, enable continuous clock if
> +	 * needed, enable switch into ULPM (ultra-low power mode) on
> +	 * all the lines.
> +	 */
> +	val = 0x0f << DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT;
> +	if (d->mdsi->lanes == 2)
> +		val |= DSI_MCTL_MAIN_PHY_CTL_LANE2_EN;
> +	if (!(d->mdsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
> +		val |= DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS;
> +	val |= DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN |
> +		DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN |
> +		DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN;
> +	writel(val, d->regs + DSI_MCTL_MAIN_PHY_CTL);
> +
> +	val = (1 << DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT) |
> +		(1 << DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT);
> +	writel(val, d->regs + DSI_MCTL_ULPOUT_TIME);
> +
> +	writel(DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90,
> +	       d->regs + DSI_DPHY_LANES_TRIM);
> +
> +	/* High PHY timeout */
> +	val = (0x0f << DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT) |
> +		(0x3fff << DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT) |
> +		(0x3fff << DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT);
> +	writel(val, d->regs + DSI_MCTL_DPHY_TIMEOUT);
> +
> +	val = DSI_MCTL_MAIN_EN_PLL_START |
> +		DSI_MCTL_MAIN_EN_CKLANE_EN |
> +		DSI_MCTL_MAIN_EN_DAT1_EN |
> +		DSI_MCTL_MAIN_EN_IF1_EN;
> +	if (d->mdsi->lanes == 2)
> +		val |= DSI_MCTL_MAIN_EN_DAT2_EN;
> +	writel(val, d->regs + DSI_MCTL_MAIN_EN);
> +
> +	/* Wait for the PLL to lock and the clock and data lines to come up */
> +	i = 0;
> +	val = DSI_MCTL_MAIN_STS_PLL_LOCK |
> +		DSI_MCTL_MAIN_STS_CLKLANE_READY |
> +		DSI_MCTL_MAIN_STS_DAT1_READY;
> +	if (d->mdsi->lanes == 2)
> +		val |= DSI_MCTL_MAIN_STS_DAT2_READY;
> +	while ((readl(d->regs + DSI_MCTL_MAIN_STS) & val) != val) {
> +		/* Sleep for a millisecond */
> +		usleep_range(1000, 1500);
> +		if (i++ == 100) {
> +			dev_warn(d->dev, "DSI lanes did not start up\n");
> +			return;
> +		}
> +	}
> +
> +	/* TODO needed? */
> +
> +	/* Command mode, clear IF1 ID */
> +	val = readl(d->regs + DSI_CMD_MODE_CTL);
> +	/* FIXME: enable low-power mode? */
> +	// val |= DSI_CMD_MODE_CTL_IF1_LP_EN;
> +	val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK;
> +	writel(val, d->regs + DSI_CMD_MODE_CTL);
> +
> +	/* Wait for DSI PHY to initialize */
> +	usleep_range(100, 200);
> +	dev_info(d->dev, "DSI link enabled\n");
> +}
> +
> +static void mcde_dsi_enable(struct drm_encoder *encoder)
> +{
> +	struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> +	struct mcde_dsi *d = encoder_to_mcde_dsi(encoder);
> +	unsigned long pixel_clock_hz = mode->clock * 1000;
> +	unsigned long hs_freq, lp_freq;
> +	u32 val;
> +	int ret;
> +
> +	if (!d->mdsi) {
> +		dev_err(d->dev, "no DSI device attached to encoder!\n");
> +		return;
> +	}
> +
> +	dev_info(d->dev, "enable DSI master for %dx%d %lu Hz %s mode\n",
> +		 mode->hdisplay, mode->vdisplay, pixel_clock_hz,
> +		 (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD"
> +		);
> +
> +	/* Copy maximum clock frequencies */
> +	if (d->mdsi->lp_rate)
> +		lp_freq = d->mdsi->lp_rate;
> +	else
> +		lp_freq = DSI_DEFAULT_LP_FREQ_HZ;
> +	if (d->mdsi->hs_rate)
> +		hs_freq = d->mdsi->hs_rate;
> +	else
> +		hs_freq = DSI_DEFAULT_HS_FREQ_HZ;
> +
> +	/* Enable LP (Low Power, Energy Save, ES) and HS (High Speed) clocks */
> +	d->lp_freq = clk_round_rate(d->mcde->dsi0es_clk, lp_freq);
> +	ret = clk_set_rate(d->mcde->dsi0es_clk, d->lp_freq);
> +	if (ret)
> +		dev_err(d->dev, "failed to set LP clock rate %lu Hz\n",
> +			d->lp_freq);
> +
> +	d->hs_freq = clk_round_rate(d->mcde->dsi0_clk, hs_freq);
> +	ret = clk_set_rate(d->mcde->dsi0_clk, d->hs_freq);
> +	if (ret)
> +		dev_err(d->dev, "failed to set HS clock rate %lu Hz\n",
> +			d->hs_freq);
> +
> +	/* Start clocks */
> +	ret = clk_prepare_enable(d->mcde->dsi0es_clk);
> +	if (ret)
> +		dev_err(d->dev, "failed to enable LP clock\n");
> +	else
> +		dev_info(d->dev, "DSI LP clock rate %lu Hz\n",
> +			 d->lp_freq);
> +	ret = clk_prepare_enable(d->mcde->dsi0_clk);
> +	if (ret)
> +		dev_err(d->dev, "failed to enable HS clock\n");
> +	else
> +		dev_info(d->dev, "DSI HS clock rate %lu Hz\n",
> +			 d->hs_freq);
> +
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
> +		mcde_dsi_setup_video_mode(d, mode);
> +	} else {
> +		/* Command mode, clear IF1 ID */
> +		val = readl(d->regs + DSI_CMD_MODE_CTL);
> +		/* FIXME: enable low-power mode? */
> +		// val |= DSI_CMD_MODE_CTL_IF1_LP_EN;
> +		val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK;
> +		writel(val, d->regs + DSI_CMD_MODE_CTL);
> +	}
> +}
> +
> +static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d)
> +{
> +	u32 val;
> +	int i;
> +
> +	/*
> +	 * Wait until we get out of command mode
> +	 * CSM = Command State Machine
> +	 */
> +	i = 0;
> +	val = DSI_CMD_MODE_STS_CSM_RUNNING;
> +	while ((readl(d->regs + DSI_CMD_MODE_STS) & val) == val) {
> +		/* Sleep for a millisecond */
> +		usleep_range(1000, 2000);
> +		if (i++ == 100) {
> +			dev_warn(d->dev,
> +				 "could not get out of command mode\n");
> +			return;
> +		}
> +	}
> +}
> +
> +static void mcde_dsi_wait_for_video_mode_stop(struct mcde_dsi *d)
> +{
> +	u32 val;
> +	int i;
> +
> +	/* Wait until we get out og video mode */
> +	i = 0;
> +	val = DSI_VID_MODE_STS_VSG_RUNNING;
> +	while ((readl(d->regs + DSI_VID_MODE_STS) & val) == val) {
> +		/* Sleep for a millisecond */
> +		usleep_range(1000, 2000);
> +		if (i++ == 100) {
> +			dev_warn(d->dev,
> +				 "could not get out of video mode\n");
> +			return;
> +		}
> +	}
> +}
> +
> +static void mcde_dsi_disable(struct drm_encoder *encoder)
> +{
> +	struct mcde_dsi *d = encoder_to_mcde_dsi(encoder);
> +	u32 val;
> +
> +	/* Disable all error interrupts */
> +	writel(0, d->regs + DSI_VID_MODE_STS_CTL);
> +
> +	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
> +		/* Stop video mode */
> +		val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +		val &= ~DSI_MCTL_MAIN_DATA_CTL_VID_EN;
> +		writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
> +		mcde_dsi_wait_for_video_mode_stop(d);
> +	} else {
> +		/* Stop command mode */
> +		mcde_dsi_wait_for_command_mode_stop(d);
> +	}
> +
> +	/* Stop clocks */
> +	clk_disable_unprepare(d->mcde->dsi0_clk);
> +	clk_disable_unprepare(d->mcde->dsi0es_clk);
> +}
> +
> +static const struct drm_encoder_helper_funcs mcde_dsi_encoder_helper_funcs = {
> +	.enable = mcde_dsi_enable,
> +	.disable = mcde_dsi_disable,
> +};
> +
> +static const struct drm_encoder_funcs mcde_dsi_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static const struct drm_connector_funcs mcde_dsi_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_connector_cleanup,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int mcde_dsi_get_modes(struct drm_connector *connector)
> +{
> +	struct mcde_dsi *d = connector_to_mcde_dsi(connector);
> +
> +	if (d->panel)
> +		return drm_panel_get_modes(d->panel);
> +
> +	/* TODO: deal with bridges */
> +
> +	return 0;
> +}
> +
> +static const struct drm_connector_helper_funcs
> +mcde_dsi_connector_helper_funcs = {
> +	.get_modes = mcde_dsi_get_modes,
> +};
> +
> +static int mcde_dsi_bind(struct device *dev, struct device *master,
> +			 void *data)
> +{
> +	struct drm_device *drm = data;
> +	struct mcde *mcde = drm->dev_private;
> +	struct mcde_dsi *d = dev_get_drvdata(dev);
> +	struct drm_encoder *encoder = &d->encoder;
> +	struct drm_connector *connector = &d->connector;
> +	struct drm_panel *panel;
> +	struct drm_bridge *bridge;
> +	int ret;
> +
> +	if (!of_get_available_child_count(dev->of_node)) {
> +		dev_info(dev, "unused DSI interface\n");
> +		d->unused = true;
> +		return 0;
> +	}
> +	d->mcde = mcde;
> +	/* If the display attached before binding, set this up */
> +	if (d->mdsi)
> +		d->mcde->mdsi = d->mdsi;
> +
> +	/* Assert RESET through the PRCMU, active low */
> +	/* FIXME: which DSI block? */
> +	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
> +			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
> +
> +	usleep_range(100, 200);
> +
> +	/* De-assert RESET again */
> +	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
> +			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN,
> +			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN);
> +
> +	/* Start up the hardware */
> +	mcde_dsi_start(d);
> +
> +	/* Create an encoder and attach the display bridge to it */
> +	drm_encoder_init(drm, encoder, &mcde_dsi_encoder_funcs,
> +			 DRM_MODE_ENCODER_DSI, NULL);
> +	drm_encoder_helper_add(encoder, &mcde_dsi_encoder_helper_funcs);
> +
> +	/* Create a connector and attach the encoder to it */
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +	ret = drm_connector_init(encoder->dev, connector,
> +				 &mcde_dsi_connector_funcs,
> +				 DRM_MODE_CONNECTOR_DSI);
> +	if (ret) {
> +		dev_err(dev, "failed to initialize connector\n");
> +		drm_encoder_cleanup(encoder);
> +		return ret;
> +	}
> +	connector->status = connector_status_disconnected;
> +	drm_connector_helper_add(connector, &mcde_dsi_connector_helper_funcs);
> +	drm_connector_attach_encoder(connector, encoder);
> +	drm_connector_register(connector);
> +
> +	dev_info(dev, "initialized encoder and connector\n");
> +
> +	/* The DSI encoder connects to a panel or other bridge */
> +	ret = drm_of_find_panel_or_bridge(dev->of_node,
> +					  0, 0, &panel, &bridge);
> +	if (ret && ret != -ENODEV) {
> +		dev_err(dev, "no panel or bridge %d\n", ret);
> +		return ret;
> +	}
> +	if (panel) {
> +		bridge = drm_panel_bridge_add(panel,
> +					      DRM_MODE_CONNECTOR_DSI);
> +		if (IS_ERR(bridge)) {
> +			dev_err(dev, "error adding panel bridge\n");
> +			return PTR_ERR(bridge);
> +		}
> +		dev_info(dev, "connected to panel\n");
> +		d->panel = panel;
> +	} else if (bridge) {
> +		/* FIXME: AV8100 HDMI encoder goes here for example */
> +		dev_info(dev, "connected to non-panel bridge (unsupported)\n");
> +		return -ENODEV;
> +	} else {
> +		dev_err(dev, "no panel or bridge\n");
> +		return -ENODEV;
> +	}
> +
> +	d->bridge = bridge;
> +	d->connector.status = connector_status_connected;
> +
> +	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	if (ret) {
> +		dev_err(dev, "bridge attach failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* FIXME: first come first serve, use a list */
> +	mcde->connector = connector;
> +	mcde->bridge = bridge;
> +	dev_info(dev, "set up DSI connector and panel bridge\n");
> +
> +	return 0;
> +}
> +
> +static void mcde_dsi_unbind(struct device *dev, struct device *master,
> +			    void *data)
> +{
> +	struct mcde_dsi *d = dev_get_drvdata(dev);
> +
> +	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
> +			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
> +	clk_disable_unprepare(d->mcde->dsi0_clk);
> +	clk_disable_unprepare(d->mcde->dsi0es_clk);
> +}
> +
> +static const struct component_ops mcde_dsi_component_ops = {
> +	.bind   = mcde_dsi_bind,
> +	.unbind = mcde_dsi_unbind,
> +};
> +
> +static int mcde_dsi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct mcde_dsi *d;
> +	struct mipi_dsi_host *host;
> +	struct resource *res;
> +	u32 dsi_id;
> +	int ret;
> +
> +	d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
> +	if (!d)
> +		return -ENOMEM;
> +	d->dev = dev;
> +	platform_set_drvdata(pdev, d);
> +
> +	/* Get a handle on the PRCMU so we can do reset */
> +	d->prcmu =
> +		syscon_regmap_lookup_by_compatible("stericsson,db8500-prcmu");
> +	if (IS_ERR(d->prcmu)) {
> +		dev_err(dev, "no PRCMU regmap\n");
> +		return PTR_ERR(d->prcmu);
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	d->regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(d->regs)) {
> +		dev_err(dev, "no DSI regs\n");
> +		return PTR_ERR(d->regs);
> +	}
> +
> +	dsi_id = readl(d->regs + DSI_ID_REG);
> +	dev_info(dev, "HW revision 0x%08x\n", dsi_id);
> +
> +	host = &d->dsi_host;
> +	host->dev = dev;
> +	host->ops = &mcde_dsi_host_ops;
> +	ret = mipi_dsi_host_register(host);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to register DSI host: %d\n", ret);
> +		return ret;
> +	}
> +	dev_info(dev, "registered DSI host\n");
> +
> +	platform_set_drvdata(pdev, d);
> +	return component_add(dev, &mcde_dsi_component_ops);
> +}
> +
> +static int mcde_dsi_remove(struct platform_device *pdev)
> +{
> +	struct mcde_dsi *d = platform_get_drvdata(pdev);
> +
> +	component_del(&pdev->dev, &mcde_dsi_component_ops);
> +	mcde_dsi_disable(&d->encoder);
> +	mipi_dsi_host_unregister(&d->dsi_host);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id mcde_dsi_of_match[] = {
> +	{
> +		.compatible = "ste,mcde-dsi",
> +	},
> +	{},
> +};
> +
> +struct platform_driver mcde_dsi_driver = {
> +	.driver = {
> +		.name           = "mcde-dsi",
> +		.of_match_table = of_match_ptr(mcde_dsi_of_match),
> +	},
> +	.probe = mcde_dsi_probe,
> +	.remove = mcde_dsi_remove,
> +};
> -- 
> 2.20.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/dri-devel




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux