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