diff mbox

[v2,2/2] drm: zte: add initial vou drm driver

Message ID 1474727185-24180-3-git-send-email-shawn.guo@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Shawn Guo Sept. 24, 2016, 2:26 p.m. UTC
It adds the initial ZTE VOU display controller DRM driver.  There are
still some features to be added, like overlay plane, scaling, and more
output devices support.  But it's already useful with dual CRTCs and
HDMI monitor working.

It's been tested on Debian Jessie LXDE desktop with modesetting driver.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 drivers/gpu/drm/Kconfig          |   2 +
 drivers/gpu/drm/Makefile         |   1 +
 drivers/gpu/drm/zte/Kconfig      |   8 +
 drivers/gpu/drm/zte/Makefile     |   8 +
 drivers/gpu/drm/zte/zx_crtc.c    | 691 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/zte/zx_crtc.h    |  47 +++
 drivers/gpu/drm/zte/zx_drm_drv.c | 258 +++++++++++++++
 drivers/gpu/drm/zte/zx_drm_drv.h |  22 ++
 drivers/gpu/drm/zte/zx_hdmi.c    | 540 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/zte/zx_plane.c   | 362 ++++++++++++++++++++
 drivers/gpu/drm/zte/zx_plane.h   |  26 ++
 11 files changed, 1965 insertions(+)
 create mode 100644 drivers/gpu/drm/zte/Kconfig
 create mode 100644 drivers/gpu/drm/zte/Makefile
 create mode 100644 drivers/gpu/drm/zte/zx_crtc.c
 create mode 100644 drivers/gpu/drm/zte/zx_crtc.h
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
 create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
 create mode 100644 drivers/gpu/drm/zte/zx_plane.c
 create mode 100644 drivers/gpu/drm/zte/zx_plane.h

Comments

Daniel Vetter Sept. 25, 2016, 8:58 p.m. UTC | #1
On Sat, Sep 24, 2016 at 10:26:25PM +0800, Shawn Guo wrote:
> It adds the initial ZTE VOU display controller DRM driver.  There are
> still some features to be added, like overlay plane, scaling, and more
> output devices support.  But it's already useful with dual CRTCs and
> HDMI monitor working.
> 
> It's been tested on Debian Jessie LXDE desktop with modesetting driver.
> 
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>

I've done a very quick read-through, looks real pretty. A few comments
below and in-line.

For testing, have you tried to run i-g-t validation tests per our
documentation? See https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#validating-changes-with-igt

Cheers, Daniel

> ---
>  drivers/gpu/drm/Kconfig          |   2 +
>  drivers/gpu/drm/Makefile         |   1 +
>  drivers/gpu/drm/zte/Kconfig      |   8 +
>  drivers/gpu/drm/zte/Makefile     |   8 +
>  drivers/gpu/drm/zte/zx_crtc.c    | 691 +++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_crtc.h    |  47 +++
>  drivers/gpu/drm/zte/zx_drm_drv.c | 258 +++++++++++++++
>  drivers/gpu/drm/zte/zx_drm_drv.h |  22 ++
>  drivers/gpu/drm/zte/zx_hdmi.c    | 540 ++++++++++++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_plane.c   | 362 ++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_plane.h   |  26 ++
>  11 files changed, 1965 insertions(+)
>  create mode 100644 drivers/gpu/drm/zte/Kconfig
>  create mode 100644 drivers/gpu/drm/zte/Makefile
>  create mode 100644 drivers/gpu/drm/zte/zx_crtc.c
>  create mode 100644 drivers/gpu/drm/zte/zx_crtc.h
>  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
>  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
>  create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
>  create mode 100644 drivers/gpu/drm/zte/zx_plane.c
>  create mode 100644 drivers/gpu/drm/zte/zx_plane.h

New entry in MAINTAINERS listening you (and probably dri-devel as the m-l)
is missing.

> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 483059a22b1b..a91f8cecbe0f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -223,6 +223,8 @@ source "drivers/gpu/drm/hisilicon/Kconfig"
>  
>  source "drivers/gpu/drm/mediatek/Kconfig"
>  
> +source "drivers/gpu/drm/zte/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 439d89b25ae0..fe461c94d266 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -85,3 +85,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
>  obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
>  obj-$(CONFIG_DRM_ARCPGU)+= arc/
>  obj-y			+= hisilicon/
> +obj-$(CONFIG_DRM_ZTE)	+= zte/
> diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig
> new file mode 100644
> index 000000000000..4065b2840f1c
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/Kconfig
> @@ -0,0 +1,8 @@
> +config DRM_ZTE
> +	tristate "DRM Support for ZTE SoCs"
> +	depends on DRM && ARCH_ZX
> +	select DRM_KMS_CMA_HELPER
> +	select DRM_KMS_FB_HELPER
> +	select DRM_KMS_HELPER
> +	help
> +	  Choose this option to enable DRM on ZTE ZX SoCs.
> diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
> new file mode 100644
> index 000000000000..b40968dc749f
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/Makefile
> @@ -0,0 +1,8 @@
> +zxdrm-y := \
> +	zx_drm_drv.o \
> +	zx_crtc.o \
> +	zx_plane.o \
> +	zx_hdmi.o
> +
> +obj-$(CONFIG_DRM_ZTE) += zxdrm.o
> +
> diff --git a/drivers/gpu/drm/zte/zx_crtc.c b/drivers/gpu/drm/zte/zx_crtc.c
> new file mode 100644
> index 000000000000..818bf9072573
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_crtc.c
> @@ -0,0 +1,691 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_plane_helper.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/of_address.h>
> +#include <video/videomode.h>
> +
> +#include "zx_drm_drv.h"
> +#include "zx_crtc.h"
> +#include "zx_plane.h"
> +
> +/* OSD (GPC_GLOBAL) registers */
> +#define OSD_INT_STA			0x04
> +#define OSD_INT_CLRSTA			0x08
> +#define OSD_INT_MSK			0x0c
> +#define OSD_INT_AUX_UPT			BIT(14)
> +#define OSD_INT_MAIN_UPT		BIT(13)
> +#define OSD_INT_GL1_LBW			BIT(10)
> +#define OSD_INT_GL0_LBW			BIT(9)
> +#define OSD_INT_VL2_LBW			BIT(8)
> +#define OSD_INT_VL1_LBW			BIT(7)
> +#define OSD_INT_VL0_LBW			BIT(6)
> +#define OSD_INT_BUS_ERR			BIT(3)
> +#define OSD_INT_CFG_ERR			BIT(2)
> +#define OSD_INT_ERROR (\
> +	OSD_INT_GL1_LBW | OSD_INT_GL0_LBW | \
> +	OSD_INT_VL2_LBW | OSD_INT_VL1_LBW | OSD_INT_VL0_LBW | \
> +	OSD_INT_BUS_ERR | OSD_INT_CFG_ERR \
> +)
> +#define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT)
> +#define OSD_CTRL0			0x10
> +#define OSD_CTRL0_GL0_EN		BIT(7)
> +#define OSD_CTRL0_GL0_SEL		BIT(6)
> +#define OSD_CTRL0_GL1_EN		BIT(5)
> +#define OSD_CTRL0_GL1_SEL		BIT(4)
> +#define OSD_RST_CLR			0x1c
> +#define RST_PER_FRAME			BIT(19)
> +
> +/* Main/Aux channel registers */
> +#define OSD_MAIN_CHN			0x470
> +#define OSD_AUX_CHN			0x4d0
> +#define CHN_CTRL0			0x00
> +#define CHN_ENABLE			BIT(0)
> +#define CHN_CTRL1			0x04
> +#define CHN_SCREEN_W_SHIFT		18
> +#define CHN_SCREEN_W_MASK		(0x1fff << CHN_SCREEN_W_SHIFT)
> +#define CHN_SCREEN_H_SHIFT		5
> +#define CHN_SCREEN_H_MASK		(0x1fff << CHN_SCREEN_H_SHIFT)
> +#define CHN_UPDATE			0x08
> +
> +/* TIMING_CTRL registers */
> +#define TIMING_TC_ENABLE		0x04
> +#define AUX_TC_EN			BIT(1)
> +#define MAIN_TC_EN			BIT(0)
> +#define FIR_MAIN_ACTIVE			0x08
> +#define FIR_AUX_ACTIVE			0x0c
> +#define FIR_MAIN_H_TIMING		0x10
> +#define FIR_MAIN_V_TIMING		0x14
> +#define FIR_AUX_H_TIMING		0x18
> +#define FIR_AUX_V_TIMING		0x1c
> +#define SYNC_WIDE_SHIFT			22
> +#define SYNC_WIDE_MASK			(0x3ff << SYNC_WIDE_SHIFT)
> +#define BACK_PORCH_SHIFT		11
> +#define BACK_PORCH_MASK			(0x7ff << BACK_PORCH_SHIFT)
> +#define FRONT_PORCH_SHIFT		0
> +#define FRONT_PORCH_MASK		(0x7ff << FRONT_PORCH_SHIFT)
> +#define TIMING_CTRL			0x20
> +#define AUX_POL_SHIFT			3
> +#define AUX_POL_MASK			(0x7 << AUX_POL_SHIFT)
> +#define MAIN_POL_SHIFT			0
> +#define MAIN_POL_MASK			(0x7 << MAIN_POL_SHIFT)
> +#define POL_DE_SHIFT			2
> +#define POL_VSYNC_SHIFT			1
> +#define POL_HSYNC_SHIFT			0
> +#define TIMING_INT_CTRL			0x24
> +#define TIMING_INT_STATE		0x28
> +#define TIMING_INT_AUX_FRAME		BIT(3)
> +#define TIMING_INT_MAIN_FRAME		BIT(1)
> +#define TIMING_INT_AUX_FRAME_SEL_VSW	(0x2 << 10)
> +#define TIMING_INT_MAIN_FRAME_SEL_VSW	(0x2 << 6)
> +#define TIMING_INT_ENABLE (\
> +	TIMING_INT_MAIN_FRAME_SEL_VSW | TIMING_INT_AUX_FRAME_SEL_VSW | \
> +	TIMING_INT_MAIN_FRAME | TIMING_INT_AUX_FRAME \
> +)
> +#define TIMING_MAIN_SHIFT		0x2c
> +#define TIMING_AUX_SHIFT		0x30
> +#define H_SHIFT_VAL			0x0048
> +#define TIMING_MAIN_PI_SHIFT		0x68
> +#define TIMING_AUX_PI_SHIFT		0x6c
> +#define H_PI_SHIFT_VAL			0x000f
> +
> +/* DTRC registers */
> +#define DTRC_F0_CTRL			0x2c
> +#define DTRC_F1_CTRL			0x5c
> +#define DTRC_DECOMPRESS_BYPASS		BIT(17)
> +#define DTRC_DETILE_CTRL		0x68
> +#define TILE2RASTESCAN_BYPASS_MODE	BIT(30)
> +#define DETILE_ARIDR_MODE_MASK		(0x3 << 0)
> +#define DETILE_ARID_ALL			0
> +#define DETILE_ARID_IN_ARIDR		1
> +#define DETILE_ARID_BYP_BUT_ARIDR	2
> +#define DETILE_ARID_IN_ARIDR2		3
> +#define DTRC_ARID			0x6c
> +#define DTRC_DEC2DDR_ARID		0x70
> +
> +/* VOU_CTRL registers */
> +#define VOU_INF_EN			0x00
> +#define VOU_INF_CH_SEL			0x04
> +#define VOU_INF_DATA_SEL		0x08
> +#define VOU_SOFT_RST			0x14
> +#define VOU_CLK_SEL			0x18
> +#define VOU_CLK_GL1_SEL			BIT(5)
> +#define VOU_CLK_GL0_SEL			BIT(4)
> +#define VOU_CLK_REQEN			0x20
> +#define VOU_CLK_EN			0x24
> +
> +/* OTFPPU_CTRL registers */
> +#define OTFPPU_RSZ_DATA_SOURCE		0x04
> +
> +#define GL_NUM				2
> +#define VL_NUM				3
> +
> +enum vou_chn_type {
> +	VOU_CHN_MAIN,
> +	VOU_CHN_AUX,
> +};
> +
> +struct zx_crtc {
> +	struct drm_crtc crtc;
> +	struct drm_plane *primary;
> +	struct device *dev;
> +	void __iomem *chnreg;
> +	enum vou_chn_type chn_type;
> +	struct clk *pixclk;
> +};
> +
> +#define to_zx_crtc(x)	container_of(crtc, struct zx_crtc, crtc)
> +
> +struct zx_vou_hw {
> +	struct device *dev;
> +	void __iomem *osd;
> +	void __iomem *timing;
> +	void __iomem *vouctl;
> +	void __iomem *otfppu;
> +	void __iomem *dtrc;
> +	struct clk *axi_clk;
> +	struct clk *ppu_clk;
> +	struct clk *main_clk;
> +	struct clk *aux_clk;
> +	struct zx_crtc *main_crtc;
> +	struct zx_crtc *aux_crtc;
> +};
> +
> +static inline bool is_main_crtc(struct drm_crtc *crtc)
> +{
> +	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +
> +	return zcrtc->chn_type == VOU_CHN_MAIN;
> +}
> +
> +void vou_inf_enable(struct vou_inf *inf)
> +{
> +	struct drm_encoder *encoder = inf->encoder;
> +	struct drm_device *drm = encoder->dev;
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct zx_vou_hw *vou = priv->vou;
> +	bool is_main = is_main_crtc(encoder->crtc);
> +	u32 data_sel_shift = inf->id << 1;
> +	u32 val;
> +
> +	/* Select data format */
> +	val = readl(vou->vouctl + VOU_INF_DATA_SEL);
> +	val &= ~(0x3 << data_sel_shift);
> +	val |= inf->data_sel << data_sel_shift;
> +	writel(val, vou->vouctl + VOU_INF_DATA_SEL);
> +
> +	/* Select channel */
> +	val = readl(vou->vouctl + VOU_INF_CH_SEL);
> +	if (is_main)
> +		val &= ~(1 << inf->id);
> +	else
> +		val |= 1 << inf->id;
> +	writel(val, vou->vouctl + VOU_INF_CH_SEL);
> +
> +	/* Select interface clocks */
> +	val = readl(vou->vouctl + VOU_CLK_SEL);
> +	if (is_main)
> +		val &= ~inf->clocks_sel_bits;
> +	else
> +		val |= inf->clocks_sel_bits;
> +	writel(val, vou->vouctl + VOU_CLK_SEL);
> +
> +	/* Enable interface clocks */
> +	val = readl(vou->vouctl + VOU_CLK_EN);
> +	val |= inf->clocks_en_bits;
> +	writel(val, vou->vouctl + VOU_CLK_EN);
> +
> +	/* Enable the device */
> +	val = readl(vou->vouctl + VOU_INF_EN);
> +	val |= 1 << inf->id;
> +	writel(val, vou->vouctl + VOU_INF_EN);
> +}
> +
> +void vou_inf_disable(struct vou_inf *inf)
> +{
> +	struct drm_encoder *encoder = inf->encoder;
> +	struct drm_device *drm = encoder->dev;
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct zx_vou_hw *vou = priv->vou;
> +	u32 val;
> +
> +	/* Disable the device */
> +	val = readl(vou->vouctl + VOU_INF_EN);
> +	val &= ~(1 << inf->id);
> +	writel(val, vou->vouctl + VOU_INF_EN);
> +
> +	/* Disable interface clocks */
> +	val = readl(vou->vouctl + VOU_CLK_EN);
> +	val &= ~inf->clocks_en_bits;
> +	writel(val, vou->vouctl + VOU_CLK_EN);
> +}
> +
> +static inline void vou_chn_set_update(struct zx_crtc *zcrtc)
> +{
> +	writel(1, zcrtc->chnreg + CHN_UPDATE);
> +}
> +
> +static void zx_crtc_enable(struct drm_crtc *crtc)
> +{
> +	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> +	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +	struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
> +	bool is_main = is_main_crtc(crtc);
> +	struct videomode vm;
> +	u32 pol = 0;
> +	u32 val;
> +
> +	drm_display_mode_to_videomode(mode, &vm);
> +
> +	/* Set up timing parameters */
> +	val = (vm.vactive - 1) << 16;
> +	val |= (vm.hactive - 1) & 0xffff;
> +	writel(val, vou->timing + (is_main ? FIR_MAIN_ACTIVE : FIR_AUX_ACTIVE));
> +
> +	val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> +	val |= ((vm.hback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> +	val |= ((vm.hfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> +	writel(val, vou->timing + (is_main ? FIR_MAIN_H_TIMING :
> +					     FIR_AUX_H_TIMING));
> +
> +	val = ((vm.vsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> +	val |= ((vm.vback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> +	val |= ((vm.vfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> +	writel(val, vou->timing + (is_main ? FIR_MAIN_V_TIMING :
> +					     FIR_AUX_V_TIMING));
> +
> +	/* Set up polarities */
> +	if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW)
> +		pol |= 1 << POL_VSYNC_SHIFT;
> +	if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW)
> +		pol |= 1 << POL_HSYNC_SHIFT;
> +
> +	val = readl(vou->timing + TIMING_CTRL);
> +	val &= ~(is_main ? MAIN_POL_MASK : AUX_POL_MASK);
> +	val |= pol << (is_main ? MAIN_POL_SHIFT : AUX_POL_SHIFT);
> +	writel(val, vou->timing + TIMING_CTRL);
> +
> +	/* Setup SHIFT register by following what ZTE BSP does */
> +	writel(H_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_SHIFT :
> +						     TIMING_AUX_SHIFT));
> +	writel(H_PI_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_PI_SHIFT :
> +							TIMING_AUX_PI_SHIFT));
> +
> +	/* Enable TIMING_CTRL */
> +	val = readl(vou->timing + TIMING_TC_ENABLE);
> +	val |= is_main ? MAIN_TC_EN : AUX_TC_EN;
> +	writel(val, vou->timing + TIMING_TC_ENABLE);
> +
> +	/* Configure channel screen size */
> +	val = readl(zcrtc->chnreg + CHN_CTRL1);
> +	val &= ~(CHN_SCREEN_W_MASK | CHN_SCREEN_H_MASK);
> +	val |= (vm.hactive << CHN_SCREEN_W_SHIFT) & CHN_SCREEN_W_MASK;
> +	val |= (vm.vactive << CHN_SCREEN_H_SHIFT) & CHN_SCREEN_H_MASK;
> +	writel(val, zcrtc->chnreg + CHN_CTRL1);
> +
> +	/* Update channel */
> +	vou_chn_set_update(zcrtc);
> +
> +	/* Enable channel */
> +	val = readl(zcrtc->chnreg + CHN_CTRL0);
> +	val |= CHN_ENABLE;
> +	writel(val, zcrtc->chnreg + CHN_CTRL0);
> +
> +	/* Enable Graphic Layer */
> +	val = readl(vou->osd + OSD_CTRL0);
> +	val |= is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN;
> +	writel(val, vou->osd + OSD_CTRL0);
> +
> +	drm_crtc_vblank_on(crtc);
> +
> +	/* Enable pixel clock */
> +	clk_set_rate(zcrtc->pixclk, mode->clock * 1000);
> +	clk_prepare_enable(zcrtc->pixclk);
> +}
> +
> +static void zx_crtc_disable(struct drm_crtc *crtc)
> +{
> +	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +	struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
> +	bool is_main = is_main_crtc(crtc);
> +	u32 val;
> +
> +	clk_disable_unprepare(zcrtc->pixclk);
> +
> +	drm_crtc_vblank_off(crtc);
> +
> +	/* Disable Graphic Layer */
> +	val = readl(vou->osd + OSD_CTRL0);
> +	val &= ~(is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN);
> +	writel(val, vou->osd + OSD_CTRL0);
> +
> +	/* Disable channel */
> +	val = readl(zcrtc->chnreg + CHN_CTRL0);
> +	val &= ~CHN_ENABLE;
> +	writel(val, zcrtc->chnreg + CHN_CTRL0);
> +
> +	/* Disable TIMING_CTRL */
> +	val = readl(vou->timing + TIMING_TC_ENABLE);
> +	val &= ~(is_main ? MAIN_TC_EN : AUX_TC_EN);
> +	writel(val, vou->timing + TIMING_TC_ENABLE);
> +}
> +
> +static void zx_crtc_atomic_begin(struct drm_crtc *crtc,
> +				 struct drm_crtc_state *state)
> +{
> +	struct drm_pending_vblank_event *event = crtc->state->event;
> +
> +	if (event) {
> +		crtc->state->event = NULL;
> +
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		if (drm_crtc_vblank_get(crtc) == 0)
> +			drm_crtc_arm_vblank_event(crtc, event);
> +		else
> +			drm_crtc_send_vblank_event(crtc, event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +	}
> +}
> +
> +static const struct drm_crtc_helper_funcs zx_crtc_helper_funcs = {
> +	.enable = zx_crtc_enable,
> +	.disable = zx_crtc_disable,
> +	.atomic_begin = zx_crtc_atomic_begin,
> +};
> +
> +static const struct drm_crtc_funcs zx_crtc_funcs = {
> +	.destroy = drm_crtc_cleanup,
> +	.set_config = drm_atomic_helper_set_config,
> +	.page_flip = drm_atomic_helper_page_flip,
> +	.reset = drm_atomic_helper_crtc_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static struct zx_crtc *zx_crtc_init(struct drm_device *drm,
> +				    enum vou_chn_type chn_type)
> +{
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct zx_vou_hw *vou = priv->vou;
> +	struct device *dev = vou->dev;
> +	struct zx_layer_data data;
> +	struct zx_crtc *zcrtc;
> +	int ret;
> +
> +	zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
> +	if (!zcrtc)
> +		return ERR_PTR(-ENOMEM);
> +
> +	zcrtc->dev = dev;
> +	zcrtc->chn_type = chn_type;
> +
> +	if (chn_type == VOU_CHN_MAIN) {
> +		data.layer = vou->osd + 0x130;
> +		data.csc = vou->osd + 0x580;
> +		data.hbsc = vou->osd + 0x820;
> +		data.rsz = vou->otfppu + 0x600;
> +		zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
> +	} else {
> +		data.layer = vou->osd + 0x200;
> +		data.csc = vou->osd + 0x5d0;
> +		data.hbsc = vou->osd + 0x860;
> +		data.rsz = vou->otfppu + 0x800;
> +		zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
> +	}
> +
> +	zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ?
> +					  "main_wclk" : "aux_wclk");
> +	if (IS_ERR(zcrtc->pixclk))
> +		return ERR_PTR(PTR_ERR(zcrtc->pixclk));
> +
> +	zcrtc->primary = zx_plane_init(drm, dev, &data, DRM_PLANE_TYPE_PRIMARY);
> +	if (IS_ERR(zcrtc->primary))
> +		return ERR_PTR(PTR_ERR(zcrtc->primary));
> +
> +	ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL,
> +					&zx_crtc_funcs, NULL);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs);
> +
> +	return zcrtc;
> +}
> +
> +int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe)
> +{
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct zx_vou_hw *vou = priv->vou;
> +	u32 intctl;
> +
> +	intctl = readl(vou->timing + TIMING_INT_CTRL);
> +	if (pipe == 0)
> +		intctl |= TIMING_INT_MAIN_FRAME;
> +	else
> +		intctl |= TIMING_INT_AUX_FRAME;
> +	writel(intctl, vou->timing + TIMING_INT_CTRL);
> +
> +	return 0;
> +}
> +
> +void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe)
> +{
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct zx_vou_hw *vou = priv->vou;
> +	u32 intctl;
> +
> +	intctl = readl(vou->timing + TIMING_INT_CTRL);
> +	if (pipe == 0)
> +		intctl &= ~TIMING_INT_MAIN_FRAME;
> +	else
> +		intctl &= ~TIMING_INT_AUX_FRAME;
> +	writel(intctl, vou->timing + TIMING_INT_CTRL);
> +}
> +
> +static irqreturn_t vou_irq_handler(int irq, void *dev_id)
> +{
> +	struct zx_vou_hw *vou = dev_id;
> +	u32 state;
> +
> +	/* Handle TIMING_CTRL frame interrupts */
> +	state = readl(vou->timing + TIMING_INT_STATE);
> +	writel(state, vou->timing + TIMING_INT_STATE);
> +
> +	if (state & TIMING_INT_MAIN_FRAME)
> +		drm_crtc_handle_vblank(&vou->main_crtc->crtc);
> +
> +	if (state & TIMING_INT_AUX_FRAME)
> +		drm_crtc_handle_vblank(&vou->aux_crtc->crtc);
> +
> +	/* Handle OSD interrupts */
> +	state = readl(vou->osd + OSD_INT_STA);
> +	writel(state, vou->osd + OSD_INT_CLRSTA);
> +
> +	if (state & OSD_INT_MAIN_UPT) {
> +		vou_chn_set_update(vou->main_crtc);
> +		zx_plane_set_update(vou->main_crtc->primary);
> +	}
> +
> +	if (state & OSD_INT_AUX_UPT) {
> +		vou_chn_set_update(vou->aux_crtc);
> +		zx_plane_set_update(vou->aux_crtc->primary);
> +	}
> +
> +	if (state & OSD_INT_ERROR)
> +		dev_err(vou->dev, "OSD ERROR: 0x%08x!\n", state);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void vou_dtrc_init(struct zx_vou_hw *vou)
> +{
> +	u32 val;
> +
> +	val = readl(vou->dtrc + DTRC_DETILE_CTRL);
> +	/* Clear bit for bypass by ID */
> +	val &= ~TILE2RASTESCAN_BYPASS_MODE;
> +	/* Select ARIDR mode */
> +	val &= ~DETILE_ARIDR_MODE_MASK;
> +	val |= DETILE_ARID_IN_ARIDR;
> +	writel(val, vou->dtrc + DTRC_DETILE_CTRL);
> +
> +	/* Bypass decompression for both frames */
> +	val = readl(vou->dtrc + DTRC_F0_CTRL);
> +	val |= DTRC_DECOMPRESS_BYPASS;
> +	writel(val, vou->dtrc + DTRC_F0_CTRL);
> +
> +	val = readl(vou->dtrc + DTRC_F1_CTRL);
> +	val |= DTRC_DECOMPRESS_BYPASS;
> +	writel(val, vou->dtrc + DTRC_F1_CTRL);
> +
> +	/* Set up ARID register */
> +	val = 0x0e;
> +	val |= 0x0f << 8;
> +	val |= 0x0e << 16;
> +	val |= 0x0f << 24;
> +	writel(val, vou->dtrc + DTRC_ARID);
> +
> +	/* Set up DEC2DDR_ARID register */
> +	val = 0x0e;
> +	val |= 0x0f << 8;
> +	writel(val, vou->dtrc + DTRC_DEC2DDR_ARID);
> +}
> +
> +static void vou_hw_init(struct zx_vou_hw *vou)
> +{
> +	u32 val;
> +
> +	/* Set GL0 to main channel and GL1 to aux channel */
> +	val = readl(vou->osd + OSD_CTRL0);
> +	val &= ~OSD_CTRL0_GL0_SEL;
> +	val |= OSD_CTRL0_GL1_SEL;
> +	writel(val, vou->osd + OSD_CTRL0);
> +
> +	/* Release reset for all VOU modules */
> +	writel(~0, vou->vouctl + VOU_SOFT_RST);
> +
> +	/* Select main clock for GL0 and aux clock for GL1 module */
> +	val = readl(vou->vouctl + VOU_CLK_SEL);
> +	val &= ~VOU_CLK_GL0_SEL;
> +	val |= VOU_CLK_GL1_SEL;
> +	writel(val, vou->vouctl + VOU_CLK_SEL);
> +
> +	/* Enable clock auto-gating for all VOU modules */
> +	writel(~0, vou->vouctl + VOU_CLK_REQEN);
> +
> +	/* Enable all VOU module clocks */
> +	writel(~0, vou->vouctl + VOU_CLK_EN);
> +
> +	/* Clear both OSD and TIMING_CTRL interrupt state */
> +	writel(~0, vou->osd + OSD_INT_CLRSTA);
> +	writel(~0, vou->timing + TIMING_INT_STATE);
> +
> +	/* Enable OSD and TIMING_CTRL interrrupts */
> +	writel(OSD_INT_ENABLE, vou->osd + OSD_INT_MSK);
> +	writel(TIMING_INT_ENABLE, vou->timing + TIMING_INT_CTRL);
> +
> +	/* Select GPC as input to gl/vl scaler as a sane default setting */
> +	writel(0x2a, vou->otfppu + OTFPPU_RSZ_DATA_SOURCE);
> +
> +	/*
> +	 * Needs to reset channel and layer logic per frame when frame starts
> +	 * to get VOU work properly.
> +	 */
> +	val = readl(vou->osd + OSD_RST_CLR);
> +	val |= RST_PER_FRAME;
> +	writel(val, vou->osd + OSD_RST_CLR);
> +
> +	vou_dtrc_init(vou);
> +}
> +
> +static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct drm_device *drm = data;
> +	struct zx_drm_private *priv = drm->dev_private;
> +	struct resource *res;
> +	struct zx_vou_hw *vou;
> +	int irq;
> +	int ret;
> +
> +	vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL);
> +	if (!vou)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd");
> +	vou->osd = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vou->osd))
> +		return PTR_ERR(vou->osd);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
> +	vou->timing = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vou->timing))
> +		return PTR_ERR(vou->timing);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
> +	vou->dtrc = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vou->dtrc))
> +		return PTR_ERR(vou->dtrc);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
> +	vou->vouctl = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vou->vouctl))
> +		return PTR_ERR(vou->vouctl);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
> +	vou->otfppu = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vou->otfppu))
> +		return PTR_ERR(vou->otfppu);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	vou->axi_clk = devm_clk_get(dev, "aclk");
> +	if (IS_ERR(vou->axi_clk))
> +		return PTR_ERR(vou->axi_clk);
> +
> +	vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
> +	if (IS_ERR(vou->ppu_clk))
> +		return PTR_ERR(vou->ppu_clk);
> +
> +	clk_prepare_enable(vou->axi_clk);
> +	clk_prepare_enable(vou->ppu_clk);
> +
> +	vou->dev = dev;
> +	priv->vou = vou;
> +	dev_set_drvdata(dev, vou);
> +
> +	vou_hw_init(vou);
> +
> +	ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou);
> +	if (ret < 0)
> +		return ret;
> +
> +	vou->main_crtc = zx_crtc_init(drm, VOU_CHN_MAIN);
> +	if (IS_ERR(vou->main_crtc))
> +		return PTR_ERR(vou->main_crtc);
> +
> +	vou->aux_crtc = zx_crtc_init(drm, VOU_CHN_AUX);
> +	if (IS_ERR(vou->aux_crtc))
> +		return PTR_ERR(vou->aux_crtc);
> +
> +	return 0;
> +}
> +
> +static void zx_crtc_unbind(struct device *dev, struct device *master,
> +			   void *data)
> +{
> +	struct zx_vou_hw *vou = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(vou->axi_clk);
> +	clk_disable_unprepare(vou->ppu_clk);
> +}
> +
> +static const struct component_ops zx_crtc_component_ops = {
> +	.bind = zx_crtc_bind,
> +	.unbind = zx_crtc_unbind,
> +};
> +
> +static int zx_crtc_probe(struct platform_device *pdev)
> +{
> +	return component_add(&pdev->dev, &zx_crtc_component_ops);
> +}
> +
> +static int zx_crtc_remove(struct platform_device *pdev)
> +{
> +	component_del(&pdev->dev, &zx_crtc_component_ops);
> +	return 0;
> +}
> +
> +static const struct of_device_id zx_crtc_of_match[] = {
> +	{ .compatible = "zte,zx296718-dpc", },
> +	{ /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_crtc_of_match);
> +
> +struct platform_driver zx_crtc_driver = {
> +	.probe = zx_crtc_probe,
> +	.remove = zx_crtc_remove,
> +	.driver	= {
> +		.name = "zx-crtc",
> +		.of_match_table	= zx_crtc_of_match,
> +	},
> +};
> diff --git a/drivers/gpu/drm/zte/zx_crtc.h b/drivers/gpu/drm/zte/zx_crtc.h
> new file mode 100644
> index 000000000000..f889208054ce
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_crtc.h
> @@ -0,0 +1,47 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_CRTC_H__
> +#define __ZX_CRTC_H__
> +
> +#define VOU_CRTC_MASK		0x3
> +
> +/* VOU output interfaces */
> +enum vou_inf_id {
> +	VOU_HDMI	= 0,
> +	VOU_RGB_LCD	= 1,
> +	VOU_TV_ENC	= 2,
> +	VOU_MIPI_DSI	= 3,
> +	VOU_LVDS	= 4,
> +	VOU_VGA		= 5,
> +};
> +
> +enum vou_inf_data_sel {
> +	VOU_YUV444	= 0,
> +	VOU_RGB_101010	= 1,
> +	VOU_RGB_888	= 2,
> +	VOU_RGB_666	= 3,
> +};
> +
> +struct vou_inf {
> +	struct drm_encoder *encoder;
> +	enum vou_inf_id id;
> +	enum vou_inf_data_sel data_sel;
> +	u32 clocks_en_bits;
> +	u32 clocks_sel_bits;
> +};
> +
> +void vou_inf_enable(struct vou_inf *inf);
> +void vou_inf_disable(struct vou_inf *inf);
> +
> +int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe);
> +void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe);
> +
> +#endif /* __ZX_CRTC_H__ */
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
> new file mode 100644
> index 000000000000..51fafb8e5f43
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.c
> @@ -0,0 +1,258 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/spinlock.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/list.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +
> +#include "zx_drm_drv.h"
> +#include "zx_crtc.h"
> +
> +static void zx_drm_fb_output_poll_changed(struct drm_device *drm)
> +{
> +	struct zx_drm_private *priv = drm->dev_private;
> +
> +	drm_fbdev_cma_hotplug_event(priv->fbdev);
> +}
> +
> +static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = {
> +	.fb_create = drm_fb_cma_create,
> +	.output_poll_changed = zx_drm_fb_output_poll_changed,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static void zx_drm_lastclose(struct drm_device *drm)
> +{
> +	struct zx_drm_private *priv = drm->dev_private;
> +
> +	drm_fbdev_cma_restore_mode(priv->fbdev);
> +}
> +
> +static const struct file_operations zx_drm_fops = {
> +	.owner = THIS_MODULE,
> +	.open = drm_open,
> +	.release = drm_release,
> +	.unlocked_ioctl = drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl = drm_compat_ioctl,
> +#endif
> +	.poll = drm_poll,
> +	.read = drm_read,
> +	.llseek = noop_llseek,
> +	.mmap = drm_gem_cma_mmap,
> +};
> +
> +static struct drm_driver zx_drm_driver = {
> +	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
> +			   DRIVER_ATOMIC,
> +	.lastclose = zx_drm_lastclose,
> +	.get_vblank_counter = drm_vblank_no_hw_counter,
> +	.enable_vblank = zx_crtc_enable_vblank,
> +	.disable_vblank = zx_crtc_disable_vblank,
> +	.gem_free_object = drm_gem_cma_free_object,
> +	.gem_vm_ops = &drm_gem_cma_vm_ops,
> +	.dumb_create = drm_gem_cma_dumb_create,
> +	.dumb_map_offset = drm_gem_cma_dumb_map_offset,
> +	.dumb_destroy = drm_gem_dumb_destroy,
> +	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> +	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> +	.gem_prime_export = drm_gem_prime_export,
> +	.gem_prime_import = drm_gem_prime_import,
> +	.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,
> +	.fops = &zx_drm_fops,
> +	.name = "zx-vou",
> +	.desc = "ZTE VOU Controller DRM",
> +	.date = "20160811",
> +	.major = 1,
> +	.minor = 0,
> +};
> +
> +static int zx_drm_bind(struct device *dev)
> +{
> +	struct drm_device *drm;
> +	struct zx_drm_private *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	drm = drm_dev_alloc(&zx_drm_driver, dev);
> +	if (!drm)
> +		return -ENOMEM;
> +
> +	drm->dev_private = priv;
> +	dev_set_drvdata(dev, drm);
> +
> +	drm_mode_config_init(drm);
> +	drm->mode_config.min_width = 16;
> +	drm->mode_config.min_height = 16;
> +	drm->mode_config.max_width = 4096;
> +	drm->mode_config.max_height = 4096;
> +	drm->mode_config.funcs = &zx_drm_mode_config_funcs;
> +
> +	ret = drm_dev_register(drm, 0);

drm_dev_register should be the last function call in your bind function.
Similar for unbind, drm_dev_register should be called first.

As a consequence of that you can remove the drm_connector_(un)register
calls, those are only needed for hotplugged connectors like dp mst. But
with correct ordering of drm_dev_(un)register that function will also take
care of connector registration and unregistration.

> +	if (ret)
> +		goto out_free;
> +
> +	ret = component_bind_all(dev, drm);
> +	if (ret) {
> +		DRM_ERROR("Failed to bind all components\n");
> +		goto out_unregister;
> +	}
> +
> +	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to initialise vblank\n");
> +		goto out_unbind;
> +	}
> +
> +	/*
> +	 * We will manage irq handler on our own.  In this case, irq_enabled
> +	 * need to be true for using vblank core support.
> +	 */
> +	drm->irq_enabled = true;
> +
> +	drm_mode_config_reset(drm);
> +	drm_kms_helper_poll_init(drm);
> +
> +	priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
> +					 drm->mode_config.num_connector);
> +	if (IS_ERR(priv->fbdev)) {
> +		ret = PTR_ERR(priv->fbdev);
> +		priv->fbdev = NULL;
> +		goto out_fini;
> +	}
> +
> +	return 0;
> +
> +out_fini:
> +	drm_kms_helper_poll_fini(drm);
> +	drm_mode_config_cleanup(drm);
> +	drm_vblank_cleanup(drm);
> +out_unbind:
> +	component_unbind_all(dev, drm);
> +out_unregister:
> +	drm_dev_unregister(drm);
> +out_free:
> +	dev_set_drvdata(dev, NULL);
> +	drm_dev_unref(drm);
> +	return ret;
> +}
> +
> +static void zx_drm_unbind(struct device *dev)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	struct zx_drm_private *priv = drm->dev_private;
> +
> +	if (priv->fbdev) {
> +		drm_fbdev_cma_fini(priv->fbdev);
> +		priv->fbdev = NULL;
> +	}
> +	drm_kms_helper_poll_fini(drm);
> +	component_unbind_all(dev, drm);
> +	drm_vblank_cleanup(drm);
> +	drm_mode_config_cleanup(drm);
> +	drm_dev_unregister(drm);
> +	drm_dev_unref(drm);
> +	drm->dev_private = NULL;
> +	dev_set_drvdata(dev, NULL);
> +}
> +
> +static const struct component_master_ops zx_drm_master_ops = {
> +	.bind = zx_drm_bind,
> +	.unbind = zx_drm_unbind,
> +};
> +
> +static int compare_of(struct device *dev, void *data)
> +{
> +	return dev->of_node == data;
> +}
> +
> +static int zx_drm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *parent = dev->of_node;
> +	struct device_node *child;
> +	struct component_match *match = NULL;
> +	int ret;
> +
> +	ret = of_platform_populate(parent, NULL, NULL, dev);
> +	if (ret)
> +		return ret;
> +
> +	for_each_available_child_of_node(parent, child) {
> +		component_match_add(dev, &match, compare_of, child);
> +		of_node_put(child);
> +	}
> +
> +	return component_master_add_with_match(dev, &zx_drm_master_ops, match);
> +}
> +
> +static int zx_drm_remove(struct platform_device *pdev)
> +{
> +	component_master_del(&pdev->dev, &zx_drm_master_ops);
> +	return 0;
> +}
> +
> +static const struct of_device_id zx_drm_of_match[] = {
> +	{ .compatible = "zte,zx296718-vou", },
> +	{ /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_drm_of_match);
> +
> +static struct platform_driver zx_drm_platform_driver = {
> +	.probe = zx_drm_probe,
> +	.remove = zx_drm_remove,
> +	.driver	= {
> +		.name = "zx-drm",
> +		.of_match_table	= zx_drm_of_match,
> +	},
> +};
> +
> +static struct platform_driver *drivers[] = {
> +	&zx_crtc_driver,
> +	&zx_hdmi_driver,
> +	&zx_drm_platform_driver,
> +};
> +
> +static int zx_drm_init(void)
> +{
> +	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_init(zx_drm_init);
> +
> +static void zx_drm_exit(void)
> +{
> +	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_exit(zx_drm_exit);
> +
> +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
> +MODULE_DESCRIPTION("ZTE ZX VOU DRM driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
> new file mode 100644
> index 000000000000..14c749949151
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.h
> @@ -0,0 +1,22 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_DRM_DRV_H__
> +#define __ZX_DRM_DRV_H__
> +
> +struct zx_drm_private {
> +	struct drm_fbdev_cma *fbdev;
> +	struct zx_vou_hw *vou;
> +};
> +
> +extern struct platform_driver zx_crtc_driver;
> +extern struct platform_driver zx_hdmi_driver;
> +
> +#endif /* __ZX_DRM_DRV_H__ */
> diff --git a/drivers/gpu/drm/zte/zx_hdmi.c b/drivers/gpu/drm/zte/zx_hdmi.c
> new file mode 100644
> index 000000000000..5aaab8493b1b
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_hdmi.c
> @@ -0,0 +1,540 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drm_of.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <linux/irq.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/hdmi.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_device.h>
> +#include <linux/component.h>
> +
> +#include "zx_crtc.h"
> +
> +#define FUNC_SEL			0x000b
> +#define FUNC_HDMI_EN			BIT(0)
> +#define CLKPWD				0x000d
> +#define CLKPWD_PDIDCK			BIT(2)
> +#define PWD_SRST			0x0010
> +#define P2T_CTRL			0x0066
> +#define P2T_DC_PKT_EN			BIT(7)
> +#define L1_INTR_STAT			0x007e
> +#define L1_INTR_STAT_INTR1		BIT(0)
> +#define INTR1_STAT			0x008f
> +#define INTR1_MASK			0x0095
> +#define INTR1_MONITOR_DETECT		(BIT(5) | BIT(6))
> +#define ZX_DDC_ADDR			0x00ed
> +#define ZX_DDC_SEGM			0x00ee
> +#define ZX_DDC_OFFSET			0x00ef
> +#define ZX_DDC_DIN_CNT1			0x00f0
> +#define ZX_DDC_DIN_CNT2			0x00f1
> +#define ZX_DDC_CMD			0x00f3
> +#define DDC_CMD_MASK			0xf
> +#define DDC_CMD_CLEAR_FIFO		0x9
> +#define DDC_CMD_SEQUENTIAL_READ		0x2
> +#define ZX_DDC_DATA			0x00f4
> +#define ZX_DDC_DOUT_CNT			0x00f5
> +#define DDC_DOUT_CNT_MASK		0x1f
> +#define TEST_TXCTRL			0x00f7
> +#define TEST_TXCTRL_HDMI_MODE		BIT(1)
> +#define HDMICTL4			0x0235
> +#define TPI_HPD_RSEN			0x063b
> +#define TPI_HPD_CONNECTION		(BIT(1) | BIT(2))
> +#define TPI_INFO_FSEL			0x06bf
> +#define FSEL_AVI			0
> +#define FSEL_GBD			1
> +#define FSEL_AUDIO			2
> +#define FSEL_SPD			3
> +#define FSEL_MPEG			4
> +#define FSEL_VSIF			5
> +#define TPI_INFO_B0			0x06c0
> +#define TPI_INFO_EN			0x06df
> +#define TPI_INFO_TRANS_EN		BIT(7)
> +#define TPI_INFO_TRANS_RPT		BIT(6)
> +#define TPI_DDC_MASTER_EN		0x06f8
> +#define HW_DDC_MASTER			BIT(7)
> +
> +#define ZX_HDMI_INFOFRAME_SIZE		31
> +
> +struct zx_hdmi {
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct device *dev;
> +	struct drm_device *drm;
> +	void __iomem *mmio;
> +	struct clk *cec_clk;
> +	struct clk *osc_clk;
> +	struct clk *xclk;
> +	bool sink_is_hdmi;
> +	bool sink_has_audio;
> +	struct vou_inf *inf;
> +};
> +
> +#define to_zx_hdmi(x)	container_of(x, struct zx_hdmi, x)
> +
> +static struct vou_inf vou_inf_hdmi = {
> +	.id = VOU_HDMI,
> +	.data_sel = VOU_YUV444,
> +	.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
> +	.clocks_sel_bits = BIT(13) | BIT(2),
> +};
> +
> +static inline u8 hdmi_readb(struct zx_hdmi *hdmi, u16 offset)
> +{
> +	return readl_relaxed(hdmi->mmio + offset * 4);
> +}
> +
> +static inline void hdmi_writeb(struct zx_hdmi *hdmi, u16 offset, u8 val)
> +{
> +	writel_relaxed(val, hdmi->mmio + offset * 4);
> +}
> +
> +static int zx_hdmi_infoframe_trans(struct zx_hdmi *hdmi,
> +				   union hdmi_infoframe *frame, u8 fsel)
> +{
> +	u8 buffer[ZX_HDMI_INFOFRAME_SIZE];
> +	u8 val;
> +	ssize_t num;
> +	int i;
> +
> +	hdmi_writeb(hdmi, TPI_INFO_FSEL, fsel);
> +
> +	num = hdmi_infoframe_pack(frame, buffer, ZX_HDMI_INFOFRAME_SIZE);
> +	if (num < 0)
> +		return num;
> +
> +	for (i = 0; i < num; i++)
> +		hdmi_writeb(hdmi, TPI_INFO_B0 + i, buffer[i]);
> +
> +	val = hdmi_readb(hdmi, TPI_INFO_EN);
> +	val |= TPI_INFO_TRANS_EN | TPI_INFO_TRANS_RPT;
> +	hdmi_writeb(hdmi, TPI_INFO_EN, val);
> +
> +	return num;
> +}
> +
> +static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi,
> +				    struct drm_display_mode *mode)
> +{
> +	union hdmi_infoframe frame;
> +	int ret;
> +
> +	ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
> +							  mode);
> +	if (ret)
> +		return ret;
> +
> +	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF);
> +}
> +
> +static int zx_hdmi_config_video_avi(struct zx_hdmi *hdmi,
> +				    struct drm_display_mode *mode)
> +{
> +	union hdmi_infoframe frame;
> +	int ret;
> +
> +	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
> +	if (ret)
> +		return ret;
> +
> +	/* We always use YUV444 for HDMI output. */
> +	frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
> +
> +	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AVI);
> +}
> +
> +static void zx_hdmi_encoder_mode_set(struct drm_encoder *encoder,
> +				     struct drm_display_mode *mode,
> +				     struct drm_display_mode *adj_mode)
> +{
> +	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +	if (hdmi->sink_is_hdmi) {
> +		zx_hdmi_config_video_avi(hdmi, mode);
> +		zx_hdmi_config_video_vsi(hdmi, mode);
> +	}
> +}
> +
> +static void zx_hdmi_encoder_enable(struct drm_encoder *encoder)
> +{
> +	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +	vou_inf_enable(hdmi->inf);
> +}
> +
> +static void zx_hdmi_encoder_disable(struct drm_encoder *encoder)
> +{
> +	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +	vou_inf_disable(hdmi->inf);
> +}
> +
> +static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = {
> +	.enable	= zx_hdmi_encoder_enable,
> +	.disable = zx_hdmi_encoder_disable,
> +	.mode_set = zx_hdmi_encoder_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static int zx_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
> +				  size_t len)
> +{
> +	struct zx_hdmi *hdmi = data;
> +	int retry = 0;
> +	int ret = 0;
> +	int i = 0;
> +	u8 val;
> +
> +	/* Enable DDC master access */
> +	val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
> +	val |= HW_DDC_MASTER;
> +	hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
> +
> +	hdmi_writeb(hdmi, ZX_DDC_ADDR, 0xa0);
> +	hdmi_writeb(hdmi, ZX_DDC_OFFSET, block * EDID_LENGTH);
> +	/* Bits [9:8] of bytes */
> +	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff);
> +	/* Bits [7:0] of bytes */
> +	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff);
> +
> +	/* Clear FIFO */
> +	val = hdmi_readb(hdmi, ZX_DDC_CMD);
> +	val &= ~DDC_CMD_MASK;
> +	val |= DDC_CMD_CLEAR_FIFO;
> +	hdmi_writeb(hdmi, ZX_DDC_CMD, val);
> +
> +	/* Kick off the read */
> +	val = hdmi_readb(hdmi, ZX_DDC_CMD);
> +	val &= ~DDC_CMD_MASK;
> +	val |= DDC_CMD_SEQUENTIAL_READ;
> +	hdmi_writeb(hdmi, ZX_DDC_CMD, val);

It looks like the ZX_DDC register range implements a hw i2c engine (since
you specifiy port and offsets and everything). Please implement it as an
i2c_adapter driver and use the normal drm_get_edid function.

> +
> +	while (len > 0) {
> +		int cnt, j;
> +
> +		/* FIFO needs some time to get ready */
> +		usleep_range(500, 1000);
> +
> +		cnt = hdmi_readb(hdmi, ZX_DDC_DOUT_CNT) & DDC_DOUT_CNT_MASK;
> +		if (cnt == 0) {
> +			if (++retry > 5) {
> +				dev_err(hdmi->dev, "DDC read timed out!");
> +				ret = -ETIMEDOUT;
> +				break;
> +			}
> +			continue;
> +		}
> +
> +		for (j = 0; j < cnt; j++)
> +			buf[i++] = hdmi_readb(hdmi, ZX_DDC_DATA);
> +		len -= cnt;
> +	}
> +
> +	/* Disable DDC master access */
> +	val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
> +	val &= ~HW_DDC_MASTER;
> +	hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
> +
> +	return ret;
> +}
> +
> +static int zx_hdmi_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
> +	struct edid *edid;
> +	int ret = 0;
> +
> +	edid = drm_do_get_edid(connector, zx_hdmi_get_edid_block, hdmi);
> +	if (edid) {
> +		hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
> +		hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
> +		drm_mode_connector_update_edid_property(connector, edid);
> +		ret = drm_add_edid_modes(connector, edid);
> +		kfree(edid);
> +	}
> +
> +	return ret;
> +}
> +
> +static enum drm_mode_status
> +zx_hdmi_connector_mode_valid(struct drm_connector *connector,
> +			     struct drm_display_mode *mode)
> +{
> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs zx_hdmi_connector_helper_funcs = {
> +	.get_modes = zx_hdmi_connector_get_modes,
> +	.mode_valid = zx_hdmi_connector_mode_valid,
> +};
> +
> +static enum drm_connector_status
> +zx_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
> +
> +	return (hdmi_readb(hdmi, TPI_HPD_RSEN) & TPI_HPD_CONNECTION) ?
> +		connector_status_connected : connector_status_disconnected;
> +}
> +
> +static void zx_hdmi_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_unregister(connector);
> +	drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs zx_hdmi_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = zx_hdmi_connector_detect,
> +	.destroy = zx_hdmi_connector_destroy,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int zx_hdmi_register(struct drm_device *drm, struct zx_hdmi *hdmi)
> +{
> +	struct drm_encoder *encoder = &hdmi->encoder;
> +
> +	encoder->possible_crtcs = VOU_CRTC_MASK;
> +
> +	drm_encoder_init(drm, encoder, &zx_hdmi_encoder_funcs,
> +			 DRM_MODE_ENCODER_TMDS, NULL);
> +	drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs);
> +
> +	hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_init(drm, &hdmi->connector, &zx_hdmi_connector_funcs,
> +			   DRM_MODE_CONNECTOR_HDMIA);
> +	drm_connector_helper_add(&hdmi->connector,
> +				 &zx_hdmi_connector_helper_funcs);
> +
> +	drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
> +	drm_connector_register(&hdmi->connector);

As mentioned, these drm_connector_register/unregister calls aren't needed
with correct ordering in bind/unbind.

> +
> +	return 0;
> +}
> +
> +static irqreturn_t zx_hdmi_irq_thread(int irq, void *dev_id)
> +{
> +	struct zx_hdmi *hdmi = dev_id;
> +
> +	drm_helper_hpd_irq_event(hdmi->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
> +{
> +	struct zx_hdmi *hdmi = dev_id;
> +	u8 lstat;
> +
> +	lstat = hdmi_readb(hdmi, L1_INTR_STAT);
> +
> +	/* Monitor detect/HPD interrupt */
> +	if (lstat & L1_INTR_STAT_INTR1) {
> +		u8 stat = hdmi_readb(hdmi, INTR1_STAT);
> +
> +		hdmi_writeb(hdmi, INTR1_STAT, stat);
> +		if (stat & INTR1_MONITOR_DETECT)
> +			return IRQ_WAKE_THREAD;
> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static void zx_hdmi_phy_start(struct zx_hdmi *hdmi)
> +{
> +	/* Copy from ZTE BSP code */
> +	hdmi_writeb(hdmi, 0x222, 0x0);
> +	hdmi_writeb(hdmi, 0x224, 0x4);
> +	hdmi_writeb(hdmi, 0x909, 0x0);
> +	hdmi_writeb(hdmi, 0x7b0, 0x90);
> +	hdmi_writeb(hdmi, 0x7b1, 0x00);
> +	hdmi_writeb(hdmi, 0x7b2, 0xa7);
> +	hdmi_writeb(hdmi, 0x7b8, 0xaa);
> +	hdmi_writeb(hdmi, 0x7b2, 0xa7);
> +	hdmi_writeb(hdmi, 0x7b3, 0x0f);
> +	hdmi_writeb(hdmi, 0x7b4, 0x0f);
> +	hdmi_writeb(hdmi, 0x7b5, 0x55);
> +	hdmi_writeb(hdmi, 0x7b7, 0x03);
> +	hdmi_writeb(hdmi, 0x7b9, 0x12);
> +	hdmi_writeb(hdmi, 0x7ba, 0x32);
> +	hdmi_writeb(hdmi, 0x7bc, 0x68);
> +	hdmi_writeb(hdmi, 0x7be, 0x40);
> +	hdmi_writeb(hdmi, 0x7bf, 0x84);
> +	hdmi_writeb(hdmi, 0x7c1, 0x0f);
> +	hdmi_writeb(hdmi, 0x7c8, 0x02);
> +	hdmi_writeb(hdmi, 0x7c9, 0x03);
> +	hdmi_writeb(hdmi, 0x7ca, 0x40);
> +	hdmi_writeb(hdmi, 0x7dc, 0x31);
> +	hdmi_writeb(hdmi, 0x7e2, 0x04);
> +	hdmi_writeb(hdmi, 0x7e0, 0x06);
> +	hdmi_writeb(hdmi, 0x7cb, 0x68);
> +	hdmi_writeb(hdmi, 0x7f9, 0x02);
> +	hdmi_writeb(hdmi, 0x7b6, 0x02);
> +	hdmi_writeb(hdmi, 0x7f3, 0x0);
> +}
> +
> +static void zx_hdmi_hw_init(struct zx_hdmi *hdmi)
> +{
> +	u8 val;
> +
> +	/* Software reset */
> +	hdmi_writeb(hdmi, PWD_SRST, 1);
> +
> +	/* Enable pclk */
> +	val = hdmi_readb(hdmi, CLKPWD);
> +	val |= CLKPWD_PDIDCK;
> +	hdmi_writeb(hdmi, CLKPWD, val);
> +
> +	/* Enable HDMI for TX */
> +	val = hdmi_readb(hdmi, FUNC_SEL);
> +	val |= FUNC_HDMI_EN;
> +	hdmi_writeb(hdmi, FUNC_SEL, val);
> +
> +	/* Enable deep color packet */
> +	val = hdmi_readb(hdmi, P2T_CTRL);
> +	val |= P2T_DC_PKT_EN;
> +	hdmi_writeb(hdmi, P2T_CTRL, val);
> +
> +	/* Enable HDMI/MHL mode for output */
> +	val = hdmi_readb(hdmi, TEST_TXCTRL);
> +	val |= TEST_TXCTRL_HDMI_MODE;
> +	hdmi_writeb(hdmi, TEST_TXCTRL, val);
> +
> +	/* Configure reg_qc_sel */
> +	hdmi_writeb(hdmi, HDMICTL4, 0x3);
> +
> +	/* Enable interrupt */
> +	val = hdmi_readb(hdmi, INTR1_MASK);
> +	val |= INTR1_MONITOR_DETECT;
> +	hdmi_writeb(hdmi, INTR1_MASK, val);
> +
> +	/* Clear reset for normal operation */
> +	hdmi_writeb(hdmi, PWD_SRST, 0);
> +
> +	/* Start up phy */
> +	zx_hdmi_phy_start(hdmi);
> +}
> +
> +static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct drm_device *drm = data;
> +	struct resource *res;
> +	struct zx_hdmi *hdmi;
> +	struct vou_inf *inf;
> +	int irq;
> +	int ret;
> +
> +	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +	if (!hdmi)
> +		return -ENOMEM;
> +
> +	hdmi->dev = dev;
> +	hdmi->drm = drm;
> +
> +	inf = &vou_inf_hdmi;
> +	inf->encoder = &hdmi->encoder;
> +	hdmi->inf = inf;
> +
> +	dev_set_drvdata(dev, hdmi);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	hdmi->mmio = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(hdmi->mmio))
> +		return PTR_ERR(hdmi->mmio);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	hdmi->cec_clk = devm_clk_get(hdmi->dev, "osc_cec");
> +	if (IS_ERR(hdmi->cec_clk))
> +		return PTR_ERR(hdmi->cec_clk);
> +
> +	hdmi->osc_clk = devm_clk_get(hdmi->dev, "osc_clk");
> +	if (IS_ERR(hdmi->osc_clk))
> +		return PTR_ERR(hdmi->osc_clk);
> +
> +	hdmi->xclk = devm_clk_get(hdmi->dev, "xclk");
> +	if (IS_ERR(hdmi->xclk))
> +		return PTR_ERR(hdmi->xclk);
> +
> +	zx_hdmi_hw_init(hdmi);
> +
> +	clk_prepare_enable(hdmi->cec_clk);
> +	clk_prepare_enable(hdmi->osc_clk);
> +	clk_prepare_enable(hdmi->xclk);
> +
> +	ret = zx_hdmi_register(drm, hdmi);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_request_threaded_irq(dev, irq, zx_hdmi_irq_handler,
> +					zx_hdmi_irq_thread, IRQF_SHARED,
> +					dev_name(dev), hdmi);
> +
> +	return 0;
> +}
> +
> +static void zx_hdmi_unbind(struct device *dev, struct device *master,
> +			   void *data)
> +{
> +	struct zx_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(hdmi->cec_clk);
> +	clk_disable_unprepare(hdmi->osc_clk);
> +	clk_disable_unprepare(hdmi->xclk);
> +}
> +
> +static const struct component_ops zx_hdmi_component_ops = {
> +	.bind = zx_hdmi_bind,
> +	.unbind = zx_hdmi_unbind,
> +};
> +
> +static int zx_hdmi_probe(struct platform_device *pdev)
> +{
> +	return component_add(&pdev->dev, &zx_hdmi_component_ops);
> +}
> +
> +static int zx_hdmi_remove(struct platform_device *pdev)
> +{
> +	component_del(&pdev->dev, &zx_hdmi_component_ops);
> +	return 0;
> +}
> +
> +static const struct of_device_id zx_hdmi_of_match[] = {
> +	{ .compatible = "zte,zx296718-hdmi", },
> +	{ /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_hdmi_of_match);
> +
> +struct platform_driver zx_hdmi_driver = {
> +	.probe = zx_hdmi_probe,
> +	.remove = zx_hdmi_remove,
> +	.driver	= {
> +		.name = "zx-hdmi",
> +		.of_match_table	= zx_hdmi_of_match,
> +	},
> +};
> diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c
> new file mode 100644
> index 000000000000..326cc1ff7950
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_plane.c
> @@ -0,0 +1,362 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include "zx_crtc.h"
> +#include "zx_plane.h"
> +
> +/* GL registers */
> +#define GL_CTRL0			0x00
> +#define GL_UPDATE			BIT(5)
> +#define GL_CTRL1			0x04
> +#define GL_DATA_FMT_SHIFT		0
> +#define GL_DATA_FMT_MASK		(0xf << GL_DATA_FMT_SHIFT)
> +#define GL_FMT_ARGB8888			0
> +#define GL_FMT_RGB888			1
> +#define GL_FMT_RGB565			2
> +#define GL_FMT_ARGB1555			3
> +#define GL_FMT_ARGB4444			4
> +#define GL_CTRL2			0x08
> +#define GL_GLOBAL_ALPHA_SHIFT		8
> +#define GL_GLOBAL_ALPHA_MASK		(0xff << GL_GLOBAL_ALPHA_SHIFT)
> +#define GL_CTRL3			0x0c
> +#define GL_SCALER_BYPASS_MODE		BIT(0)
> +#define GL_STRIDE			0x18
> +#define GL_ADDR				0x1c
> +#define GL_SRC_SIZE			0x38
> +#define GL_SRC_W_SHIFT			16
> +#define GL_SRC_W_MASK			(0x3fff << GL_SRC_W_SHIFT)
> +#define GL_SRC_H_SHIFT			0
> +#define GL_SRC_H_MASK			(0x3fff << GL_SRC_H_SHIFT)
> +#define GL_POS_START			0x9c
> +#define GL_POS_END			0xa0
> +#define GL_POS_X_SHIFT			16
> +#define GL_POS_X_MASK			(0x1fff << GL_POS_X_SHIFT)
> +#define GL_POS_Y_SHIFT			0
> +#define GL_POS_Y_MASK			(0x1fff << GL_POS_Y_SHIFT)
> +
> +/* CSC registers */
> +#define CSC_CTRL0			0x30
> +#define CSC_COV_MODE_SHIFT		16
> +#define CSC_COV_MODE_MASK		(0xffff << CSC_COV_MODE_SHIFT)
> +#define CSC_BT601_IMAGE_RGB2YCBCR	0
> +#define CSC_BT601_IMAGE_YCBCR2RGB	1
> +#define CSC_BT601_VIDEO_RGB2YCBCR	2
> +#define CSC_BT601_VIDEO_YCBCR2RGB	3
> +#define CSC_BT709_IMAGE_RGB2YCBCR	4
> +#define CSC_BT709_IMAGE_YCBCR2RGB	5
> +#define CSC_BT709_VIDEO_RGB2YCBCR	6
> +#define CSC_BT709_VIDEO_YCBCR2RGB	7
> +#define CSC_BT2020_IMAGE_RGB2YCBCR	8
> +#define CSC_BT2020_IMAGE_YCBCR2RGB	9
> +#define CSC_BT2020_VIDEO_RGB2YCBCR	10
> +#define CSC_BT2020_VIDEO_YCBCR2RGB	11
> +#define CSC_WORK_ENABLE			BIT(0)
> +
> +/* RSZ registers */
> +#define RSZ_SRC_CFG			0x00
> +#define RSZ_DEST_CFG			0x04
> +#define RSZ_ENABLE_CFG			0x14
> +
> +/* HBSC registers */
> +#define HBSC_SATURATION			0x00
> +#define HBSC_HUE			0x04
> +#define HBSC_BRIGHT			0x08
> +#define HBSC_CONTRAST			0x0c
> +#define HBSC_THRESHOLD_COL1		0x10
> +#define HBSC_THRESHOLD_COL2		0x14
> +#define HBSC_THRESHOLD_COL3		0x18
> +#define HBSC_CTRL0			0x28
> +#define HBSC_CTRL_EN			BIT(2)
> +
> +struct zx_plane {
> +	struct drm_plane plane;
> +	void __iomem *layer;
> +	void __iomem *csc;
> +	void __iomem *hbsc;
> +	void __iomem *rsz;
> +};
> +
> +#define to_zx_plane(plane)	container_of(plane, struct zx_plane, plane)
> +
> +static const uint32_t gl_formats[] = {
> +	DRM_FORMAT_ARGB8888,
> +	DRM_FORMAT_XRGB8888,
> +	DRM_FORMAT_RGB888,
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_ARGB1555,
> +	DRM_FORMAT_ARGB4444,
> +};
> +
> +static int zx_gl_plane_atomic_check(struct drm_plane *plane,
> +				    struct drm_plane_state *state)
> +{
> +	u32 src_w, src_h;
> +
> +	src_w = state->src_w >> 16;
> +	src_h = state->src_h >> 16;
> +
> +	/* TODO: support scaling of the plane source */
> +	if ((src_w != state->crtc_w) || (src_h != state->crtc_h))
> +		return -EINVAL;

This is generally not enough checking. You probably need a call to
drm_plane_helper_check_state.

> +
> +	return 0;
> +}
> +
> +static int zx_gl_get_fmt(uint32_t format)
> +{
> +	switch (format) {
> +	case DRM_FORMAT_ARGB8888:
> +	case DRM_FORMAT_XRGB8888:
> +		return GL_FMT_ARGB8888;
> +	case DRM_FORMAT_RGB888:
> +		return GL_FMT_RGB888;
> +	case DRM_FORMAT_RGB565:
> +		return GL_FMT_RGB565;
> +	case DRM_FORMAT_ARGB1555:
> +		return GL_FMT_ARGB1555;
> +	case DRM_FORMAT_ARGB4444:
> +		return GL_FMT_ARGB4444;
> +	default:
> +		WARN_ONCE(1, "invalid pixel format %d\n", format);
> +		return -EINVAL;
> +	}
> +}
> +
> +static inline void zx_gl_set_update(struct zx_plane *zplane)
> +{
> +	void __iomem *layer = zplane->layer;
> +	u32 val;
> +
> +	val = readl(layer + GL_CTRL0);
> +	val |= GL_UPDATE;
> +	writel(val, layer + GL_CTRL0);
> +}
> +
> +static inline void zx_gl_rsz_set_update(struct zx_plane *zplane)
> +{
> +	writel(1, zplane->rsz + RSZ_ENABLE_CFG);
> +}
> +
> +void zx_plane_set_update(struct drm_plane *plane)
> +{
> +	struct zx_plane *zplane = to_zx_plane(plane);
> +
> +	zx_gl_rsz_set_update(zplane);
> +	zx_gl_set_update(zplane);
> +}
> +
> +static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h,
> +			    u32 dst_w, u32 dst_h)
> +{
> +	void __iomem *rsz = zplane->rsz;
> +	u32 val;
> +
> +	val = ((src_h - 1) & 0xffff) << 16;
> +	val |= (src_w - 1) & 0xffff;
> +	writel(val, rsz + RSZ_SRC_CFG);
> +
> +	val = ((dst_h - 1) & 0xffff) << 16;
> +	val |= (dst_w - 1) & 0xffff;
> +	writel(val, rsz + RSZ_DEST_CFG);
> +
> +	zx_gl_rsz_set_update(zplane);
> +}
> +
> +static void zx_gl_plane_atomic_update(struct drm_plane *plane,
> +				      struct drm_plane_state *old_state)
> +{
> +	struct zx_plane *zplane = to_zx_plane(plane);
> +	struct drm_framebuffer *fb = plane->state->fb;
> +	struct drm_gem_cma_object *cma_obj;
> +	void __iomem *layer = zplane->layer;
> +	void __iomem *csc = zplane->csc;
> +	void __iomem *hbsc = zplane->hbsc;
> +	u32 src_x, src_y, src_w, src_h;
> +	u32 dst_x, dst_y, dst_w, dst_h;
> +	unsigned int depth, bpp;
> +	uint32_t format;
> +	dma_addr_t paddr;
> +	u32 stride;
> +	int fmt;
> +	u32 val;
> +
> +	if (!fb)
> +		return;
> +
> +	format = fb->pixel_format;
> +	stride = fb->pitches[0];
> +
> +	src_x = plane->state->src_x >> 16;
> +	src_y = plane->state->src_y >> 16;
> +	src_w = plane->state->src_w >> 16;
> +	src_h = plane->state->src_h >> 16;
> +
> +	dst_x = plane->state->crtc_x;
> +	dst_y = plane->state->crtc_y;
> +	dst_w = plane->state->crtc_w;
> +	dst_h = plane->state->crtc_h;
> +
> +	drm_fb_get_bpp_depth(format, &depth, &bpp);
> +
> +	cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +	paddr = cma_obj->paddr + fb->offsets[0];
> +	paddr += src_y * stride + src_x * bpp / 8;
> +	writel(paddr, layer + GL_ADDR);
> +
> +	/* Set up source height/width register */
> +	val = (src_w << GL_SRC_W_SHIFT) & GL_SRC_W_MASK;
> +	val |= (src_h << GL_SRC_H_SHIFT) & GL_SRC_H_MASK;
> +	writel(val, layer + GL_SRC_SIZE);
> +
> +	/* Set up start position register */
> +	val = (dst_x << GL_POS_X_SHIFT) & GL_POS_X_MASK;
> +	val |= (dst_y << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
> +	writel(val, layer + GL_POS_START);
> +
> +	/* Set up end position register */
> +	val = ((dst_x + dst_w) << GL_POS_X_SHIFT) & GL_POS_X_MASK;
> +	val |= ((dst_y + dst_h) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
> +	writel(val, layer + GL_POS_END);
> +
> +	/* Set up stride register */
> +	writel(stride & 0xffff, layer + GL_STRIDE);
> +
> +	/* Set up graphic layer data format */
> +	fmt = zx_gl_get_fmt(format);
> +	if (fmt >= 0) {
> +		val = readl(layer + GL_CTRL1);
> +		val &= ~GL_DATA_FMT_MASK;
> +		val |= fmt << GL_DATA_FMT_SHIFT;
> +		writel(val, layer + GL_CTRL1);
> +	}
> +
> +	/* Initialize global alpha with a sane value */
> +	val = readl(layer + GL_CTRL2);
> +	val &= ~GL_GLOBAL_ALPHA_MASK;
> +	val |= 0xff << GL_GLOBAL_ALPHA_SHIFT;
> +	writel(val, layer + GL_CTRL2);
> +
> +	/* Setup CSC for the GL */
> +	val = readl(csc + CSC_CTRL0);
> +	val &= ~CSC_COV_MODE_MASK;
> +	if (dst_h > 720)
> +		val |= CSC_BT709_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
> +	else
> +		val |= CSC_BT601_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
> +	val |= CSC_WORK_ENABLE;
> +	writel(val, csc + CSC_CTRL0);
> +
> +	/* Always use scaler since it exists */
> +	val = readl(layer + GL_CTRL3);
> +	val |= GL_SCALER_BYPASS_MODE;	/* set for not bypass */
> +	writel(val, layer + GL_CTRL3);
> +
> +	zx_gl_rsz_setup(zplane, src_w, src_h, dst_w, dst_h);
> +
> +	/* Enable HBSC block */
> +	val = readl(hbsc + HBSC_CTRL0);
> +	val |= HBSC_CTRL_EN;
> +	writel(val, hbsc + HBSC_CTRL0);
> +
> +	zx_gl_set_update(zplane);
> +}
> +
> +static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = {
> +	.atomic_check = zx_gl_plane_atomic_check,
> +	.atomic_update = zx_gl_plane_atomic_update,
> +};
> +
> +static void zx_plane_destroy(struct drm_plane *plane)
> +{
> +	drm_plane_helper_disable(plane);
> +	drm_plane_cleanup(plane);
> +}
> +
> +static const struct drm_plane_funcs zx_plane_funcs = {
> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = zx_plane_destroy,
> +	.reset = drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static void zx_plane_hbsc_init(struct zx_plane *zplane)
> +{
> +	void __iomem *hbsc = zplane->hbsc;
> +
> +	/*
> +	 *  Initialize HBSC block with a sane configuration per recommedation
> +	 *  from ZTE BSP code.
> +	 */
> +	writel(0x200, hbsc + HBSC_SATURATION);
> +	writel(0x0, hbsc + HBSC_HUE);
> +	writel(0x0, hbsc + HBSC_BRIGHT);
> +	writel(0x200, hbsc + HBSC_CONTRAST);
> +
> +	writel((0x3ac << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL1);
> +	writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL2);
> +	writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL3);
> +}
> +
> +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
> +				struct zx_layer_data *data,
> +				enum drm_plane_type type)
> +{
> +	const struct drm_plane_helper_funcs *helper;
> +	struct zx_plane *zplane;
> +	struct drm_plane *plane;
> +	const uint32_t *formats;
> +	unsigned int format_count;
> +	int ret;
> +
> +	zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
> +	if (!zplane)
> +		return ERR_PTR(-ENOMEM);
> +
> +	plane = &zplane->plane;
> +
> +	zplane->layer = data->layer;
> +	zplane->hbsc = data->hbsc;
> +	zplane->csc = data->csc;
> +	zplane->rsz = data->rsz;
> +
> +	zx_plane_hbsc_init(zplane);
> +
> +	switch (type) {
> +	case DRM_PLANE_TYPE_PRIMARY:
> +		helper = &zx_gl_plane_helper_funcs;
> +		formats = gl_formats;
> +		format_count = ARRAY_SIZE(gl_formats);
> +		break;
> +	case DRM_PLANE_TYPE_OVERLAY:
> +		/* TODO: add video layer (vl) support */
> +		break;
> +	default:
> +		return ERR_PTR(-ENODEV);
> +	}
> +
> +	ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK,
> +				       &zx_plane_funcs, formats, format_count,
> +				       type, NULL);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	drm_plane_helper_add(plane, helper);
> +
> +	return plane;
> +}
> diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h
> new file mode 100644
> index 000000000000..2b82cd558d9d
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_plane.h
> @@ -0,0 +1,26 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_PLANE_H__
> +#define __ZX_PLANE_H__
> +
> +struct zx_layer_data {
> +	void __iomem *layer;
> +	void __iomem *csc;
> +	void __iomem *hbsc;
> +	void __iomem *rsz;
> +};
> +
> +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
> +				struct zx_layer_data *data,
> +				enum drm_plane_type type);
> +void zx_plane_set_update(struct drm_plane *plane);
> +
> +#endif /* __ZX_PLANE_H__ */
> -- 
> 1.9.1
>
Sean Paul Sept. 27, 2016, 3:48 p.m. UTC | #2
On Sat, Sep 24, 2016 at 10:26 AM, Shawn Guo <shawn.guo@linaro.org> wrote:
> It adds the initial ZTE VOU display controller DRM driver.  There are
> still some features to be added, like overlay plane, scaling, and more
> output devices support.  But it's already useful with dual CRTCs and
> HDMI monitor working.
>
> It's been tested on Debian Jessie LXDE desktop with modesetting driver.
>
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>

Hi Shawn,
I think overall this is very well done! A couple of things stuck out
to me, I've pointed them out below, hopefully you can use some of
them.

Sean


> ---
>  drivers/gpu/drm/Kconfig          |   2 +
>  drivers/gpu/drm/Makefile         |   1 +
>  drivers/gpu/drm/zte/Kconfig      |   8 +
>  drivers/gpu/drm/zte/Makefile     |   8 +
>  drivers/gpu/drm/zte/zx_crtc.c    | 691 +++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_crtc.h    |  47 +++
>  drivers/gpu/drm/zte/zx_drm_drv.c | 258 +++++++++++++++
>  drivers/gpu/drm/zte/zx_drm_drv.h |  22 ++
>  drivers/gpu/drm/zte/zx_hdmi.c    | 540 ++++++++++++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_plane.c   | 362 ++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_plane.h   |  26 ++
>  11 files changed, 1965 insertions(+)
>  create mode 100644 drivers/gpu/drm/zte/Kconfig
>  create mode 100644 drivers/gpu/drm/zte/Makefile
>  create mode 100644 drivers/gpu/drm/zte/zx_crtc.c
>  create mode 100644 drivers/gpu/drm/zte/zx_crtc.h
>  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
>  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
>  create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
>  create mode 100644 drivers/gpu/drm/zte/zx_plane.c
>  create mode 100644 drivers/gpu/drm/zte/zx_plane.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 483059a22b1b..a91f8cecbe0f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -223,6 +223,8 @@ source "drivers/gpu/drm/hisilicon/Kconfig"
>
>  source "drivers/gpu/drm/mediatek/Kconfig"
>
> +source "drivers/gpu/drm/zte/Kconfig"
> +
>  # Keep legacy drivers last
>
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 439d89b25ae0..fe461c94d266 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -85,3 +85,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
>  obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
>  obj-$(CONFIG_DRM_ARCPGU)+= arc/
>  obj-y                  += hisilicon/
> +obj-$(CONFIG_DRM_ZTE)  += zte/
> diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig
> new file mode 100644
> index 000000000000..4065b2840f1c
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/Kconfig
> @@ -0,0 +1,8 @@
> +config DRM_ZTE
> +       tristate "DRM Support for ZTE SoCs"
> +       depends on DRM && ARCH_ZX
> +       select DRM_KMS_CMA_HELPER
> +       select DRM_KMS_FB_HELPER
> +       select DRM_KMS_HELPER
> +       help
> +         Choose this option to enable DRM on ZTE ZX SoCs.
> diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
> new file mode 100644
> index 000000000000..b40968dc749f
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/Makefile
> @@ -0,0 +1,8 @@
> +zxdrm-y := \
> +       zx_drm_drv.o \
> +       zx_crtc.o \
> +       zx_plane.o \
> +       zx_hdmi.o
> +
> +obj-$(CONFIG_DRM_ZTE) += zxdrm.o
> +
> diff --git a/drivers/gpu/drm/zte/zx_crtc.c b/drivers/gpu/drm/zte/zx_crtc.c

I was a little tripped up when I first read this file since I assumed
there was one instance of this driver per-crtc. However, there's
really N crtcs per driver. Might it be less confusing to call it
zx_vou.c instead?

> new file mode 100644
> index 000000000000..818bf9072573
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_crtc.c
> @@ -0,0 +1,691 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_plane_helper.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/of_address.h>
> +#include <video/videomode.h>
> +
> +#include "zx_drm_drv.h"
> +#include "zx_crtc.h"
> +#include "zx_plane.h"
> +
> +/* OSD (GPC_GLOBAL) registers */
> +#define OSD_INT_STA                    0x04
> +#define OSD_INT_CLRSTA                 0x08
> +#define OSD_INT_MSK                    0x0c
> +#define OSD_INT_AUX_UPT                        BIT(14)
> +#define OSD_INT_MAIN_UPT               BIT(13)
> +#define OSD_INT_GL1_LBW                        BIT(10)
> +#define OSD_INT_GL0_LBW                        BIT(9)
> +#define OSD_INT_VL2_LBW                        BIT(8)
> +#define OSD_INT_VL1_LBW                        BIT(7)
> +#define OSD_INT_VL0_LBW                        BIT(6)
> +#define OSD_INT_BUS_ERR                        BIT(3)
> +#define OSD_INT_CFG_ERR                        BIT(2)
> +#define OSD_INT_ERROR (\
> +       OSD_INT_GL1_LBW | OSD_INT_GL0_LBW | \
> +       OSD_INT_VL2_LBW | OSD_INT_VL1_LBW | OSD_INT_VL0_LBW | \
> +       OSD_INT_BUS_ERR | OSD_INT_CFG_ERR \
> +)
> +#define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT)
> +#define OSD_CTRL0                      0x10
> +#define OSD_CTRL0_GL0_EN               BIT(7)
> +#define OSD_CTRL0_GL0_SEL              BIT(6)
> +#define OSD_CTRL0_GL1_EN               BIT(5)
> +#define OSD_CTRL0_GL1_SEL              BIT(4)
> +#define OSD_RST_CLR                    0x1c
> +#define RST_PER_FRAME                  BIT(19)
> +
> +/* Main/Aux channel registers */
> +#define OSD_MAIN_CHN                   0x470
> +#define OSD_AUX_CHN                    0x4d0
> +#define CHN_CTRL0                      0x00
> +#define CHN_ENABLE                     BIT(0)
> +#define CHN_CTRL1                      0x04
> +#define CHN_SCREEN_W_SHIFT             18
> +#define CHN_SCREEN_W_MASK              (0x1fff << CHN_SCREEN_W_SHIFT)
> +#define CHN_SCREEN_H_SHIFT             5
> +#define CHN_SCREEN_H_MASK              (0x1fff << CHN_SCREEN_H_SHIFT)
> +#define CHN_UPDATE                     0x08
> +
> +/* TIMING_CTRL registers */
> +#define TIMING_TC_ENABLE               0x04
> +#define AUX_TC_EN                      BIT(1)
> +#define MAIN_TC_EN                     BIT(0)
> +#define FIR_MAIN_ACTIVE                        0x08
> +#define FIR_AUX_ACTIVE                 0x0c
> +#define FIR_MAIN_H_TIMING              0x10
> +#define FIR_MAIN_V_TIMING              0x14
> +#define FIR_AUX_H_TIMING               0x18
> +#define FIR_AUX_V_TIMING               0x1c
> +#define SYNC_WIDE_SHIFT                        22
> +#define SYNC_WIDE_MASK                 (0x3ff << SYNC_WIDE_SHIFT)
> +#define BACK_PORCH_SHIFT               11
> +#define BACK_PORCH_MASK                        (0x7ff << BACK_PORCH_SHIFT)
> +#define FRONT_PORCH_SHIFT              0
> +#define FRONT_PORCH_MASK               (0x7ff << FRONT_PORCH_SHIFT)
> +#define TIMING_CTRL                    0x20
> +#define AUX_POL_SHIFT                  3
> +#define AUX_POL_MASK                   (0x7 << AUX_POL_SHIFT)
> +#define MAIN_POL_SHIFT                 0
> +#define MAIN_POL_MASK                  (0x7 << MAIN_POL_SHIFT)
> +#define POL_DE_SHIFT                   2
> +#define POL_VSYNC_SHIFT                        1
> +#define POL_HSYNC_SHIFT                        0
> +#define TIMING_INT_CTRL                        0x24
> +#define TIMING_INT_STATE               0x28
> +#define TIMING_INT_AUX_FRAME           BIT(3)
> +#define TIMING_INT_MAIN_FRAME          BIT(1)
> +#define TIMING_INT_AUX_FRAME_SEL_VSW   (0x2 << 10)
> +#define TIMING_INT_MAIN_FRAME_SEL_VSW  (0x2 << 6)
> +#define TIMING_INT_ENABLE (\
> +       TIMING_INT_MAIN_FRAME_SEL_VSW | TIMING_INT_AUX_FRAME_SEL_VSW | \
> +       TIMING_INT_MAIN_FRAME | TIMING_INT_AUX_FRAME \
> +)
> +#define TIMING_MAIN_SHIFT              0x2c
> +#define TIMING_AUX_SHIFT               0x30
> +#define H_SHIFT_VAL                    0x0048
> +#define TIMING_MAIN_PI_SHIFT           0x68
> +#define TIMING_AUX_PI_SHIFT            0x6c
> +#define H_PI_SHIFT_VAL                 0x000f
> +
> +/* DTRC registers */
> +#define DTRC_F0_CTRL                   0x2c
> +#define DTRC_F1_CTRL                   0x5c
> +#define DTRC_DECOMPRESS_BYPASS         BIT(17)
> +#define DTRC_DETILE_CTRL               0x68
> +#define TILE2RASTESCAN_BYPASS_MODE     BIT(30)
> +#define DETILE_ARIDR_MODE_MASK         (0x3 << 0)
> +#define DETILE_ARID_ALL                        0
> +#define DETILE_ARID_IN_ARIDR           1
> +#define DETILE_ARID_BYP_BUT_ARIDR      2
> +#define DETILE_ARID_IN_ARIDR2          3
> +#define DTRC_ARID                      0x6c
> +#define DTRC_DEC2DDR_ARID              0x70
> +
> +/* VOU_CTRL registers */
> +#define VOU_INF_EN                     0x00
> +#define VOU_INF_CH_SEL                 0x04
> +#define VOU_INF_DATA_SEL               0x08
> +#define VOU_SOFT_RST                   0x14
> +#define VOU_CLK_SEL                    0x18
> +#define VOU_CLK_GL1_SEL                        BIT(5)
> +#define VOU_CLK_GL0_SEL                        BIT(4)
> +#define VOU_CLK_REQEN                  0x20
> +#define VOU_CLK_EN                     0x24
> +
> +/* OTFPPU_CTRL registers */
> +#define OTFPPU_RSZ_DATA_SOURCE         0x04
> +
> +#define GL_NUM                         2
> +#define VL_NUM                         3
> +
> +enum vou_chn_type {
> +       VOU_CHN_MAIN,
> +       VOU_CHN_AUX,
> +};
> +
> +struct zx_crtc {
> +       struct drm_crtc crtc;
> +       struct drm_plane *primary;
> +       struct device *dev;
> +       void __iomem *chnreg;
> +       enum vou_chn_type chn_type;
> +       struct clk *pixclk;
> +};
> +
> +#define to_zx_crtc(x)  container_of(crtc, struct zx_crtc, crtc)
> +
> +struct zx_vou_hw {
> +       struct device *dev;
> +       void __iomem *osd;
> +       void __iomem *timing;
> +       void __iomem *vouctl;
> +       void __iomem *otfppu;
> +       void __iomem *dtrc;
> +       struct clk *axi_clk;
> +       struct clk *ppu_clk;
> +       struct clk *main_clk;
> +       struct clk *aux_clk;
> +       struct zx_crtc *main_crtc;
> +       struct zx_crtc *aux_crtc;
> +};
> +
> +static inline bool is_main_crtc(struct drm_crtc *crtc)
> +{
> +       struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +
> +       return zcrtc->chn_type == VOU_CHN_MAIN;
> +}
> +
> +void vou_inf_enable(struct vou_inf *inf)
> +{
> +       struct drm_encoder *encoder = inf->encoder;
> +       struct drm_device *drm = encoder->dev;
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct zx_vou_hw *vou = priv->vou;
> +       bool is_main = is_main_crtc(encoder->crtc);
> +       u32 data_sel_shift = inf->id << 1;
> +       u32 val;
> +
> +       /* Select data format */
> +       val = readl(vou->vouctl + VOU_INF_DATA_SEL);
> +       val &= ~(0x3 << data_sel_shift);
> +       val |= inf->data_sel << data_sel_shift;
> +       writel(val, vou->vouctl + VOU_INF_DATA_SEL);
> +
> +       /* Select channel */
> +       val = readl(vou->vouctl + VOU_INF_CH_SEL);
> +       if (is_main)
> +               val &= ~(1 << inf->id);
> +       else
> +               val |= 1 << inf->id;
> +       writel(val, vou->vouctl + VOU_INF_CH_SEL);
> +
> +       /* Select interface clocks */
> +       val = readl(vou->vouctl + VOU_CLK_SEL);
> +       if (is_main)
> +               val &= ~inf->clocks_sel_bits;
> +       else
> +               val |= inf->clocks_sel_bits;
> +       writel(val, vou->vouctl + VOU_CLK_SEL);
> +
> +       /* Enable interface clocks */
> +       val = readl(vou->vouctl + VOU_CLK_EN);
> +       val |= inf->clocks_en_bits;
> +       writel(val, vou->vouctl + VOU_CLK_EN);
> +
> +       /* Enable the device */
> +       val = readl(vou->vouctl + VOU_INF_EN);
> +       val |= 1 << inf->id;
> +       writel(val, vou->vouctl + VOU_INF_EN);
> +}
> +
> +void vou_inf_disable(struct vou_inf *inf)
> +{
> +       struct drm_encoder *encoder = inf->encoder;
> +       struct drm_device *drm = encoder->dev;
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct zx_vou_hw *vou = priv->vou;
> +       u32 val;
> +
> +       /* Disable the device */
> +       val = readl(vou->vouctl + VOU_INF_EN);
> +       val &= ~(1 << inf->id);
> +       writel(val, vou->vouctl + VOU_INF_EN);
> +
> +       /* Disable interface clocks */
> +       val = readl(vou->vouctl + VOU_CLK_EN);
> +       val &= ~inf->clocks_en_bits;
> +       writel(val, vou->vouctl + VOU_CLK_EN);
> +}
> +
> +static inline void vou_chn_set_update(struct zx_crtc *zcrtc)
> +{
> +       writel(1, zcrtc->chnreg + CHN_UPDATE);
> +}
> +
> +static void zx_crtc_enable(struct drm_crtc *crtc)
> +{
> +       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> +       struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +       struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);

IMO, it would be better to store a pointer to vou in each zx_crtc
rather than reaching into drvdata.

> +       bool is_main = is_main_crtc(crtc);
> +       struct videomode vm;
> +       u32 pol = 0;
> +       u32 val;
> +
> +       drm_display_mode_to_videomode(mode, &vm);

Why do this conversion? You should be able to get everything you need
from drm_display_mode

> +
> +       /* Set up timing parameters */
> +       val = (vm.vactive - 1) << 16;
> +       val |= (vm.hactive - 1) & 0xffff;
> +       writel(val, vou->timing + (is_main ? FIR_MAIN_ACTIVE : FIR_AUX_ACTIVE));
> +
> +       val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> +       val |= ((vm.hback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> +       val |= ((vm.hfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> +       writel(val, vou->timing + (is_main ? FIR_MAIN_H_TIMING :
> +                                            FIR_AUX_H_TIMING));
> +
> +       val = ((vm.vsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> +       val |= ((vm.vback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> +       val |= ((vm.vfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> +       writel(val, vou->timing + (is_main ? FIR_MAIN_V_TIMING :
> +                                            FIR_AUX_V_TIMING));

It would be nice to figure out a better way of handing the main/aux
switch as opposed to sprinkling all of these inline conditionals
around. Perhaps you could introduce a struct which stores the
addresses per-crtc and then reference the struct in the driver as
opposed to the #defines.

ie:

writel(val, vou->timing + zcrtc->regs->v_timing);


> +
> +       /* Set up polarities */
> +       if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW)
> +               pol |= 1 << POL_VSYNC_SHIFT;
> +       if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW)
> +               pol |= 1 << POL_HSYNC_SHIFT;
> +
> +       val = readl(vou->timing + TIMING_CTRL);
> +       val &= ~(is_main ? MAIN_POL_MASK : AUX_POL_MASK);
> +       val |= pol << (is_main ? MAIN_POL_SHIFT : AUX_POL_SHIFT);
> +       writel(val, vou->timing + TIMING_CTRL);
> +
> +       /* Setup SHIFT register by following what ZTE BSP does */
> +       writel(H_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_SHIFT :
> +                                                    TIMING_AUX_SHIFT));
> +       writel(H_PI_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_PI_SHIFT :
> +                                                       TIMING_AUX_PI_SHIFT));
> +
> +       /* Enable TIMING_CTRL */
> +       val = readl(vou->timing + TIMING_TC_ENABLE);
> +       val |= is_main ? MAIN_TC_EN : AUX_TC_EN;
> +       writel(val, vou->timing + TIMING_TC_ENABLE);
> +
> +       /* Configure channel screen size */
> +       val = readl(zcrtc->chnreg + CHN_CTRL1);
> +       val &= ~(CHN_SCREEN_W_MASK | CHN_SCREEN_H_MASK);
> +       val |= (vm.hactive << CHN_SCREEN_W_SHIFT) & CHN_SCREEN_W_MASK;
> +       val |= (vm.vactive << CHN_SCREEN_H_SHIFT) & CHN_SCREEN_H_MASK;
> +       writel(val, zcrtc->chnreg + CHN_CTRL1);
> +
> +       /* Update channel */
> +       vou_chn_set_update(zcrtc);
> +
> +       /* Enable channel */
> +       val = readl(zcrtc->chnreg + CHN_CTRL0);
> +       val |= CHN_ENABLE;
> +       writel(val, zcrtc->chnreg + CHN_CTRL0);
> +
> +       /* Enable Graphic Layer */
> +       val = readl(vou->osd + OSD_CTRL0);
> +       val |= is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN;
> +       writel(val, vou->osd + OSD_CTRL0);
> +
> +       drm_crtc_vblank_on(crtc);
> +
> +       /* Enable pixel clock */
> +       clk_set_rate(zcrtc->pixclk, mode->clock * 1000);
> +       clk_prepare_enable(zcrtc->pixclk);
> +}
> +
> +static void zx_crtc_disable(struct drm_crtc *crtc)
> +{
> +       struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> +       struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
> +       bool is_main = is_main_crtc(crtc);
> +       u32 val;
> +
> +       clk_disable_unprepare(zcrtc->pixclk);
> +
> +       drm_crtc_vblank_off(crtc);
> +
> +       /* Disable Graphic Layer */
> +       val = readl(vou->osd + OSD_CTRL0);
> +       val &= ~(is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN);
> +       writel(val, vou->osd + OSD_CTRL0);
> +
> +       /* Disable channel */
> +       val = readl(zcrtc->chnreg + CHN_CTRL0);
> +       val &= ~CHN_ENABLE;
> +       writel(val, zcrtc->chnreg + CHN_CTRL0);
> +
> +       /* Disable TIMING_CTRL */
> +       val = readl(vou->timing + TIMING_TC_ENABLE);
> +       val &= ~(is_main ? MAIN_TC_EN : AUX_TC_EN);
> +       writel(val, vou->timing + TIMING_TC_ENABLE);
> +}
> +
> +static void zx_crtc_atomic_begin(struct drm_crtc *crtc,
> +                                struct drm_crtc_state *state)
> +{
> +       struct drm_pending_vblank_event *event = crtc->state->event;
> +
> +       if (event) {


nit: you can save yourself a level of indentation by exiting early on
!event instead of scoping the entire function on event.

> +               crtc->state->event = NULL;
> +
> +               spin_lock_irq(&crtc->dev->event_lock);
> +               if (drm_crtc_vblank_get(crtc) == 0)
> +                       drm_crtc_arm_vblank_event(crtc, event);
> +               else
> +                       drm_crtc_send_vblank_event(crtc, event);
> +               spin_unlock_irq(&crtc->dev->event_lock);
> +       }
> +}
> +
> +static const struct drm_crtc_helper_funcs zx_crtc_helper_funcs = {
> +       .enable = zx_crtc_enable,
> +       .disable = zx_crtc_disable,
> +       .atomic_begin = zx_crtc_atomic_begin,
> +};
> +
> +static const struct drm_crtc_funcs zx_crtc_funcs = {
> +       .destroy = drm_crtc_cleanup,
> +       .set_config = drm_atomic_helper_set_config,
> +       .page_flip = drm_atomic_helper_page_flip,
> +       .reset = drm_atomic_helper_crtc_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static struct zx_crtc *zx_crtc_init(struct drm_device *drm,
> +                                   enum vou_chn_type chn_type)
> +{
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct zx_vou_hw *vou = priv->vou;
> +       struct device *dev = vou->dev;
> +       struct zx_layer_data data;
> +       struct zx_crtc *zcrtc;
> +       int ret;
> +
> +       zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
> +       if (!zcrtc)
> +               return ERR_PTR(-ENOMEM);
> +
> +       zcrtc->dev = dev;
> +       zcrtc->chn_type = chn_type;
> +
> +       if (chn_type == VOU_CHN_MAIN) {
> +               data.layer = vou->osd + 0x130;
> +               data.csc = vou->osd + 0x580;
> +               data.hbsc = vou->osd + 0x820;
> +               data.rsz = vou->otfppu + 0x600;
> +               zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
> +       } else {
> +               data.layer = vou->osd + 0x200;
> +               data.csc = vou->osd + 0x5d0;
> +               data.hbsc = vou->osd + 0x860;
> +               data.rsz = vou->otfppu + 0x800;
> +               zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
> +       }

These magic values should find their way into #defines

> +
> +       zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ?
> +                                         "main_wclk" : "aux_wclk");
> +       if (IS_ERR(zcrtc->pixclk))
> +               return ERR_PTR(PTR_ERR(zcrtc->pixclk));
> +
> +       zcrtc->primary = zx_plane_init(drm, dev, &data, DRM_PLANE_TYPE_PRIMARY);
> +       if (IS_ERR(zcrtc->primary))
> +               return ERR_PTR(PTR_ERR(zcrtc->primary));
> +
> +       ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL,
> +                                       &zx_crtc_funcs, NULL);
> +       if (ret)
> +               return ERR_PTR(ret);
> +
> +       drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs);
> +
> +       return zcrtc;
> +}
> +
> +int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe)
> +{
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct zx_vou_hw *vou = priv->vou;
> +       u32 intctl;
> +
> +       intctl = readl(vou->timing + TIMING_INT_CTRL);
> +       if (pipe == 0)
> +               intctl |= TIMING_INT_MAIN_FRAME;
> +       else
> +               intctl |= TIMING_INT_AUX_FRAME;
> +       writel(intctl, vou->timing + TIMING_INT_CTRL);
> +
> +       return 0;
> +}
> +
> +void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe)
> +{
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct zx_vou_hw *vou = priv->vou;
> +       u32 intctl;
> +
> +       intctl = readl(vou->timing + TIMING_INT_CTRL);
> +       if (pipe == 0)
> +               intctl &= ~TIMING_INT_MAIN_FRAME;
> +       else
> +               intctl &= ~TIMING_INT_AUX_FRAME;
> +       writel(intctl, vou->timing + TIMING_INT_CTRL);
> +}
> +
> +static irqreturn_t vou_irq_handler(int irq, void *dev_id)
> +{
> +       struct zx_vou_hw *vou = dev_id;
> +       u32 state;
> +
> +       /* Handle TIMING_CTRL frame interrupts */
> +       state = readl(vou->timing + TIMING_INT_STATE);
> +       writel(state, vou->timing + TIMING_INT_STATE);
> +
> +       if (state & TIMING_INT_MAIN_FRAME)
> +               drm_crtc_handle_vblank(&vou->main_crtc->crtc);
> +
> +       if (state & TIMING_INT_AUX_FRAME)
> +               drm_crtc_handle_vblank(&vou->aux_crtc->crtc);
> +
> +       /* Handle OSD interrupts */
> +       state = readl(vou->osd + OSD_INT_STA);
> +       writel(state, vou->osd + OSD_INT_CLRSTA);
> +
> +       if (state & OSD_INT_MAIN_UPT) {
> +               vou_chn_set_update(vou->main_crtc);
> +               zx_plane_set_update(vou->main_crtc->primary);
> +       }
> +
> +       if (state & OSD_INT_AUX_UPT) {
> +               vou_chn_set_update(vou->aux_crtc);
> +               zx_plane_set_update(vou->aux_crtc->primary);
> +       }
> +
> +       if (state & OSD_INT_ERROR)
> +               dev_err(vou->dev, "OSD ERROR: 0x%08x!\n", state);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static void vou_dtrc_init(struct zx_vou_hw *vou)
> +{
> +       u32 val;
> +
> +       val = readl(vou->dtrc + DTRC_DETILE_CTRL);
> +       /* Clear bit for bypass by ID */
> +       val &= ~TILE2RASTESCAN_BYPASS_MODE;
> +       /* Select ARIDR mode */
> +       val &= ~DETILE_ARIDR_MODE_MASK;
> +       val |= DETILE_ARID_IN_ARIDR;
> +       writel(val, vou->dtrc + DTRC_DETILE_CTRL);
> +
> +       /* Bypass decompression for both frames */
> +       val = readl(vou->dtrc + DTRC_F0_CTRL);
> +       val |= DTRC_DECOMPRESS_BYPASS;
> +       writel(val, vou->dtrc + DTRC_F0_CTRL);
> +
> +       val = readl(vou->dtrc + DTRC_F1_CTRL);
> +       val |= DTRC_DECOMPRESS_BYPASS;
> +       writel(val, vou->dtrc + DTRC_F1_CTRL);
> +
> +       /* Set up ARID register */
> +       val = 0x0e;
> +       val |= 0x0f << 8;
> +       val |= 0x0e << 16;
> +       val |= 0x0f << 24;

#define

> +       writel(val, vou->dtrc + DTRC_ARID);
> +
> +       /* Set up DEC2DDR_ARID register */
> +       val = 0x0e;
> +       val |= 0x0f << 8;
> +       writel(val, vou->dtrc + DTRC_DEC2DDR_ARID);
> +}
> +
> +static void vou_hw_init(struct zx_vou_hw *vou)
> +{
> +       u32 val;
> +
> +       /* Set GL0 to main channel and GL1 to aux channel */
> +       val = readl(vou->osd + OSD_CTRL0);
> +       val &= ~OSD_CTRL0_GL0_SEL;
> +       val |= OSD_CTRL0_GL1_SEL;
> +       writel(val, vou->osd + OSD_CTRL0);
> +
> +       /* Release reset for all VOU modules */
> +       writel(~0, vou->vouctl + VOU_SOFT_RST);
> +
> +       /* Select main clock for GL0 and aux clock for GL1 module */
> +       val = readl(vou->vouctl + VOU_CLK_SEL);
> +       val &= ~VOU_CLK_GL0_SEL;
> +       val |= VOU_CLK_GL1_SEL;
> +       writel(val, vou->vouctl + VOU_CLK_SEL);
> +
> +       /* Enable clock auto-gating for all VOU modules */
> +       writel(~0, vou->vouctl + VOU_CLK_REQEN);
> +
> +       /* Enable all VOU module clocks */
> +       writel(~0, vou->vouctl + VOU_CLK_EN);
> +
> +       /* Clear both OSD and TIMING_CTRL interrupt state */
> +       writel(~0, vou->osd + OSD_INT_CLRSTA);
> +       writel(~0, vou->timing + TIMING_INT_STATE);
> +
> +       /* Enable OSD and TIMING_CTRL interrrupts */
> +       writel(OSD_INT_ENABLE, vou->osd + OSD_INT_MSK);
> +       writel(TIMING_INT_ENABLE, vou->timing + TIMING_INT_CTRL);
> +
> +       /* Select GPC as input to gl/vl scaler as a sane default setting */
> +       writel(0x2a, vou->otfppu + OTFPPU_RSZ_DATA_SOURCE);
> +
> +       /*
> +        * Needs to reset channel and layer logic per frame when frame starts
> +        * to get VOU work properly.
> +        */
> +       val = readl(vou->osd + OSD_RST_CLR);
> +       val |= RST_PER_FRAME;
> +       writel(val, vou->osd + OSD_RST_CLR);
> +
> +       vou_dtrc_init(vou);
> +}
> +
> +static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct drm_device *drm = data;
> +       struct zx_drm_private *priv = drm->dev_private;
> +       struct resource *res;
> +       struct zx_vou_hw *vou;
> +       int irq;
> +       int ret;
> +
> +       vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL);
> +       if (!vou)
> +               return -ENOMEM;
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd");
> +       vou->osd = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(vou->osd))
> +               return PTR_ERR(vou->osd);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
> +       vou->timing = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(vou->timing))
> +               return PTR_ERR(vou->timing);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
> +       vou->dtrc = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(vou->dtrc))
> +               return PTR_ERR(vou->dtrc);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
> +       vou->vouctl = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(vou->vouctl))
> +               return PTR_ERR(vou->vouctl);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
> +       vou->otfppu = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(vou->otfppu))
> +               return PTR_ERR(vou->otfppu);
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0)
> +               return irq;
> +
> +       vou->axi_clk = devm_clk_get(dev, "aclk");
> +       if (IS_ERR(vou->axi_clk))
> +               return PTR_ERR(vou->axi_clk);
> +
> +       vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
> +       if (IS_ERR(vou->ppu_clk))
> +               return PTR_ERR(vou->ppu_clk);
> +
> +       clk_prepare_enable(vou->axi_clk);
> +       clk_prepare_enable(vou->ppu_clk);
> +
> +       vou->dev = dev;
> +       priv->vou = vou;
> +       dev_set_drvdata(dev, vou);

I think you should be able to avoid storing vou in priv and drvdata.

> +
> +       vou_hw_init(vou);
> +
> +       ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou);
> +       if (ret < 0)
> +               return ret;
> +
> +       vou->main_crtc = zx_crtc_init(drm, VOU_CHN_MAIN);
> +       if (IS_ERR(vou->main_crtc))
> +               return PTR_ERR(vou->main_crtc);
> +
> +       vou->aux_crtc = zx_crtc_init(drm, VOU_CHN_AUX);
> +       if (IS_ERR(vou->aux_crtc))
> +               return PTR_ERR(vou->aux_crtc);
> +
> +       return 0;
> +}
> +
> +static void zx_crtc_unbind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct zx_vou_hw *vou = dev_get_drvdata(dev);
> +
> +       clk_disable_unprepare(vou->axi_clk);
> +       clk_disable_unprepare(vou->ppu_clk);
> +}
> +
> +static const struct component_ops zx_crtc_component_ops = {
> +       .bind = zx_crtc_bind,
> +       .unbind = zx_crtc_unbind,
> +};
> +
> +static int zx_crtc_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &zx_crtc_component_ops);
> +}
> +
> +static int zx_crtc_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &zx_crtc_component_ops);
> +       return 0;
> +}
> +
> +static const struct of_device_id zx_crtc_of_match[] = {
> +       { .compatible = "zte,zx296718-dpc", },
> +       { /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_crtc_of_match);
> +
> +struct platform_driver zx_crtc_driver = {
> +       .probe = zx_crtc_probe,
> +       .remove = zx_crtc_remove,
> +       .driver = {
> +               .name = "zx-crtc",
> +               .of_match_table = zx_crtc_of_match,
> +       },
> +};
> diff --git a/drivers/gpu/drm/zte/zx_crtc.h b/drivers/gpu/drm/zte/zx_crtc.h
> new file mode 100644
> index 000000000000..f889208054ce
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_crtc.h
> @@ -0,0 +1,47 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_CRTC_H__
> +#define __ZX_CRTC_H__
> +
> +#define VOU_CRTC_MASK          0x3
> +
> +/* VOU output interfaces */
> +enum vou_inf_id {
> +       VOU_HDMI        = 0,
> +       VOU_RGB_LCD     = 1,
> +       VOU_TV_ENC      = 2,
> +       VOU_MIPI_DSI    = 3,
> +       VOU_LVDS        = 4,
> +       VOU_VGA         = 5,
> +};
> +
> +enum vou_inf_data_sel {
> +       VOU_YUV444      = 0,
> +       VOU_RGB_101010  = 1,
> +       VOU_RGB_888     = 2,
> +       VOU_RGB_666     = 3,
> +};
> +
> +struct vou_inf {
> +       struct drm_encoder *encoder;
> +       enum vou_inf_id id;
> +       enum vou_inf_data_sel data_sel;
> +       u32 clocks_en_bits;
> +       u32 clocks_sel_bits;
> +};
> +
> +void vou_inf_enable(struct vou_inf *inf);
> +void vou_inf_disable(struct vou_inf *inf);
> +
> +int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe);
> +void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe);
> +
> +#endif /* __ZX_CRTC_H__ */
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
> new file mode 100644
> index 000000000000..51fafb8e5f43
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.c
> @@ -0,0 +1,258 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/spinlock.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/list.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>

nit: Alphabetical?


> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +
> +#include "zx_drm_drv.h"
> +#include "zx_crtc.h"
> +
> +static void zx_drm_fb_output_poll_changed(struct drm_device *drm)
> +{
> +       struct zx_drm_private *priv = drm->dev_private;
> +
> +       drm_fbdev_cma_hotplug_event(priv->fbdev);
> +}
> +
> +static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = {
> +       .fb_create = drm_fb_cma_create,
> +       .output_poll_changed = zx_drm_fb_output_poll_changed,
> +       .atomic_check = drm_atomic_helper_check,
> +       .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static void zx_drm_lastclose(struct drm_device *drm)
> +{
> +       struct zx_drm_private *priv = drm->dev_private;
> +
> +       drm_fbdev_cma_restore_mode(priv->fbdev);
> +}
> +
> +static const struct file_operations zx_drm_fops = {
> +       .owner = THIS_MODULE,
> +       .open = drm_open,
> +       .release = drm_release,
> +       .unlocked_ioctl = drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +       .compat_ioctl = drm_compat_ioctl,
> +#endif
> +       .poll = drm_poll,
> +       .read = drm_read,
> +       .llseek = noop_llseek,
> +       .mmap = drm_gem_cma_mmap,
> +};
> +
> +static struct drm_driver zx_drm_driver = {
> +       .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
> +                          DRIVER_ATOMIC,
> +       .lastclose = zx_drm_lastclose,
> +       .get_vblank_counter = drm_vblank_no_hw_counter,
> +       .enable_vblank = zx_crtc_enable_vblank,
> +       .disable_vblank = zx_crtc_disable_vblank,
> +       .gem_free_object = drm_gem_cma_free_object,
> +       .gem_vm_ops = &drm_gem_cma_vm_ops,
> +       .dumb_create = drm_gem_cma_dumb_create,
> +       .dumb_map_offset = drm_gem_cma_dumb_map_offset,
> +       .dumb_destroy = drm_gem_dumb_destroy,
> +       .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> +       .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> +       .gem_prime_export = drm_gem_prime_export,
> +       .gem_prime_import = drm_gem_prime_import,
> +       .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,
> +       .fops = &zx_drm_fops,
> +       .name = "zx-vou",
> +       .desc = "ZTE VOU Controller DRM",
> +       .date = "20160811",
> +       .major = 1,
> +       .minor = 0,
> +};
> +
> +static int zx_drm_bind(struct device *dev)
> +{
> +       struct drm_device *drm;
> +       struct zx_drm_private *priv;
> +       int ret;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       drm = drm_dev_alloc(&zx_drm_driver, dev);
> +       if (!drm)
> +               return -ENOMEM;
> +
> +       drm->dev_private = priv;
> +       dev_set_drvdata(dev, drm);
> +
> +       drm_mode_config_init(drm);
> +       drm->mode_config.min_width = 16;
> +       drm->mode_config.min_height = 16;
> +       drm->mode_config.max_width = 4096;
> +       drm->mode_config.max_height = 4096;
> +       drm->mode_config.funcs = &zx_drm_mode_config_funcs;
> +
> +       ret = drm_dev_register(drm, 0);
> +       if (ret)
> +               goto out_free;
> +
> +       ret = component_bind_all(dev, drm);
> +       if (ret) {
> +               DRM_ERROR("Failed to bind all components\n");
> +               goto out_unregister;
> +       }
> +
> +       ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
> +       if (ret < 0) {
> +               DRM_ERROR("failed to initialise vblank\n");
> +               goto out_unbind;
> +       }
> +
> +       /*
> +        * We will manage irq handler on our own.  In this case, irq_enabled
> +        * need to be true for using vblank core support.
> +        */
> +       drm->irq_enabled = true;
> +
> +       drm_mode_config_reset(drm);
> +       drm_kms_helper_poll_init(drm);
> +
> +       priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
> +                                        drm->mode_config.num_connector);
> +       if (IS_ERR(priv->fbdev)) {
> +               ret = PTR_ERR(priv->fbdev);
> +               priv->fbdev = NULL;
> +               goto out_fini;
> +       }
> +
> +       return 0;
> +
> +out_fini:
> +       drm_kms_helper_poll_fini(drm);
> +       drm_mode_config_cleanup(drm);
> +       drm_vblank_cleanup(drm);
> +out_unbind:
> +       component_unbind_all(dev, drm);
> +out_unregister:
> +       drm_dev_unregister(drm);
> +out_free:
> +       dev_set_drvdata(dev, NULL);
> +       drm_dev_unref(drm);
> +       return ret;
> +}
> +
> +static void zx_drm_unbind(struct device *dev)
> +{
> +       struct drm_device *drm = dev_get_drvdata(dev);
> +       struct zx_drm_private *priv = drm->dev_private;
> +
> +       if (priv->fbdev) {
> +               drm_fbdev_cma_fini(priv->fbdev);
> +               priv->fbdev = NULL;
> +       }
> +       drm_kms_helper_poll_fini(drm);
> +       component_unbind_all(dev, drm);
> +       drm_vblank_cleanup(drm);
> +       drm_mode_config_cleanup(drm);
> +       drm_dev_unregister(drm);
> +       drm_dev_unref(drm);
> +       drm->dev_private = NULL;
> +       dev_set_drvdata(dev, NULL);
> +}
> +
> +static const struct component_master_ops zx_drm_master_ops = {
> +       .bind = zx_drm_bind,
> +       .unbind = zx_drm_unbind,
> +};
> +
> +static int compare_of(struct device *dev, void *data)
> +{
> +       return dev->of_node == data;
> +}
> +
> +static int zx_drm_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *parent = dev->of_node;
> +       struct device_node *child;
> +       struct component_match *match = NULL;
> +       int ret;
> +
> +       ret = of_platform_populate(parent, NULL, NULL, dev);
> +       if (ret)
> +               return ret;
> +
> +       for_each_available_child_of_node(parent, child) {
> +               component_match_add(dev, &match, compare_of, child);
> +               of_node_put(child);
> +       }
> +
> +       return component_master_add_with_match(dev, &zx_drm_master_ops, match);
> +}
> +
> +static int zx_drm_remove(struct platform_device *pdev)
> +{
> +       component_master_del(&pdev->dev, &zx_drm_master_ops);
> +       return 0;
> +}
> +
> +static const struct of_device_id zx_drm_of_match[] = {
> +       { .compatible = "zte,zx296718-vou", },
> +       { /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_drm_of_match);
> +
> +static struct platform_driver zx_drm_platform_driver = {
> +       .probe = zx_drm_probe,
> +       .remove = zx_drm_remove,
> +       .driver = {
> +               .name = "zx-drm",
> +               .of_match_table = zx_drm_of_match,
> +       },
> +};
> +
> +static struct platform_driver *drivers[] = {
> +       &zx_crtc_driver,
> +       &zx_hdmi_driver,
> +       &zx_drm_platform_driver,
> +};
> +
> +static int zx_drm_init(void)
> +{
> +       return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_init(zx_drm_init);
> +
> +static void zx_drm_exit(void)
> +{
> +       platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_exit(zx_drm_exit);
> +
> +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
> +MODULE_DESCRIPTION("ZTE ZX VOU DRM driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
> new file mode 100644
> index 000000000000..14c749949151
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.h
> @@ -0,0 +1,22 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_DRM_DRV_H__
> +#define __ZX_DRM_DRV_H__
> +
> +struct zx_drm_private {
> +       struct drm_fbdev_cma *fbdev;
> +       struct zx_vou_hw *vou;
> +};
> +
> +extern struct platform_driver zx_crtc_driver;
> +extern struct platform_driver zx_hdmi_driver;
> +
> +#endif /* __ZX_DRM_DRV_H__ */
> diff --git a/drivers/gpu/drm/zte/zx_hdmi.c b/drivers/gpu/drm/zte/zx_hdmi.c
> new file mode 100644
> index 000000000000..5aaab8493b1b
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_hdmi.c
> @@ -0,0 +1,540 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drm_of.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <linux/irq.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/hdmi.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_device.h>
> +#include <linux/component.h>
> +
> +#include "zx_crtc.h"
> +
> +#define FUNC_SEL                       0x000b
> +#define FUNC_HDMI_EN                   BIT(0)
> +#define CLKPWD                         0x000d
> +#define CLKPWD_PDIDCK                  BIT(2)
> +#define PWD_SRST                       0x0010
> +#define P2T_CTRL                       0x0066
> +#define P2T_DC_PKT_EN                  BIT(7)
> +#define L1_INTR_STAT                   0x007e
> +#define L1_INTR_STAT_INTR1             BIT(0)
> +#define INTR1_STAT                     0x008f
> +#define INTR1_MASK                     0x0095
> +#define INTR1_MONITOR_DETECT           (BIT(5) | BIT(6))
> +#define ZX_DDC_ADDR                    0x00ed
> +#define ZX_DDC_SEGM                    0x00ee
> +#define ZX_DDC_OFFSET                  0x00ef
> +#define ZX_DDC_DIN_CNT1                        0x00f0
> +#define ZX_DDC_DIN_CNT2                        0x00f1
> +#define ZX_DDC_CMD                     0x00f3
> +#define DDC_CMD_MASK                   0xf
> +#define DDC_CMD_CLEAR_FIFO             0x9
> +#define DDC_CMD_SEQUENTIAL_READ                0x2
> +#define ZX_DDC_DATA                    0x00f4
> +#define ZX_DDC_DOUT_CNT                        0x00f5
> +#define DDC_DOUT_CNT_MASK              0x1f
> +#define TEST_TXCTRL                    0x00f7
> +#define TEST_TXCTRL_HDMI_MODE          BIT(1)
> +#define HDMICTL4                       0x0235
> +#define TPI_HPD_RSEN                   0x063b
> +#define TPI_HPD_CONNECTION             (BIT(1) | BIT(2))
> +#define TPI_INFO_FSEL                  0x06bf
> +#define FSEL_AVI                       0
> +#define FSEL_GBD                       1
> +#define FSEL_AUDIO                     2
> +#define FSEL_SPD                       3
> +#define FSEL_MPEG                      4
> +#define FSEL_VSIF                      5
> +#define TPI_INFO_B0                    0x06c0
> +#define TPI_INFO_EN                    0x06df
> +#define TPI_INFO_TRANS_EN              BIT(7)
> +#define TPI_INFO_TRANS_RPT             BIT(6)
> +#define TPI_DDC_MASTER_EN              0x06f8
> +#define HW_DDC_MASTER                  BIT(7)
> +
> +#define ZX_HDMI_INFOFRAME_SIZE         31
> +
> +struct zx_hdmi {
> +       struct drm_connector connector;
> +       struct drm_encoder encoder;
> +       struct device *dev;
> +       struct drm_device *drm;
> +       void __iomem *mmio;
> +       struct clk *cec_clk;
> +       struct clk *osc_clk;
> +       struct clk *xclk;
> +       bool sink_is_hdmi;
> +       bool sink_has_audio;
> +       struct vou_inf *inf;
> +};
> +
> +#define to_zx_hdmi(x)  container_of(x, struct zx_hdmi, x)
> +
> +static struct vou_inf vou_inf_hdmi = {
> +       .id = VOU_HDMI,
> +       .data_sel = VOU_YUV444,
> +       .clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
> +       .clocks_sel_bits = BIT(13) | BIT(2),
> +};

This should be static const, but I suppose you can't b/c you're
storing a pointer to encoder in vou_inf. This type of information
lends itself well to being defined and mapped as of_device_id.data,
you'll need to pass the encoder around in a different manner.


> +
> +static inline u8 hdmi_readb(struct zx_hdmi *hdmi, u16 offset)
> +{
> +       return readl_relaxed(hdmi->mmio + offset * 4);
> +}
> +
> +static inline void hdmi_writeb(struct zx_hdmi *hdmi, u16 offset, u8 val)
> +{
> +       writel_relaxed(val, hdmi->mmio + offset * 4);
> +}
> +
> +static int zx_hdmi_infoframe_trans(struct zx_hdmi *hdmi,
> +                                  union hdmi_infoframe *frame, u8 fsel)
> +{
> +       u8 buffer[ZX_HDMI_INFOFRAME_SIZE];
> +       u8 val;
> +       ssize_t num;
> +       int i;
> +
> +       hdmi_writeb(hdmi, TPI_INFO_FSEL, fsel);
> +
> +       num = hdmi_infoframe_pack(frame, buffer, ZX_HDMI_INFOFRAME_SIZE);
> +       if (num < 0)
> +               return num;
> +
> +       for (i = 0; i < num; i++)
> +               hdmi_writeb(hdmi, TPI_INFO_B0 + i, buffer[i]);
> +
> +       val = hdmi_readb(hdmi, TPI_INFO_EN);
> +       val |= TPI_INFO_TRANS_EN | TPI_INFO_TRANS_RPT;
> +       hdmi_writeb(hdmi, TPI_INFO_EN, val);
> +
> +       return num;
> +}
> +
> +static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi,
> +                                   struct drm_display_mode *mode)
> +{
> +       union hdmi_infoframe frame;
> +       int ret;
> +
> +       ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
> +                                                         mode);
> +       if (ret)

Should you log in cases of failure (here and elsewhere)?

> +               return ret;
> +
> +       return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF);
> +}
> +
> +static int zx_hdmi_config_video_avi(struct zx_hdmi *hdmi,
> +                                   struct drm_display_mode *mode)
> +{
> +       union hdmi_infoframe frame;
> +       int ret;
> +
> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
> +       if (ret)
> +               return ret;
> +
> +       /* We always use YUV444 for HDMI output. */
> +       frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
> +
> +       return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AVI);
> +}
> +
> +static void zx_hdmi_encoder_mode_set(struct drm_encoder *encoder,
> +                                    struct drm_display_mode *mode,
> +                                    struct drm_display_mode *adj_mode)
> +{
> +       struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +       if (hdmi->sink_is_hdmi) {
> +               zx_hdmi_config_video_avi(hdmi, mode);
> +               zx_hdmi_config_video_vsi(hdmi, mode);
> +       }
> +}
> +
> +static void zx_hdmi_encoder_enable(struct drm_encoder *encoder)
> +{
> +       struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +       vou_inf_enable(hdmi->inf);

I think you can remove the encoder from inf by passing encoder->crtc
here as well. That will tell vou which encoder/crtc pair to enable
without having that pesky static global floating around.


> +}
> +
> +static void zx_hdmi_encoder_disable(struct drm_encoder *encoder)
> +{
> +       struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> +
> +       vou_inf_disable(hdmi->inf);

Same here

> +}
> +
> +static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = {
> +       .enable = zx_hdmi_encoder_enable,
> +       .disable = zx_hdmi_encoder_disable,
> +       .mode_set = zx_hdmi_encoder_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = {
> +       .destroy = drm_encoder_cleanup,
> +};
> +
> +static int zx_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
> +                                 size_t len)

Instead of open-coding this, consider implementing an i2c adapter for
the DDC bus and use drm_get_edid below.

> +{
> +       struct zx_hdmi *hdmi = data;
> +       int retry = 0;
> +       int ret = 0;
> +       int i = 0;
> +       u8 val;
> +
> +       /* Enable DDC master access */
> +       val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
> +       val |= HW_DDC_MASTER;
> +       hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
> +
> +       hdmi_writeb(hdmi, ZX_DDC_ADDR, 0xa0);
> +       hdmi_writeb(hdmi, ZX_DDC_OFFSET, block * EDID_LENGTH);
> +       /* Bits [9:8] of bytes */
> +       hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff);
> +       /* Bits [7:0] of bytes */
> +       hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff);
> +
> +       /* Clear FIFO */
> +       val = hdmi_readb(hdmi, ZX_DDC_CMD);
> +       val &= ~DDC_CMD_MASK;
> +       val |= DDC_CMD_CLEAR_FIFO;
> +       hdmi_writeb(hdmi, ZX_DDC_CMD, val);
> +
> +       /* Kick off the read */
> +       val = hdmi_readb(hdmi, ZX_DDC_CMD);
> +       val &= ~DDC_CMD_MASK;
> +       val |= DDC_CMD_SEQUENTIAL_READ;
> +       hdmi_writeb(hdmi, ZX_DDC_CMD, val);
> +
> +       while (len > 0) {
> +               int cnt, j;
> +
> +               /* FIFO needs some time to get ready */
> +               usleep_range(500, 1000);
> +
> +               cnt = hdmi_readb(hdmi, ZX_DDC_DOUT_CNT) & DDC_DOUT_CNT_MASK;
> +               if (cnt == 0) {
> +                       if (++retry > 5) {
> +                               dev_err(hdmi->dev, "DDC read timed out!");
> +                               ret = -ETIMEDOUT;
> +                               break;
> +                       }
> +                       continue;
> +               }
> +
> +               for (j = 0; j < cnt; j++)
> +                       buf[i++] = hdmi_readb(hdmi, ZX_DDC_DATA);
> +               len -= cnt;
> +       }
> +
> +       /* Disable DDC master access */
> +       val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
> +       val &= ~HW_DDC_MASTER;
> +       hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
> +
> +       return ret;
> +}
> +
> +static int zx_hdmi_connector_get_modes(struct drm_connector *connector)
> +{
> +       struct zx_hdmi *hdmi = to_zx_hdmi(connector);
> +       struct edid *edid;
> +       int ret = 0;
> +
> +       edid = drm_do_get_edid(connector, zx_hdmi_get_edid_block, hdmi);
> +       if (edid) {
> +               hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
> +               hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
> +               drm_mode_connector_update_edid_property(connector, edid);
> +               ret = drm_add_edid_modes(connector, edid);
> +               kfree(edid);
> +       }
> +
> +       return ret;
> +}
> +
> +static enum drm_mode_status
> +zx_hdmi_connector_mode_valid(struct drm_connector *connector,
> +                            struct drm_display_mode *mode)
> +{
> +       return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs zx_hdmi_connector_helper_funcs = {
> +       .get_modes = zx_hdmi_connector_get_modes,
> +       .mode_valid = zx_hdmi_connector_mode_valid,
> +};
> +
> +static enum drm_connector_status
> +zx_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct zx_hdmi *hdmi = to_zx_hdmi(connector);
> +
> +       return (hdmi_readb(hdmi, TPI_HPD_RSEN) & TPI_HPD_CONNECTION) ?
> +               connector_status_connected : connector_status_disconnected;
> +}
> +
> +static void zx_hdmi_connector_destroy(struct drm_connector *connector)
> +{
> +       drm_connector_unregister(connector);
> +       drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs zx_hdmi_connector_funcs = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .detect = zx_hdmi_connector_detect,
> +       .destroy = zx_hdmi_connector_destroy,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int zx_hdmi_register(struct drm_device *drm, struct zx_hdmi *hdmi)
> +{
> +       struct drm_encoder *encoder = &hdmi->encoder;
> +
> +       encoder->possible_crtcs = VOU_CRTC_MASK;
> +
> +       drm_encoder_init(drm, encoder, &zx_hdmi_encoder_funcs,
> +                        DRM_MODE_ENCODER_TMDS, NULL);
> +       drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs);
> +
> +       hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
> +
> +       drm_connector_init(drm, &hdmi->connector, &zx_hdmi_connector_funcs,
> +                          DRM_MODE_CONNECTOR_HDMIA);
> +       drm_connector_helper_add(&hdmi->connector,
> +                                &zx_hdmi_connector_helper_funcs);
> +
> +       drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
> +       drm_connector_register(&hdmi->connector);
> +
> +       return 0;
> +}
> +
> +static irqreturn_t zx_hdmi_irq_thread(int irq, void *dev_id)
> +{
> +       struct zx_hdmi *hdmi = dev_id;
> +
> +       drm_helper_hpd_irq_event(hdmi->connector.dev);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
> +{
> +       struct zx_hdmi *hdmi = dev_id;
> +       u8 lstat;
> +
> +       lstat = hdmi_readb(hdmi, L1_INTR_STAT);
> +
> +       /* Monitor detect/HPD interrupt */
> +       if (lstat & L1_INTR_STAT_INTR1) {
> +               u8 stat = hdmi_readb(hdmi, INTR1_STAT);
> +
> +               hdmi_writeb(hdmi, INTR1_STAT, stat);
> +               if (stat & INTR1_MONITOR_DETECT)
> +                       return IRQ_WAKE_THREAD;
> +       }
> +
> +       return IRQ_NONE;
> +}
> +
> +static void zx_hdmi_phy_start(struct zx_hdmi *hdmi)
> +{
> +       /* Copy from ZTE BSP code */
> +       hdmi_writeb(hdmi, 0x222, 0x0);
> +       hdmi_writeb(hdmi, 0x224, 0x4);
> +       hdmi_writeb(hdmi, 0x909, 0x0);
> +       hdmi_writeb(hdmi, 0x7b0, 0x90);
> +       hdmi_writeb(hdmi, 0x7b1, 0x00);
> +       hdmi_writeb(hdmi, 0x7b2, 0xa7);
> +       hdmi_writeb(hdmi, 0x7b8, 0xaa);
> +       hdmi_writeb(hdmi, 0x7b2, 0xa7);
> +       hdmi_writeb(hdmi, 0x7b3, 0x0f);
> +       hdmi_writeb(hdmi, 0x7b4, 0x0f);
> +       hdmi_writeb(hdmi, 0x7b5, 0x55);
> +       hdmi_writeb(hdmi, 0x7b7, 0x03);
> +       hdmi_writeb(hdmi, 0x7b9, 0x12);
> +       hdmi_writeb(hdmi, 0x7ba, 0x32);
> +       hdmi_writeb(hdmi, 0x7bc, 0x68);
> +       hdmi_writeb(hdmi, 0x7be, 0x40);
> +       hdmi_writeb(hdmi, 0x7bf, 0x84);
> +       hdmi_writeb(hdmi, 0x7c1, 0x0f);
> +       hdmi_writeb(hdmi, 0x7c8, 0x02);
> +       hdmi_writeb(hdmi, 0x7c9, 0x03);
> +       hdmi_writeb(hdmi, 0x7ca, 0x40);
> +       hdmi_writeb(hdmi, 0x7dc, 0x31);
> +       hdmi_writeb(hdmi, 0x7e2, 0x04);
> +       hdmi_writeb(hdmi, 0x7e0, 0x06);
> +       hdmi_writeb(hdmi, 0x7cb, 0x68);
> +       hdmi_writeb(hdmi, 0x7f9, 0x02);
> +       hdmi_writeb(hdmi, 0x7b6, 0x02);
> +       hdmi_writeb(hdmi, 0x7f3, 0x0);
> +}
> +
> +static void zx_hdmi_hw_init(struct zx_hdmi *hdmi)
> +{
> +       u8 val;
> +
> +       /* Software reset */
> +       hdmi_writeb(hdmi, PWD_SRST, 1);
> +
> +       /* Enable pclk */
> +       val = hdmi_readb(hdmi, CLKPWD);
> +       val |= CLKPWD_PDIDCK;
> +       hdmi_writeb(hdmi, CLKPWD, val);
> +
> +       /* Enable HDMI for TX */
> +       val = hdmi_readb(hdmi, FUNC_SEL);
> +       val |= FUNC_HDMI_EN;
> +       hdmi_writeb(hdmi, FUNC_SEL, val);
> +
> +       /* Enable deep color packet */
> +       val = hdmi_readb(hdmi, P2T_CTRL);
> +       val |= P2T_DC_PKT_EN;
> +       hdmi_writeb(hdmi, P2T_CTRL, val);
> +
> +       /* Enable HDMI/MHL mode for output */
> +       val = hdmi_readb(hdmi, TEST_TXCTRL);
> +       val |= TEST_TXCTRL_HDMI_MODE;
> +       hdmi_writeb(hdmi, TEST_TXCTRL, val);
> +
> +       /* Configure reg_qc_sel */
> +       hdmi_writeb(hdmi, HDMICTL4, 0x3);
> +
> +       /* Enable interrupt */
> +       val = hdmi_readb(hdmi, INTR1_MASK);
> +       val |= INTR1_MONITOR_DETECT;
> +       hdmi_writeb(hdmi, INTR1_MASK, val);
> +
> +       /* Clear reset for normal operation */
> +       hdmi_writeb(hdmi, PWD_SRST, 0);
> +
> +       /* Start up phy */
> +       zx_hdmi_phy_start(hdmi);
> +}
> +
> +static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct drm_device *drm = data;
> +       struct resource *res;
> +       struct zx_hdmi *hdmi;
> +       struct vou_inf *inf;
> +       int irq;
> +       int ret;
> +
> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +       if (!hdmi)
> +               return -ENOMEM;
> +
> +       hdmi->dev = dev;
> +       hdmi->drm = drm;
> +
> +       inf = &vou_inf_hdmi;
> +       inf->encoder = &hdmi->encoder;
> +       hdmi->inf = inf;
> +
> +       dev_set_drvdata(dev, hdmi);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi->mmio = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(hdmi->mmio))
> +               return PTR_ERR(hdmi->mmio);
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0)
> +               return irq;
> +
> +       hdmi->cec_clk = devm_clk_get(hdmi->dev, "osc_cec");
> +       if (IS_ERR(hdmi->cec_clk))
> +               return PTR_ERR(hdmi->cec_clk);
> +
> +       hdmi->osc_clk = devm_clk_get(hdmi->dev, "osc_clk");
> +       if (IS_ERR(hdmi->osc_clk))
> +               return PTR_ERR(hdmi->osc_clk);
> +
> +       hdmi->xclk = devm_clk_get(hdmi->dev, "xclk");
> +       if (IS_ERR(hdmi->xclk))
> +               return PTR_ERR(hdmi->xclk);
> +
> +       zx_hdmi_hw_init(hdmi);
> +
> +       clk_prepare_enable(hdmi->cec_clk);
> +       clk_prepare_enable(hdmi->osc_clk);
> +       clk_prepare_enable(hdmi->xclk);
> +
> +       ret = zx_hdmi_register(drm, hdmi);
> +       if (ret)
> +               return ret;
> +
> +       ret = devm_request_threaded_irq(dev, irq, zx_hdmi_irq_handler,
> +                                       zx_hdmi_irq_thread, IRQF_SHARED,
> +                                       dev_name(dev), hdmi);
> +
> +       return 0;
> +}
> +
> +static void zx_hdmi_unbind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       clk_disable_unprepare(hdmi->cec_clk);
> +       clk_disable_unprepare(hdmi->osc_clk);
> +       clk_disable_unprepare(hdmi->xclk);
> +}
> +
> +static const struct component_ops zx_hdmi_component_ops = {
> +       .bind = zx_hdmi_bind,
> +       .unbind = zx_hdmi_unbind,
> +};
> +
> +static int zx_hdmi_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &zx_hdmi_component_ops);
> +}
> +
> +static int zx_hdmi_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &zx_hdmi_component_ops);
> +       return 0;
> +}
> +
> +static const struct of_device_id zx_hdmi_of_match[] = {
> +       { .compatible = "zte,zx296718-hdmi", },
> +       { /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_hdmi_of_match);
> +
> +struct platform_driver zx_hdmi_driver = {
> +       .probe = zx_hdmi_probe,
> +       .remove = zx_hdmi_remove,
> +       .driver = {
> +               .name = "zx-hdmi",
> +               .of_match_table = zx_hdmi_of_match,
> +       },
> +};
> diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c
> new file mode 100644
> index 000000000000..326cc1ff7950
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_plane.c
> @@ -0,0 +1,362 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include "zx_crtc.h"
> +#include "zx_plane.h"
> +
> +/* GL registers */
> +#define GL_CTRL0                       0x00
> +#define GL_UPDATE                      BIT(5)
> +#define GL_CTRL1                       0x04
> +#define GL_DATA_FMT_SHIFT              0
> +#define GL_DATA_FMT_MASK               (0xf << GL_DATA_FMT_SHIFT)
> +#define GL_FMT_ARGB8888                        0
> +#define GL_FMT_RGB888                  1
> +#define GL_FMT_RGB565                  2
> +#define GL_FMT_ARGB1555                        3
> +#define GL_FMT_ARGB4444                        4
> +#define GL_CTRL2                       0x08
> +#define GL_GLOBAL_ALPHA_SHIFT          8
> +#define GL_GLOBAL_ALPHA_MASK           (0xff << GL_GLOBAL_ALPHA_SHIFT)
> +#define GL_CTRL3                       0x0c
> +#define GL_SCALER_BYPASS_MODE          BIT(0)
> +#define GL_STRIDE                      0x18
> +#define GL_ADDR                                0x1c
> +#define GL_SRC_SIZE                    0x38
> +#define GL_SRC_W_SHIFT                 16
> +#define GL_SRC_W_MASK                  (0x3fff << GL_SRC_W_SHIFT)
> +#define GL_SRC_H_SHIFT                 0
> +#define GL_SRC_H_MASK                  (0x3fff << GL_SRC_H_SHIFT)
> +#define GL_POS_START                   0x9c
> +#define GL_POS_END                     0xa0
> +#define GL_POS_X_SHIFT                 16
> +#define GL_POS_X_MASK                  (0x1fff << GL_POS_X_SHIFT)
> +#define GL_POS_Y_SHIFT                 0
> +#define GL_POS_Y_MASK                  (0x1fff << GL_POS_Y_SHIFT)
> +
> +/* CSC registers */
> +#define CSC_CTRL0                      0x30
> +#define CSC_COV_MODE_SHIFT             16
> +#define CSC_COV_MODE_MASK              (0xffff << CSC_COV_MODE_SHIFT)
> +#define CSC_BT601_IMAGE_RGB2YCBCR      0
> +#define CSC_BT601_IMAGE_YCBCR2RGB      1
> +#define CSC_BT601_VIDEO_RGB2YCBCR      2
> +#define CSC_BT601_VIDEO_YCBCR2RGB      3
> +#define CSC_BT709_IMAGE_RGB2YCBCR      4
> +#define CSC_BT709_IMAGE_YCBCR2RGB      5
> +#define CSC_BT709_VIDEO_RGB2YCBCR      6
> +#define CSC_BT709_VIDEO_YCBCR2RGB      7
> +#define CSC_BT2020_IMAGE_RGB2YCBCR     8
> +#define CSC_BT2020_IMAGE_YCBCR2RGB     9
> +#define CSC_BT2020_VIDEO_RGB2YCBCR     10
> +#define CSC_BT2020_VIDEO_YCBCR2RGB     11
> +#define CSC_WORK_ENABLE                        BIT(0)
> +
> +/* RSZ registers */
> +#define RSZ_SRC_CFG                    0x00
> +#define RSZ_DEST_CFG                   0x04
> +#define RSZ_ENABLE_CFG                 0x14
> +
> +/* HBSC registers */
> +#define HBSC_SATURATION                        0x00
> +#define HBSC_HUE                       0x04
> +#define HBSC_BRIGHT                    0x08
> +#define HBSC_CONTRAST                  0x0c
> +#define HBSC_THRESHOLD_COL1            0x10
> +#define HBSC_THRESHOLD_COL2            0x14
> +#define HBSC_THRESHOLD_COL3            0x18
> +#define HBSC_CTRL0                     0x28
> +#define HBSC_CTRL_EN                   BIT(2)
> +
> +struct zx_plane {
> +       struct drm_plane plane;
> +       void __iomem *layer;
> +       void __iomem *csc;
> +       void __iomem *hbsc;
> +       void __iomem *rsz;
> +};
> +
> +#define to_zx_plane(plane)     container_of(plane, struct zx_plane, plane)
> +
> +static const uint32_t gl_formats[] = {
> +       DRM_FORMAT_ARGB8888,
> +       DRM_FORMAT_XRGB8888,
> +       DRM_FORMAT_RGB888,
> +       DRM_FORMAT_RGB565,
> +       DRM_FORMAT_ARGB1555,
> +       DRM_FORMAT_ARGB4444,
> +};
> +
> +static int zx_gl_plane_atomic_check(struct drm_plane *plane,
> +                                   struct drm_plane_state *state)
> +{
> +       u32 src_w, src_h;
> +
> +       src_w = state->src_w >> 16;
> +       src_h = state->src_h >> 16;
> +
> +       /* TODO: support scaling of the plane source */
> +       if ((src_w != state->crtc_w) || (src_h != state->crtc_h))
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static int zx_gl_get_fmt(uint32_t format)
> +{
> +       switch (format) {
> +       case DRM_FORMAT_ARGB8888:
> +       case DRM_FORMAT_XRGB8888:
> +               return GL_FMT_ARGB8888;
> +       case DRM_FORMAT_RGB888:
> +               return GL_FMT_RGB888;
> +       case DRM_FORMAT_RGB565:
> +               return GL_FMT_RGB565;
> +       case DRM_FORMAT_ARGB1555:
> +               return GL_FMT_ARGB1555;
> +       case DRM_FORMAT_ARGB4444:
> +               return GL_FMT_ARGB4444;
> +       default:
> +               WARN_ONCE(1, "invalid pixel format %d\n", format);
> +               return -EINVAL;
> +       }
> +}
> +
> +static inline void zx_gl_set_update(struct zx_plane *zplane)
> +{
> +       void __iomem *layer = zplane->layer;
> +       u32 val;
> +
> +       val = readl(layer + GL_CTRL0);
> +       val |= GL_UPDATE;
> +       writel(val, layer + GL_CTRL0);
> +}
> +
> +static inline void zx_gl_rsz_set_update(struct zx_plane *zplane)
> +{
> +       writel(1, zplane->rsz + RSZ_ENABLE_CFG);
> +}
> +
> +void zx_plane_set_update(struct drm_plane *plane)
> +{
> +       struct zx_plane *zplane = to_zx_plane(plane);
> +
> +       zx_gl_rsz_set_update(zplane);
> +       zx_gl_set_update(zplane);
> +}
> +
> +static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h,
> +                           u32 dst_w, u32 dst_h)
> +{
> +       void __iomem *rsz = zplane->rsz;
> +       u32 val;
> +
> +       val = ((src_h - 1) & 0xffff) << 16;
> +       val |= (src_w - 1) & 0xffff;
> +       writel(val, rsz + RSZ_SRC_CFG);
> +
> +       val = ((dst_h - 1) & 0xffff) << 16;
> +       val |= (dst_w - 1) & 0xffff;
> +       writel(val, rsz + RSZ_DEST_CFG);
> +
> +       zx_gl_rsz_set_update(zplane);
> +}
> +
> +static void zx_gl_plane_atomic_update(struct drm_plane *plane,
> +                                     struct drm_plane_state *old_state)
> +{
> +       struct zx_plane *zplane = to_zx_plane(plane);
> +       struct drm_framebuffer *fb = plane->state->fb;
> +       struct drm_gem_cma_object *cma_obj;
> +       void __iomem *layer = zplane->layer;
> +       void __iomem *csc = zplane->csc;
> +       void __iomem *hbsc = zplane->hbsc;
> +       u32 src_x, src_y, src_w, src_h;
> +       u32 dst_x, dst_y, dst_w, dst_h;
> +       unsigned int depth, bpp;
> +       uint32_t format;
> +       dma_addr_t paddr;
> +       u32 stride;
> +       int fmt;
> +       u32 val;
> +
> +       if (!fb)
> +               return;
> +
> +       format = fb->pixel_format;
> +       stride = fb->pitches[0];
> +
> +       src_x = plane->state->src_x >> 16;
> +       src_y = plane->state->src_y >> 16;
> +       src_w = plane->state->src_w >> 16;
> +       src_h = plane->state->src_h >> 16;
> +
> +       dst_x = plane->state->crtc_x;
> +       dst_y = plane->state->crtc_y;
> +       dst_w = plane->state->crtc_w;
> +       dst_h = plane->state->crtc_h;
> +
> +       drm_fb_get_bpp_depth(format, &depth, &bpp);
> +
> +       cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +       paddr = cma_obj->paddr + fb->offsets[0];
> +       paddr += src_y * stride + src_x * bpp / 8;
> +       writel(paddr, layer + GL_ADDR);
> +
> +       /* Set up source height/width register */
> +       val = (src_w << GL_SRC_W_SHIFT) & GL_SRC_W_MASK;
> +       val |= (src_h << GL_SRC_H_SHIFT) & GL_SRC_H_MASK;
> +       writel(val, layer + GL_SRC_SIZE);
> +
> +       /* Set up start position register */
> +       val = (dst_x << GL_POS_X_SHIFT) & GL_POS_X_MASK;
> +       val |= (dst_y << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
> +       writel(val, layer + GL_POS_START);
> +
> +       /* Set up end position register */
> +       val = ((dst_x + dst_w) << GL_POS_X_SHIFT) & GL_POS_X_MASK;
> +       val |= ((dst_y + dst_h) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
> +       writel(val, layer + GL_POS_END);
> +
> +       /* Set up stride register */
> +       writel(stride & 0xffff, layer + GL_STRIDE);
> +
> +       /* Set up graphic layer data format */
> +       fmt = zx_gl_get_fmt(format);
> +       if (fmt >= 0) {
> +               val = readl(layer + GL_CTRL1);
> +               val &= ~GL_DATA_FMT_MASK;
> +               val |= fmt << GL_DATA_FMT_SHIFT;
> +               writel(val, layer + GL_CTRL1);
> +       }
> +
> +       /* Initialize global alpha with a sane value */
> +       val = readl(layer + GL_CTRL2);
> +       val &= ~GL_GLOBAL_ALPHA_MASK;
> +       val |= 0xff << GL_GLOBAL_ALPHA_SHIFT;
> +       writel(val, layer + GL_CTRL2);
> +
> +       /* Setup CSC for the GL */
> +       val = readl(csc + CSC_CTRL0);
> +       val &= ~CSC_COV_MODE_MASK;
> +       if (dst_h > 720)
> +               val |= CSC_BT709_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
> +       else
> +               val |= CSC_BT601_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
> +       val |= CSC_WORK_ENABLE;
> +       writel(val, csc + CSC_CTRL0);
> +
> +       /* Always use scaler since it exists */
> +       val = readl(layer + GL_CTRL3);
> +       val |= GL_SCALER_BYPASS_MODE;   /* set for not bypass */
> +       writel(val, layer + GL_CTRL3);
> +
> +       zx_gl_rsz_setup(zplane, src_w, src_h, dst_w, dst_h);
> +
> +       /* Enable HBSC block */
> +       val = readl(hbsc + HBSC_CTRL0);
> +       val |= HBSC_CTRL_EN;
> +       writel(val, hbsc + HBSC_CTRL0);
> +
> +       zx_gl_set_update(zplane);
> +}
> +
> +static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = {
> +       .atomic_check = zx_gl_plane_atomic_check,
> +       .atomic_update = zx_gl_plane_atomic_update,
> +};
> +
> +static void zx_plane_destroy(struct drm_plane *plane)
> +{
> +       drm_plane_helper_disable(plane);
> +       drm_plane_cleanup(plane);
> +}
> +
> +static const struct drm_plane_funcs zx_plane_funcs = {
> +       .update_plane = drm_atomic_helper_update_plane,
> +       .disable_plane = drm_atomic_helper_disable_plane,
> +       .destroy = zx_plane_destroy,
> +       .reset = drm_atomic_helper_plane_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static void zx_plane_hbsc_init(struct zx_plane *zplane)
> +{
> +       void __iomem *hbsc = zplane->hbsc;
> +
> +       /*
> +        *  Initialize HBSC block with a sane configuration per recommedation
> +        *  from ZTE BSP code.
> +        */
> +       writel(0x200, hbsc + HBSC_SATURATION);
> +       writel(0x0, hbsc + HBSC_HUE);
> +       writel(0x0, hbsc + HBSC_BRIGHT);
> +       writel(0x200, hbsc + HBSC_CONTRAST);
> +
> +       writel((0x3ac << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL1);
> +       writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL2);
> +       writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL3);
> +}
> +
> +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
> +                               struct zx_layer_data *data,
> +                               enum drm_plane_type type)
> +{
> +       const struct drm_plane_helper_funcs *helper;
> +       struct zx_plane *zplane;
> +       struct drm_plane *plane;
> +       const uint32_t *formats;
> +       unsigned int format_count;
> +       int ret;
> +
> +       zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
> +       if (!zplane)
> +               return ERR_PTR(-ENOMEM);
> +
> +       plane = &zplane->plane;
> +
> +       zplane->layer = data->layer;
> +       zplane->hbsc = data->hbsc;
> +       zplane->csc = data->csc;
> +       zplane->rsz = data->rsz;
> +
> +       zx_plane_hbsc_init(zplane);
> +
> +       switch (type) {
> +       case DRM_PLANE_TYPE_PRIMARY:
> +               helper = &zx_gl_plane_helper_funcs;
> +               formats = gl_formats;
> +               format_count = ARRAY_SIZE(gl_formats);
> +               break;
> +       case DRM_PLANE_TYPE_OVERLAY:
> +               /* TODO: add video layer (vl) support */
> +               break;
> +       default:
> +               return ERR_PTR(-ENODEV);
> +       }
> +
> +       ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK,
> +                                      &zx_plane_funcs, formats, format_count,
> +                                      type, NULL);
> +       if (ret)
> +               return ERR_PTR(ret);
> +
> +       drm_plane_helper_add(plane, helper);
> +
> +       return plane;
> +}
> diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h
> new file mode 100644
> index 000000000000..2b82cd558d9d
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_plane.h
> @@ -0,0 +1,26 @@
> +/*
> + * Copyright 2016 Linaro Ltd.
> + * Copyright 2016 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_PLANE_H__
> +#define __ZX_PLANE_H__
> +
> +struct zx_layer_data {
> +       void __iomem *layer;
> +       void __iomem *csc;
> +       void __iomem *hbsc;
> +       void __iomem *rsz;
> +};
> +
> +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
> +                               struct zx_layer_data *data,
> +                               enum drm_plane_type type);
> +void zx_plane_set_update(struct drm_plane *plane);
> +
> +#endif /* __ZX_PLANE_H__ */
> --
> 1.9.1
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Shawn Guo Sept. 29, 2016, 11:42 p.m. UTC | #3
On Sun, Sep 25, 2016 at 10:58:09PM +0200, Daniel Vetter wrote:
> On Sat, Sep 24, 2016 at 10:26:25PM +0800, Shawn Guo wrote:
> > It adds the initial ZTE VOU display controller DRM driver.  There are
> > still some features to be added, like overlay plane, scaling, and more
> > output devices support.  But it's already useful with dual CRTCs and
> > HDMI monitor working.
> > 
> > It's been tested on Debian Jessie LXDE desktop with modesetting driver.
> > 
> > Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> 
> I've done a very quick read-through, looks real pretty. A few comments
> below and in-line.

Thanks much for looking at it.

> For testing, have you tried to run i-g-t validation tests per our
> documentation? See https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#validating-changes-with-igt

Sorry for my ignorance on that.  I'm on business travel right now, and
will give it a try once I get back to the hardware.

> >  drivers/gpu/drm/Kconfig          |   2 +
> >  drivers/gpu/drm/Makefile         |   1 +
> >  drivers/gpu/drm/zte/Kconfig      |   8 +
> >  drivers/gpu/drm/zte/Makefile     |   8 +
> >  drivers/gpu/drm/zte/zx_crtc.c    | 691 +++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/zte/zx_crtc.h    |  47 +++
> >  drivers/gpu/drm/zte/zx_drm_drv.c | 258 +++++++++++++++
> >  drivers/gpu/drm/zte/zx_drm_drv.h |  22 ++
> >  drivers/gpu/drm/zte/zx_hdmi.c    | 540 ++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/zte/zx_plane.c   | 362 ++++++++++++++++++++
> >  drivers/gpu/drm/zte/zx_plane.h   |  26 ++
> >  11 files changed, 1965 insertions(+)
> >  create mode 100644 drivers/gpu/drm/zte/Kconfig
> >  create mode 100644 drivers/gpu/drm/zte/Makefile
> >  create mode 100644 drivers/gpu/drm/zte/zx_crtc.c
> >  create mode 100644 drivers/gpu/drm/zte/zx_crtc.h
> >  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
> >  create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
> >  create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
> >  create mode 100644 drivers/gpu/drm/zte/zx_plane.c
> >  create mode 100644 drivers/gpu/drm/zte/zx_plane.h
> 
> New entry in MAINTAINERS listening you (and probably dri-devel as the m-l)
> is missing.

Okay.  I will add a patch to do that in the next version.

> > +static int zx_drm_bind(struct device *dev)
> > +{
> > +	struct drm_device *drm;
> > +	struct zx_drm_private *priv;
> > +	int ret;
> > +
> > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	drm = drm_dev_alloc(&zx_drm_driver, dev);
> > +	if (!drm)
> > +		return -ENOMEM;
> > +
> > +	drm->dev_private = priv;
> > +	dev_set_drvdata(dev, drm);
> > +
> > +	drm_mode_config_init(drm);
> > +	drm->mode_config.min_width = 16;
> > +	drm->mode_config.min_height = 16;
> > +	drm->mode_config.max_width = 4096;
> > +	drm->mode_config.max_height = 4096;
> > +	drm->mode_config.funcs = &zx_drm_mode_config_funcs;
> > +
> > +	ret = drm_dev_register(drm, 0);
> 
> drm_dev_register should be the last function call in your bind function.
> Similar for unbind, drm_dev_register should be called first.
> 
> As a consequence of that you can remove the drm_connector_(un)register
> calls, those are only needed for hotplugged connectors like dp mst. But
> with correct ordering of drm_dev_(un)register that function will also take
> care of connector registration and unregistration.

Aha, that's the trick to save the call to drm_connector_register() from
connector driver.  Thanks for the info.

> > +static int zx_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
> > +				  size_t len)
> > +{
> > +	struct zx_hdmi *hdmi = data;
> > +	int retry = 0;
> > +	int ret = 0;
> > +	int i = 0;
> > +	u8 val;
> > +
> > +	/* Enable DDC master access */
> > +	val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
> > +	val |= HW_DDC_MASTER;
> > +	hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
> > +
> > +	hdmi_writeb(hdmi, ZX_DDC_ADDR, 0xa0);
> > +	hdmi_writeb(hdmi, ZX_DDC_OFFSET, block * EDID_LENGTH);
> > +	/* Bits [9:8] of bytes */
> > +	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff);
> > +	/* Bits [7:0] of bytes */
> > +	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff);
> > +
> > +	/* Clear FIFO */
> > +	val = hdmi_readb(hdmi, ZX_DDC_CMD);
> > +	val &= ~DDC_CMD_MASK;
> > +	val |= DDC_CMD_CLEAR_FIFO;
> > +	hdmi_writeb(hdmi, ZX_DDC_CMD, val);
> > +
> > +	/* Kick off the read */
> > +	val = hdmi_readb(hdmi, ZX_DDC_CMD);
> > +	val &= ~DDC_CMD_MASK;
> > +	val |= DDC_CMD_SEQUENTIAL_READ;
> > +	hdmi_writeb(hdmi, ZX_DDC_CMD, val);
> 
> It looks like the ZX_DDC register range implements a hw i2c engine (since
> you specifiy port and offsets and everything). Please implement it as an
> i2c_adapter driver and use the normal drm_get_edid function.

Okay.  I will give it a try to see if it works.

> > +static int zx_gl_plane_atomic_check(struct drm_plane *plane,
> > +				    struct drm_plane_state *state)
> > +{
> > +	u32 src_w, src_h;
> > +
> > +	src_w = state->src_w >> 16;
> > +	src_h = state->src_h >> 16;
> > +
> > +	/* TODO: support scaling of the plane source */
> > +	if ((src_w != state->crtc_w) || (src_h != state->crtc_h))
> > +		return -EINVAL;
> 
> This is generally not enough checking. You probably need a call to
> drm_plane_helper_check_state.

Okay, will do.

Shawn
Shawn Guo Sept. 30, 2016, 1:43 a.m. UTC | #4
Hi Sean,

On Tue, Sep 27, 2016 at 11:48:37AM -0400, Sean Paul wrote:
> On Sat, Sep 24, 2016 at 10:26 AM, Shawn Guo <shawn.guo@linaro.org> wrote:
> > It adds the initial ZTE VOU display controller DRM driver.  There are
> > still some features to be added, like overlay plane, scaling, and more
> > output devices support.  But it's already useful with dual CRTCs and
> > HDMI monitor working.
> >
> > It's been tested on Debian Jessie LXDE desktop with modesetting driver.
> >
> > Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> 
> Hi Shawn,
> I think overall this is very well done! A couple of things stuck out
> to me, I've pointed them out below, hopefully you can use some of
> them.

Thanks for reviewing the patch.  The comments are helpful, and I will
try to address them immediately once I get back from the business
travel.

> > diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
> > new file mode 100644
> > index 000000000000..b40968dc749f
> > --- /dev/null
> > +++ b/drivers/gpu/drm/zte/Makefile
> > @@ -0,0 +1,8 @@
> > +zxdrm-y := \
> > +       zx_drm_drv.o \
> > +       zx_crtc.o \
> > +       zx_plane.o \
> > +       zx_hdmi.o
> > +
> > +obj-$(CONFIG_DRM_ZTE) += zxdrm.o
> > +
> > diff --git a/drivers/gpu/drm/zte/zx_crtc.c b/drivers/gpu/drm/zte/zx_crtc.c
> 
> I was a little tripped up when I first read this file since I assumed
> there was one instance of this driver per-crtc. However, there's
> really N crtcs per driver. Might it be less confusing to call it
> zx_vou.c instead?

Okay, will rename it.

> > +static void zx_crtc_enable(struct drm_crtc *crtc)
> > +{
> > +       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> > +       struct zx_crtc *zcrtc = to_zx_crtc(crtc);
> > +       struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
> 
> IMO, it would be better to store a pointer to vou in each zx_crtc
> rather than reaching into drvdata.

Yeah, agree on that.

> > +       bool is_main = is_main_crtc(crtc);
> > +       struct videomode vm;
> > +       u32 pol = 0;
> > +       u32 val;
> > +
> > +       drm_display_mode_to_videomode(mode, &vm);
> 
> Why do this conversion? You should be able to get everything you need
> from drm_display_mode

It's a helper function, and the calculation of front_porch, back_porch
and sync_len helps me.

> > +
> > +       /* Set up timing parameters */
> > +       val = (vm.vactive - 1) << 16;
> > +       val |= (vm.hactive - 1) & 0xffff;
> > +       writel(val, vou->timing + (is_main ? FIR_MAIN_ACTIVE : FIR_AUX_ACTIVE));
> > +
> > +       val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> > +       val |= ((vm.hback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> > +       val |= ((vm.hfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> > +       writel(val, vou->timing + (is_main ? FIR_MAIN_H_TIMING :
> > +                                            FIR_AUX_H_TIMING));
> > +
> > +       val = ((vm.vsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> > +       val |= ((vm.vback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
> > +       val |= ((vm.vfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
> > +       writel(val, vou->timing + (is_main ? FIR_MAIN_V_TIMING :
> > +                                            FIR_AUX_V_TIMING));
> 
> It would be nice to figure out a better way of handing the main/aux
> switch as opposed to sprinkling all of these inline conditionals
> around. Perhaps you could introduce a struct which stores the
> addresses per-crtc and then reference the struct in the driver as
> opposed to the #defines.
> 
> ie:
> 
> writel(val, vou->timing + zcrtc->regs->v_timing);

Yeah, good suggestion.

> > +static void zx_crtc_atomic_begin(struct drm_crtc *crtc,
> > +                                struct drm_crtc_state *state)
> > +{
> > +       struct drm_pending_vblank_event *event = crtc->state->event;
> > +
> > +       if (event) {
> 
> nit: you can save yourself a level of indentation by exiting early on
> !event instead of scoping the entire function on event.

Aha, right!

> 
> > +               crtc->state->event = NULL;
> > +
> > +               spin_lock_irq(&crtc->dev->event_lock);
> > +               if (drm_crtc_vblank_get(crtc) == 0)
> > +                       drm_crtc_arm_vblank_event(crtc, event);
> > +               else
> > +                       drm_crtc_send_vblank_event(crtc, event);
> > +               spin_unlock_irq(&crtc->dev->event_lock);
> > +       }
> > +}

<snip>

> > +static struct zx_crtc *zx_crtc_init(struct drm_device *drm,
> > +                                   enum vou_chn_type chn_type)
> > +{
> > +       struct zx_drm_private *priv = drm->dev_private;
> > +       struct zx_vou_hw *vou = priv->vou;
> > +       struct device *dev = vou->dev;
> > +       struct zx_layer_data data;
> > +       struct zx_crtc *zcrtc;
> > +       int ret;
> > +
> > +       zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
> > +       if (!zcrtc)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       zcrtc->dev = dev;
> > +       zcrtc->chn_type = chn_type;
> > +
> > +       if (chn_type == VOU_CHN_MAIN) {
> > +               data.layer = vou->osd + 0x130;
> > +               data.csc = vou->osd + 0x580;
> > +               data.hbsc = vou->osd + 0x820;
> > +               data.rsz = vou->otfppu + 0x600;
> > +               zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
> > +       } else {
> > +               data.layer = vou->osd + 0x200;
> > +               data.csc = vou->osd + 0x5d0;
> > +               data.hbsc = vou->osd + 0x860;
> > +               data.rsz = vou->otfppu + 0x800;
> > +               zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
> > +       }
> 
> These magic values should find their way into #defines

Yeah, will do.

> > +static void vou_dtrc_init(struct zx_vou_hw *vou)
> > +{
> > +       u32 val;
> > +
> > +       val = readl(vou->dtrc + DTRC_DETILE_CTRL);
> > +       /* Clear bit for bypass by ID */
> > +       val &= ~TILE2RASTESCAN_BYPASS_MODE;
> > +       /* Select ARIDR mode */
> > +       val &= ~DETILE_ARIDR_MODE_MASK;
> > +       val |= DETILE_ARID_IN_ARIDR;
> > +       writel(val, vou->dtrc + DTRC_DETILE_CTRL);
> > +
> > +       /* Bypass decompression for both frames */
> > +       val = readl(vou->dtrc + DTRC_F0_CTRL);
> > +       val |= DTRC_DECOMPRESS_BYPASS;
> > +       writel(val, vou->dtrc + DTRC_F0_CTRL);
> > +
> > +       val = readl(vou->dtrc + DTRC_F1_CTRL);
> > +       val |= DTRC_DECOMPRESS_BYPASS;
> > +       writel(val, vou->dtrc + DTRC_F1_CTRL);
> > +
> > +       /* Set up ARID register */
> > +       val = 0x0e;
> > +       val |= 0x0f << 8;
> > +       val |= 0x0e << 16;
> > +       val |= 0x0f << 24;
> 
> #define

Okay.

> > +static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
> > +{
> > +       struct platform_device *pdev = to_platform_device(dev);
> > +       struct drm_device *drm = data;
> > +       struct zx_drm_private *priv = drm->dev_private;
> > +       struct resource *res;
> > +       struct zx_vou_hw *vou;
> > +       int irq;
> > +       int ret;
> > +
> > +       vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL);
> > +       if (!vou)
> > +               return -ENOMEM;
> > +
> > +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd");
> > +       vou->osd = devm_ioremap_resource(dev, res);
> > +       if (IS_ERR(vou->osd))
> > +               return PTR_ERR(vou->osd);
> > +
> > +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
> > +       vou->timing = devm_ioremap_resource(dev, res);
> > +       if (IS_ERR(vou->timing))
> > +               return PTR_ERR(vou->timing);
> > +
> > +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
> > +       vou->dtrc = devm_ioremap_resource(dev, res);
> > +       if (IS_ERR(vou->dtrc))
> > +               return PTR_ERR(vou->dtrc);
> > +
> > +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
> > +       vou->vouctl = devm_ioremap_resource(dev, res);
> > +       if (IS_ERR(vou->vouctl))
> > +               return PTR_ERR(vou->vouctl);
> > +
> > +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
> > +       vou->otfppu = devm_ioremap_resource(dev, res);
> > +       if (IS_ERR(vou->otfppu))
> > +               return PTR_ERR(vou->otfppu);
> > +
> > +       irq = platform_get_irq(pdev, 0);
> > +       if (irq < 0)
> > +               return irq;
> > +
> > +       vou->axi_clk = devm_clk_get(dev, "aclk");
> > +       if (IS_ERR(vou->axi_clk))
> > +               return PTR_ERR(vou->axi_clk);
> > +
> > +       vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
> > +       if (IS_ERR(vou->ppu_clk))
> > +               return PTR_ERR(vou->ppu_clk);
> > +
> > +       clk_prepare_enable(vou->axi_clk);
> > +       clk_prepare_enable(vou->ppu_clk);
> > +
> > +       vou->dev = dev;
> > +       priv->vou = vou;
> > +       dev_set_drvdata(dev, vou);
> 
> I think you should be able to avoid storing vou in priv and drvdata.

I see I can do something by avoid storing vou in priv, but not sure how
to access vou in unbind function without storing it in drvdata.  Any
suggestion?

> > +
> > +       vou_hw_init(vou);
> > +
> > +       ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       vou->main_crtc = zx_crtc_init(drm, VOU_CHN_MAIN);
> > +       if (IS_ERR(vou->main_crtc))
> > +               return PTR_ERR(vou->main_crtc);
> > +
> > +       vou->aux_crtc = zx_crtc_init(drm, VOU_CHN_AUX);
> > +       if (IS_ERR(vou->aux_crtc))
> > +               return PTR_ERR(vou->aux_crtc);
> > +
> > +       return 0;
> > +}

<snip>

> > diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
> > new file mode 100644
> > index 000000000000..51fafb8e5f43
> > --- /dev/null
> > +++ b/drivers/gpu/drm/zte/zx_drm_drv.c
> > @@ -0,0 +1,258 @@
> > +/*
> > + * Copyright 2016 Linaro Ltd.
> > + * Copyright 2016 ZTE Corporation.
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + *
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/clk.h>
> > +#include <linux/component.h>
> > +#include <linux/list.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/of_platform.h>
> 
> nit: Alphabetical?

Okay, will do.

> > +static struct vou_inf vou_inf_hdmi = {
> > +       .id = VOU_HDMI,
> > +       .data_sel = VOU_YUV444,
> > +       .clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
> > +       .clocks_sel_bits = BIT(13) | BIT(2),
> > +};
> 
> This should be static const, but I suppose you can't b/c you're
> storing a pointer to encoder in vou_inf. This type of information
> lends itself well to being defined and mapped as of_device_id.data,
> you'll need to pass the encoder around in a different manner.

Okay, I will find some way to remove the encoder pointer out of the
structure.

> > +static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi,
> > +                                   struct drm_display_mode *mode)
> > +{
> > +       union hdmi_infoframe frame;
> > +       int ret;
> > +
> > +       ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
> > +                                                         mode);
> > +       if (ret)
> 
> Should you log in cases of failure (here and elsewhere)?

Okay, will do.

> > +               return ret;
> > +
> > +       return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF);
> > +}

<snip>

> > +static void zx_hdmi_encoder_enable(struct drm_encoder *encoder)
> > +{
> > +       struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> > +
> > +       vou_inf_enable(hdmi->inf);
> 
> I think you can remove the encoder from inf by passing encoder->crtc
> here as well. That will tell vou which encoder/crtc pair to enable
> without having that pesky static global floating around.

Yes.

> > +}
> > +
> > +static void zx_hdmi_encoder_disable(struct drm_encoder *encoder)
> > +{
> > +       struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
> > +
> > +       vou_inf_disable(hdmi->inf);
> 
> Same here

Yes.

> > +}
> > +
> > +static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = {
> > +       .enable = zx_hdmi_encoder_enable,
> > +       .disable = zx_hdmi_encoder_disable,
> > +       .mode_set = zx_hdmi_encoder_mode_set,
> > +};
> > +
> > +static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = {
> > +       .destroy = drm_encoder_cleanup,
> > +};
> > +
> > +static int zx_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
> > +                                 size_t len)
> 
> Instead of open-coding this, consider implementing an i2c adapter for
> the DDC bus and use drm_get_edid below.

Okay.  I got the same suggestion from Daniel, so it must be something
good :)

Shawn
Emil Velikov Sept. 30, 2016, 12:34 p.m. UTC | #5
Hi Shawn,

A couple of fly-by suggestions, which I hope you'll find useful :-)

On 24 September 2016 at 15:26, Shawn Guo <shawn.guo@linaro.org> wrote:

> +
 > +       val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
To save some writing/minimise the chances to typos getting, in you can
have single/collection to static inline functions similar to msm [1].
On a similar note inline wrappers zte_read/write/mask (around
writel/readl) will provide quite useful for debugging/tracing :-)

[1] drivers/gpu/drm/msm/adreno/a4xx.xml.h


> +       if (IS_ERR(zcrtc->pixclk))
> +               return ERR_PTR(PTR_ERR(zcrtc->pixclk));
You might want to s/ERR_PTR(PTR_ERR// or s/ERR_PTR(PTR_ERR/ERR_CAST/
through the patch.


> +static int zx_drm_bind(struct device *dev)
> +{
> +       struct drm_device *drm;
> +       struct zx_drm_private *priv;
> +       int ret;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       drm = drm_dev_alloc(&zx_drm_driver, dev);
> +       if (!drm)
> +               return -ENOMEM;
> +
> +       drm->dev_private = priv;
> +       dev_set_drvdata(dev, drm);
> +
> +       drm_mode_config_init(drm);
> +       drm->mode_config.min_width = 16;
> +       drm->mode_config.min_height = 16;
> +       drm->mode_config.max_width = 4096;
> +       drm->mode_config.max_height = 4096;
> +       drm->mode_config.funcs = &zx_drm_mode_config_funcs;
> +
> +       ret = drm_dev_register(drm, 0);
The documentation states that drm_dev_register() should be called
after the hardware is setup. which some drivers seems to interpret as
...

> +       if (ret)
> +               goto out_free;
> +
> +       ret = component_bind_all(dev, drm);
> +       if (ret) {
> +               DRM_ERROR("Failed to bind all components\n");
> +               goto out_unregister;
> +       }
> +
> +       ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
> +       if (ret < 0) {
> +               DRM_ERROR("failed to initialise vblank\n");
> +               goto out_unbind;
> +       }
> +
> +       /*
> +        * We will manage irq handler on our own.  In this case, irq_enabled
> +        * need to be true for using vblank core support.
> +        */
> +       drm->irq_enabled = true;
> +
> +       drm_mode_config_reset(drm);
> +       drm_kms_helper_poll_init(drm);
> +
> +       priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
> +                                        drm->mode_config.num_connector);
... calling it after these. If that's the correct case - perhaps we
can throw a WARN or similar within the above functions to catch
present/future misuse ?


> +       if (IS_ERR(priv->fbdev)) {
> +               ret = PTR_ERR(priv->fbdev);
> +               priv->fbdev = NULL;
> +               goto out_fini;
> +       }
> +
> +       return 0;
> +
> +out_fini:
> +       drm_kms_helper_poll_fini(drm);
> +       drm_mode_config_cleanup(drm);
> +       drm_vblank_cleanup(drm);
> +out_unbind:
> +       component_unbind_all(dev, drm);
> +out_unregister:
> +       drm_dev_unregister(drm);
> +out_free:
> +       dev_set_drvdata(dev, NULL);
> +       drm_dev_unref(drm);
> +       return ret;
> +}
> +
> +static void zx_drm_unbind(struct device *dev)
> +{
> +       struct drm_device *drm = dev_get_drvdata(dev);
> +       struct zx_drm_private *priv = drm->dev_private;
> +
> +       if (priv->fbdev) {
> +               drm_fbdev_cma_fini(priv->fbdev);
> +               priv->fbdev = NULL;
> +       }
> +       drm_kms_helper_poll_fini(drm);
> +       component_unbind_all(dev, drm);
> +       drm_vblank_cleanup(drm);
> +       drm_mode_config_cleanup(drm);
> +       drm_dev_unregister(drm);
> +       drm_dev_unref(drm);
> +       drm->dev_private = NULL;
> +       dev_set_drvdata(dev, NULL);
This and the teardown path in bind() are asymmetrical. Furthermore you
want to call drm_dev_unregister() first, according to its
documentation.
As mentioned above - perhaps it's worth providing feedback for drivers
which get the order wrong ?



> +static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
> +{


> +
> +       clk_prepare_enable(hdmi->cec_clk);
> +       clk_prepare_enable(hdmi->osc_clk);
> +       clk_prepare_enable(hdmi->xclk);
> +
> +       ret = zx_hdmi_register(drm, hdmi);
> +       if (ret)
> +               return ret;
> +

> +       return 0;
> +}
> +
> +static void zx_hdmi_unbind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       clk_disable_unprepare(hdmi->cec_clk);
> +       clk_disable_unprepare(hdmi->osc_clk);
> +       clk_disable_unprepare(hdmi->xclk);
Nit: you want the teardown to happen in reverse order of the setup. I
might have missed a few similar cases within the patch, so please
double-check.


> +static int zx_gl_get_fmt(uint32_t format)
> +{
> +       switch (format) {
> +       case DRM_FORMAT_ARGB8888:
> +       case DRM_FORMAT_XRGB8888:
> +               return GL_FMT_ARGB8888;
> +       case DRM_FORMAT_RGB888:
> +               return GL_FMT_RGB888;
> +       case DRM_FORMAT_RGB565:
> +               return GL_FMT_RGB565;
> +       case DRM_FORMAT_ARGB1555:
> +               return GL_FMT_ARGB1555;
> +       case DRM_FORMAT_ARGB4444:
> +               return GL_FMT_ARGB4444;
> +       default:
> +               WARN_ONCE(1, "invalid pixel format %d\n", format);
> +               return -EINVAL;
Afaics the only user of this is atomic_update() and that function
cannot fail. You might want to move this to the _check() function.
Same logic goes for the rest of the driver, in case I've missed any.


Regards,
Emil
Shawn Guo Oct. 1, 2016, 12:22 a.m. UTC | #6
Hi Emil,

On Fri, Sep 30, 2016 at 01:34:14PM +0100, Emil Velikov wrote:
> Hi Shawn,
> 
> A couple of fly-by suggestions, which I hope you'll find useful :-)

Thanks for the suggestions.

> On 24 September 2016 at 15:26, Shawn Guo <shawn.guo@linaro.org> wrote:
> 
> > +
>  > +       val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
> To save some writing/minimise the chances to typos getting, in you can
> have single/collection to static inline functions similar to msm [1].
> On a similar note inline wrappers zte_read/write/mask (around
> writel/readl) will provide quite useful for debugging/tracing :-)
> 
> [1] drivers/gpu/drm/msm/adreno/a4xx.xml.h

I would not add a header file with hundreds or thousands of defines
while only tens of them are actually used.  For debugging, I prefer
to print particular registers than every single read/write, which
might not be easy to check what I want to check.

> > +       if (IS_ERR(zcrtc->pixclk))
> > +               return ERR_PTR(PTR_ERR(zcrtc->pixclk));
> You might want to s/ERR_PTR(PTR_ERR// or s/ERR_PTR(PTR_ERR/ERR_CAST/
> through the patch.

Aha, yes.

> > +static int zx_drm_bind(struct device *dev)
> > +{
> > +       struct drm_device *drm;
> > +       struct zx_drm_private *priv;
> > +       int ret;
> > +
> > +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +       if (!priv)
> > +               return -ENOMEM;
> > +
> > +       drm = drm_dev_alloc(&zx_drm_driver, dev);
> > +       if (!drm)
> > +               return -ENOMEM;
> > +
> > +       drm->dev_private = priv;
> > +       dev_set_drvdata(dev, drm);
> > +
> > +       drm_mode_config_init(drm);
> > +       drm->mode_config.min_width = 16;
> > +       drm->mode_config.min_height = 16;
> > +       drm->mode_config.max_width = 4096;
> > +       drm->mode_config.max_height = 4096;
> > +       drm->mode_config.funcs = &zx_drm_mode_config_funcs;
> > +
> > +       ret = drm_dev_register(drm, 0);
> The documentation states that drm_dev_register() should be called
> after the hardware is setup. which some drivers seems to interpret as
> ...
> 
> > +       if (ret)
> > +               goto out_free;
> > +
> > +       ret = component_bind_all(dev, drm);
> > +       if (ret) {
> > +               DRM_ERROR("Failed to bind all components\n");
> > +               goto out_unregister;
> > +       }
> > +
> > +       ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
> > +       if (ret < 0) {
> > +               DRM_ERROR("failed to initialise vblank\n");
> > +               goto out_unbind;
> > +       }
> > +
> > +       /*
> > +        * We will manage irq handler on our own.  In this case, irq_enabled
> > +        * need to be true for using vblank core support.
> > +        */
> > +       drm->irq_enabled = true;
> > +
> > +       drm_mode_config_reset(drm);
> > +       drm_kms_helper_poll_init(drm);
> > +
> > +       priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
> > +                                        drm->mode_config.num_connector);
> ... calling it after these. If that's the correct case - perhaps we
> can throw a WARN or similar within the above functions to catch
> present/future misuse ?

Yes.  Daniel also pointed it out in his review of the patch.

> > +       if (IS_ERR(priv->fbdev)) {
> > +               ret = PTR_ERR(priv->fbdev);
> > +               priv->fbdev = NULL;
> > +               goto out_fini;
> > +       }
> > +
> > +       return 0;
> > +
> > +out_fini:
> > +       drm_kms_helper_poll_fini(drm);
> > +       drm_mode_config_cleanup(drm);
> > +       drm_vblank_cleanup(drm);
> > +out_unbind:
> > +       component_unbind_all(dev, drm);
> > +out_unregister:
> > +       drm_dev_unregister(drm);
> > +out_free:
> > +       dev_set_drvdata(dev, NULL);
> > +       drm_dev_unref(drm);
> > +       return ret;
> > +}
> > +
> > +static void zx_drm_unbind(struct device *dev)
> > +{
> > +       struct drm_device *drm = dev_get_drvdata(dev);
> > +       struct zx_drm_private *priv = drm->dev_private;
> > +
> > +       if (priv->fbdev) {
> > +               drm_fbdev_cma_fini(priv->fbdev);
> > +               priv->fbdev = NULL;
> > +       }
> > +       drm_kms_helper_poll_fini(drm);
> > +       component_unbind_all(dev, drm);
> > +       drm_vblank_cleanup(drm);
> > +       drm_mode_config_cleanup(drm);
> > +       drm_dev_unregister(drm);
> > +       drm_dev_unref(drm);
> > +       drm->dev_private = NULL;
> > +       dev_set_drvdata(dev, NULL);
> This and the teardown path in bind() are asymmetrical. Furthermore you
> want to call drm_dev_unregister() first, according to its
> documentation.
> As mentioned above - perhaps it's worth providing feedback for drivers
> which get the order wrong ?
> 
> 
> 
> > +static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
> > +{
> 
> 
> > +
> > +       clk_prepare_enable(hdmi->cec_clk);
> > +       clk_prepare_enable(hdmi->osc_clk);
> > +       clk_prepare_enable(hdmi->xclk);
> > +
> > +       ret = zx_hdmi_register(drm, hdmi);
> > +       if (ret)
> > +               return ret;
> > +
> 
> > +       return 0;
> > +}
> > +
> > +static void zx_hdmi_unbind(struct device *dev, struct device *master,
> > +                          void *data)
> > +{
> > +       struct zx_hdmi *hdmi = dev_get_drvdata(dev);
> > +
> > +       clk_disable_unprepare(hdmi->cec_clk);
> > +       clk_disable_unprepare(hdmi->osc_clk);
> > +       clk_disable_unprepare(hdmi->xclk);
> Nit: you want the teardown to happen in reverse order of the setup. I
> might have missed a few similar cases within the patch, so please
> double-check.

Okay, I will give it a check through the patch.

> > +static int zx_gl_get_fmt(uint32_t format)
> > +{
> > +       switch (format) {
> > +       case DRM_FORMAT_ARGB8888:
> > +       case DRM_FORMAT_XRGB8888:
> > +               return GL_FMT_ARGB8888;
> > +       case DRM_FORMAT_RGB888:
> > +               return GL_FMT_RGB888;
> > +       case DRM_FORMAT_RGB565:
> > +               return GL_FMT_RGB565;
> > +       case DRM_FORMAT_ARGB1555:
> > +               return GL_FMT_ARGB1555;
> > +       case DRM_FORMAT_ARGB4444:
> > +               return GL_FMT_ARGB4444;
> > +       default:
> > +               WARN_ONCE(1, "invalid pixel format %d\n", format);
> > +               return -EINVAL;
> Afaics the only user of this is atomic_update() and that function
> cannot fail. You might want to move this to the _check() function.
> Same logic goes for the rest of the driver, in case I've missed any.

The function does the conversion from DRM format values to the ones that
hardware accepts.  And I need to set up hardware register with the
converted value.

I suppose that the error case in 'default' should never be hit, since
all valid format have been reported to DRM core by gl_formats?  In that
case, I can simply drop the WARN and return a sane default format
instead?

Shawn
Emil Velikov Oct. 3, 2016, 10:36 a.m. UTC | #7
On 1 October 2016 at 01:22, Shawn Guo <shawnguo@kernel.org> wrote:
> Hi Emil,
>
> On Fri, Sep 30, 2016 at 01:34:14PM +0100, Emil Velikov wrote:
>> Hi Shawn,
>>
>> A couple of fly-by suggestions, which I hope you'll find useful :-)
>
> Thanks for the suggestions.
>
>> On 24 September 2016 at 15:26, Shawn Guo <shawn.guo@linaro.org> wrote:
>>
>> > +
>>  > +       val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
>> To save some writing/minimise the chances to typos getting, in you can
>> have single/collection to static inline functions similar to msm [1].
>> On a similar note inline wrappers zte_read/write/mask (around
>> writel/readl) will provide quite useful for debugging/tracing :-)
>>
>> [1] drivers/gpu/drm/msm/adreno/a4xx.xml.h
>
> I would not add a header file with hundreds or thousands of defines
> while only tens of them are actually used.  For debugging, I prefer
> to print particular registers than every single read/write, which
> might not be easy to check what I want to check.
>
I've never implied adding hundreds/etc defines, but a few
macros/inlines which get you from

  val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
  ....
  val = readl(vou->vouctl + VOU_CLK_EN);
  val |= inf->clocks_en_bits;
  writel(val, vou->vouctl + VOU_CLK_EN);

to

  val = SYNC_WIDE(vm.hsync_len - 1);
  ...
  zte_vouctl_mask(vou, VOU_CLK_EN, mask, value); // or any permutation
of upper/lower case, argument order, use/discard the VOU_ register
prefix, etc.


The latter tends to be easier to read and less error prone. Either
way, it was just a suggestion.


>> > +static int zx_gl_get_fmt(uint32_t format)
>> > +{
>> > +       switch (format) {
>> > +       case DRM_FORMAT_ARGB8888:
>> > +       case DRM_FORMAT_XRGB8888:
>> > +               return GL_FMT_ARGB8888;
>> > +       case DRM_FORMAT_RGB888:
>> > +               return GL_FMT_RGB888;
>> > +       case DRM_FORMAT_RGB565:
>> > +               return GL_FMT_RGB565;
>> > +       case DRM_FORMAT_ARGB1555:
>> > +               return GL_FMT_ARGB1555;
>> > +       case DRM_FORMAT_ARGB4444:
>> > +               return GL_FMT_ARGB4444;
>> > +       default:
>> > +               WARN_ONCE(1, "invalid pixel format %d\n", format);
>> > +               return -EINVAL;
>> Afaics the only user of this is atomic_update() and that function
>> cannot fail. You might want to move this to the _check() function.
>> Same logic goes for the rest of the driver, in case I've missed any.
>
> The function does the conversion from DRM format values to the ones that
> hardware accepts.  And I need to set up hardware register with the
> converted value.
>
> I suppose that the error case in 'default' should never be hit, since
> all valid format have been reported to DRM core by gl_formats?  In that
> case, I can simply drop the WARN and return a sane default format
> instead?
>
I'd just do the error checking in check() function and keep the
mapping in update(). As devs add new formats to DRM core it's not
possible for them to test every driver so getting a failure early is
better (imho) than getting subtle visual and/or other issues. Either
way it's up-to you really.

Regards,
Emil
Daniel Vetter Oct. 3, 2016, 1:49 p.m. UTC | #8
On Mon, Oct 3, 2016 at 12:36 PM, Emil Velikov <emil.l.velikov@gmail.com> wrote:
>>> > +static int zx_gl_get_fmt(uint32_t format)
>>> > +{
>>> > +       switch (format) {
>>> > +       case DRM_FORMAT_ARGB8888:
>>> > +       case DRM_FORMAT_XRGB8888:
>>> > +               return GL_FMT_ARGB8888;
>>> > +       case DRM_FORMAT_RGB888:
>>> > +               return GL_FMT_RGB888;
>>> > +       case DRM_FORMAT_RGB565:
>>> > +               return GL_FMT_RGB565;
>>> > +       case DRM_FORMAT_ARGB1555:
>>> > +               return GL_FMT_ARGB1555;
>>> > +       case DRM_FORMAT_ARGB4444:
>>> > +               return GL_FMT_ARGB4444;
>>> > +       default:
>>> > +               WARN_ONCE(1, "invalid pixel format %d\n", format);
>>> > +               return -EINVAL;
>>> Afaics the only user of this is atomic_update() and that function
>>> cannot fail. You might want to move this to the _check() function.
>>> Same logic goes for the rest of the driver, in case I've missed any.
>>
>> The function does the conversion from DRM format values to the ones that
>> hardware accepts.  And I need to set up hardware register with the
>> converted value.
>>
>> I suppose that the error case in 'default' should never be hit, since
>> all valid format have been reported to DRM core by gl_formats?  In that
>> case, I can simply drop the WARN and return a sane default format
>> instead?
>>
> I'd just do the error checking in check() function and keep the
> mapping in update(). As devs add new formats to DRM core it's not
> possible for them to test every driver so getting a failure early is
> better (imho) than getting subtle visual and/or other issues. Either
> way it's up-to you really.

I think a WARN_ON here is perfectly fine. Like Shawn said, any invalid
formats should already be filtered out by the drm core (by checking
against the list of valid formats for this plane), so no need to move
this to ->check(). In i915.ko we have a MISSING_CASE macro to catch
these cases when someone adds a new format, but forgets to update all
the switch statements.
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 483059a22b1b..a91f8cecbe0f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -223,6 +223,8 @@  source "drivers/gpu/drm/hisilicon/Kconfig"
 
 source "drivers/gpu/drm/mediatek/Kconfig"
 
+source "drivers/gpu/drm/zte/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 439d89b25ae0..fe461c94d266 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -85,3 +85,4 @@  obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
 obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
+obj-$(CONFIG_DRM_ZTE)	+= zte/
diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig
new file mode 100644
index 000000000000..4065b2840f1c
--- /dev/null
+++ b/drivers/gpu/drm/zte/Kconfig
@@ -0,0 +1,8 @@ 
+config DRM_ZTE
+	tristate "DRM Support for ZTE SoCs"
+	depends on DRM && ARCH_ZX
+	select DRM_KMS_CMA_HELPER
+	select DRM_KMS_FB_HELPER
+	select DRM_KMS_HELPER
+	help
+	  Choose this option to enable DRM on ZTE ZX SoCs.
diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
new file mode 100644
index 000000000000..b40968dc749f
--- /dev/null
+++ b/drivers/gpu/drm/zte/Makefile
@@ -0,0 +1,8 @@ 
+zxdrm-y := \
+	zx_drm_drv.o \
+	zx_crtc.o \
+	zx_plane.o \
+	zx_hdmi.o
+
+obj-$(CONFIG_DRM_ZTE) += zxdrm.o
+
diff --git a/drivers/gpu/drm/zte/zx_crtc.c b/drivers/gpu/drm/zte/zx_crtc.c
new file mode 100644
index 000000000000..818bf9072573
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_crtc.c
@@ -0,0 +1,691 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_plane_helper.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of_address.h>
+#include <video/videomode.h>
+
+#include "zx_drm_drv.h"
+#include "zx_crtc.h"
+#include "zx_plane.h"
+
+/* OSD (GPC_GLOBAL) registers */
+#define OSD_INT_STA			0x04
+#define OSD_INT_CLRSTA			0x08
+#define OSD_INT_MSK			0x0c
+#define OSD_INT_AUX_UPT			BIT(14)
+#define OSD_INT_MAIN_UPT		BIT(13)
+#define OSD_INT_GL1_LBW			BIT(10)
+#define OSD_INT_GL0_LBW			BIT(9)
+#define OSD_INT_VL2_LBW			BIT(8)
+#define OSD_INT_VL1_LBW			BIT(7)
+#define OSD_INT_VL0_LBW			BIT(6)
+#define OSD_INT_BUS_ERR			BIT(3)
+#define OSD_INT_CFG_ERR			BIT(2)
+#define OSD_INT_ERROR (\
+	OSD_INT_GL1_LBW | OSD_INT_GL0_LBW | \
+	OSD_INT_VL2_LBW | OSD_INT_VL1_LBW | OSD_INT_VL0_LBW | \
+	OSD_INT_BUS_ERR | OSD_INT_CFG_ERR \
+)
+#define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT)
+#define OSD_CTRL0			0x10
+#define OSD_CTRL0_GL0_EN		BIT(7)
+#define OSD_CTRL0_GL0_SEL		BIT(6)
+#define OSD_CTRL0_GL1_EN		BIT(5)
+#define OSD_CTRL0_GL1_SEL		BIT(4)
+#define OSD_RST_CLR			0x1c
+#define RST_PER_FRAME			BIT(19)
+
+/* Main/Aux channel registers */
+#define OSD_MAIN_CHN			0x470
+#define OSD_AUX_CHN			0x4d0
+#define CHN_CTRL0			0x00
+#define CHN_ENABLE			BIT(0)
+#define CHN_CTRL1			0x04
+#define CHN_SCREEN_W_SHIFT		18
+#define CHN_SCREEN_W_MASK		(0x1fff << CHN_SCREEN_W_SHIFT)
+#define CHN_SCREEN_H_SHIFT		5
+#define CHN_SCREEN_H_MASK		(0x1fff << CHN_SCREEN_H_SHIFT)
+#define CHN_UPDATE			0x08
+
+/* TIMING_CTRL registers */
+#define TIMING_TC_ENABLE		0x04
+#define AUX_TC_EN			BIT(1)
+#define MAIN_TC_EN			BIT(0)
+#define FIR_MAIN_ACTIVE			0x08
+#define FIR_AUX_ACTIVE			0x0c
+#define FIR_MAIN_H_TIMING		0x10
+#define FIR_MAIN_V_TIMING		0x14
+#define FIR_AUX_H_TIMING		0x18
+#define FIR_AUX_V_TIMING		0x1c
+#define SYNC_WIDE_SHIFT			22
+#define SYNC_WIDE_MASK			(0x3ff << SYNC_WIDE_SHIFT)
+#define BACK_PORCH_SHIFT		11
+#define BACK_PORCH_MASK			(0x7ff << BACK_PORCH_SHIFT)
+#define FRONT_PORCH_SHIFT		0
+#define FRONT_PORCH_MASK		(0x7ff << FRONT_PORCH_SHIFT)
+#define TIMING_CTRL			0x20
+#define AUX_POL_SHIFT			3
+#define AUX_POL_MASK			(0x7 << AUX_POL_SHIFT)
+#define MAIN_POL_SHIFT			0
+#define MAIN_POL_MASK			(0x7 << MAIN_POL_SHIFT)
+#define POL_DE_SHIFT			2
+#define POL_VSYNC_SHIFT			1
+#define POL_HSYNC_SHIFT			0
+#define TIMING_INT_CTRL			0x24
+#define TIMING_INT_STATE		0x28
+#define TIMING_INT_AUX_FRAME		BIT(3)
+#define TIMING_INT_MAIN_FRAME		BIT(1)
+#define TIMING_INT_AUX_FRAME_SEL_VSW	(0x2 << 10)
+#define TIMING_INT_MAIN_FRAME_SEL_VSW	(0x2 << 6)
+#define TIMING_INT_ENABLE (\
+	TIMING_INT_MAIN_FRAME_SEL_VSW | TIMING_INT_AUX_FRAME_SEL_VSW | \
+	TIMING_INT_MAIN_FRAME | TIMING_INT_AUX_FRAME \
+)
+#define TIMING_MAIN_SHIFT		0x2c
+#define TIMING_AUX_SHIFT		0x30
+#define H_SHIFT_VAL			0x0048
+#define TIMING_MAIN_PI_SHIFT		0x68
+#define TIMING_AUX_PI_SHIFT		0x6c
+#define H_PI_SHIFT_VAL			0x000f
+
+/* DTRC registers */
+#define DTRC_F0_CTRL			0x2c
+#define DTRC_F1_CTRL			0x5c
+#define DTRC_DECOMPRESS_BYPASS		BIT(17)
+#define DTRC_DETILE_CTRL		0x68
+#define TILE2RASTESCAN_BYPASS_MODE	BIT(30)
+#define DETILE_ARIDR_MODE_MASK		(0x3 << 0)
+#define DETILE_ARID_ALL			0
+#define DETILE_ARID_IN_ARIDR		1
+#define DETILE_ARID_BYP_BUT_ARIDR	2
+#define DETILE_ARID_IN_ARIDR2		3
+#define DTRC_ARID			0x6c
+#define DTRC_DEC2DDR_ARID		0x70
+
+/* VOU_CTRL registers */
+#define VOU_INF_EN			0x00
+#define VOU_INF_CH_SEL			0x04
+#define VOU_INF_DATA_SEL		0x08
+#define VOU_SOFT_RST			0x14
+#define VOU_CLK_SEL			0x18
+#define VOU_CLK_GL1_SEL			BIT(5)
+#define VOU_CLK_GL0_SEL			BIT(4)
+#define VOU_CLK_REQEN			0x20
+#define VOU_CLK_EN			0x24
+
+/* OTFPPU_CTRL registers */
+#define OTFPPU_RSZ_DATA_SOURCE		0x04
+
+#define GL_NUM				2
+#define VL_NUM				3
+
+enum vou_chn_type {
+	VOU_CHN_MAIN,
+	VOU_CHN_AUX,
+};
+
+struct zx_crtc {
+	struct drm_crtc crtc;
+	struct drm_plane *primary;
+	struct device *dev;
+	void __iomem *chnreg;
+	enum vou_chn_type chn_type;
+	struct clk *pixclk;
+};
+
+#define to_zx_crtc(x)	container_of(crtc, struct zx_crtc, crtc)
+
+struct zx_vou_hw {
+	struct device *dev;
+	void __iomem *osd;
+	void __iomem *timing;
+	void __iomem *vouctl;
+	void __iomem *otfppu;
+	void __iomem *dtrc;
+	struct clk *axi_clk;
+	struct clk *ppu_clk;
+	struct clk *main_clk;
+	struct clk *aux_clk;
+	struct zx_crtc *main_crtc;
+	struct zx_crtc *aux_crtc;
+};
+
+static inline bool is_main_crtc(struct drm_crtc *crtc)
+{
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+
+	return zcrtc->chn_type == VOU_CHN_MAIN;
+}
+
+void vou_inf_enable(struct vou_inf *inf)
+{
+	struct drm_encoder *encoder = inf->encoder;
+	struct drm_device *drm = encoder->dev;
+	struct zx_drm_private *priv = drm->dev_private;
+	struct zx_vou_hw *vou = priv->vou;
+	bool is_main = is_main_crtc(encoder->crtc);
+	u32 data_sel_shift = inf->id << 1;
+	u32 val;
+
+	/* Select data format */
+	val = readl(vou->vouctl + VOU_INF_DATA_SEL);
+	val &= ~(0x3 << data_sel_shift);
+	val |= inf->data_sel << data_sel_shift;
+	writel(val, vou->vouctl + VOU_INF_DATA_SEL);
+
+	/* Select channel */
+	val = readl(vou->vouctl + VOU_INF_CH_SEL);
+	if (is_main)
+		val &= ~(1 << inf->id);
+	else
+		val |= 1 << inf->id;
+	writel(val, vou->vouctl + VOU_INF_CH_SEL);
+
+	/* Select interface clocks */
+	val = readl(vou->vouctl + VOU_CLK_SEL);
+	if (is_main)
+		val &= ~inf->clocks_sel_bits;
+	else
+		val |= inf->clocks_sel_bits;
+	writel(val, vou->vouctl + VOU_CLK_SEL);
+
+	/* Enable interface clocks */
+	val = readl(vou->vouctl + VOU_CLK_EN);
+	val |= inf->clocks_en_bits;
+	writel(val, vou->vouctl + VOU_CLK_EN);
+
+	/* Enable the device */
+	val = readl(vou->vouctl + VOU_INF_EN);
+	val |= 1 << inf->id;
+	writel(val, vou->vouctl + VOU_INF_EN);
+}
+
+void vou_inf_disable(struct vou_inf *inf)
+{
+	struct drm_encoder *encoder = inf->encoder;
+	struct drm_device *drm = encoder->dev;
+	struct zx_drm_private *priv = drm->dev_private;
+	struct zx_vou_hw *vou = priv->vou;
+	u32 val;
+
+	/* Disable the device */
+	val = readl(vou->vouctl + VOU_INF_EN);
+	val &= ~(1 << inf->id);
+	writel(val, vou->vouctl + VOU_INF_EN);
+
+	/* Disable interface clocks */
+	val = readl(vou->vouctl + VOU_CLK_EN);
+	val &= ~inf->clocks_en_bits;
+	writel(val, vou->vouctl + VOU_CLK_EN);
+}
+
+static inline void vou_chn_set_update(struct zx_crtc *zcrtc)
+{
+	writel(1, zcrtc->chnreg + CHN_UPDATE);
+}
+
+static void zx_crtc_enable(struct drm_crtc *crtc)
+{
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+	struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
+	bool is_main = is_main_crtc(crtc);
+	struct videomode vm;
+	u32 pol = 0;
+	u32 val;
+
+	drm_display_mode_to_videomode(mode, &vm);
+
+	/* Set up timing parameters */
+	val = (vm.vactive - 1) << 16;
+	val |= (vm.hactive - 1) & 0xffff;
+	writel(val, vou->timing + (is_main ? FIR_MAIN_ACTIVE : FIR_AUX_ACTIVE));
+
+	val = ((vm.hsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
+	val |= ((vm.hback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
+	val |= ((vm.hfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
+	writel(val, vou->timing + (is_main ? FIR_MAIN_H_TIMING :
+					     FIR_AUX_H_TIMING));
+
+	val = ((vm.vsync_len - 1) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK;
+	val |= ((vm.vback_porch - 1) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK;
+	val |= ((vm.vfront_porch - 1) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK;
+	writel(val, vou->timing + (is_main ? FIR_MAIN_V_TIMING :
+					     FIR_AUX_V_TIMING));
+
+	/* Set up polarities */
+	if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW)
+		pol |= 1 << POL_VSYNC_SHIFT;
+	if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW)
+		pol |= 1 << POL_HSYNC_SHIFT;
+
+	val = readl(vou->timing + TIMING_CTRL);
+	val &= ~(is_main ? MAIN_POL_MASK : AUX_POL_MASK);
+	val |= pol << (is_main ? MAIN_POL_SHIFT : AUX_POL_SHIFT);
+	writel(val, vou->timing + TIMING_CTRL);
+
+	/* Setup SHIFT register by following what ZTE BSP does */
+	writel(H_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_SHIFT :
+						     TIMING_AUX_SHIFT));
+	writel(H_PI_SHIFT_VAL, vou->timing + (is_main ? TIMING_MAIN_PI_SHIFT :
+							TIMING_AUX_PI_SHIFT));
+
+	/* Enable TIMING_CTRL */
+	val = readl(vou->timing + TIMING_TC_ENABLE);
+	val |= is_main ? MAIN_TC_EN : AUX_TC_EN;
+	writel(val, vou->timing + TIMING_TC_ENABLE);
+
+	/* Configure channel screen size */
+	val = readl(zcrtc->chnreg + CHN_CTRL1);
+	val &= ~(CHN_SCREEN_W_MASK | CHN_SCREEN_H_MASK);
+	val |= (vm.hactive << CHN_SCREEN_W_SHIFT) & CHN_SCREEN_W_MASK;
+	val |= (vm.vactive << CHN_SCREEN_H_SHIFT) & CHN_SCREEN_H_MASK;
+	writel(val, zcrtc->chnreg + CHN_CTRL1);
+
+	/* Update channel */
+	vou_chn_set_update(zcrtc);
+
+	/* Enable channel */
+	val = readl(zcrtc->chnreg + CHN_CTRL0);
+	val |= CHN_ENABLE;
+	writel(val, zcrtc->chnreg + CHN_CTRL0);
+
+	/* Enable Graphic Layer */
+	val = readl(vou->osd + OSD_CTRL0);
+	val |= is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN;
+	writel(val, vou->osd + OSD_CTRL0);
+
+	drm_crtc_vblank_on(crtc);
+
+	/* Enable pixel clock */
+	clk_set_rate(zcrtc->pixclk, mode->clock * 1000);
+	clk_prepare_enable(zcrtc->pixclk);
+}
+
+static void zx_crtc_disable(struct drm_crtc *crtc)
+{
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+	struct zx_vou_hw *vou = dev_get_drvdata(zcrtc->dev);
+	bool is_main = is_main_crtc(crtc);
+	u32 val;
+
+	clk_disable_unprepare(zcrtc->pixclk);
+
+	drm_crtc_vblank_off(crtc);
+
+	/* Disable Graphic Layer */
+	val = readl(vou->osd + OSD_CTRL0);
+	val &= ~(is_main ? OSD_CTRL0_GL0_EN : OSD_CTRL0_GL1_EN);
+	writel(val, vou->osd + OSD_CTRL0);
+
+	/* Disable channel */
+	val = readl(zcrtc->chnreg + CHN_CTRL0);
+	val &= ~CHN_ENABLE;
+	writel(val, zcrtc->chnreg + CHN_CTRL0);
+
+	/* Disable TIMING_CTRL */
+	val = readl(vou->timing + TIMING_TC_ENABLE);
+	val &= ~(is_main ? MAIN_TC_EN : AUX_TC_EN);
+	writel(val, vou->timing + TIMING_TC_ENABLE);
+}
+
+static void zx_crtc_atomic_begin(struct drm_crtc *crtc,
+				 struct drm_crtc_state *state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static const struct drm_crtc_helper_funcs zx_crtc_helper_funcs = {
+	.enable = zx_crtc_enable,
+	.disable = zx_crtc_disable,
+	.atomic_begin = zx_crtc_atomic_begin,
+};
+
+static const struct drm_crtc_funcs zx_crtc_funcs = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static struct zx_crtc *zx_crtc_init(struct drm_device *drm,
+				    enum vou_chn_type chn_type)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+	struct zx_vou_hw *vou = priv->vou;
+	struct device *dev = vou->dev;
+	struct zx_layer_data data;
+	struct zx_crtc *zcrtc;
+	int ret;
+
+	zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
+	if (!zcrtc)
+		return ERR_PTR(-ENOMEM);
+
+	zcrtc->dev = dev;
+	zcrtc->chn_type = chn_type;
+
+	if (chn_type == VOU_CHN_MAIN) {
+		data.layer = vou->osd + 0x130;
+		data.csc = vou->osd + 0x580;
+		data.hbsc = vou->osd + 0x820;
+		data.rsz = vou->otfppu + 0x600;
+		zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
+	} else {
+		data.layer = vou->osd + 0x200;
+		data.csc = vou->osd + 0x5d0;
+		data.hbsc = vou->osd + 0x860;
+		data.rsz = vou->otfppu + 0x800;
+		zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
+	}
+
+	zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ?
+					  "main_wclk" : "aux_wclk");
+	if (IS_ERR(zcrtc->pixclk))
+		return ERR_PTR(PTR_ERR(zcrtc->pixclk));
+
+	zcrtc->primary = zx_plane_init(drm, dev, &data, DRM_PLANE_TYPE_PRIMARY);
+	if (IS_ERR(zcrtc->primary))
+		return ERR_PTR(PTR_ERR(zcrtc->primary));
+
+	ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL,
+					&zx_crtc_funcs, NULL);
+	if (ret)
+		return ERR_PTR(ret);
+
+	drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs);
+
+	return zcrtc;
+}
+
+int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+	struct zx_vou_hw *vou = priv->vou;
+	u32 intctl;
+
+	intctl = readl(vou->timing + TIMING_INT_CTRL);
+	if (pipe == 0)
+		intctl |= TIMING_INT_MAIN_FRAME;
+	else
+		intctl |= TIMING_INT_AUX_FRAME;
+	writel(intctl, vou->timing + TIMING_INT_CTRL);
+
+	return 0;
+}
+
+void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+	struct zx_vou_hw *vou = priv->vou;
+	u32 intctl;
+
+	intctl = readl(vou->timing + TIMING_INT_CTRL);
+	if (pipe == 0)
+		intctl &= ~TIMING_INT_MAIN_FRAME;
+	else
+		intctl &= ~TIMING_INT_AUX_FRAME;
+	writel(intctl, vou->timing + TIMING_INT_CTRL);
+}
+
+static irqreturn_t vou_irq_handler(int irq, void *dev_id)
+{
+	struct zx_vou_hw *vou = dev_id;
+	u32 state;
+
+	/* Handle TIMING_CTRL frame interrupts */
+	state = readl(vou->timing + TIMING_INT_STATE);
+	writel(state, vou->timing + TIMING_INT_STATE);
+
+	if (state & TIMING_INT_MAIN_FRAME)
+		drm_crtc_handle_vblank(&vou->main_crtc->crtc);
+
+	if (state & TIMING_INT_AUX_FRAME)
+		drm_crtc_handle_vblank(&vou->aux_crtc->crtc);
+
+	/* Handle OSD interrupts */
+	state = readl(vou->osd + OSD_INT_STA);
+	writel(state, vou->osd + OSD_INT_CLRSTA);
+
+	if (state & OSD_INT_MAIN_UPT) {
+		vou_chn_set_update(vou->main_crtc);
+		zx_plane_set_update(vou->main_crtc->primary);
+	}
+
+	if (state & OSD_INT_AUX_UPT) {
+		vou_chn_set_update(vou->aux_crtc);
+		zx_plane_set_update(vou->aux_crtc->primary);
+	}
+
+	if (state & OSD_INT_ERROR)
+		dev_err(vou->dev, "OSD ERROR: 0x%08x!\n", state);
+
+	return IRQ_HANDLED;
+}
+
+static void vou_dtrc_init(struct zx_vou_hw *vou)
+{
+	u32 val;
+
+	val = readl(vou->dtrc + DTRC_DETILE_CTRL);
+	/* Clear bit for bypass by ID */
+	val &= ~TILE2RASTESCAN_BYPASS_MODE;
+	/* Select ARIDR mode */
+	val &= ~DETILE_ARIDR_MODE_MASK;
+	val |= DETILE_ARID_IN_ARIDR;
+	writel(val, vou->dtrc + DTRC_DETILE_CTRL);
+
+	/* Bypass decompression for both frames */
+	val = readl(vou->dtrc + DTRC_F0_CTRL);
+	val |= DTRC_DECOMPRESS_BYPASS;
+	writel(val, vou->dtrc + DTRC_F0_CTRL);
+
+	val = readl(vou->dtrc + DTRC_F1_CTRL);
+	val |= DTRC_DECOMPRESS_BYPASS;
+	writel(val, vou->dtrc + DTRC_F1_CTRL);
+
+	/* Set up ARID register */
+	val = 0x0e;
+	val |= 0x0f << 8;
+	val |= 0x0e << 16;
+	val |= 0x0f << 24;
+	writel(val, vou->dtrc + DTRC_ARID);
+
+	/* Set up DEC2DDR_ARID register */
+	val = 0x0e;
+	val |= 0x0f << 8;
+	writel(val, vou->dtrc + DTRC_DEC2DDR_ARID);
+}
+
+static void vou_hw_init(struct zx_vou_hw *vou)
+{
+	u32 val;
+
+	/* Set GL0 to main channel and GL1 to aux channel */
+	val = readl(vou->osd + OSD_CTRL0);
+	val &= ~OSD_CTRL0_GL0_SEL;
+	val |= OSD_CTRL0_GL1_SEL;
+	writel(val, vou->osd + OSD_CTRL0);
+
+	/* Release reset for all VOU modules */
+	writel(~0, vou->vouctl + VOU_SOFT_RST);
+
+	/* Select main clock for GL0 and aux clock for GL1 module */
+	val = readl(vou->vouctl + VOU_CLK_SEL);
+	val &= ~VOU_CLK_GL0_SEL;
+	val |= VOU_CLK_GL1_SEL;
+	writel(val, vou->vouctl + VOU_CLK_SEL);
+
+	/* Enable clock auto-gating for all VOU modules */
+	writel(~0, vou->vouctl + VOU_CLK_REQEN);
+
+	/* Enable all VOU module clocks */
+	writel(~0, vou->vouctl + VOU_CLK_EN);
+
+	/* Clear both OSD and TIMING_CTRL interrupt state */
+	writel(~0, vou->osd + OSD_INT_CLRSTA);
+	writel(~0, vou->timing + TIMING_INT_STATE);
+
+	/* Enable OSD and TIMING_CTRL interrrupts */
+	writel(OSD_INT_ENABLE, vou->osd + OSD_INT_MSK);
+	writel(TIMING_INT_ENABLE, vou->timing + TIMING_INT_CTRL);
+
+	/* Select GPC as input to gl/vl scaler as a sane default setting */
+	writel(0x2a, vou->otfppu + OTFPPU_RSZ_DATA_SOURCE);
+
+	/*
+	 * Needs to reset channel and layer logic per frame when frame starts
+	 * to get VOU work properly.
+	 */
+	val = readl(vou->osd + OSD_RST_CLR);
+	val |= RST_PER_FRAME;
+	writel(val, vou->osd + OSD_RST_CLR);
+
+	vou_dtrc_init(vou);
+}
+
+static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct zx_drm_private *priv = drm->dev_private;
+	struct resource *res;
+	struct zx_vou_hw *vou;
+	int irq;
+	int ret;
+
+	vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL);
+	if (!vou)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd");
+	vou->osd = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->osd))
+		return PTR_ERR(vou->osd);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
+	vou->timing = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->timing))
+		return PTR_ERR(vou->timing);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
+	vou->dtrc = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->dtrc))
+		return PTR_ERR(vou->dtrc);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
+	vou->vouctl = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->vouctl))
+		return PTR_ERR(vou->vouctl);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
+	vou->otfppu = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->otfppu))
+		return PTR_ERR(vou->otfppu);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	vou->axi_clk = devm_clk_get(dev, "aclk");
+	if (IS_ERR(vou->axi_clk))
+		return PTR_ERR(vou->axi_clk);
+
+	vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
+	if (IS_ERR(vou->ppu_clk))
+		return PTR_ERR(vou->ppu_clk);
+
+	clk_prepare_enable(vou->axi_clk);
+	clk_prepare_enable(vou->ppu_clk);
+
+	vou->dev = dev;
+	priv->vou = vou;
+	dev_set_drvdata(dev, vou);
+
+	vou_hw_init(vou);
+
+	ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou);
+	if (ret < 0)
+		return ret;
+
+	vou->main_crtc = zx_crtc_init(drm, VOU_CHN_MAIN);
+	if (IS_ERR(vou->main_crtc))
+		return PTR_ERR(vou->main_crtc);
+
+	vou->aux_crtc = zx_crtc_init(drm, VOU_CHN_AUX);
+	if (IS_ERR(vou->aux_crtc))
+		return PTR_ERR(vou->aux_crtc);
+
+	return 0;
+}
+
+static void zx_crtc_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct zx_vou_hw *vou = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(vou->axi_clk);
+	clk_disable_unprepare(vou->ppu_clk);
+}
+
+static const struct component_ops zx_crtc_component_ops = {
+	.bind = zx_crtc_bind,
+	.unbind = zx_crtc_unbind,
+};
+
+static int zx_crtc_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &zx_crtc_component_ops);
+}
+
+static int zx_crtc_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &zx_crtc_component_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_crtc_of_match[] = {
+	{ .compatible = "zte,zx296718-dpc", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_crtc_of_match);
+
+struct platform_driver zx_crtc_driver = {
+	.probe = zx_crtc_probe,
+	.remove = zx_crtc_remove,
+	.driver	= {
+		.name = "zx-crtc",
+		.of_match_table	= zx_crtc_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/zte/zx_crtc.h b/drivers/gpu/drm/zte/zx_crtc.h
new file mode 100644
index 000000000000..f889208054ce
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_crtc.h
@@ -0,0 +1,47 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ZX_CRTC_H__
+#define __ZX_CRTC_H__
+
+#define VOU_CRTC_MASK		0x3
+
+/* VOU output interfaces */
+enum vou_inf_id {
+	VOU_HDMI	= 0,
+	VOU_RGB_LCD	= 1,
+	VOU_TV_ENC	= 2,
+	VOU_MIPI_DSI	= 3,
+	VOU_LVDS	= 4,
+	VOU_VGA		= 5,
+};
+
+enum vou_inf_data_sel {
+	VOU_YUV444	= 0,
+	VOU_RGB_101010	= 1,
+	VOU_RGB_888	= 2,
+	VOU_RGB_666	= 3,
+};
+
+struct vou_inf {
+	struct drm_encoder *encoder;
+	enum vou_inf_id id;
+	enum vou_inf_data_sel data_sel;
+	u32 clocks_en_bits;
+	u32 clocks_sel_bits;
+};
+
+void vou_inf_enable(struct vou_inf *inf);
+void vou_inf_disable(struct vou_inf *inf);
+
+int zx_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe);
+void zx_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe);
+
+#endif /* __ZX_CRTC_H__ */
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
new file mode 100644
index 000000000000..51fafb8e5f43
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_drm_drv.c
@@ -0,0 +1,258 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/list.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+
+#include "zx_drm_drv.h"
+#include "zx_crtc.h"
+
+static void zx_drm_fb_output_poll_changed(struct drm_device *drm)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+
+	drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = {
+	.fb_create = drm_fb_cma_create,
+	.output_poll_changed = zx_drm_fb_output_poll_changed,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static void zx_drm_lastclose(struct drm_device *drm)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations zx_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.release = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.poll = drm_poll,
+	.read = drm_read,
+	.llseek = noop_llseek,
+	.mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver zx_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+			   DRIVER_ATOMIC,
+	.lastclose = zx_drm_lastclose,
+	.get_vblank_counter = drm_vblank_no_hw_counter,
+	.enable_vblank = zx_crtc_enable_vblank,
+	.disable_vblank = zx_crtc_disable_vblank,
+	.gem_free_object = drm_gem_cma_free_object,
+	.gem_vm_ops = &drm_gem_cma_vm_ops,
+	.dumb_create = drm_gem_cma_dumb_create,
+	.dumb_map_offset = drm_gem_cma_dumb_map_offset,
+	.dumb_destroy = drm_gem_dumb_destroy,
+	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+	.gem_prime_export = drm_gem_prime_export,
+	.gem_prime_import = drm_gem_prime_import,
+	.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,
+	.fops = &zx_drm_fops,
+	.name = "zx-vou",
+	.desc = "ZTE VOU Controller DRM",
+	.date = "20160811",
+	.major = 1,
+	.minor = 0,
+};
+
+static int zx_drm_bind(struct device *dev)
+{
+	struct drm_device *drm;
+	struct zx_drm_private *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	drm = drm_dev_alloc(&zx_drm_driver, dev);
+	if (!drm)
+		return -ENOMEM;
+
+	drm->dev_private = priv;
+	dev_set_drvdata(dev, drm);
+
+	drm_mode_config_init(drm);
+	drm->mode_config.min_width = 16;
+	drm->mode_config.min_height = 16;
+	drm->mode_config.max_width = 4096;
+	drm->mode_config.max_height = 4096;
+	drm->mode_config.funcs = &zx_drm_mode_config_funcs;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto out_free;
+
+	ret = component_bind_all(dev, drm);
+	if (ret) {
+		DRM_ERROR("Failed to bind all components\n");
+		goto out_unregister;
+	}
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret < 0) {
+		DRM_ERROR("failed to initialise vblank\n");
+		goto out_unbind;
+	}
+
+	/*
+	 * We will manage irq handler on our own.  In this case, irq_enabled
+	 * need to be true for using vblank core support.
+	 */
+	drm->irq_enabled = true;
+
+	drm_mode_config_reset(drm);
+	drm_kms_helper_poll_init(drm);
+
+	priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
+					 drm->mode_config.num_connector);
+	if (IS_ERR(priv->fbdev)) {
+		ret = PTR_ERR(priv->fbdev);
+		priv->fbdev = NULL;
+		goto out_fini;
+	}
+
+	return 0;
+
+out_fini:
+	drm_kms_helper_poll_fini(drm);
+	drm_mode_config_cleanup(drm);
+	drm_vblank_cleanup(drm);
+out_unbind:
+	component_unbind_all(dev, drm);
+out_unregister:
+	drm_dev_unregister(drm);
+out_free:
+	dev_set_drvdata(dev, NULL);
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void zx_drm_unbind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct zx_drm_private *priv = drm->dev_private;
+
+	if (priv->fbdev) {
+		drm_fbdev_cma_fini(priv->fbdev);
+		priv->fbdev = NULL;
+	}
+	drm_kms_helper_poll_fini(drm);
+	component_unbind_all(dev, drm);
+	drm_vblank_cleanup(drm);
+	drm_mode_config_cleanup(drm);
+	drm_dev_unregister(drm);
+	drm_dev_unref(drm);
+	drm->dev_private = NULL;
+	dev_set_drvdata(dev, NULL);
+}
+
+static const struct component_master_ops zx_drm_master_ops = {
+	.bind = zx_drm_bind,
+	.unbind = zx_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int zx_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *parent = dev->of_node;
+	struct device_node *child;
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = of_platform_populate(parent, NULL, NULL, dev);
+	if (ret)
+		return ret;
+
+	for_each_available_child_of_node(parent, child) {
+		component_match_add(dev, &match, compare_of, child);
+		of_node_put(child);
+	}
+
+	return component_master_add_with_match(dev, &zx_drm_master_ops, match);
+}
+
+static int zx_drm_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &zx_drm_master_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_drm_of_match[] = {
+	{ .compatible = "zte,zx296718-vou", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_drm_of_match);
+
+static struct platform_driver zx_drm_platform_driver = {
+	.probe = zx_drm_probe,
+	.remove = zx_drm_remove,
+	.driver	= {
+		.name = "zx-drm",
+		.of_match_table	= zx_drm_of_match,
+	},
+};
+
+static struct platform_driver *drivers[] = {
+	&zx_crtc_driver,
+	&zx_hdmi_driver,
+	&zx_drm_platform_driver,
+};
+
+static int zx_drm_init(void)
+{
+	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_init(zx_drm_init);
+
+static void zx_drm_exit(void)
+{
+	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_exit(zx_drm_exit);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("ZTE ZX VOU DRM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
new file mode 100644
index 000000000000..14c749949151
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_drm_drv.h
@@ -0,0 +1,22 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ZX_DRM_DRV_H__
+#define __ZX_DRM_DRV_H__
+
+struct zx_drm_private {
+	struct drm_fbdev_cma *fbdev;
+	struct zx_vou_hw *vou;
+};
+
+extern struct platform_driver zx_crtc_driver;
+extern struct platform_driver zx_hdmi_driver;
+
+#endif /* __ZX_DRM_DRV_H__ */
diff --git a/drivers/gpu/drm/zte/zx_hdmi.c b/drivers/gpu/drm/zte/zx_hdmi.c
new file mode 100644
index 000000000000..5aaab8493b1b
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_hdmi.c
@@ -0,0 +1,540 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/component.h>
+
+#include "zx_crtc.h"
+
+#define FUNC_SEL			0x000b
+#define FUNC_HDMI_EN			BIT(0)
+#define CLKPWD				0x000d
+#define CLKPWD_PDIDCK			BIT(2)
+#define PWD_SRST			0x0010
+#define P2T_CTRL			0x0066
+#define P2T_DC_PKT_EN			BIT(7)
+#define L1_INTR_STAT			0x007e
+#define L1_INTR_STAT_INTR1		BIT(0)
+#define INTR1_STAT			0x008f
+#define INTR1_MASK			0x0095
+#define INTR1_MONITOR_DETECT		(BIT(5) | BIT(6))
+#define ZX_DDC_ADDR			0x00ed
+#define ZX_DDC_SEGM			0x00ee
+#define ZX_DDC_OFFSET			0x00ef
+#define ZX_DDC_DIN_CNT1			0x00f0
+#define ZX_DDC_DIN_CNT2			0x00f1
+#define ZX_DDC_CMD			0x00f3
+#define DDC_CMD_MASK			0xf
+#define DDC_CMD_CLEAR_FIFO		0x9
+#define DDC_CMD_SEQUENTIAL_READ		0x2
+#define ZX_DDC_DATA			0x00f4
+#define ZX_DDC_DOUT_CNT			0x00f5
+#define DDC_DOUT_CNT_MASK		0x1f
+#define TEST_TXCTRL			0x00f7
+#define TEST_TXCTRL_HDMI_MODE		BIT(1)
+#define HDMICTL4			0x0235
+#define TPI_HPD_RSEN			0x063b
+#define TPI_HPD_CONNECTION		(BIT(1) | BIT(2))
+#define TPI_INFO_FSEL			0x06bf
+#define FSEL_AVI			0
+#define FSEL_GBD			1
+#define FSEL_AUDIO			2
+#define FSEL_SPD			3
+#define FSEL_MPEG			4
+#define FSEL_VSIF			5
+#define TPI_INFO_B0			0x06c0
+#define TPI_INFO_EN			0x06df
+#define TPI_INFO_TRANS_EN		BIT(7)
+#define TPI_INFO_TRANS_RPT		BIT(6)
+#define TPI_DDC_MASTER_EN		0x06f8
+#define HW_DDC_MASTER			BIT(7)
+
+#define ZX_HDMI_INFOFRAME_SIZE		31
+
+struct zx_hdmi {
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct device *dev;
+	struct drm_device *drm;
+	void __iomem *mmio;
+	struct clk *cec_clk;
+	struct clk *osc_clk;
+	struct clk *xclk;
+	bool sink_is_hdmi;
+	bool sink_has_audio;
+	struct vou_inf *inf;
+};
+
+#define to_zx_hdmi(x)	container_of(x, struct zx_hdmi, x)
+
+static struct vou_inf vou_inf_hdmi = {
+	.id = VOU_HDMI,
+	.data_sel = VOU_YUV444,
+	.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
+	.clocks_sel_bits = BIT(13) | BIT(2),
+};
+
+static inline u8 hdmi_readb(struct zx_hdmi *hdmi, u16 offset)
+{
+	return readl_relaxed(hdmi->mmio + offset * 4);
+}
+
+static inline void hdmi_writeb(struct zx_hdmi *hdmi, u16 offset, u8 val)
+{
+	writel_relaxed(val, hdmi->mmio + offset * 4);
+}
+
+static int zx_hdmi_infoframe_trans(struct zx_hdmi *hdmi,
+				   union hdmi_infoframe *frame, u8 fsel)
+{
+	u8 buffer[ZX_HDMI_INFOFRAME_SIZE];
+	u8 val;
+	ssize_t num;
+	int i;
+
+	hdmi_writeb(hdmi, TPI_INFO_FSEL, fsel);
+
+	num = hdmi_infoframe_pack(frame, buffer, ZX_HDMI_INFOFRAME_SIZE);
+	if (num < 0)
+		return num;
+
+	for (i = 0; i < num; i++)
+		hdmi_writeb(hdmi, TPI_INFO_B0 + i, buffer[i]);
+
+	val = hdmi_readb(hdmi, TPI_INFO_EN);
+	val |= TPI_INFO_TRANS_EN | TPI_INFO_TRANS_RPT;
+	hdmi_writeb(hdmi, TPI_INFO_EN, val);
+
+	return num;
+}
+
+static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi,
+				    struct drm_display_mode *mode)
+{
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
+							  mode);
+	if (ret)
+		return ret;
+
+	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF);
+}
+
+static int zx_hdmi_config_video_avi(struct zx_hdmi *hdmi,
+				    struct drm_display_mode *mode)
+{
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
+	if (ret)
+		return ret;
+
+	/* We always use YUV444 for HDMI output. */
+	frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+
+	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AVI);
+}
+
+static void zx_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adj_mode)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	if (hdmi->sink_is_hdmi) {
+		zx_hdmi_config_video_avi(hdmi, mode);
+		zx_hdmi_config_video_vsi(hdmi, mode);
+	}
+}
+
+static void zx_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	vou_inf_enable(hdmi->inf);
+}
+
+static void zx_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	vou_inf_disable(hdmi->inf);
+}
+
+static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = {
+	.enable	= zx_hdmi_encoder_enable,
+	.disable = zx_hdmi_encoder_disable,
+	.mode_set = zx_hdmi_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int zx_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
+				  size_t len)
+{
+	struct zx_hdmi *hdmi = data;
+	int retry = 0;
+	int ret = 0;
+	int i = 0;
+	u8 val;
+
+	/* Enable DDC master access */
+	val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
+	val |= HW_DDC_MASTER;
+	hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
+
+	hdmi_writeb(hdmi, ZX_DDC_ADDR, 0xa0);
+	hdmi_writeb(hdmi, ZX_DDC_OFFSET, block * EDID_LENGTH);
+	/* Bits [9:8] of bytes */
+	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff);
+	/* Bits [7:0] of bytes */
+	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff);
+
+	/* Clear FIFO */
+	val = hdmi_readb(hdmi, ZX_DDC_CMD);
+	val &= ~DDC_CMD_MASK;
+	val |= DDC_CMD_CLEAR_FIFO;
+	hdmi_writeb(hdmi, ZX_DDC_CMD, val);
+
+	/* Kick off the read */
+	val = hdmi_readb(hdmi, ZX_DDC_CMD);
+	val &= ~DDC_CMD_MASK;
+	val |= DDC_CMD_SEQUENTIAL_READ;
+	hdmi_writeb(hdmi, ZX_DDC_CMD, val);
+
+	while (len > 0) {
+		int cnt, j;
+
+		/* FIFO needs some time to get ready */
+		usleep_range(500, 1000);
+
+		cnt = hdmi_readb(hdmi, ZX_DDC_DOUT_CNT) & DDC_DOUT_CNT_MASK;
+		if (cnt == 0) {
+			if (++retry > 5) {
+				dev_err(hdmi->dev, "DDC read timed out!");
+				ret = -ETIMEDOUT;
+				break;
+			}
+			continue;
+		}
+
+		for (j = 0; j < cnt; j++)
+			buf[i++] = hdmi_readb(hdmi, ZX_DDC_DATA);
+		len -= cnt;
+	}
+
+	/* Disable DDC master access */
+	val = hdmi_readb(hdmi, TPI_DDC_MASTER_EN);
+	val &= ~HW_DDC_MASTER;
+	hdmi_writeb(hdmi, TPI_DDC_MASTER_EN, val);
+
+	return ret;
+}
+
+static int zx_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
+	struct edid *edid;
+	int ret = 0;
+
+	edid = drm_do_get_edid(connector, zx_hdmi_get_edid_block, hdmi);
+	if (edid) {
+		hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+		hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
+		drm_mode_connector_update_edid_property(connector, edid);
+		ret = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
+
+	return ret;
+}
+
+static enum drm_mode_status
+zx_hdmi_connector_mode_valid(struct drm_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs zx_hdmi_connector_helper_funcs = {
+	.get_modes = zx_hdmi_connector_get_modes,
+	.mode_valid = zx_hdmi_connector_mode_valid,
+};
+
+static enum drm_connector_status
+zx_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
+
+	return (hdmi_readb(hdmi, TPI_HPD_RSEN) & TPI_HPD_CONNECTION) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static void zx_hdmi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs zx_hdmi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = zx_hdmi_connector_detect,
+	.destroy = zx_hdmi_connector_destroy,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int zx_hdmi_register(struct drm_device *drm, struct zx_hdmi *hdmi)
+{
+	struct drm_encoder *encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = VOU_CRTC_MASK;
+
+	drm_encoder_init(drm, encoder, &zx_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+	drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs);
+
+	hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_init(drm, &hdmi->connector, &zx_hdmi_connector_funcs,
+			   DRM_MODE_CONNECTOR_HDMIA);
+	drm_connector_helper_add(&hdmi->connector,
+				 &zx_hdmi_connector_helper_funcs);
+
+	drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
+	drm_connector_register(&hdmi->connector);
+
+	return 0;
+}
+
+static irqreturn_t zx_hdmi_irq_thread(int irq, void *dev_id)
+{
+	struct zx_hdmi *hdmi = dev_id;
+
+	drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
+{
+	struct zx_hdmi *hdmi = dev_id;
+	u8 lstat;
+
+	lstat = hdmi_readb(hdmi, L1_INTR_STAT);
+
+	/* Monitor detect/HPD interrupt */
+	if (lstat & L1_INTR_STAT_INTR1) {
+		u8 stat = hdmi_readb(hdmi, INTR1_STAT);
+
+		hdmi_writeb(hdmi, INTR1_STAT, stat);
+		if (stat & INTR1_MONITOR_DETECT)
+			return IRQ_WAKE_THREAD;
+	}
+
+	return IRQ_NONE;
+}
+
+static void zx_hdmi_phy_start(struct zx_hdmi *hdmi)
+{
+	/* Copy from ZTE BSP code */
+	hdmi_writeb(hdmi, 0x222, 0x0);
+	hdmi_writeb(hdmi, 0x224, 0x4);
+	hdmi_writeb(hdmi, 0x909, 0x0);
+	hdmi_writeb(hdmi, 0x7b0, 0x90);
+	hdmi_writeb(hdmi, 0x7b1, 0x00);
+	hdmi_writeb(hdmi, 0x7b2, 0xa7);
+	hdmi_writeb(hdmi, 0x7b8, 0xaa);
+	hdmi_writeb(hdmi, 0x7b2, 0xa7);
+	hdmi_writeb(hdmi, 0x7b3, 0x0f);
+	hdmi_writeb(hdmi, 0x7b4, 0x0f);
+	hdmi_writeb(hdmi, 0x7b5, 0x55);
+	hdmi_writeb(hdmi, 0x7b7, 0x03);
+	hdmi_writeb(hdmi, 0x7b9, 0x12);
+	hdmi_writeb(hdmi, 0x7ba, 0x32);
+	hdmi_writeb(hdmi, 0x7bc, 0x68);
+	hdmi_writeb(hdmi, 0x7be, 0x40);
+	hdmi_writeb(hdmi, 0x7bf, 0x84);
+	hdmi_writeb(hdmi, 0x7c1, 0x0f);
+	hdmi_writeb(hdmi, 0x7c8, 0x02);
+	hdmi_writeb(hdmi, 0x7c9, 0x03);
+	hdmi_writeb(hdmi, 0x7ca, 0x40);
+	hdmi_writeb(hdmi, 0x7dc, 0x31);
+	hdmi_writeb(hdmi, 0x7e2, 0x04);
+	hdmi_writeb(hdmi, 0x7e0, 0x06);
+	hdmi_writeb(hdmi, 0x7cb, 0x68);
+	hdmi_writeb(hdmi, 0x7f9, 0x02);
+	hdmi_writeb(hdmi, 0x7b6, 0x02);
+	hdmi_writeb(hdmi, 0x7f3, 0x0);
+}
+
+static void zx_hdmi_hw_init(struct zx_hdmi *hdmi)
+{
+	u8 val;
+
+	/* Software reset */
+	hdmi_writeb(hdmi, PWD_SRST, 1);
+
+	/* Enable pclk */
+	val = hdmi_readb(hdmi, CLKPWD);
+	val |= CLKPWD_PDIDCK;
+	hdmi_writeb(hdmi, CLKPWD, val);
+
+	/* Enable HDMI for TX */
+	val = hdmi_readb(hdmi, FUNC_SEL);
+	val |= FUNC_HDMI_EN;
+	hdmi_writeb(hdmi, FUNC_SEL, val);
+
+	/* Enable deep color packet */
+	val = hdmi_readb(hdmi, P2T_CTRL);
+	val |= P2T_DC_PKT_EN;
+	hdmi_writeb(hdmi, P2T_CTRL, val);
+
+	/* Enable HDMI/MHL mode for output */
+	val = hdmi_readb(hdmi, TEST_TXCTRL);
+	val |= TEST_TXCTRL_HDMI_MODE;
+	hdmi_writeb(hdmi, TEST_TXCTRL, val);
+
+	/* Configure reg_qc_sel */
+	hdmi_writeb(hdmi, HDMICTL4, 0x3);
+
+	/* Enable interrupt */
+	val = hdmi_readb(hdmi, INTR1_MASK);
+	val |= INTR1_MONITOR_DETECT;
+	hdmi_writeb(hdmi, INTR1_MASK, val);
+
+	/* Clear reset for normal operation */
+	hdmi_writeb(hdmi, PWD_SRST, 0);
+
+	/* Start up phy */
+	zx_hdmi_phy_start(hdmi);
+}
+
+static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct resource *res;
+	struct zx_hdmi *hdmi;
+	struct vou_inf *inf;
+	int irq;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	hdmi->dev = dev;
+	hdmi->drm = drm;
+
+	inf = &vou_inf_hdmi;
+	inf->encoder = &hdmi->encoder;
+	hdmi->inf = inf;
+
+	dev_set_drvdata(dev, hdmi);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->mmio))
+		return PTR_ERR(hdmi->mmio);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	hdmi->cec_clk = devm_clk_get(hdmi->dev, "osc_cec");
+	if (IS_ERR(hdmi->cec_clk))
+		return PTR_ERR(hdmi->cec_clk);
+
+	hdmi->osc_clk = devm_clk_get(hdmi->dev, "osc_clk");
+	if (IS_ERR(hdmi->osc_clk))
+		return PTR_ERR(hdmi->osc_clk);
+
+	hdmi->xclk = devm_clk_get(hdmi->dev, "xclk");
+	if (IS_ERR(hdmi->xclk))
+		return PTR_ERR(hdmi->xclk);
+
+	zx_hdmi_hw_init(hdmi);
+
+	clk_prepare_enable(hdmi->cec_clk);
+	clk_prepare_enable(hdmi->osc_clk);
+	clk_prepare_enable(hdmi->xclk);
+
+	ret = zx_hdmi_register(drm, hdmi);
+	if (ret)
+		return ret;
+
+	ret = devm_request_threaded_irq(dev, irq, zx_hdmi_irq_handler,
+					zx_hdmi_irq_thread, IRQF_SHARED,
+					dev_name(dev), hdmi);
+
+	return 0;
+}
+
+static void zx_hdmi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(hdmi->cec_clk);
+	clk_disable_unprepare(hdmi->osc_clk);
+	clk_disable_unprepare(hdmi->xclk);
+}
+
+static const struct component_ops zx_hdmi_component_ops = {
+	.bind = zx_hdmi_bind,
+	.unbind = zx_hdmi_unbind,
+};
+
+static int zx_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &zx_hdmi_component_ops);
+}
+
+static int zx_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &zx_hdmi_component_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_hdmi_of_match[] = {
+	{ .compatible = "zte,zx296718-hdmi", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_hdmi_of_match);
+
+struct platform_driver zx_hdmi_driver = {
+	.probe = zx_hdmi_probe,
+	.remove = zx_hdmi_remove,
+	.driver	= {
+		.name = "zx-hdmi",
+		.of_match_table	= zx_hdmi_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c
new file mode 100644
index 000000000000..326cc1ff7950
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_plane.c
@@ -0,0 +1,362 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane_helper.h>
+
+#include "zx_crtc.h"
+#include "zx_plane.h"
+
+/* GL registers */
+#define GL_CTRL0			0x00
+#define GL_UPDATE			BIT(5)
+#define GL_CTRL1			0x04
+#define GL_DATA_FMT_SHIFT		0
+#define GL_DATA_FMT_MASK		(0xf << GL_DATA_FMT_SHIFT)
+#define GL_FMT_ARGB8888			0
+#define GL_FMT_RGB888			1
+#define GL_FMT_RGB565			2
+#define GL_FMT_ARGB1555			3
+#define GL_FMT_ARGB4444			4
+#define GL_CTRL2			0x08
+#define GL_GLOBAL_ALPHA_SHIFT		8
+#define GL_GLOBAL_ALPHA_MASK		(0xff << GL_GLOBAL_ALPHA_SHIFT)
+#define GL_CTRL3			0x0c
+#define GL_SCALER_BYPASS_MODE		BIT(0)
+#define GL_STRIDE			0x18
+#define GL_ADDR				0x1c
+#define GL_SRC_SIZE			0x38
+#define GL_SRC_W_SHIFT			16
+#define GL_SRC_W_MASK			(0x3fff << GL_SRC_W_SHIFT)
+#define GL_SRC_H_SHIFT			0
+#define GL_SRC_H_MASK			(0x3fff << GL_SRC_H_SHIFT)
+#define GL_POS_START			0x9c
+#define GL_POS_END			0xa0
+#define GL_POS_X_SHIFT			16
+#define GL_POS_X_MASK			(0x1fff << GL_POS_X_SHIFT)
+#define GL_POS_Y_SHIFT			0
+#define GL_POS_Y_MASK			(0x1fff << GL_POS_Y_SHIFT)
+
+/* CSC registers */
+#define CSC_CTRL0			0x30
+#define CSC_COV_MODE_SHIFT		16
+#define CSC_COV_MODE_MASK		(0xffff << CSC_COV_MODE_SHIFT)
+#define CSC_BT601_IMAGE_RGB2YCBCR	0
+#define CSC_BT601_IMAGE_YCBCR2RGB	1
+#define CSC_BT601_VIDEO_RGB2YCBCR	2
+#define CSC_BT601_VIDEO_YCBCR2RGB	3
+#define CSC_BT709_IMAGE_RGB2YCBCR	4
+#define CSC_BT709_IMAGE_YCBCR2RGB	5
+#define CSC_BT709_VIDEO_RGB2YCBCR	6
+#define CSC_BT709_VIDEO_YCBCR2RGB	7
+#define CSC_BT2020_IMAGE_RGB2YCBCR	8
+#define CSC_BT2020_IMAGE_YCBCR2RGB	9
+#define CSC_BT2020_VIDEO_RGB2YCBCR	10
+#define CSC_BT2020_VIDEO_YCBCR2RGB	11
+#define CSC_WORK_ENABLE			BIT(0)
+
+/* RSZ registers */
+#define RSZ_SRC_CFG			0x00
+#define RSZ_DEST_CFG			0x04
+#define RSZ_ENABLE_CFG			0x14
+
+/* HBSC registers */
+#define HBSC_SATURATION			0x00
+#define HBSC_HUE			0x04
+#define HBSC_BRIGHT			0x08
+#define HBSC_CONTRAST			0x0c
+#define HBSC_THRESHOLD_COL1		0x10
+#define HBSC_THRESHOLD_COL2		0x14
+#define HBSC_THRESHOLD_COL3		0x18
+#define HBSC_CTRL0			0x28
+#define HBSC_CTRL_EN			BIT(2)
+
+struct zx_plane {
+	struct drm_plane plane;
+	void __iomem *layer;
+	void __iomem *csc;
+	void __iomem *hbsc;
+	void __iomem *rsz;
+};
+
+#define to_zx_plane(plane)	container_of(plane, struct zx_plane, plane)
+
+static const uint32_t gl_formats[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ARGB4444,
+};
+
+static int zx_gl_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_plane_state *state)
+{
+	u32 src_w, src_h;
+
+	src_w = state->src_w >> 16;
+	src_h = state->src_h >> 16;
+
+	/* TODO: support scaling of the plane source */
+	if ((src_w != state->crtc_w) || (src_h != state->crtc_h))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int zx_gl_get_fmt(uint32_t format)
+{
+	switch (format) {
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		return GL_FMT_ARGB8888;
+	case DRM_FORMAT_RGB888:
+		return GL_FMT_RGB888;
+	case DRM_FORMAT_RGB565:
+		return GL_FMT_RGB565;
+	case DRM_FORMAT_ARGB1555:
+		return GL_FMT_ARGB1555;
+	case DRM_FORMAT_ARGB4444:
+		return GL_FMT_ARGB4444;
+	default:
+		WARN_ONCE(1, "invalid pixel format %d\n", format);
+		return -EINVAL;
+	}
+}
+
+static inline void zx_gl_set_update(struct zx_plane *zplane)
+{
+	void __iomem *layer = zplane->layer;
+	u32 val;
+
+	val = readl(layer + GL_CTRL0);
+	val |= GL_UPDATE;
+	writel(val, layer + GL_CTRL0);
+}
+
+static inline void zx_gl_rsz_set_update(struct zx_plane *zplane)
+{
+	writel(1, zplane->rsz + RSZ_ENABLE_CFG);
+}
+
+void zx_plane_set_update(struct drm_plane *plane)
+{
+	struct zx_plane *zplane = to_zx_plane(plane);
+
+	zx_gl_rsz_set_update(zplane);
+	zx_gl_set_update(zplane);
+}
+
+static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h,
+			    u32 dst_w, u32 dst_h)
+{
+	void __iomem *rsz = zplane->rsz;
+	u32 val;
+
+	val = ((src_h - 1) & 0xffff) << 16;
+	val |= (src_w - 1) & 0xffff;
+	writel(val, rsz + RSZ_SRC_CFG);
+
+	val = ((dst_h - 1) & 0xffff) << 16;
+	val |= (dst_w - 1) & 0xffff;
+	writel(val, rsz + RSZ_DEST_CFG);
+
+	zx_gl_rsz_set_update(zplane);
+}
+
+static void zx_gl_plane_atomic_update(struct drm_plane *plane,
+				      struct drm_plane_state *old_state)
+{
+	struct zx_plane *zplane = to_zx_plane(plane);
+	struct drm_framebuffer *fb = plane->state->fb;
+	struct drm_gem_cma_object *cma_obj;
+	void __iomem *layer = zplane->layer;
+	void __iomem *csc = zplane->csc;
+	void __iomem *hbsc = zplane->hbsc;
+	u32 src_x, src_y, src_w, src_h;
+	u32 dst_x, dst_y, dst_w, dst_h;
+	unsigned int depth, bpp;
+	uint32_t format;
+	dma_addr_t paddr;
+	u32 stride;
+	int fmt;
+	u32 val;
+
+	if (!fb)
+		return;
+
+	format = fb->pixel_format;
+	stride = fb->pitches[0];
+
+	src_x = plane->state->src_x >> 16;
+	src_y = plane->state->src_y >> 16;
+	src_w = plane->state->src_w >> 16;
+	src_h = plane->state->src_h >> 16;
+
+	dst_x = plane->state->crtc_x;
+	dst_y = plane->state->crtc_y;
+	dst_w = plane->state->crtc_w;
+	dst_h = plane->state->crtc_h;
+
+	drm_fb_get_bpp_depth(format, &depth, &bpp);
+
+	cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	paddr = cma_obj->paddr + fb->offsets[0];
+	paddr += src_y * stride + src_x * bpp / 8;
+	writel(paddr, layer + GL_ADDR);
+
+	/* Set up source height/width register */
+	val = (src_w << GL_SRC_W_SHIFT) & GL_SRC_W_MASK;
+	val |= (src_h << GL_SRC_H_SHIFT) & GL_SRC_H_MASK;
+	writel(val, layer + GL_SRC_SIZE);
+
+	/* Set up start position register */
+	val = (dst_x << GL_POS_X_SHIFT) & GL_POS_X_MASK;
+	val |= (dst_y << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
+	writel(val, layer + GL_POS_START);
+
+	/* Set up end position register */
+	val = ((dst_x + dst_w) << GL_POS_X_SHIFT) & GL_POS_X_MASK;
+	val |= ((dst_y + dst_h) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK;
+	writel(val, layer + GL_POS_END);
+
+	/* Set up stride register */
+	writel(stride & 0xffff, layer + GL_STRIDE);
+
+	/* Set up graphic layer data format */
+	fmt = zx_gl_get_fmt(format);
+	if (fmt >= 0) {
+		val = readl(layer + GL_CTRL1);
+		val &= ~GL_DATA_FMT_MASK;
+		val |= fmt << GL_DATA_FMT_SHIFT;
+		writel(val, layer + GL_CTRL1);
+	}
+
+	/* Initialize global alpha with a sane value */
+	val = readl(layer + GL_CTRL2);
+	val &= ~GL_GLOBAL_ALPHA_MASK;
+	val |= 0xff << GL_GLOBAL_ALPHA_SHIFT;
+	writel(val, layer + GL_CTRL2);
+
+	/* Setup CSC for the GL */
+	val = readl(csc + CSC_CTRL0);
+	val &= ~CSC_COV_MODE_MASK;
+	if (dst_h > 720)
+		val |= CSC_BT709_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
+	else
+		val |= CSC_BT601_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT;
+	val |= CSC_WORK_ENABLE;
+	writel(val, csc + CSC_CTRL0);
+
+	/* Always use scaler since it exists */
+	val = readl(layer + GL_CTRL3);
+	val |= GL_SCALER_BYPASS_MODE;	/* set for not bypass */
+	writel(val, layer + GL_CTRL3);
+
+	zx_gl_rsz_setup(zplane, src_w, src_h, dst_w, dst_h);
+
+	/* Enable HBSC block */
+	val = readl(hbsc + HBSC_CTRL0);
+	val |= HBSC_CTRL_EN;
+	writel(val, hbsc + HBSC_CTRL0);
+
+	zx_gl_set_update(zplane);
+}
+
+static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = {
+	.atomic_check = zx_gl_plane_atomic_check,
+	.atomic_update = zx_gl_plane_atomic_update,
+};
+
+static void zx_plane_destroy(struct drm_plane *plane)
+{
+	drm_plane_helper_disable(plane);
+	drm_plane_cleanup(plane);
+}
+
+static const struct drm_plane_funcs zx_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = zx_plane_destroy,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static void zx_plane_hbsc_init(struct zx_plane *zplane)
+{
+	void __iomem *hbsc = zplane->hbsc;
+
+	/*
+	 *  Initialize HBSC block with a sane configuration per recommedation
+	 *  from ZTE BSP code.
+	 */
+	writel(0x200, hbsc + HBSC_SATURATION);
+	writel(0x0, hbsc + HBSC_HUE);
+	writel(0x0, hbsc + HBSC_BRIGHT);
+	writel(0x200, hbsc + HBSC_CONTRAST);
+
+	writel((0x3ac << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL1);
+	writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL2);
+	writel((0x3c0 << 16) | 0x40, hbsc + HBSC_THRESHOLD_COL3);
+}
+
+struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
+				struct zx_layer_data *data,
+				enum drm_plane_type type)
+{
+	const struct drm_plane_helper_funcs *helper;
+	struct zx_plane *zplane;
+	struct drm_plane *plane;
+	const uint32_t *formats;
+	unsigned int format_count;
+	int ret;
+
+	zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
+	if (!zplane)
+		return ERR_PTR(-ENOMEM);
+
+	plane = &zplane->plane;
+
+	zplane->layer = data->layer;
+	zplane->hbsc = data->hbsc;
+	zplane->csc = data->csc;
+	zplane->rsz = data->rsz;
+
+	zx_plane_hbsc_init(zplane);
+
+	switch (type) {
+	case DRM_PLANE_TYPE_PRIMARY:
+		helper = &zx_gl_plane_helper_funcs;
+		formats = gl_formats;
+		format_count = ARRAY_SIZE(gl_formats);
+		break;
+	case DRM_PLANE_TYPE_OVERLAY:
+		/* TODO: add video layer (vl) support */
+		break;
+	default:
+		return ERR_PTR(-ENODEV);
+	}
+
+	ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK,
+				       &zx_plane_funcs, formats, format_count,
+				       type, NULL);
+	if (ret)
+		return ERR_PTR(ret);
+
+	drm_plane_helper_add(plane, helper);
+
+	return plane;
+}
diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h
new file mode 100644
index 000000000000..2b82cd558d9d
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_plane.h
@@ -0,0 +1,26 @@ 
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ZX_PLANE_H__
+#define __ZX_PLANE_H__
+
+struct zx_layer_data {
+	void __iomem *layer;
+	void __iomem *csc;
+	void __iomem *hbsc;
+	void __iomem *rsz;
+};
+
+struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
+				struct zx_layer_data *data,
+				enum drm_plane_type type);
+void zx_plane_set_update(struct drm_plane *plane);
+
+#endif /* __ZX_PLANE_H__ */