/*
 * 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/clk.h>
#include <linux/component.h>
#include <linux/of_address.h>
#include <video/videomode.h>

#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_plane_helper.h>
#include <drm/drmP.h>

#include "zx_common_regs.h"
#include "zx_drm_drv.h"
#include "zx_plane.h"
#include "zx_vou.h"
#include "zx_vou_regs.h"

#define GL_NUM	2
#define VL_NUM	3

enum vou_chn_type {
	VOU_CHN_MAIN,
	VOU_CHN_AUX,
};

struct zx_crtc_regs {
	u32 fir_active;
	u32 fir_htiming;
	u32 fir_vtiming;
	u32 sec_vtiming;
	u32 timing_shift;
	u32 timing_pi_shift;
};

static const struct zx_crtc_regs main_crtc_regs = {
	.fir_active = FIR_MAIN_ACTIVE,
	.fir_htiming = FIR_MAIN_H_TIMING,
	.fir_vtiming = FIR_MAIN_V_TIMING,
	.sec_vtiming = SEC_MAIN_V_TIMING,
	.timing_shift = TIMING_MAIN_SHIFT,
	.timing_pi_shift = TIMING_MAIN_PI_SHIFT,
};

static const struct zx_crtc_regs aux_crtc_regs = {
	.fir_active = FIR_AUX_ACTIVE,
	.fir_htiming = FIR_AUX_H_TIMING,
	.fir_vtiming = FIR_AUX_V_TIMING,
	.sec_vtiming = SEC_AUX_V_TIMING,
	.timing_shift = TIMING_AUX_SHIFT,
	.timing_pi_shift = TIMING_AUX_PI_SHIFT,
};

struct zx_crtc_bits {
	u32 polarity_mask;
	u32 polarity_shift;
	u32 int_frame_mask;
	u32 tc_enable;
	u32 sec_vactive_shift;
	u32 sec_vactive_mask;
	u32 interlace_select;
	u32 pi_enable;
	u32 div_vga_shift;
	u32 div_pic_shift;
	u32 div_tvenc_shift;
	u32 div_hdmi_pnx_shift;
	u32 div_hdmi_shift;
	u32 div_inf_shift;
	u32 div_layer_shift;
};

static const struct zx_crtc_bits main_crtc_bits = {
	.polarity_mask = MAIN_POL_MASK,
	.polarity_shift = MAIN_POL_SHIFT,
	.int_frame_mask = TIMING_INT_MAIN_FRAME,
	.tc_enable = MAIN_TC_EN,
	.sec_vactive_shift = SEC_VACT_MAIN_SHIFT,
	.sec_vactive_mask = SEC_VACT_MAIN_MASK,
	.interlace_select = MAIN_INTERLACE_SEL,
	.pi_enable = MAIN_PI_EN,
	.div_vga_shift = VGA_MAIN_DIV_SHIFT,
	.div_pic_shift = PIC_MAIN_DIV_SHIFT,
	.div_tvenc_shift = TVENC_MAIN_DIV_SHIFT,
	.div_hdmi_pnx_shift = HDMI_MAIN_PNX_DIV_SHIFT,
	.div_hdmi_shift = HDMI_MAIN_DIV_SHIFT,
	.div_inf_shift = INF_MAIN_DIV_SHIFT,
	.div_layer_shift = LAYER_MAIN_DIV_SHIFT,
};

static const struct zx_crtc_bits aux_crtc_bits = {
	.polarity_mask = AUX_POL_MASK,
	.polarity_shift = AUX_POL_SHIFT,
	.int_frame_mask = TIMING_INT_AUX_FRAME,
	.tc_enable = AUX_TC_EN,
	.sec_vactive_shift = SEC_VACT_AUX_SHIFT,
	.sec_vactive_mask = SEC_VACT_AUX_MASK,
	.interlace_select = AUX_INTERLACE_SEL,
	.pi_enable = AUX_PI_EN,
	.div_vga_shift = VGA_AUX_DIV_SHIFT,
	.div_pic_shift = PIC_AUX_DIV_SHIFT,
	.div_tvenc_shift = TVENC_AUX_DIV_SHIFT,
	.div_hdmi_pnx_shift = HDMI_AUX_PNX_DIV_SHIFT,
	.div_hdmi_shift = HDMI_AUX_DIV_SHIFT,
	.div_inf_shift = INF_AUX_DIV_SHIFT,
	.div_layer_shift = LAYER_AUX_DIV_SHIFT,
};

struct zx_crtc {
	struct drm_crtc crtc;
	struct drm_plane *primary;
	struct zx_vou_hw *vou;
	void __iomem *chnreg;
	void __iomem *chncsc;
	void __iomem *dither;
	const struct zx_crtc_regs *regs;
	const struct zx_crtc_bits *bits;
	enum vou_chn_type chn_type;
	struct clk *pixclk;
};

#define to_zx_crtc(x) container_of(x, struct zx_crtc, crtc)

struct vou_layer_bits {
	u32 enable;
	u32 chnsel;
	u32 clksel;
};

static const struct vou_layer_bits zx_gl_bits[GL_NUM] = {
	{
		.enable = OSD_CTRL0_GL0_EN,
		.chnsel = OSD_CTRL0_GL0_SEL,
		.clksel = VOU_CLK_GL0_SEL,
	}, {
		.enable = OSD_CTRL0_GL1_EN,
		.chnsel = OSD_CTRL0_GL1_SEL,
		.clksel = VOU_CLK_GL1_SEL,
	},
};

static const struct vou_layer_bits zx_vl_bits[VL_NUM] = {
	{
		.enable = OSD_CTRL0_VL0_EN,
		.chnsel = OSD_CTRL0_VL0_SEL,
		.clksel = VOU_CLK_VL0_SEL,
	}, {
		.enable = OSD_CTRL0_VL1_EN,
		.chnsel = OSD_CTRL0_VL1_SEL,
		.clksel = VOU_CLK_VL1_SEL,
	}, {
		.enable = OSD_CTRL0_VL2_EN,
		.chnsel = OSD_CTRL0_VL2_SEL,
		.clksel = VOU_CLK_VL2_SEL,
	},
};

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;
};

enum vou_inf_data_sel {
	VOU_YUV444	= 0,
	VOU_RGB_101010	= 1,
	VOU_RGB_888	= 2,
	VOU_RGB_666	= 3,
};

struct vou_inf {
	enum vou_inf_id id;
	enum vou_inf_data_sel data_sel;
	u32 clocks_en_bits;
	u32 clocks_sel_bits;
};

static struct vou_inf vou_infs[] = {
	[VOU_HDMI] = {
		.data_sel = VOU_YUV444,
		.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
		.clocks_sel_bits = BIT(13) | BIT(2),
	},
	[VOU_TV_ENC] = {
		.data_sel = VOU_YUV444,
		.clocks_en_bits = BIT(15),
		.clocks_sel_bits = BIT(11) | BIT(0),
	},
	[VOU_VGA] = {
		.data_sel = VOU_RGB_888,
		.clocks_en_bits = BIT(1),
		.clocks_sel_bits = BIT(10),
	},
};

static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);

	return zcrtc->vou;
}

void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc,
			    enum vou_inf_hdmi_audio aud)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = zcrtc->vou;

	zx_writel_mask(vou->vouctl + VOU_INF_HDMI_CTRL, VOU_HDMI_AUD_MASK, aud);
}

void vou_inf_enable(enum vou_inf_id id, struct drm_crtc *crtc)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = zcrtc->vou;
	struct vou_inf *inf = &vou_infs[id];
	void __iomem *dither = zcrtc->dither;
	void __iomem *csc = zcrtc->chncsc;
	bool is_main = zcrtc->chn_type == VOU_CHN_MAIN;
	u32 data_sel_shift = id << 1;

	if (inf->data_sel != VOU_YUV444) {
		/* Enable channel CSC for RGB output */
		zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK,
			       CSC_BT709_IMAGE_YCBCR2RGB << CSC_COV_MODE_SHIFT);
		zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE,
			       CSC_WORK_ENABLE);

		/* Bypass Dither block for RGB output */
		zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS,
			       DITHER_BYSPASS);
	} else {
		zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, 0);
		zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS, 0);
	}

	/* Select data format */
	zx_writel_mask(vou->vouctl + VOU_INF_DATA_SEL, 0x3 << data_sel_shift,
		       inf->data_sel << data_sel_shift);

	/* Select channel */
	zx_writel_mask(vou->vouctl + VOU_INF_CH_SEL, 0x1 << id,
		       zcrtc->chn_type << id);

	/* Select interface clocks */
	zx_writel_mask(vou->vouctl + VOU_CLK_SEL, inf->clocks_sel_bits,
		       is_main ? 0 : inf->clocks_sel_bits);

	/* Enable interface clocks */
	zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits,
		       inf->clocks_en_bits);

	/* Enable the device */
	zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << id, 1 << id);
}

void vou_inf_disable(enum vou_inf_id id, struct drm_crtc *crtc)
{
	struct zx_vou_hw *vou = crtc_to_vou(crtc);
	struct vou_inf *inf = &vou_infs[id];

	/* Disable the device */
	zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << id, 0);

	/* Disable interface clocks */
	zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits, 0);
}

void zx_vou_config_dividers(struct drm_crtc *crtc,
			    struct vou_div_config *configs, int num)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = zcrtc->vou;
	const struct zx_crtc_bits *bits = zcrtc->bits;
	int i;

	/* Clear update flag bit */
	zx_writel_mask(vou->vouctl + VOU_DIV_PARA, DIV_PARA_UPDATE, 0);

	for (i = 0; i < num; i++) {
		struct vou_div_config *cfg = configs + i;
		u32 reg, shift;

		switch (cfg->id) {
		case VOU_DIV_VGA:
			reg = VOU_CLK_SEL;
			shift = bits->div_vga_shift;
			break;
		case VOU_DIV_PIC:
			reg = VOU_CLK_SEL;
			shift = bits->div_pic_shift;
			break;
		case VOU_DIV_TVENC:
			reg = VOU_DIV_PARA;
			shift = bits->div_tvenc_shift;
			break;
		case VOU_DIV_HDMI_PNX:
			reg = VOU_DIV_PARA;
			shift = bits->div_hdmi_pnx_shift;
			break;
		case VOU_DIV_HDMI:
			reg = VOU_DIV_PARA;
			shift = bits->div_hdmi_shift;
			break;
		case VOU_DIV_INF:
			reg = VOU_DIV_PARA;
			shift = bits->div_inf_shift;
			break;
		case VOU_DIV_LAYER:
			reg = VOU_DIV_PARA;
			shift = bits->div_layer_shift;
			break;
		default:
			continue;
		}

		/* Each divider occupies 3 bits */
		zx_writel_mask(vou->vouctl + reg, 0x7 << shift,
			       cfg->val << shift);
	}

	/* Set update flag bit to get dividers effected */
	zx_writel_mask(vou->vouctl + VOU_DIV_PARA, DIV_PARA_UPDATE,
		       DIV_PARA_UPDATE);
}

static inline void vou_chn_set_update(struct zx_crtc *zcrtc)
{
	zx_writel(zcrtc->chnreg + CHN_UPDATE, 1);
}

static void zx_crtc_atomic_enable(struct drm_crtc *crtc,
				  struct drm_crtc_state *old_state)
{
	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
	bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = zcrtc->vou;
	const struct zx_crtc_regs *regs = zcrtc->regs;
	const struct zx_crtc_bits *bits = zcrtc->bits;
	struct videomode vm;
	u32 scan_mask;
	u32 pol = 0;
	u32 val;
	int ret;

	drm_display_mode_to_videomode(mode, &vm);

	/* Set up timing parameters */
	val = V_ACTIVE((interlaced ? vm.vactive / 2 : vm.vactive) - 1);
	val |= H_ACTIVE(vm.hactive - 1);
	zx_writel(vou->timing + regs->fir_active, val);

	val = SYNC_WIDE(vm.hsync_len - 1);
	val |= BACK_PORCH(vm.hback_porch - 1);
	val |= FRONT_PORCH(vm.hfront_porch - 1);
	zx_writel(vou->timing + regs->fir_htiming, val);

	val = SYNC_WIDE(vm.vsync_len - 1);
	val |= BACK_PORCH(vm.vback_porch - 1);
	val |= FRONT_PORCH(vm.vfront_porch - 1);
	zx_writel(vou->timing + regs->fir_vtiming, val);

	if (interlaced) {
		u32 shift = bits->sec_vactive_shift;
		u32 mask = bits->sec_vactive_mask;

		val = zx_readl(vou->timing + SEC_V_ACTIVE);
		val &= ~mask;
		val |= ((vm.vactive / 2 - 1) << shift) & mask;
		zx_writel(vou->timing + SEC_V_ACTIVE, val);

		val = SYNC_WIDE(vm.vsync_len - 1);
		/*
		 * The vback_porch for the second field needs to shift one on
		 * the value for the first field.
		 */
		val |= BACK_PORCH(vm.vback_porch);
		val |= FRONT_PORCH(vm.vfront_porch - 1);
		zx_writel(vou->timing + regs->sec_vtiming, val);
	}

	/* 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;

	zx_writel_mask(vou->timing + TIMING_CTRL, bits->polarity_mask,
		       pol << bits->polarity_shift);

	/* Setup SHIFT register by following what ZTE BSP does */
	val = H_SHIFT_VAL;
	if (interlaced)
		val |= V_SHIFT_VAL << 16;
	zx_writel(vou->timing + regs->timing_shift, val);
	zx_writel(vou->timing + regs->timing_pi_shift, H_PI_SHIFT_VAL);

	/* Progressive or interlace scan select */
	scan_mask = bits->interlace_select | bits->pi_enable;
	zx_writel_mask(vou->timing + SCAN_CTRL, scan_mask,
		       interlaced ? scan_mask : 0);

	/* Enable TIMING_CTRL */
	zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable,
		       bits->tc_enable);

	/* Configure channel screen size */
	zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_W_MASK,
		       vm.hactive << CHN_SCREEN_W_SHIFT);
	zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_H_MASK,
		       vm.vactive << CHN_SCREEN_H_SHIFT);

	/* Configure channel interlace buffer control */
	zx_writel_mask(zcrtc->chnreg + CHN_INTERLACE_BUF_CTRL, CHN_INTERLACE_EN,
		       interlaced ? CHN_INTERLACE_EN : 0);

	/* Update channel */
	vou_chn_set_update(zcrtc);

	/* Enable channel */
	zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, CHN_ENABLE);

	drm_crtc_vblank_on(crtc);

	ret = clk_set_rate(zcrtc->pixclk, mode->clock * 1000);
	if (ret) {
		DRM_DEV_ERROR(vou->dev, "failed to set pixclk rate: %d\n", ret);
		return;
	}

	ret = clk_prepare_enable(zcrtc->pixclk);
	if (ret)
		DRM_DEV_ERROR(vou->dev, "failed to enable pixclk: %d\n", ret);
}

static void zx_crtc_atomic_disable(struct drm_crtc *crtc,
				   struct drm_crtc_state *old_state)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	const struct zx_crtc_bits *bits = zcrtc->bits;
	struct zx_vou_hw *vou = zcrtc->vou;

	clk_disable_unprepare(zcrtc->pixclk);

	drm_crtc_vblank_off(crtc);

	/* Disable channel */
	zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, 0);

	/* Disable TIMING_CTRL */
	zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable, 0);
}

static void zx_crtc_atomic_flush(struct drm_crtc *crtc,
				  struct drm_crtc_state *old_state)
{
	struct drm_pending_vblank_event *event = crtc->state->event;

	if (!event)
		return;

	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 = {
	.atomic_flush = zx_crtc_atomic_flush,
	.atomic_enable = zx_crtc_atomic_enable,
	.atomic_disable = zx_crtc_atomic_disable,
};

static int zx_vou_enable_vblank(struct drm_crtc *crtc)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = crtc_to_vou(crtc);
	u32 int_frame_mask = zcrtc->bits->int_frame_mask;

	zx_writel_mask(vou->timing + TIMING_INT_CTRL, int_frame_mask,
		       int_frame_mask);

	return 0;
}

static void zx_vou_disable_vblank(struct drm_crtc *crtc)
{
	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
	struct zx_vou_hw *vou = crtc_to_vou(crtc);

	zx_writel_mask(vou->timing + TIMING_INT_CTRL,
		       zcrtc->bits->int_frame_mask, 0);
}

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,
	.enable_vblank = zx_vou_enable_vblank,
	.disable_vblank = zx_vou_disable_vblank,
};

static int zx_crtc_init(struct drm_device *drm, struct zx_vou_hw *vou,
			enum vou_chn_type chn_type)
{
	struct device *dev = vou->dev;
	struct zx_plane *zplane;
	struct zx_crtc *zcrtc;
	int ret;

	zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
	if (!zcrtc)
		return -ENOMEM;

	zcrtc->vou = vou;
	zcrtc->chn_type = chn_type;

	zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
	if (!zplane)
		return -ENOMEM;

	zplane->dev = dev;

	if (chn_type == VOU_CHN_MAIN) {
		zplane->layer = vou->osd + MAIN_GL_OFFSET;
		zplane->csc = vou->osd + MAIN_GL_CSC_OFFSET;
		zplane->hbsc = vou->osd + MAIN_HBSC_OFFSET;
		zplane->rsz = vou->otfppu + MAIN_RSZ_OFFSET;
		zplane->bits = &zx_gl_bits[0];
		zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
		zcrtc->chncsc = vou->osd + MAIN_CHN_CSC_OFFSET;
		zcrtc->dither = vou->osd + MAIN_DITHER_OFFSET;
		zcrtc->regs = &main_crtc_regs;
		zcrtc->bits = &main_crtc_bits;
	} else {
		zplane->layer = vou->osd + AUX_GL_OFFSET;
		zplane->csc = vou->osd + AUX_GL_CSC_OFFSET;
		zplane->hbsc = vou->osd + AUX_HBSC_OFFSET;
		zplane->rsz = vou->otfppu + AUX_RSZ_OFFSET;
		zplane->bits = &zx_gl_bits[1];
		zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
		zcrtc->chncsc = vou->osd + AUX_CHN_CSC_OFFSET;
		zcrtc->dither = vou->osd + AUX_DITHER_OFFSET;
		zcrtc->regs = &aux_crtc_regs;
		zcrtc->bits = &aux_crtc_bits;
	}

	zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ?
					  "main_wclk" : "aux_wclk");
	if (IS_ERR(zcrtc->pixclk)) {
		ret = PTR_ERR(zcrtc->pixclk);
		DRM_DEV_ERROR(dev, "failed to get pix clk: %d\n", ret);
		return ret;
	}

	ret = zx_plane_init(drm, zplane, DRM_PLANE_TYPE_PRIMARY);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to init primary plane: %d\n", ret);
		return ret;
	}

	zcrtc->primary = &zplane->plane;

	ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL,
					&zx_crtc_funcs, NULL);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to init drm crtc: %d\n", ret);
		return ret;
	}

	drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs);

	if (chn_type == VOU_CHN_MAIN)
		vou->main_crtc = zcrtc;
	else
		vou->aux_crtc = zcrtc;

	return 0;
}

void zx_vou_layer_enable(struct drm_plane *plane)
{
	struct zx_crtc *zcrtc = to_zx_crtc(plane->state->crtc);
	struct zx_vou_hw *vou = zcrtc->vou;
	struct zx_plane *zplane = to_zx_plane(plane);
	const struct vou_layer_bits *bits = zplane->bits;

	if (zcrtc->chn_type == VOU_CHN_MAIN) {
		zx_writel_mask(vou->osd + OSD_CTRL0, bits->chnsel, 0);
		zx_writel_mask(vou->vouctl + VOU_CLK_SEL, bits->clksel, 0);
	} else {
		zx_writel_mask(vou->osd + OSD_CTRL0, bits->chnsel,
			       bits->chnsel);
		zx_writel_mask(vou->vouctl + VOU_CLK_SEL, bits->clksel,
			       bits->clksel);
	}

	zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, bits->enable);
}

void zx_vou_layer_disable(struct drm_plane *plane,
			  struct drm_plane_state *old_state)
{
	struct zx_crtc *zcrtc = to_zx_crtc(old_state->crtc);
	struct zx_vou_hw *vou = zcrtc->vou;
	struct zx_plane *zplane = to_zx_plane(plane);
	const struct vou_layer_bits *bits = zplane->bits;

	zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, 0);
}

static void zx_overlay_init(struct drm_device *drm, struct zx_vou_hw *vou)
{
	struct device *dev = vou->dev;
	struct zx_plane *zplane;
	int i;
	int ret;

	/*
	 * VL0 has some quirks on scaling support which need special handling.
	 * Let's leave it out for now.
	 */
	for (i = 1; i < VL_NUM; i++) {
		zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
		if (!zplane) {
			DRM_DEV_ERROR(dev, "failed to allocate zplane %d\n", i);
			return;
		}

		zplane->layer = vou->osd + OSD_VL_OFFSET(i);
		zplane->hbsc = vou->osd + HBSC_VL_OFFSET(i);
		zplane->rsz = vou->otfppu + RSZ_VL_OFFSET(i);
		zplane->bits = &zx_vl_bits[i];

		ret = zx_plane_init(drm, zplane, DRM_PLANE_TYPE_OVERLAY);
		if (ret) {
			DRM_DEV_ERROR(dev, "failed to init overlay %d\n", i);
			continue;
		}
	}
}

static inline void zx_osd_int_update(struct zx_crtc *zcrtc)
{
	struct drm_crtc *crtc = &zcrtc->crtc;
	struct drm_plane *plane;

	vou_chn_set_update(zcrtc);

	drm_for_each_plane_mask(plane, crtc->dev, crtc->state->plane_mask)
		zx_plane_set_update(plane);
}

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 = zx_readl(vou->timing + TIMING_INT_STATE);
	zx_writel(vou->timing + TIMING_INT_STATE, 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 = zx_readl(vou->osd + OSD_INT_STA);
	zx_writel(vou->osd + OSD_INT_CLRSTA, state);

	if (state & OSD_INT_MAIN_UPT)
		zx_osd_int_update(vou->main_crtc);

	if (state & OSD_INT_AUX_UPT)
		zx_osd_int_update(vou->aux_crtc);

	if (state & OSD_INT_ERROR)
		DRM_DEV_ERROR(vou->dev, "OSD ERROR: 0x%08x!\n", state);

	return IRQ_HANDLED;
}

static void vou_dtrc_init(struct zx_vou_hw *vou)
{
	/* Clear bit for bypass by ID */
	zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL,
		       TILE2RASTESCAN_BYPASS_MODE, 0);

	/* Select ARIDR mode */
	zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL, DETILE_ARIDR_MODE_MASK,
		       DETILE_ARID_IN_ARIDR);

	/* Bypass decompression for both frames */
	zx_writel_mask(vou->dtrc + DTRC_F0_CTRL, DTRC_DECOMPRESS_BYPASS,
		       DTRC_DECOMPRESS_BYPASS);
	zx_writel_mask(vou->dtrc + DTRC_F1_CTRL, DTRC_DECOMPRESS_BYPASS,
		       DTRC_DECOMPRESS_BYPASS);

	/* Set up ARID register */
	zx_writel(vou->dtrc + DTRC_ARID, DTRC_ARID3(0xf) | DTRC_ARID2(0xe) |
		  DTRC_ARID1(0xf) | DTRC_ARID0(0xe));
}

static void vou_hw_init(struct zx_vou_hw *vou)
{
	/* Release reset for all VOU modules */
	zx_writel(vou->vouctl + VOU_SOFT_RST, ~0);

	/* Enable all VOU module clocks */
	zx_writel(vou->vouctl + VOU_CLK_EN, ~0);

	/* Clear both OSD and TIMING_CTRL interrupt state */
	zx_writel(vou->osd + OSD_INT_CLRSTA, ~0);
	zx_writel(vou->timing + TIMING_INT_STATE, ~0);

	/* Enable OSD and TIMING_CTRL interrrupts */
	zx_writel(vou->osd + OSD_INT_MSK, OSD_INT_ENABLE);
	zx_writel(vou->timing + TIMING_INT_CTRL, TIMING_INT_ENABLE);

	/* Select GPC as input to gl/vl scaler as a sane default setting */
	zx_writel(vou->otfppu + OTFPPU_RSZ_DATA_SOURCE, 0x2a);

	/*
	 * Needs to reset channel and layer logic per frame when frame starts
	 * to get VOU work properly.
	 */
	zx_writel_mask(vou->osd + OSD_RST_CLR, RST_PER_FRAME, RST_PER_FRAME);

	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_vou_hw *vou;
	struct resource *res;
	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)) {
		ret = PTR_ERR(vou->osd);
		DRM_DEV_ERROR(dev, "failed to remap osd region: %d\n", ret);
		return ret;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
	vou->timing = devm_ioremap_resource(dev, res);
	if (IS_ERR(vou->timing)) {
		ret = PTR_ERR(vou->timing);
		DRM_DEV_ERROR(dev, "failed to remap timing_ctrl region: %d\n",
			      ret);
		return ret;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
	vou->dtrc = devm_ioremap_resource(dev, res);
	if (IS_ERR(vou->dtrc)) {
		ret = PTR_ERR(vou->dtrc);
		DRM_DEV_ERROR(dev, "failed to remap dtrc region: %d\n", ret);
		return ret;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
	vou->vouctl = devm_ioremap_resource(dev, res);
	if (IS_ERR(vou->vouctl)) {
		ret = PTR_ERR(vou->vouctl);
		DRM_DEV_ERROR(dev, "failed to remap vou_ctrl region: %d\n",
			      ret);
		return ret;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
	vou->otfppu = devm_ioremap_resource(dev, res);
	if (IS_ERR(vou->otfppu)) {
		ret = PTR_ERR(vou->otfppu);
		DRM_DEV_ERROR(dev, "failed to remap otfppu region: %d\n", ret);
		return ret;
	}

	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)) {
		ret = PTR_ERR(vou->axi_clk);
		DRM_DEV_ERROR(dev, "failed to get axi_clk: %d\n", ret);
		return ret;
	}

	vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
	if (IS_ERR(vou->ppu_clk)) {
		ret = PTR_ERR(vou->ppu_clk);
		DRM_DEV_ERROR(dev, "failed to get ppu_clk: %d\n", ret);
		return ret;
	}

	ret = clk_prepare_enable(vou->axi_clk);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to enable axi_clk: %d\n", ret);
		return ret;
	}

	clk_prepare_enable(vou->ppu_clk);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to enable ppu_clk: %d\n", ret);
		goto disable_axi_clk;
	}

	vou->dev = dev;
	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) {
		DRM_DEV_ERROR(dev, "failed to request vou irq: %d\n", ret);
		goto disable_ppu_clk;
	}

	ret = zx_crtc_init(drm, vou, VOU_CHN_MAIN);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to init main channel crtc: %d\n",
			      ret);
		goto disable_ppu_clk;
	}

	ret = zx_crtc_init(drm, vou, VOU_CHN_AUX);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to init aux channel crtc: %d\n",
			      ret);
		goto disable_ppu_clk;
	}

	zx_overlay_init(drm, vou);

	return 0;

disable_ppu_clk:
	clk_disable_unprepare(vou->ppu_clk);
disable_axi_clk:
	clk_disable_unprepare(vou->axi_clk);
	return ret;
}

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,
	},
};
