summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/exynos
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/gpu/drm/exynos
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/gpu/drm/exynos')
-rw-r--r--drivers/gpu/drm/exynos/Kconfig131
-rw-r--r--drivers/gpu/drm/exynos/Makefile27
-rw-r--r--drivers/gpu/drm/exynos/exynos5433_drm_decon.c881
-rw-r--r--drivers/gpu/drm/exynos/exynos7_drm_decon.c852
-rw-r--r--drivers/gpu/drm/exynos/exynos_dp.c260
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_crtc.c240
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_crtc.h39
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_dma.c140
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_dpi.c262
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_drv.c482
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_drv.h263
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_dsi.c211
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fb.c183
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fb.h22
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fbdev.c137
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fbdev.h27
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fimc.c1417
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_fimd.c1345
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_g2d.c1617
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_g2d.h44
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_gem.c454
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_gem.h100
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_gsc.c1428
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_ipp.c943
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_ipp.h170
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_mic.c472
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_plane.c336
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_plane.h9
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_rotator.c460
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_scaler.c728
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_vidi.c535
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_vidi.h18
-rw-r--r--drivers/gpu/drm/exynos/exynos_hdmi.c2149
-rw-r--r--drivers/gpu/drm/exynos/exynos_mixer.c1340
-rw-r--r--drivers/gpu/drm/exynos/regs-decon5433.h228
-rw-r--r--drivers/gpu/drm/exynos/regs-decon7.h348
-rw-r--r--drivers/gpu/drm/exynos/regs-fimc.h665
-rw-r--r--drivers/gpu/drm/exynos/regs-gsc.h282
-rw-r--r--drivers/gpu/drm/exynos/regs-hdmi.h608
-rw-r--r--drivers/gpu/drm/exynos/regs-mixer.h161
-rw-r--r--drivers/gpu/drm/exynos/regs-rotator.h70
-rw-r--r--drivers/gpu/drm/exynos/regs-scaler.h423
-rw-r--r--drivers/gpu/drm/exynos/regs-vp.h88
43 files changed, 20595 insertions, 0 deletions
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
new file mode 100644
index 000000000..a59131ae1
--- /dev/null
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_EXYNOS
+ tristate "DRM Support for Samsung SoC Exynos Series"
+ depends on OF && DRM && COMMON_CLK
+ depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ depends on MMU
+ select DRM_CLIENT_SELECTION
+ select DRM_DISPLAY_HELPER if DRM_EXYNOS_DP
+ select DRM_KMS_HELPER
+ select VIDEOMODE_HELPERS
+ select FB_DMAMEM_HELPERS if DRM_FBDEV_EMULATION
+ select SND_SOC_HDMI_CODEC if SND_SOC
+ help
+ Choose this option if you have a Samsung SoC Exynos chipset.
+ If M is selected the module will be called exynosdrm.
+
+if DRM_EXYNOS
+
+comment "CRTCs"
+
+config DRM_EXYNOS_FIMD
+ bool "FIMD"
+ depends on !FB_S3C
+ select MFD_SYSCON
+ help
+ Choose this option if you want to use Exynos FIMD for DRM.
+
+config DRM_EXYNOS5433_DECON
+ bool "DECON on Exynos5433"
+ help
+ Choose this option if you want to use Exynos5433 DECON for DRM.
+
+config DRM_EXYNOS7_DECON
+ bool "DECON on Exynos7"
+ depends on !FB_S3C
+ help
+ Choose this option if you want to use Exynos DECON for DRM.
+
+config DRM_EXYNOS_MIXER
+ bool "Mixer"
+ help
+ Choose this option if you want to use Exynos Mixer for DRM.
+
+config DRM_EXYNOS_VIDI
+ bool "Virtual Display"
+ help
+ Choose this option if you want to use Exynos VIDI for DRM.
+
+comment "Encoders and Bridges"
+
+config DRM_EXYNOS_DPI
+ bool "Parallel output"
+ depends on DRM_EXYNOS_FIMD
+ select DRM_PANEL
+ default n
+ help
+ This enables support for Exynos parallel output.
+
+config DRM_EXYNOS_DSI
+ bool "MIPI-DSI host"
+ depends on DRM_EXYNOS_FIMD || DRM_EXYNOS5433_DECON || DRM_EXYNOS7_DECON
+ select DRM_MIPI_DSI
+ select DRM_PANEL
+ select DRM_SAMSUNG_DSIM
+ default n
+ help
+ This enables support for Exynos MIPI-DSI device.
+
+config DRM_EXYNOS_DP
+ bool "Exynos specific extensions for Analogix DP driver"
+ depends on DRM_EXYNOS_FIMD || DRM_EXYNOS7_DECON
+ depends on OF
+ select DRM_ANALOGIX_DP
+ select DRM_BRIDGE_CONNECTOR
+ select DRM_DISPLAY_DP_HELPER
+ default DRM_EXYNOS
+ select DRM_OF_DISPLAY_MODE_BRIDGE
+ select DRM_PANEL
+ help
+ This enables support for DP device.
+
+config DRM_EXYNOS_HDMI
+ bool "HDMI"
+ depends on DRM_EXYNOS_MIXER || DRM_EXYNOS5433_DECON
+ select CEC_CORE if CEC_NOTIFIER
+ help
+ Choose this option if you want to use Exynos HDMI for DRM.
+
+config DRM_EXYNOS_MIC
+ bool "Mobile Image Compressor"
+ depends on DRM_EXYNOS5433_DECON
+ help
+ Choose this option if you want to use Exynos MIC for DRM.
+
+comment "Sub-drivers"
+
+config DRM_EXYNOS_G2D
+ bool "G2D"
+ depends on VIDEO_SAMSUNG_S5P_G2D=n || COMPILE_TEST
+ help
+ Choose this option if you want to use Exynos G2D for DRM.
+
+config DRM_EXYNOS_IPP
+ bool
+
+config DRM_EXYNOS_FIMC
+ bool "FIMC"
+ select DRM_EXYNOS_IPP
+ help
+ Choose this option if you want to use Exynos FIMC for DRM.
+
+config DRM_EXYNOS_ROTATOR
+ bool "Rotator"
+ select DRM_EXYNOS_IPP
+ help
+ Choose this option if you want to use Exynos Rotator for DRM.
+
+config DRM_EXYNOS_SCALER
+ bool "Scaler"
+ select DRM_EXYNOS_IPP
+ help
+ Choose this option if you want to use Exynos Scaler for DRM.
+
+config DRM_EXYNOS_GSC
+ bool "GScaler"
+ depends on VIDEO_SAMSUNG_EXYNOS_GSC=n || COMPILE_TEST
+ select DRM_EXYNOS_IPP
+ help
+ Choose this option if you want to use Exynos GSC for DRM.
+
+endif
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile
new file mode 100644
index 000000000..2fd2f3ee4
--- /dev/null
+++ b/drivers/gpu/drm/exynos/Makefile
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+exynosdrm-y := exynos_drm_drv.o exynos_drm_crtc.o exynos_drm_fb.o \
+ exynos_drm_gem.o exynos_drm_plane.o exynos_drm_dma.o
+
+exynosdrm-$(CONFIG_DRM_FBDEV_EMULATION) += exynos_drm_fbdev.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o
+exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o
+exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_FIMC) += exynos_drm_fimc.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_ROTATOR) += exynos_drm_rotator.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_SCALER) += exynos_drm_scaler.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_GSC) += exynos_drm_gsc.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_MIC) += exynos_drm_mic.o
+
+obj-$(CONFIG_DRM_EXYNOS) += exynosdrm.o
diff --git a/drivers/gpu/drm/exynos/exynos5433_drm_decon.c b/drivers/gpu/drm/exynos/exynos5433_drm_decon.c
new file mode 100644
index 000000000..9ae0fa466
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos5433_drm_decon.c
@@ -0,0 +1,881 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* drivers/gpu/drm/exynos5433_drm_decon.c
+ *
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ * Authors:
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Hyungwon Hwang <human.hwang@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_blend.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_plane.h"
+#include "regs-decon5433.h"
+
+#define DSD_CFG_MUX 0x1004
+#define DSD_CFG_MUX_TE_UNMASK_GLOBAL BIT(13)
+
+#define WINDOWS_NR 5
+#define PRIMARY_WIN 2
+#define CURSON_WIN 4
+
+#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
+
+#define I80_HW_TRG (1 << 0)
+#define IFTYPE_HDMI (1 << 1)
+
+static const char * const decon_clks_name[] = {
+ "pclk",
+ "aclk_decon",
+ "aclk_smmu_decon0x",
+ "aclk_xiu_decon0x",
+ "pclk_smmu_decon0x",
+ "aclk_smmu_decon1x",
+ "aclk_xiu_decon1x",
+ "pclk_smmu_decon1x",
+ "sclk_decon_vclk",
+ "sclk_decon_eclk",
+};
+
+struct decon_context {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct exynos_drm_crtc *crtc;
+ struct exynos_drm_plane planes[WINDOWS_NR];
+ struct exynos_drm_plane_config configs[WINDOWS_NR];
+ void __iomem *addr;
+ struct regmap *sysreg;
+ struct clk *clks[ARRAY_SIZE(decon_clks_name)];
+ unsigned int irq;
+ unsigned int irq_vsync;
+ unsigned int irq_lcd_sys;
+ unsigned int te_irq;
+ unsigned long out_type;
+ int first_win;
+ spinlock_t vblank_lock;
+ u32 frame_id;
+};
+
+static const uint32_t decon_formats[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+};
+
+static const enum drm_plane_type decon_win_types[WINDOWS_NR] = {
+ [PRIMARY_WIN] = DRM_PLANE_TYPE_PRIMARY,
+ [CURSON_WIN] = DRM_PLANE_TYPE_CURSOR,
+};
+
+static const unsigned int capabilities[WINDOWS_NR] = {
+ 0,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+};
+
+static inline void decon_set_bits(struct decon_context *ctx, u32 reg, u32 mask,
+ u32 val)
+{
+ val = (val & mask) | (readl(ctx->addr + reg) & ~mask);
+ writel(val, ctx->addr + reg);
+}
+
+static int decon_enable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ u32 val;
+
+ val = VIDINTCON0_INTEN;
+ if (crtc->i80_mode)
+ val |= VIDINTCON0_FRAMEDONE;
+ else
+ val |= VIDINTCON0_INTFRMEN | VIDINTCON0_FRAMESEL_FP;
+
+ writel(val, ctx->addr + DECON_VIDINTCON0);
+
+ enable_irq(ctx->irq);
+ if (!(ctx->out_type & I80_HW_TRG))
+ enable_irq(ctx->te_irq);
+
+ return 0;
+}
+
+static void decon_disable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+
+ if (!(ctx->out_type & I80_HW_TRG))
+ disable_irq_nosync(ctx->te_irq);
+ disable_irq_nosync(ctx->irq);
+
+ writel(0, ctx->addr + DECON_VIDINTCON0);
+}
+
+/* return number of starts/ends of frame transmissions since reset */
+static u32 decon_get_frame_count(struct decon_context *ctx, bool end)
+{
+ u32 frm, pfrm, status, cnt = 2;
+
+ /* To get consistent result repeat read until frame id is stable.
+ * Usually the loop will be executed once, in rare cases when the loop
+ * is executed at frame change time 2nd pass will be needed.
+ */
+ frm = readl(ctx->addr + DECON_CRFMID);
+ do {
+ status = readl(ctx->addr + DECON_VIDCON1);
+ pfrm = frm;
+ frm = readl(ctx->addr + DECON_CRFMID);
+ } while (frm != pfrm && --cnt);
+
+ /* CRFMID is incremented on BPORCH in case of I80 and on VSYNC in case
+ * of RGB, it should be taken into account.
+ */
+ if (!frm)
+ return 0;
+
+ switch (status & (VIDCON1_VSTATUS_MASK | VIDCON1_I80_ACTIVE)) {
+ case VIDCON1_VSTATUS_VS:
+ if (!(ctx->crtc->i80_mode))
+ --frm;
+ break;
+ case VIDCON1_VSTATUS_BP:
+ --frm;
+ break;
+ case VIDCON1_I80_ACTIVE:
+ case VIDCON1_VSTATUS_AC:
+ if (end)
+ --frm;
+ break;
+ default:
+ break;
+ }
+
+ return frm;
+}
+
+static void decon_setup_trigger(struct decon_context *ctx)
+{
+ if (!ctx->crtc->i80_mode && !(ctx->out_type & I80_HW_TRG))
+ return;
+
+ if (!(ctx->out_type & I80_HW_TRG)) {
+ writel(TRIGCON_TRIGEN_PER_F | TRIGCON_TRIGEN_F |
+ TRIGCON_TE_AUTO_MASK | TRIGCON_SWTRIGEN,
+ ctx->addr + DECON_TRIGCON);
+ return;
+ }
+
+ writel(TRIGCON_TRIGEN_PER_F | TRIGCON_TRIGEN_F | TRIGCON_HWTRIGMASK
+ | TRIGCON_HWTRIGEN, ctx->addr + DECON_TRIGCON);
+
+ if (regmap_update_bits(ctx->sysreg, DSD_CFG_MUX,
+ DSD_CFG_MUX_TE_UNMASK_GLOBAL, ~0))
+ DRM_DEV_ERROR(ctx->dev, "Cannot update sysreg.\n");
+}
+
+static void decon_commit(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ struct drm_display_mode *m = &crtc->base.mode;
+ bool interlaced = false;
+ u32 val;
+
+ if (ctx->out_type & IFTYPE_HDMI) {
+ m->crtc_hsync_start = m->crtc_hdisplay + 10;
+ m->crtc_hsync_end = m->crtc_htotal - 92;
+ m->crtc_vsync_start = m->crtc_vdisplay + 1;
+ m->crtc_vsync_end = m->crtc_vsync_start + 1;
+ if (m->flags & DRM_MODE_FLAG_INTERLACE)
+ interlaced = true;
+ }
+
+ decon_setup_trigger(ctx);
+
+ /* lcd on and use command if */
+ val = VIDOUT_LCD_ON;
+ if (interlaced)
+ val |= VIDOUT_INTERLACE_EN_F;
+ if (crtc->i80_mode) {
+ val |= VIDOUT_COMMAND_IF;
+ } else {
+ val |= VIDOUT_RGB_IF;
+ }
+
+ writel(val, ctx->addr + DECON_VIDOUTCON0);
+
+ if (interlaced)
+ val = VIDTCON2_LINEVAL(m->vdisplay / 2 - 1) |
+ VIDTCON2_HOZVAL(m->hdisplay - 1);
+ else
+ val = VIDTCON2_LINEVAL(m->vdisplay - 1) |
+ VIDTCON2_HOZVAL(m->hdisplay - 1);
+ writel(val, ctx->addr + DECON_VIDTCON2);
+
+ if (!crtc->i80_mode) {
+ int vbp = m->crtc_vtotal - m->crtc_vsync_end;
+ int vfp = m->crtc_vsync_start - m->crtc_vdisplay;
+
+ if (interlaced)
+ vbp = vbp / 2 - 1;
+ val = VIDTCON00_VBPD_F(vbp - 1) | VIDTCON00_VFPD_F(vfp - 1);
+ writel(val, ctx->addr + DECON_VIDTCON00);
+
+ val = VIDTCON01_VSPW_F(
+ m->crtc_vsync_end - m->crtc_vsync_start - 1);
+ writel(val, ctx->addr + DECON_VIDTCON01);
+
+ val = VIDTCON10_HBPD_F(
+ m->crtc_htotal - m->crtc_hsync_end - 1) |
+ VIDTCON10_HFPD_F(
+ m->crtc_hsync_start - m->crtc_hdisplay - 1);
+ writel(val, ctx->addr + DECON_VIDTCON10);
+
+ val = VIDTCON11_HSPW_F(
+ m->crtc_hsync_end - m->crtc_hsync_start - 1);
+ writel(val, ctx->addr + DECON_VIDTCON11);
+ }
+
+ /* enable output and display signal */
+ decon_set_bits(ctx, DECON_VIDCON0, VIDCON0_ENVID | VIDCON0_ENVID_F, ~0);
+
+ decon_set_bits(ctx, DECON_UPDATE, STANDALONE_UPDATE_F, ~0);
+}
+
+static void decon_win_set_bldeq(struct decon_context *ctx, unsigned int win,
+ unsigned int alpha, unsigned int pixel_alpha)
+{
+ u32 mask = BLENDERQ_A_FUNC_F(0xf) | BLENDERQ_B_FUNC_F(0xf);
+ u32 val = 0;
+
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ case DRM_MODE_BLEND_COVERAGE:
+ val |= BLENDERQ_A_FUNC_F(BLENDERQ_ALPHA_A);
+ val |= BLENDERQ_B_FUNC_F(BLENDERQ_ONE_MINUS_ALPHA_A);
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ default:
+ if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
+ val |= BLENDERQ_A_FUNC_F(BLENDERQ_ALPHA0);
+ val |= BLENDERQ_B_FUNC_F(BLENDERQ_ONE_MINUS_ALPHA_A);
+ } else {
+ val |= BLENDERQ_A_FUNC_F(BLENDERQ_ONE);
+ val |= BLENDERQ_B_FUNC_F(BLENDERQ_ONE_MINUS_ALPHA_A);
+ }
+ break;
+ }
+ decon_set_bits(ctx, DECON_BLENDERQx(win), mask, val);
+}
+
+static void decon_win_set_bldmod(struct decon_context *ctx, unsigned int win,
+ unsigned int alpha, unsigned int pixel_alpha)
+{
+ u32 win_alpha = alpha >> 8;
+ u32 val = 0;
+
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ break;
+ case DRM_MODE_BLEND_COVERAGE:
+ case DRM_MODE_BLEND_PREMULTI:
+ default:
+ val |= WINCONx_ALPHA_SEL_F;
+ val |= WINCONx_BLD_PIX_F;
+ val |= WINCONx_ALPHA_MUL_F;
+ break;
+ }
+ decon_set_bits(ctx, DECON_WINCONx(win), WINCONx_BLEND_MODE_MASK, val);
+
+ if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
+ val = VIDOSD_Wx_ALPHA_R_F(win_alpha) |
+ VIDOSD_Wx_ALPHA_G_F(win_alpha) |
+ VIDOSD_Wx_ALPHA_B_F(win_alpha);
+ decon_set_bits(ctx, DECON_VIDOSDxC(win),
+ VIDOSDxC_ALPHA0_RGB_MASK, val);
+ decon_set_bits(ctx, DECON_BLENDCON, BLEND_NEW, BLEND_NEW);
+ }
+}
+
+static void decon_win_set_pixfmt(struct decon_context *ctx, unsigned int win,
+ struct drm_framebuffer *fb)
+{
+ struct exynos_drm_plane *plane = &ctx->planes[win];
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ unsigned int alpha = state->base.alpha;
+ unsigned int pixel_alpha;
+ unsigned long val;
+
+ if (fb->format->has_alpha)
+ pixel_alpha = state->base.pixel_blend_mode;
+ else
+ pixel_alpha = DRM_MODE_BLEND_PIXEL_NONE;
+
+ val = readl(ctx->addr + DECON_WINCONx(win));
+ val &= WINCONx_ENWIN_F;
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_XRGB1555:
+ val |= WINCONx_BPPMODE_16BPP_I1555;
+ val |= WINCONx_HAWSWP_F;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_RGB565:
+ val |= WINCONx_BPPMODE_16BPP_565;
+ val |= WINCONx_HAWSWP_F;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ val |= WINCONx_BPPMODE_24BPP_888;
+ val |= WINCONx_WSWP_F;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ default:
+ val |= WINCONx_BPPMODE_32BPP_A8888;
+ val |= WINCONx_WSWP_F;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ }
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "cpp = %u\n", fb->format->cpp[0]);
+
+ /*
+ * In case of exynos, setting dma-burst to 16Word causes permanent
+ * tearing for very small buffers, e.g. cursor buffer. Burst Mode
+ * switching which is based on plane size is not recommended as
+ * plane size varies a lot towards the end of the screen and rapid
+ * movement causes unstable DMA which results into iommu crash/tear.
+ */
+
+ if (fb->width < MIN_FB_WIDTH_FOR_16WORD_BURST) {
+ val &= ~WINCONx_BURSTLEN_MASK;
+ val |= WINCONx_BURSTLEN_8WORD;
+ }
+ decon_set_bits(ctx, DECON_WINCONx(win), ~WINCONx_BLEND_MODE_MASK, val);
+
+ if (win > 0) {
+ decon_win_set_bldmod(ctx, win, alpha, pixel_alpha);
+ decon_win_set_bldeq(ctx, win, alpha, pixel_alpha);
+ }
+}
+
+static void decon_shadow_protect(struct decon_context *ctx, bool protect)
+{
+ decon_set_bits(ctx, DECON_SHADOWCON, SHADOWCON_PROTECT_MASK,
+ protect ? ~0 : 0);
+}
+
+static void decon_atomic_begin(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+
+ decon_shadow_protect(ctx, true);
+}
+
+#define BIT_VAL(x, e, s) (((x) & ((1 << ((e) - (s) + 1)) - 1)) << (s))
+#define COORDINATE_X(x) BIT_VAL((x), 23, 12)
+#define COORDINATE_Y(x) BIT_VAL((x), 11, 0)
+
+static void decon_update_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ struct decon_context *ctx = crtc->ctx;
+ struct drm_framebuffer *fb = state->base.fb;
+ unsigned int win = plane->index;
+ unsigned int cpp = fb->format->cpp[0];
+ unsigned int pitch = fb->pitches[0];
+ dma_addr_t dma_addr = exynos_drm_fb_dma_addr(fb, 0);
+ u32 val;
+
+ if (crtc->base.mode.flags & DRM_MODE_FLAG_INTERLACE) {
+ val = COORDINATE_X(state->crtc.x) |
+ COORDINATE_Y(state->crtc.y / 2);
+ writel(val, ctx->addr + DECON_VIDOSDxA(win));
+
+ val = COORDINATE_X(state->crtc.x + state->crtc.w - 1) |
+ COORDINATE_Y((state->crtc.y + state->crtc.h) / 2 - 1);
+ writel(val, ctx->addr + DECON_VIDOSDxB(win));
+ } else {
+ val = COORDINATE_X(state->crtc.x) | COORDINATE_Y(state->crtc.y);
+ writel(val, ctx->addr + DECON_VIDOSDxA(win));
+
+ val = COORDINATE_X(state->crtc.x + state->crtc.w - 1) |
+ COORDINATE_Y(state->crtc.y + state->crtc.h - 1);
+ writel(val, ctx->addr + DECON_VIDOSDxB(win));
+ }
+
+ val = VIDOSD_Wx_ALPHA_R_F(0xff) | VIDOSD_Wx_ALPHA_G_F(0xff) |
+ VIDOSD_Wx_ALPHA_B_F(0xff);
+ writel(val, ctx->addr + DECON_VIDOSDxC(win));
+
+ val = VIDOSD_Wx_ALPHA_R_F(0x0) | VIDOSD_Wx_ALPHA_G_F(0x0) |
+ VIDOSD_Wx_ALPHA_B_F(0x0);
+ writel(val, ctx->addr + DECON_VIDOSDxD(win));
+
+ writel(dma_addr, ctx->addr + DECON_VIDW0xADD0B0(win));
+
+ val = dma_addr + pitch * state->src.h;
+ writel(val, ctx->addr + DECON_VIDW0xADD1B0(win));
+
+ if (!(ctx->out_type & IFTYPE_HDMI))
+ val = BIT_VAL(pitch - state->crtc.w * cpp, 27, 14)
+ | BIT_VAL(state->crtc.w * cpp, 13, 0);
+ else
+ val = BIT_VAL(pitch - state->crtc.w * cpp, 29, 15)
+ | BIT_VAL(state->crtc.w * cpp, 14, 0);
+ writel(val, ctx->addr + DECON_VIDW0xADD2(win));
+
+ decon_win_set_pixfmt(ctx, win, fb);
+
+ /* window enable */
+ decon_set_bits(ctx, DECON_WINCONx(win), WINCONx_ENWIN_F, ~0);
+}
+
+static void decon_disable_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct decon_context *ctx = crtc->ctx;
+ unsigned int win = plane->index;
+
+ decon_set_bits(ctx, DECON_WINCONx(win), WINCONx_ENWIN_F, 0);
+}
+
+static void decon_atomic_flush(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->vblank_lock, flags);
+
+ decon_shadow_protect(ctx, false);
+
+ decon_set_bits(ctx, DECON_UPDATE, STANDALONE_UPDATE_F, ~0);
+
+ ctx->frame_id = decon_get_frame_count(ctx, true);
+
+ exynos_crtc_handle_event(crtc);
+
+ spin_unlock_irqrestore(&ctx->vblank_lock, flags);
+}
+
+static void decon_swreset(struct decon_context *ctx)
+{
+ unsigned long flags;
+ u32 val;
+ int ret;
+
+ writel(0, ctx->addr + DECON_VIDCON0);
+ readl_poll_timeout(ctx->addr + DECON_VIDCON0, val,
+ ~val & VIDCON0_STOP_STATUS, 12, 20000);
+
+ writel(VIDCON0_SWRESET, ctx->addr + DECON_VIDCON0);
+ ret = readl_poll_timeout(ctx->addr + DECON_VIDCON0, val,
+ ~val & VIDCON0_SWRESET, 12, 20000);
+
+ WARN(ret < 0, "failed to software reset DECON\n");
+
+ spin_lock_irqsave(&ctx->vblank_lock, flags);
+ ctx->frame_id = 0;
+ spin_unlock_irqrestore(&ctx->vblank_lock, flags);
+
+ if (!(ctx->out_type & IFTYPE_HDMI))
+ return;
+
+ writel(VIDCON0_CLKVALUP | VIDCON0_VLCKFREE, ctx->addr + DECON_VIDCON0);
+ decon_set_bits(ctx, DECON_CMU,
+ CMU_CLKGAGE_MODE_SFR_F | CMU_CLKGAGE_MODE_MEM_F, ~0);
+ writel(VIDCON1_VCLK_RUN_VDEN_DISABLE, ctx->addr + DECON_VIDCON1);
+ writel(CRCCTRL_CRCEN | CRCCTRL_CRCSTART_F | CRCCTRL_CRCCLKEN,
+ ctx->addr + DECON_CRCCTRL);
+}
+
+static void decon_atomic_enable(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(ctx->dev, "failed to enable DECON device.\n");
+ return;
+ }
+
+ exynos_drm_pipe_clk_enable(crtc, true);
+
+ decon_swreset(ctx);
+
+ decon_commit(ctx->crtc);
+}
+
+static void decon_atomic_disable(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int i;
+
+ if (!(ctx->out_type & I80_HW_TRG))
+ synchronize_irq(ctx->te_irq);
+ synchronize_irq(ctx->irq);
+
+ /*
+ * We need to make sure that all windows are disabled before we
+ * suspend that connector. Otherwise we might try to scan from
+ * a destroyed buffer later.
+ */
+ for (i = ctx->first_win; i < WINDOWS_NR; i++)
+ decon_disable_plane(crtc, &ctx->planes[i]);
+
+ decon_swreset(ctx);
+
+ exynos_drm_pipe_clk_enable(crtc, false);
+
+ pm_runtime_put_sync(ctx->dev);
+}
+
+static irqreturn_t decon_te_irq_handler(int irq, void *dev_id)
+{
+ struct decon_context *ctx = dev_id;
+
+ decon_set_bits(ctx, DECON_TRIGCON, TRIGCON_SWTRIGCMD, ~0);
+
+ return IRQ_HANDLED;
+}
+
+static void decon_clear_channels(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int win, i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(decon_clks_name); i++) {
+ ret = clk_prepare_enable(ctx->clks[i]);
+ if (ret < 0)
+ goto err;
+ }
+
+ decon_shadow_protect(ctx, true);
+ for (win = 0; win < WINDOWS_NR; win++)
+ decon_set_bits(ctx, DECON_WINCONx(win), WINCONx_ENWIN_F, 0);
+ decon_shadow_protect(ctx, false);
+
+ decon_set_bits(ctx, DECON_UPDATE, STANDALONE_UPDATE_F, ~0);
+
+ /* TODO: wait for possible vsync */
+ msleep(50);
+
+err:
+ while (--i >= 0)
+ clk_disable_unprepare(ctx->clks[i]);
+}
+
+static enum drm_mode_status decon_mode_valid(struct exynos_drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct decon_context *ctx = crtc->ctx;
+
+ ctx->irq = crtc->i80_mode ? ctx->irq_lcd_sys : ctx->irq_vsync;
+
+ if (ctx->irq)
+ return MODE_OK;
+
+ dev_info(ctx->dev, "Sink requires %s mode, but appropriate interrupt is not provided.\n",
+ crtc->i80_mode ? "command" : "video");
+
+ return MODE_BAD;
+}
+
+static const struct exynos_drm_crtc_ops decon_crtc_ops = {
+ .atomic_enable = decon_atomic_enable,
+ .atomic_disable = decon_atomic_disable,
+ .enable_vblank = decon_enable_vblank,
+ .disable_vblank = decon_disable_vblank,
+ .atomic_begin = decon_atomic_begin,
+ .update_plane = decon_update_plane,
+ .disable_plane = decon_disable_plane,
+ .mode_valid = decon_mode_valid,
+ .atomic_flush = decon_atomic_flush,
+};
+
+static int decon_bind(struct device *dev, struct device *master, void *data)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_plane *exynos_plane;
+ enum exynos_drm_output_type out_type;
+ unsigned int win;
+ int ret;
+
+ ctx->drm_dev = drm_dev;
+
+ for (win = ctx->first_win; win < WINDOWS_NR; win++) {
+ ctx->configs[win].pixel_formats = decon_formats;
+ ctx->configs[win].num_pixel_formats = ARRAY_SIZE(decon_formats);
+ ctx->configs[win].zpos = win - ctx->first_win;
+ ctx->configs[win].type = decon_win_types[win];
+ ctx->configs[win].capabilities = capabilities[win];
+
+ ret = exynos_plane_init(drm_dev, &ctx->planes[win], win,
+ &ctx->configs[win]);
+ if (ret)
+ return ret;
+ }
+
+ exynos_plane = &ctx->planes[PRIMARY_WIN];
+ out_type = (ctx->out_type & IFTYPE_HDMI) ? EXYNOS_DISPLAY_TYPE_HDMI
+ : EXYNOS_DISPLAY_TYPE_LCD;
+ ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
+ out_type, &decon_crtc_ops, ctx);
+ if (IS_ERR(ctx->crtc))
+ return PTR_ERR(ctx->crtc);
+
+ decon_clear_channels(ctx->crtc);
+
+ return exynos_drm_register_dma(drm_dev, dev, &ctx->dma_priv);
+}
+
+static void decon_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+
+ decon_atomic_disable(ctx->crtc);
+
+ /* detach this sub driver from iommu mapping if supported. */
+ exynos_drm_unregister_dma(ctx->drm_dev, ctx->dev, &ctx->dma_priv);
+}
+
+static const struct component_ops decon_component_ops = {
+ .bind = decon_bind,
+ .unbind = decon_unbind,
+};
+
+static void decon_handle_vblank(struct decon_context *ctx)
+{
+ u32 frm;
+
+ spin_lock(&ctx->vblank_lock);
+
+ frm = decon_get_frame_count(ctx, true);
+
+ if (frm != ctx->frame_id) {
+ /* handle only if incremented, take care of wrap-around */
+ if ((s32)(frm - ctx->frame_id) > 0)
+ drm_crtc_handle_vblank(&ctx->crtc->base);
+ ctx->frame_id = frm;
+ }
+
+ spin_unlock(&ctx->vblank_lock);
+}
+
+static irqreturn_t decon_irq_handler(int irq, void *dev_id)
+{
+ struct decon_context *ctx = dev_id;
+ u32 val;
+
+ val = readl(ctx->addr + DECON_VIDINTCON1);
+ val &= VIDINTCON1_INTFRMDONEPEND | VIDINTCON1_INTFRMPEND;
+
+ if (val) {
+ writel(val, ctx->addr + DECON_VIDINTCON1);
+ if (ctx->out_type & IFTYPE_HDMI) {
+ val = readl(ctx->addr + DECON_VIDOUTCON0);
+ val &= VIDOUT_INTERLACE_EN_F | VIDOUT_INTERLACE_FIELD_F;
+ if (val ==
+ (VIDOUT_INTERLACE_EN_F | VIDOUT_INTERLACE_FIELD_F))
+ return IRQ_HANDLED;
+ }
+ decon_handle_vblank(ctx);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int exynos5433_decon_suspend(struct device *dev)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+ int i = ARRAY_SIZE(decon_clks_name);
+
+ while (--i >= 0)
+ clk_disable_unprepare(ctx->clks[i]);
+
+ return 0;
+}
+
+static int exynos5433_decon_resume(struct device *dev)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(decon_clks_name); i++) {
+ ret = clk_prepare_enable(ctx->clks[i]);
+ if (ret < 0)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ while (--i >= 0)
+ clk_disable_unprepare(ctx->clks[i]);
+
+ return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(exynos5433_decon_pm_ops,
+ exynos5433_decon_suspend,
+ exynos5433_decon_resume, NULL);
+
+static const struct of_device_id exynos5433_decon_driver_dt_match[] = {
+ {
+ .compatible = "samsung,exynos5433-decon",
+ .data = (void *)I80_HW_TRG
+ },
+ {
+ .compatible = "samsung,exynos5433-decon-tv",
+ .data = (void *)(I80_HW_TRG | IFTYPE_HDMI)
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5433_decon_driver_dt_match);
+
+static int decon_conf_irq(struct decon_context *ctx, const char *name,
+ irq_handler_t handler, unsigned long int flags)
+{
+ struct platform_device *pdev = to_platform_device(ctx->dev);
+ int ret, irq = platform_get_irq_byname(pdev, name);
+
+ if (irq < 0) {
+ switch (irq) {
+ case -EPROBE_DEFER:
+ return irq;
+ case -ENODATA:
+ case -ENXIO:
+ return 0;
+ default:
+ dev_err(ctx->dev, "IRQ %s get failed, %d\n", name, irq);
+ return irq;
+ }
+ }
+ ret = devm_request_irq(ctx->dev, irq, handler,
+ flags | IRQF_NO_AUTOEN, "drm_decon", ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "IRQ %s request failed\n", name);
+ return ret;
+ }
+
+ return irq;
+}
+
+static int exynos5433_decon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct decon_context *ctx;
+ int ret;
+ int i;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->out_type = (unsigned long)of_device_get_match_data(dev);
+ spin_lock_init(&ctx->vblank_lock);
+
+ if (ctx->out_type & IFTYPE_HDMI)
+ ctx->first_win = 1;
+
+ for (i = 0; i < ARRAY_SIZE(decon_clks_name); i++) {
+ struct clk *clk;
+
+ clk = devm_clk_get(ctx->dev, decon_clks_name[i]);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ ctx->clks[i] = clk;
+ }
+
+ ctx->addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->addr))
+ return PTR_ERR(ctx->addr);
+
+ ret = decon_conf_irq(ctx, "vsync", decon_irq_handler, 0);
+ if (ret < 0)
+ return ret;
+ ctx->irq_vsync = ret;
+
+ ret = decon_conf_irq(ctx, "lcd_sys", decon_irq_handler, 0);
+ if (ret < 0)
+ return ret;
+ ctx->irq_lcd_sys = ret;
+
+ ret = decon_conf_irq(ctx, "te", decon_te_irq_handler,
+ IRQF_TRIGGER_RISING);
+ if (ret < 0)
+ return ret;
+ if (ret) {
+ ctx->te_irq = ret;
+ ctx->out_type &= ~I80_HW_TRG;
+ }
+
+ if (ctx->out_type & I80_HW_TRG) {
+ ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,disp-sysreg");
+ if (IS_ERR(ctx->sysreg)) {
+ dev_err(dev, "failed to get system register\n");
+ return PTR_ERR(ctx->sysreg);
+ }
+ }
+
+ platform_set_drvdata(pdev, ctx);
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &decon_component_ops);
+ if (ret)
+ goto err_disable_pm_runtime;
+
+ return 0;
+
+err_disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void exynos5433_decon_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ component_del(&pdev->dev, &decon_component_ops);
+}
+
+struct platform_driver exynos5433_decon_driver = {
+ .probe = exynos5433_decon_probe,
+ .remove = exynos5433_decon_remove,
+ .driver = {
+ .name = "exynos5433-decon",
+ .pm = pm_ptr(&exynos5433_decon_pm_ops),
+ .of_match_table = exynos5433_decon_driver_dt_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos7_drm_decon.c b/drivers/gpu/drm/exynos/exynos7_drm_decon.c
new file mode 100644
index 000000000..bb74b17f9
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos7_drm_decon.c
@@ -0,0 +1,852 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* drivers/gpu/drm/exynos/exynos7_drm_decon.c
+ *
+ * Copyright (C) 2014 Samsung Electronics Co.Ltd
+ * Authors:
+ * Akshu Agarwal <akshua@gmail.com>
+ * Ajay Kumar <ajaykumar.rs@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_plane.h"
+#include "regs-decon7.h"
+
+/*
+ * DECON stands for Display and Enhancement controller.
+ */
+
+#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
+
+#define WINDOWS_NR 2
+
+struct decon_data {
+ unsigned int vidw_buf_start_base;
+ unsigned int shadowcon_win_protect_shift;
+ unsigned int wincon_burstlen_shift;
+};
+
+static const struct decon_data exynos7_decon_data = {
+ .vidw_buf_start_base = 0x80,
+ .shadowcon_win_protect_shift = 10,
+ .wincon_burstlen_shift = 11,
+};
+
+static const struct decon_data exynos7870_decon_data = {
+ .vidw_buf_start_base = 0x880,
+ .shadowcon_win_protect_shift = 8,
+ .wincon_burstlen_shift = 10,
+};
+
+struct decon_context {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct exynos_drm_crtc *crtc;
+ struct exynos_drm_plane planes[WINDOWS_NR];
+ struct exynos_drm_plane_config configs[WINDOWS_NR];
+ struct clk *pclk;
+ struct clk *aclk;
+ struct clk *eclk;
+ struct clk *vclk;
+ void __iomem *regs;
+ unsigned long irq_flags;
+ bool i80_if;
+ wait_queue_head_t wait_vsync_queue;
+ atomic_t wait_vsync_event;
+
+ const struct decon_data *data;
+ struct drm_encoder *encoder;
+};
+
+static const struct of_device_id decon_driver_dt_match[] = {
+ {
+ .compatible = "samsung,exynos7-decon",
+ .data = &exynos7_decon_data,
+ },
+ {
+ .compatible = "samsung,exynos7870-decon",
+ .data = &exynos7870_decon_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, decon_driver_dt_match);
+
+static const uint32_t decon_formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_BGRA8888,
+};
+
+static const enum drm_plane_type decon_win_types[WINDOWS_NR] = {
+ DRM_PLANE_TYPE_PRIMARY,
+ DRM_PLANE_TYPE_CURSOR,
+};
+
+/**
+ * decon_shadow_protect_win() - disable updating values from shadow registers at vsync
+ *
+ * @ctx: display and enhancement controller context
+ * @win: window to protect registers for
+ * @protect: 1 to protect (disable updates)
+ */
+static void decon_shadow_protect_win(struct decon_context *ctx,
+ unsigned int win, bool protect)
+{
+ u32 bits, val;
+ unsigned int shift = ctx->data->shadowcon_win_protect_shift;
+
+ bits = SHADOWCON_WINx_PROTECT(shift, win);
+
+ val = readl(ctx->regs + SHADOWCON);
+ if (protect)
+ val |= bits;
+ else
+ val &= ~bits;
+ writel(val, ctx->regs + SHADOWCON);
+}
+
+static void decon_wait_for_vblank(struct decon_context *ctx)
+{
+ atomic_set(&ctx->wait_vsync_event, 1);
+
+ /*
+ * wait for DECON to signal VSYNC interrupt or return after
+ * timeout which is set to 50ms (refresh rate of 20).
+ */
+ if (!wait_event_timeout(ctx->wait_vsync_queue,
+ !atomic_read(&ctx->wait_vsync_event),
+ HZ/20))
+ DRM_DEV_DEBUG_KMS(ctx->dev, "vblank wait timed out.\n");
+}
+
+static void decon_clear_channels(struct decon_context *ctx)
+{
+ unsigned int win, ch_enabled = 0;
+ u32 val;
+
+ /* Check if any channel is enabled. */
+ for (win = 0; win < WINDOWS_NR; win++) {
+ val = readl(ctx->regs + WINCON(win));
+
+ if (val & WINCONx_ENWIN) {
+ decon_shadow_protect_win(ctx, win, true);
+
+ val &= ~WINCONx_ENWIN;
+ writel(val, ctx->regs + WINCON(win));
+ ch_enabled = 1;
+
+ decon_shadow_protect_win(ctx, win, false);
+ }
+ }
+
+ val = readl(ctx->regs + DECON_UPDATE);
+ val |= DECON_UPDATE_STANDALONE_F;
+ writel(val, ctx->regs + DECON_UPDATE);
+
+ /* Wait for vsync, as disable channel takes effect at next vsync */
+ if (ch_enabled)
+ decon_wait_for_vblank(ctx);
+}
+
+static int decon_ctx_initialize(struct decon_context *ctx,
+ struct drm_device *drm_dev)
+{
+ ctx->drm_dev = drm_dev;
+
+ decon_clear_channels(ctx);
+
+ return exynos_drm_register_dma(drm_dev, ctx->dev, &ctx->dma_priv);
+}
+
+static void decon_ctx_remove(struct decon_context *ctx)
+{
+ /* detach this sub driver from iommu mapping if supported. */
+ exynos_drm_unregister_dma(ctx->drm_dev, ctx->dev, &ctx->dma_priv);
+}
+
+static u32 decon_calc_clkdiv(struct decon_context *ctx,
+ const struct drm_display_mode *mode)
+{
+ unsigned long ideal_clk = mode->clock * 1000;
+ u32 clkdiv;
+
+ /* Find the clock divider value that gets us closest to ideal_clk */
+ clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->vclk), ideal_clk);
+
+ return (clkdiv < 0x100) ? clkdiv : 0xff;
+}
+
+static void decon_commit(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ struct drm_display_mode *mode = &crtc->base.state->adjusted_mode;
+ u32 val, clkdiv;
+
+ /* nothing to do if we haven't set the mode yet */
+ if (mode->htotal == 0 || mode->vtotal == 0)
+ return;
+
+ if (!ctx->i80_if) {
+ int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
+ /* setup vertical timing values. */
+ vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+ vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
+ vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
+
+ val = VIDTCON0_VBPD(vbpd - 1) | VIDTCON0_VFPD(vfpd - 1);
+ writel(val, ctx->regs + VIDTCON0);
+
+ val = VIDTCON1_VSPW(vsync_len - 1);
+ writel(val, ctx->regs + VIDTCON1);
+
+ /* setup horizontal timing values. */
+ hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+ hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
+ hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
+
+ /* setup horizontal timing values. */
+ val = VIDTCON2_HBPD(hbpd - 1) | VIDTCON2_HFPD(hfpd - 1);
+ writel(val, ctx->regs + VIDTCON2);
+
+ val = VIDTCON3_HSPW(hsync_len - 1);
+ writel(val, ctx->regs + VIDTCON3);
+ }
+
+ /* setup horizontal and vertical display size. */
+ val = VIDTCON4_LINEVAL(mode->vdisplay - 1) |
+ VIDTCON4_HOZVAL(mode->hdisplay - 1);
+ writel(val, ctx->regs + VIDTCON4);
+
+ writel(mode->vdisplay - 1, ctx->regs + LINECNT_OP_THRESHOLD);
+
+ /*
+ * fields of register with prefix '_F' would be updated
+ * at vsync(same as dma start)
+ */
+ val = VIDCON0_ENVID | VIDCON0_ENVID_F;
+ writel(val, ctx->regs + VIDCON0);
+
+ clkdiv = decon_calc_clkdiv(ctx, mode);
+ if (clkdiv > 1) {
+ val = VCLKCON1_CLKVAL_NUM_VCLK(clkdiv - 1);
+ writel(val, ctx->regs + VCLKCON1);
+ writel(val, ctx->regs + VCLKCON2);
+ }
+
+ val = readl(ctx->regs + DECON_UPDATE);
+ val |= DECON_UPDATE_STANDALONE_F;
+ writel(val, ctx->regs + DECON_UPDATE);
+}
+
+static int decon_enable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ u32 val;
+
+ if (!test_and_set_bit(0, &ctx->irq_flags)) {
+ val = readl(ctx->regs + VIDINTCON0);
+
+ val |= VIDINTCON0_INT_ENABLE;
+
+ if (!ctx->i80_if) {
+ val |= VIDINTCON0_INT_FRAME;
+ val &= ~VIDINTCON0_FRAMESEL0_MASK;
+ val |= VIDINTCON0_FRAMESEL0_VSYNC;
+ }
+
+ writel(val, ctx->regs + VIDINTCON0);
+ }
+
+ return 0;
+}
+
+static void decon_disable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ u32 val;
+
+ if (test_and_clear_bit(0, &ctx->irq_flags)) {
+ val = readl(ctx->regs + VIDINTCON0);
+
+ val &= ~VIDINTCON0_INT_ENABLE;
+ if (!ctx->i80_if)
+ val &= ~VIDINTCON0_INT_FRAME;
+
+ writel(val, ctx->regs + VIDINTCON0);
+ }
+}
+
+static void decon_win_set_pixfmt(struct decon_context *ctx, unsigned int win,
+ struct drm_framebuffer *fb)
+{
+ unsigned long val;
+ int padding;
+ unsigned int shift = ctx->data->wincon_burstlen_shift;
+
+ val = readl(ctx->regs + WINCON(win));
+ val &= ~WINCONx_BPPMODE_MASK;
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ val |= WINCONx_BPPMODE_16BPP_565;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_XRGB8888:
+ val |= WINCONx_BPPMODE_24BPP_xRGB;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_XBGR8888:
+ val |= WINCONx_BPPMODE_24BPP_xBGR;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_RGBX8888:
+ val |= WINCONx_BPPMODE_24BPP_RGBx;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_BGRX8888:
+ val |= WINCONx_BPPMODE_24BPP_BGRx;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_ARGB8888:
+ val |= WINCONx_BPPMODE_32BPP_ARGB | WINCONx_BLD_PIX |
+ WINCONx_ALPHA_SEL;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_ABGR8888:
+ val |= WINCONx_BPPMODE_32BPP_ABGR | WINCONx_BLD_PIX |
+ WINCONx_ALPHA_SEL;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_RGBA8888:
+ val |= WINCONx_BPPMODE_32BPP_RGBA | WINCONx_BLD_PIX |
+ WINCONx_ALPHA_SEL;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ case DRM_FORMAT_BGRA8888:
+ default:
+ val |= WINCONx_BPPMODE_32BPP_BGRA | WINCONx_BLD_PIX |
+ WINCONx_ALPHA_SEL;
+ val |= WINCONx_BURSTLEN_16WORD(shift);
+ break;
+ }
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "cpp = %d\n", fb->format->cpp[0]);
+
+ /*
+ * In case of exynos, setting dma-burst to 16Word causes permanent
+ * tearing for very small buffers, e.g. cursor buffer. Burst Mode
+ * switching which is based on plane size is not recommended as
+ * plane size varies a lot towards the end of the screen and rapid
+ * movement causes unstable DMA which results into iommu crash/tear.
+ */
+
+ padding = (fb->pitches[0] / fb->format->cpp[0]) - fb->width;
+ if (fb->width + padding < MIN_FB_WIDTH_FOR_16WORD_BURST) {
+ val &= ~WINCONx_BURSTLEN_MASK(shift);
+ val |= WINCONx_BURSTLEN_8WORD(shift);
+ }
+
+ writel(val, ctx->regs + WINCON(win));
+}
+
+static void decon_win_set_colkey(struct decon_context *ctx, unsigned int win)
+{
+ unsigned int keycon0 = 0, keycon1 = 0;
+
+ keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
+ WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);
+
+ keycon1 = WxKEYCON1_COLVAL(0xffffffff);
+
+ writel(keycon0, ctx->regs + WKEYCON0_BASE(win));
+ writel(keycon1, ctx->regs + WKEYCON1_BASE(win));
+}
+
+static void decon_atomic_begin(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int i;
+
+ for (i = 0; i < WINDOWS_NR; i++)
+ decon_shadow_protect_win(ctx, i, true);
+}
+
+static void decon_update_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ struct decon_context *ctx = crtc->ctx;
+ struct drm_framebuffer *fb = state->base.fb;
+ int padding;
+ unsigned long val, alpha;
+ unsigned int last_x;
+ unsigned int last_y;
+ unsigned int win = plane->index;
+ unsigned int cpp = fb->format->cpp[0];
+ unsigned int pitch = fb->pitches[0];
+ unsigned int vidw_addr0_base = ctx->data->vidw_buf_start_base;
+
+ /*
+ * SHADOWCON/PRTCON register is used for enabling timing.
+ *
+ * for example, once only width value of a register is set,
+ * if the dma is started then decon hardware could malfunction so
+ * with protect window setting, the register fields with prefix '_F'
+ * wouldn't be updated at vsync also but updated once unprotect window
+ * is set.
+ */
+
+ /* buffer start address */
+ val = (unsigned long)exynos_drm_fb_dma_addr(fb, 0);
+ writel(val, ctx->regs + VIDW_BUF_START(vidw_addr0_base, win));
+
+ padding = (pitch / cpp) - fb->width;
+
+ /* buffer size */
+ writel(fb->width + padding, ctx->regs + VIDW_WHOLE_X(win));
+ writel(fb->height, ctx->regs + VIDW_WHOLE_Y(win));
+
+ /* offset from the start of the buffer to read */
+ writel(state->src.x, ctx->regs + VIDW_OFFSET_X(win));
+ writel(state->src.y, ctx->regs + VIDW_OFFSET_Y(win));
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "start addr = 0x%lx\n",
+ (unsigned long)val);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "ovl_width = %d, ovl_height = %d\n",
+ state->crtc.w, state->crtc.h);
+
+ val = VIDOSDxA_TOPLEFT_X(state->crtc.x) |
+ VIDOSDxA_TOPLEFT_Y(state->crtc.y);
+ writel(val, ctx->regs + VIDOSD_A(win));
+
+ last_x = state->crtc.x + state->crtc.w;
+ if (last_x)
+ last_x--;
+ last_y = state->crtc.y + state->crtc.h;
+ if (last_y)
+ last_y--;
+
+ val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y);
+
+ writel(val, ctx->regs + VIDOSD_B(win));
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "osd pos: tx = %d, ty = %d, bx = %d, by = %d\n",
+ state->crtc.x, state->crtc.y, last_x, last_y);
+
+ /* OSD alpha */
+ alpha = VIDOSDxC_ALPHA0_R_F(0x0) |
+ VIDOSDxC_ALPHA0_G_F(0x0) |
+ VIDOSDxC_ALPHA0_B_F(0x0);
+
+ writel(alpha, ctx->regs + VIDOSD_C(win));
+
+ alpha = VIDOSDxD_ALPHA1_R_F(0xff) |
+ VIDOSDxD_ALPHA1_G_F(0xff) |
+ VIDOSDxD_ALPHA1_B_F(0xff);
+
+ writel(alpha, ctx->regs + VIDOSD_D(win));
+
+ decon_win_set_pixfmt(ctx, win, fb);
+
+ /* hardware window 0 doesn't support color key. */
+ if (win != 0)
+ decon_win_set_colkey(ctx, win);
+
+ /* wincon */
+ val = readl(ctx->regs + WINCON(win));
+ val |= WINCONx_TRIPLE_BUF_MODE;
+ val |= WINCONx_ENWIN;
+ writel(val, ctx->regs + WINCON(win));
+
+ /* Enable DMA channel and unprotect windows */
+ decon_shadow_protect_win(ctx, win, false);
+
+ val = readl(ctx->regs + DECON_UPDATE);
+ val |= DECON_UPDATE_STANDALONE_F;
+ writel(val, ctx->regs + DECON_UPDATE);
+}
+
+static void decon_disable_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct decon_context *ctx = crtc->ctx;
+ unsigned int win = plane->index;
+ u32 val;
+
+ /* protect windows */
+ decon_shadow_protect_win(ctx, win, true);
+
+ /* wincon */
+ val = readl(ctx->regs + WINCON(win));
+ val &= ~WINCONx_ENWIN;
+ writel(val, ctx->regs + WINCON(win));
+
+ val = readl(ctx->regs + DECON_UPDATE);
+ val |= DECON_UPDATE_STANDALONE_F;
+ writel(val, ctx->regs + DECON_UPDATE);
+}
+
+static void decon_atomic_flush(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int i;
+
+ for (i = 0; i < WINDOWS_NR; i++)
+ decon_shadow_protect_win(ctx, i, false);
+ exynos_crtc_handle_event(crtc);
+}
+
+static void decon_init(struct decon_context *ctx)
+{
+ u32 val;
+
+ writel(VIDCON0_SWRESET, ctx->regs + VIDCON0);
+
+ val = VIDOUTCON0_DISP_IF_0_ON;
+ if (!ctx->i80_if)
+ val |= VIDOUTCON0_RGBIF;
+ writel(val, ctx->regs + VIDOUTCON0);
+
+ writel(VCLKCON0_CLKVALUP | VCLKCON0_VCLKFREE, ctx->regs + VCLKCON0);
+
+ if (!ctx->i80_if)
+ writel(VIDCON1_VCLK_HOLD, ctx->regs + VIDCON1(0));
+}
+
+static void decon_atomic_enable(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(ctx->dev, "failed to enable DECON device.\n");
+ return;
+ }
+
+ decon_init(ctx);
+
+ /* if vblank was enabled status, enable it again. */
+ if (test_and_clear_bit(0, &ctx->irq_flags))
+ decon_enable_vblank(ctx->crtc);
+
+ decon_commit(ctx->crtc);
+}
+
+static void decon_atomic_disable(struct exynos_drm_crtc *crtc)
+{
+ struct decon_context *ctx = crtc->ctx;
+ int i;
+
+ /*
+ * We need to make sure that all windows are disabled before we
+ * suspend that connector. Otherwise we might try to scan from
+ * a destroyed buffer later.
+ */
+ for (i = 0; i < WINDOWS_NR; i++)
+ decon_disable_plane(crtc, &ctx->planes[i]);
+
+ pm_runtime_put_sync(ctx->dev);
+}
+
+static const struct exynos_drm_crtc_ops decon_crtc_ops = {
+ .atomic_enable = decon_atomic_enable,
+ .atomic_disable = decon_atomic_disable,
+ .enable_vblank = decon_enable_vblank,
+ .disable_vblank = decon_disable_vblank,
+ .atomic_begin = decon_atomic_begin,
+ .update_plane = decon_update_plane,
+ .disable_plane = decon_disable_plane,
+ .atomic_flush = decon_atomic_flush,
+};
+
+
+static irqreturn_t decon_irq_handler(int irq, void *dev_id)
+{
+ struct decon_context *ctx = (struct decon_context *)dev_id;
+ u32 val, clear_bit;
+
+ val = readl(ctx->regs + VIDINTCON1);
+
+ clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME;
+ if (val & clear_bit)
+ writel(clear_bit, ctx->regs + VIDINTCON1);
+
+ /* check the crtc is detached already from encoder */
+ if (!ctx->drm_dev)
+ goto out;
+
+ /* check if crtc and vblank have been initialized properly */
+ if (!drm_dev_has_vblank(ctx->drm_dev))
+ goto out;
+
+ if (!ctx->i80_if) {
+ drm_crtc_handle_vblank(&ctx->crtc->base);
+
+ /* set wait vsync event to zero and wake up queue. */
+ if (atomic_read(&ctx->wait_vsync_event)) {
+ atomic_set(&ctx->wait_vsync_event, 0);
+ wake_up(&ctx->wait_vsync_queue);
+ }
+ }
+out:
+ return IRQ_HANDLED;
+}
+
+static int decon_bind(struct device *dev, struct device *master, void *data)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_plane *exynos_plane;
+ unsigned int i;
+ int ret;
+
+ ret = decon_ctx_initialize(ctx, drm_dev);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "decon_ctx_initialize failed.\n");
+ return ret;
+ }
+
+ for (i = 0; i < WINDOWS_NR; i++) {
+ ctx->configs[i].pixel_formats = decon_formats;
+ ctx->configs[i].num_pixel_formats = ARRAY_SIZE(decon_formats);
+ ctx->configs[i].zpos = i;
+ ctx->configs[i].type = decon_win_types[i];
+
+ ret = exynos_plane_init(drm_dev, &ctx->planes[i], i,
+ &ctx->configs[i]);
+ if (ret)
+ return ret;
+ }
+
+ exynos_plane = &ctx->planes[DEFAULT_WIN];
+ ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
+ EXYNOS_DISPLAY_TYPE_LCD, &decon_crtc_ops, ctx);
+ if (IS_ERR(ctx->crtc)) {
+ decon_ctx_remove(ctx);
+ return PTR_ERR(ctx->crtc);
+ }
+
+ if (ctx->encoder)
+ exynos_dpi_bind(drm_dev, ctx->encoder);
+
+ return 0;
+
+}
+
+static void decon_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+
+ decon_atomic_disable(ctx->crtc);
+
+ if (ctx->encoder)
+ exynos_dpi_remove(ctx->encoder);
+
+ decon_ctx_remove(ctx);
+}
+
+static const struct component_ops decon_component_ops = {
+ .bind = decon_bind,
+ .unbind = decon_unbind,
+};
+
+static int decon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct decon_context *ctx;
+ struct device_node *i80_if_timings;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->data = of_device_get_match_data(dev);
+
+ i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings");
+ if (i80_if_timings)
+ ctx->i80_if = true;
+ of_node_put(i80_if_timings);
+
+ ctx->regs = of_iomap(dev->of_node, 0);
+ if (!ctx->regs)
+ return -ENOMEM;
+
+ ctx->pclk = devm_clk_get(dev, "pclk_decon0");
+ if (IS_ERR(ctx->pclk)) {
+ dev_err(dev, "failed to get bus clock pclk\n");
+ ret = PTR_ERR(ctx->pclk);
+ goto err_iounmap;
+ }
+
+ ctx->aclk = devm_clk_get(dev, "aclk_decon0");
+ if (IS_ERR(ctx->aclk)) {
+ dev_err(dev, "failed to get bus clock aclk\n");
+ ret = PTR_ERR(ctx->aclk);
+ goto err_iounmap;
+ }
+
+ ctx->eclk = devm_clk_get(dev, "decon0_eclk");
+ if (IS_ERR(ctx->eclk)) {
+ dev_err(dev, "failed to get eclock\n");
+ ret = PTR_ERR(ctx->eclk);
+ goto err_iounmap;
+ }
+
+ ctx->vclk = devm_clk_get(dev, "decon0_vclk");
+ if (IS_ERR(ctx->vclk)) {
+ dev_err(dev, "failed to get vclock\n");
+ ret = PTR_ERR(ctx->vclk);
+ goto err_iounmap;
+ }
+
+ ret = platform_get_irq_byname(pdev, ctx->i80_if ? "lcd_sys" : "vsync");
+ if (ret < 0)
+ goto err_iounmap;
+
+ ret = devm_request_irq(dev, ret, decon_irq_handler, 0, "drm_decon", ctx);
+ if (ret) {
+ dev_err(dev, "irq request failed.\n");
+ goto err_iounmap;
+ }
+
+ init_waitqueue_head(&ctx->wait_vsync_queue);
+ atomic_set(&ctx->wait_vsync_event, 0);
+
+ platform_set_drvdata(pdev, ctx);
+
+ ctx->encoder = exynos_dpi_probe(dev);
+ if (IS_ERR(ctx->encoder)) {
+ ret = PTR_ERR(ctx->encoder);
+ goto err_iounmap;
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &decon_component_ops);
+ if (ret)
+ goto err_disable_pm_runtime;
+
+ return ret;
+
+err_disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+err_iounmap:
+ iounmap(ctx->regs);
+
+ return ret;
+}
+
+static void decon_remove(struct platform_device *pdev)
+{
+ struct decon_context *ctx = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ iounmap(ctx->regs);
+
+ component_del(&pdev->dev, &decon_component_ops);
+}
+
+static int exynos7_decon_suspend(struct device *dev)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(ctx->vclk);
+ clk_disable_unprepare(ctx->eclk);
+ clk_disable_unprepare(ctx->aclk);
+ clk_disable_unprepare(ctx->pclk);
+
+ return 0;
+}
+
+static int exynos7_decon_resume(struct device *dev)
+{
+ struct decon_context *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(ctx->pclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to prepare_enable the pclk [%d]\n",
+ ret);
+ goto err_pclk_enable;
+ }
+
+ ret = clk_prepare_enable(ctx->aclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to prepare_enable the aclk [%d]\n",
+ ret);
+ goto err_aclk_enable;
+ }
+
+ ret = clk_prepare_enable(ctx->eclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to prepare_enable the eclk [%d]\n",
+ ret);
+ goto err_eclk_enable;
+ }
+
+ ret = clk_prepare_enable(ctx->vclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to prepare_enable the vclk [%d]\n",
+ ret);
+ goto err_vclk_enable;
+ }
+
+ return 0;
+
+err_vclk_enable:
+ clk_disable_unprepare(ctx->eclk);
+err_eclk_enable:
+ clk_disable_unprepare(ctx->aclk);
+err_aclk_enable:
+ clk_disable_unprepare(ctx->pclk);
+err_pclk_enable:
+ return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(exynos7_decon_pm_ops, exynos7_decon_suspend,
+ exynos7_decon_resume, NULL);
+
+struct platform_driver decon_driver = {
+ .probe = decon_probe,
+ .remove = decon_remove,
+ .driver = {
+ .name = "exynos-decon",
+ .pm = pm_ptr(&exynos7_decon_pm_ops),
+ .of_match_table = decon_driver_dt_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
new file mode 100644
index 000000000..e1c9e2946
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung SoC DP (Display Port) interface driver.
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * Author: Jingoo Han <jg1.han@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/bridge/analogix_dp.h>
+#include <drm/bridge/of-display-mode-bridge.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+
+#define to_dp(nm) container_of(nm, struct exynos_dp_device, nm)
+
+struct exynos_dp_device {
+ struct drm_encoder encoder;
+ struct drm_device *drm_dev;
+ struct device *dev;
+
+ struct analogix_dp_device *adp;
+ struct analogix_dp_plat_data plat_data;
+};
+
+static int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data,
+ bool enable)
+{
+ struct exynos_dp_device *dp = to_dp(plat_data);
+ struct drm_encoder *encoder = &dp->encoder;
+
+ if (!encoder->crtc)
+ return -EPERM;
+
+ exynos_drm_pipe_clk_enable(to_exynos_crtc(encoder->crtc), enable);
+
+ return 0;
+}
+
+static int exynos_dp_poweron(struct analogix_dp_plat_data *plat_data)
+{
+ return exynos_dp_crtc_clock_enable(plat_data, true);
+}
+
+static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data)
+{
+ return exynos_dp_crtc_clock_enable(plat_data, false);
+}
+
+static void exynos_dp_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void exynos_dp_nop(struct drm_encoder *encoder)
+{
+ /* do nothing */
+}
+
+static const struct drm_encoder_funcs exynos_dp_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = {
+ .mode_set = exynos_dp_mode_set,
+ .enable = exynos_dp_nop,
+ .disable = exynos_dp_nop,
+};
+
+static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
+{
+ struct exynos_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &dp->encoder;
+ struct drm_device *drm_dev = data;
+ struct drm_connector *connector;
+ int ret;
+
+ dp->drm_dev = drm_dev;
+
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ dev_err(dp->dev, "Failed to initialize encoder\n");
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs);
+
+ ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
+ if (ret < 0)
+ return ret;
+
+ dp->plat_data.encoder = encoder;
+
+ ret = analogix_dp_bind(dp->adp, dp->drm_dev);
+ if (ret) {
+ dp->encoder.funcs->destroy(&dp->encoder);
+ return ret;
+ }
+
+ connector = drm_bridge_connector_init(dp->drm_dev, dp->plat_data.encoder);
+ if (IS_ERR(connector)) {
+ ret = PTR_ERR(connector);
+ dev_err(dp->dev, "Failed to initialize bridge_connector\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void exynos_dp_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct exynos_dp_device *dp = dev_get_drvdata(dev);
+
+ analogix_dp_unbind(dp->adp);
+ dp->encoder.funcs->destroy(&dp->encoder);
+}
+
+static const struct component_ops exynos_dp_ops = {
+ .bind = exynos_dp_bind,
+ .unbind = exynos_dp_unbind,
+};
+
+static int exynos_dp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np;
+ struct exynos_dp_device *dp;
+
+ dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device),
+ GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ dp->dev = dev;
+ /*
+ * We just use the drvdata until driver run into component
+ * add function, and then we would set drvdata to null, so
+ * that analogix dp driver would take charge of the drvdata.
+ */
+ platform_set_drvdata(pdev, dp);
+
+ /* This is for the backward compatibility. */
+ np = of_parse_phandle(dev->of_node, "panel", 0);
+ if (np) {
+ dp->plat_data.panel = of_drm_find_panel(np);
+
+ of_node_put(np);
+ if (IS_ERR(dp->plat_data.panel))
+ return PTR_ERR(dp->plat_data.panel);
+
+ goto out;
+ }
+
+ if (of_get_display_timings(dev->of_node)) {
+ dp->plat_data.next_bridge = devm_drm_of_display_mode_bridge(dp->dev,
+ dp->dev->of_node,
+ DRM_MODE_CONNECTOR_eDP);
+ if (IS_ERR(dp->plat_data.next_bridge))
+ return PTR_ERR(dp->plat_data.next_bridge);
+ }
+
+ /* The remote port can be either a panel or a bridge */
+ dp->plat_data.dev_type = EXYNOS_DP;
+ dp->plat_data.power_on = exynos_dp_poweron;
+ dp->plat_data.power_off = exynos_dp_poweroff;
+ dp->plat_data.ops = &exynos_dp_ops;
+
+out:
+ dp->adp = analogix_dp_probe(dev, &dp->plat_data);
+ if (IS_ERR(dp->adp)) {
+ /*
+ * The driver core does not invoke remove() for failed probes,
+ * so release the probe-time panel reference here.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
+ return PTR_ERR(dp->adp);
+ }
+
+ if (dp->plat_data.panel || dp->plat_data.next_bridge)
+ return component_add(&pdev->dev, &exynos_dp_ops);
+ else
+ return analogix_dp_finish_probe(dp->adp);
+}
+
+static void exynos_dp_remove(struct platform_device *pdev)
+{
+ struct exynos_dp_device *dp = platform_get_drvdata(pdev);
+
+ /*
+ * Release the probe-time reference from of_drm_find_panel(). If bind
+ * ran, the panel_bridge holds a second reference that devm cleanup
+ * will release when the bridge is destroyed after remove() returns.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
+
+ component_del(&pdev->dev, &exynos_dp_ops);
+}
+
+static int exynos_dp_suspend(struct device *dev)
+{
+ struct exynos_dp_device *dp = dev_get_drvdata(dev);
+
+ return analogix_dp_suspend(dp->adp);
+}
+
+static int exynos_dp_resume(struct device *dev)
+{
+ struct exynos_dp_device *dp = dev_get_drvdata(dev);
+
+ return analogix_dp_resume(dp->adp);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(exynos_dp_pm_ops, exynos_dp_suspend,
+ exynos_dp_resume, NULL);
+
+static const struct of_device_id exynos_dp_match[] = {
+ { .compatible = "samsung,exynos5-dp" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_dp_match);
+
+struct platform_driver dp_driver = {
+ .probe = exynos_dp_probe,
+ .remove = exynos_dp_remove,
+ .driver = {
+ .name = "exynos-dp",
+ .pm = pm_ptr(&exynos_dp_pm_ops),
+ .of_match_table = exynos_dp_match,
+ },
+};
+
+MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
+MODULE_DESCRIPTION("Samsung Specific Analogix-DP Driver Extension");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
new file mode 100644
index 000000000..b071c0196
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_crtc.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_plane.h"
+
+static void exynos_drm_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->atomic_enable)
+ exynos_crtc->ops->atomic_enable(exynos_crtc);
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void exynos_drm_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ drm_crtc_vblank_off(crtc);
+
+ if (exynos_crtc->ops->atomic_disable)
+ exynos_crtc->ops->atomic_disable(exynos_crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event && !crtc->state->active) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static int exynos_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (!crtc_state->enable)
+ return 0;
+
+ if (exynos_crtc->ops->atomic_check)
+ return exynos_crtc->ops->atomic_check(exynos_crtc, crtc_state);
+
+ return 0;
+}
+
+static void exynos_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->atomic_begin)
+ exynos_crtc->ops->atomic_begin(exynos_crtc);
+}
+
+static void exynos_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->atomic_flush)
+ exynos_crtc->ops->atomic_flush(exynos_crtc);
+}
+
+static enum drm_mode_status exynos_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->mode_valid)
+ return exynos_crtc->ops->mode_valid(exynos_crtc, mode);
+
+ return MODE_OK;
+}
+
+static bool exynos_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->mode_fixup)
+ return exynos_crtc->ops->mode_fixup(exynos_crtc, mode,
+ adjusted_mode);
+
+ return true;
+}
+
+
+static const struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = {
+ .mode_valid = exynos_crtc_mode_valid,
+ .mode_fixup = exynos_crtc_mode_fixup,
+ .atomic_check = exynos_crtc_atomic_check,
+ .atomic_begin = exynos_crtc_atomic_begin,
+ .atomic_flush = exynos_crtc_atomic_flush,
+ .atomic_enable = exynos_drm_crtc_atomic_enable,
+ .atomic_disable = exynos_drm_crtc_atomic_disable,
+};
+
+void exynos_crtc_handle_event(struct exynos_drm_crtc *exynos_crtc)
+{
+ struct drm_crtc *crtc = &exynos_crtc->base;
+ struct drm_pending_vblank_event *event = crtc->state->event;
+ unsigned long flags;
+
+ if (!event)
+ return;
+ crtc->state->event = NULL;
+
+ WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ drm_crtc_arm_vblank_event(crtc, event);
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+static void exynos_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ drm_crtc_cleanup(crtc);
+ kfree(exynos_crtc);
+}
+
+static int exynos_drm_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->enable_vblank)
+ return exynos_crtc->ops->enable_vblank(exynos_crtc);
+
+ return 0;
+}
+
+static void exynos_drm_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->disable_vblank)
+ exynos_crtc->ops->disable_vblank(exynos_crtc);
+}
+
+static const struct drm_crtc_funcs exynos_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .destroy = exynos_drm_crtc_destroy,
+ .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 = exynos_drm_crtc_enable_vblank,
+ .disable_vblank = exynos_drm_crtc_disable_vblank,
+};
+
+struct exynos_drm_crtc *exynos_drm_crtc_create(struct drm_device *drm_dev,
+ struct drm_plane *plane,
+ enum exynos_drm_output_type type,
+ const struct exynos_drm_crtc_ops *ops,
+ void *ctx)
+{
+ struct exynos_drm_crtc *exynos_crtc;
+ struct drm_crtc *crtc;
+ int ret;
+
+ exynos_crtc = kzalloc_obj(*exynos_crtc);
+ if (!exynos_crtc)
+ return ERR_PTR(-ENOMEM);
+
+ exynos_crtc->type = type;
+ exynos_crtc->ops = ops;
+ exynos_crtc->ctx = ctx;
+
+ crtc = &exynos_crtc->base;
+
+ ret = drm_crtc_init_with_planes(drm_dev, crtc, plane, NULL,
+ &exynos_crtc_funcs, NULL);
+ if (ret < 0)
+ goto err_crtc;
+
+ drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs);
+
+ return exynos_crtc;
+
+err_crtc:
+ plane->funcs->destroy(plane);
+ kfree(exynos_crtc);
+ return ERR_PTR(ret);
+}
+
+struct exynos_drm_crtc *exynos_drm_crtc_get_by_type(struct drm_device *drm_dev,
+ enum exynos_drm_output_type out_type)
+{
+ struct drm_crtc *crtc;
+
+ drm_for_each_crtc(crtc, drm_dev)
+ if (to_exynos_crtc(crtc)->type == out_type)
+ return to_exynos_crtc(crtc);
+
+ return ERR_PTR(-ENODEV);
+}
+
+int exynos_drm_set_possible_crtcs(struct drm_encoder *encoder,
+ enum exynos_drm_output_type out_type)
+{
+ struct exynos_drm_crtc *crtc = exynos_drm_crtc_get_by_type(encoder->dev,
+ out_type);
+
+ if (IS_ERR(crtc))
+ return PTR_ERR(crtc);
+
+ encoder->possible_crtcs = drm_crtc_mask(&crtc->base);
+
+ return 0;
+}
+
+void exynos_drm_crtc_te_handler(struct drm_crtc *crtc)
+{
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+
+ if (exynos_crtc->ops->te_handler)
+ exynos_crtc->ops->te_handler(exynos_crtc);
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h
new file mode 100644
index 000000000..1815374c3
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* exynos_drm_crtc.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_CRTC_H_
+#define _EXYNOS_DRM_CRTC_H_
+
+
+#include "exynos_drm_drv.h"
+
+struct exynos_drm_crtc *exynos_drm_crtc_create(struct drm_device *drm_dev,
+ struct drm_plane *plane,
+ enum exynos_drm_output_type out_type,
+ const struct exynos_drm_crtc_ops *ops,
+ void *context);
+
+/* This function gets crtc device matched with out_type. */
+struct exynos_drm_crtc *exynos_drm_crtc_get_by_type(struct drm_device *drm_dev,
+ enum exynos_drm_output_type out_type);
+
+int exynos_drm_set_possible_crtcs(struct drm_encoder *encoder,
+ enum exynos_drm_output_type out_type);
+
+/*
+ * This function calls the crtc device(manager)'s te_handler() callback
+ * to trigger to transfer video image at the tearing effect synchronization
+ * signal.
+ */
+void exynos_drm_crtc_te_handler(struct drm_crtc *crtc);
+
+void exynos_crtc_handle_event(struct exynos_drm_crtc *exynos_crtc);
+
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dma.c b/drivers/gpu/drm/exynos/exynos_drm_dma.c
new file mode 100644
index 000000000..734741c80
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_dma.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2012 Samsung Electronics Co., Ltd.
+// Author: Inki Dae <inki.dae@samsung.com>
+// Author: Andrzej Hajda <a.hajda@samsung.com>
+
+#include <linux/dma-map-ops.h>
+#include <linux/iommu.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+
+#if defined(CONFIG_ARM_DMA_USE_IOMMU)
+#include <asm/dma-iommu.h>
+#else
+#define arm_iommu_create_mapping(...) ({ NULL; })
+#define arm_iommu_attach_device(...) ({ -ENODEV; })
+#define arm_iommu_release_mapping(...) ({ })
+#define arm_iommu_detach_device(...) ({ })
+#define to_dma_iommu_mapping(dev) NULL
+#endif
+
+#define EXYNOS_DEV_ADDR_START 0x20000000
+#define EXYNOS_DEV_ADDR_SIZE 0x40000000
+
+/*
+ * drm_iommu_attach_device- attach device to iommu mapping
+ *
+ * @drm_dev: DRM device
+ * @subdrv_dev: device to be attach
+ *
+ * This function should be called by sub drivers to attach it to iommu
+ * mapping.
+ */
+static int drm_iommu_attach_device(struct drm_device *drm_dev,
+ struct device *subdrv_dev, void **dma_priv)
+{
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ int ret = 0;
+
+ if (get_dma_ops(drm_dev_dma_dev(drm_dev)) != get_dma_ops(subdrv_dev)) {
+ DRM_DEV_ERROR(subdrv_dev, "Device %s lacks support for IOMMU\n",
+ dev_name(subdrv_dev));
+ return -EINVAL;
+ }
+
+ dma_set_max_seg_size(subdrv_dev, DMA_BIT_MASK(32));
+ if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) {
+ /*
+ * Keep the original DMA mapping of the sub-device and
+ * restore it on Exynos DRM detach, otherwise the DMA
+ * framework considers it as IOMMU-less during the next
+ * probe (in case of deferred probe or modular build)
+ */
+ *dma_priv = to_dma_iommu_mapping(subdrv_dev);
+ if (*dma_priv)
+ arm_iommu_detach_device(subdrv_dev);
+
+ ret = arm_iommu_attach_device(subdrv_dev, priv->mapping);
+ } else if (IS_ENABLED(CONFIG_IOMMU_DMA)) {
+ ret = iommu_attach_device(priv->mapping, subdrv_dev);
+ }
+
+ return ret;
+}
+
+/*
+ * drm_iommu_detach_device -detach device address space mapping from device
+ *
+ * @drm_dev: DRM device
+ * @subdrv_dev: device to be detached
+ *
+ * This function should be called by sub drivers to detach it from iommu
+ * mapping
+ */
+static void drm_iommu_detach_device(struct drm_device *drm_dev,
+ struct device *subdrv_dev, void **dma_priv)
+{
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+
+ if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) {
+ arm_iommu_detach_device(subdrv_dev);
+ arm_iommu_attach_device(subdrv_dev, *dma_priv);
+ } else if (IS_ENABLED(CONFIG_IOMMU_DMA))
+ iommu_detach_device(priv->mapping, subdrv_dev);
+}
+
+int exynos_drm_register_dma(struct drm_device *drm, struct device *dev,
+ void **dma_priv)
+{
+ struct exynos_drm_private *priv = drm->dev_private;
+
+ if (drm_dev_dma_dev(drm) == drm->dev) {
+ drm_dev_set_dma_dev(drm, dev);
+ DRM_INFO("Exynos DRM: using %s device for DMA mapping operations\n",
+ dev_name(dev));
+ }
+
+ if (!IS_ENABLED(CONFIG_EXYNOS_IOMMU))
+ return 0;
+
+ if (!priv->mapping) {
+ void *mapping = NULL;
+
+ if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU))
+ mapping = arm_iommu_create_mapping(dev,
+ EXYNOS_DEV_ADDR_START, EXYNOS_DEV_ADDR_SIZE);
+ else if (IS_ENABLED(CONFIG_IOMMU_DMA))
+ mapping = iommu_get_domain_for_dev(dev);
+
+ if (!mapping)
+ return -ENODEV;
+ priv->mapping = mapping;
+ }
+
+ return drm_iommu_attach_device(drm, dev, dma_priv);
+}
+
+void exynos_drm_unregister_dma(struct drm_device *drm, struct device *dev,
+ void **dma_priv)
+{
+ if (IS_ENABLED(CONFIG_EXYNOS_IOMMU))
+ drm_iommu_detach_device(drm, dev, dma_priv);
+}
+
+void exynos_drm_cleanup_dma(struct drm_device *drm)
+{
+ struct exynos_drm_private *priv = drm->dev_private;
+
+ if (!IS_ENABLED(CONFIG_EXYNOS_IOMMU))
+ return;
+
+ arm_iommu_release_mapping(priv->mapping);
+ priv->mapping = NULL;
+ drm_dev_set_dma_dev(drm, NULL);
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
new file mode 100644
index 000000000..0683236aa
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Exynos DRM Parallel output support.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * Contacts: Andrzej Hajda <a.hajda@samsung.com>
+*/
+
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "exynos_drm_crtc.h"
+
+struct exynos_dpi {
+ struct drm_encoder encoder;
+ struct device *dev;
+ struct device_node *panel_node;
+
+ struct drm_panel *panel;
+ struct drm_connector connector;
+
+ struct videomode *vm;
+};
+
+#define connector_to_dpi(c) container_of(c, struct exynos_dpi, connector)
+
+static inline struct exynos_dpi *encoder_to_dpi(struct drm_encoder *e)
+{
+ return container_of(e, struct exynos_dpi, encoder);
+}
+
+static enum drm_connector_status
+exynos_dpi_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static void exynos_dpi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs exynos_dpi_connector_funcs = {
+ .detect = exynos_dpi_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = exynos_dpi_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 exynos_dpi_get_modes(struct drm_connector *connector)
+{
+ struct exynos_dpi *ctx = connector_to_dpi(connector);
+
+ /* fimd timings gets precedence over panel modes */
+ if (ctx->vm) {
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ DRM_DEV_ERROR(ctx->dev,
+ "failed to create a new display mode\n");
+ return 0;
+ }
+ drm_display_mode_from_videomode(ctx->vm, mode);
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ return 1;
+ }
+
+ if (ctx->panel)
+ return drm_panel_get_modes(ctx->panel, connector);
+
+ return 0;
+}
+
+static const struct drm_connector_helper_funcs exynos_dpi_connector_helper_funcs = {
+ .get_modes = exynos_dpi_get_modes,
+};
+
+static int exynos_dpi_create_connector(struct drm_encoder *encoder)
+{
+ struct exynos_dpi *ctx = encoder_to_dpi(encoder);
+ struct drm_connector *connector = &ctx->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(encoder->dev, connector,
+ &exynos_dpi_connector_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+ if (ret) {
+ DRM_DEV_ERROR(ctx->dev,
+ "failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &exynos_dpi_connector_helper_funcs);
+ drm_connector_attach_encoder(connector, encoder);
+
+ return 0;
+}
+
+static void exynos_dpi_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void exynos_dpi_enable(struct drm_encoder *encoder)
+{
+ struct exynos_dpi *ctx = encoder_to_dpi(encoder);
+
+ if (ctx->panel) {
+ drm_panel_prepare(ctx->panel);
+ drm_panel_enable(ctx->panel);
+ }
+}
+
+static void exynos_dpi_disable(struct drm_encoder *encoder)
+{
+ struct exynos_dpi *ctx = encoder_to_dpi(encoder);
+
+ if (ctx->panel) {
+ drm_panel_disable(ctx->panel);
+ drm_panel_unprepare(ctx->panel);
+ }
+}
+
+static const struct drm_encoder_funcs exynos_dpi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_encoder_helper_funcs exynos_dpi_encoder_helper_funcs = {
+ .mode_set = exynos_dpi_mode_set,
+ .enable = exynos_dpi_enable,
+ .disable = exynos_dpi_disable,
+};
+
+enum {
+ FIMD_PORT_IN0,
+ FIMD_PORT_IN1,
+ FIMD_PORT_IN2,
+ FIMD_PORT_RGB,
+ FIMD_PORT_WRB,
+};
+
+static int exynos_dpi_parse_dt(struct exynos_dpi *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *dn = dev->of_node;
+ struct device_node *np;
+
+ ctx->panel_node = of_graph_get_remote_node(dn, FIMD_PORT_RGB, 0);
+
+ np = of_get_child_by_name(dn, "display-timings");
+ if (np) {
+ struct videomode *vm;
+ int ret;
+
+ of_node_put(np);
+
+ vm = devm_kzalloc(dev, sizeof(*ctx->vm), GFP_KERNEL);
+ if (!vm)
+ return -ENOMEM;
+
+ ret = of_get_videomode(dn, vm, 0);
+ if (ret < 0) {
+ devm_kfree(dev, vm);
+ return ret;
+ }
+
+ ctx->vm = vm;
+
+ return 0;
+ }
+
+ if (!ctx->panel_node)
+ return -EINVAL;
+
+ return 0;
+}
+
+int exynos_dpi_bind(struct drm_device *dev, struct drm_encoder *encoder)
+{
+ int ret;
+
+ ret = drm_encoder_init(dev, encoder, &exynos_dpi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(encoder_to_dpi(encoder)->dev,
+ "failed to create encoder ret = %d\n", ret);
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &exynos_dpi_encoder_helper_funcs);
+
+ ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
+ if (ret < 0)
+ return ret;
+
+ ret = exynos_dpi_create_connector(encoder);
+ if (ret) {
+ DRM_DEV_ERROR(encoder_to_dpi(encoder)->dev,
+ "failed to create connector ret = %d\n", ret);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct drm_encoder *exynos_dpi_probe(struct device *dev)
+{
+ struct exynos_dpi *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->dev = dev;
+
+ ret = exynos_dpi_parse_dt(ctx);
+ if (ret < 0) {
+ devm_kfree(dev, ctx);
+ return NULL;
+ }
+
+ if (ctx->panel_node) {
+ ctx->panel = of_drm_find_panel(ctx->panel_node);
+ if (IS_ERR(ctx->panel))
+ return ERR_CAST(ctx->panel);
+ }
+
+ return &ctx->encoder;
+}
+
+int exynos_dpi_remove(struct drm_encoder *encoder)
+{
+ struct exynos_dpi *ctx = encoder_to_dpi(encoder);
+
+ exynos_dpi_disable(&ctx->encoder);
+
+ if (ctx->panel)
+ drm_panel_put(ctx->panel);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c
new file mode 100644
index 000000000..9ee300868
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#include <linux/component.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/uaccess.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_fbdev.h"
+#include "exynos_drm_g2d.h"
+#include "exynos_drm_gem.h"
+#include "exynos_drm_ipp.h"
+#include "exynos_drm_plane.h"
+#include "exynos_drm_vidi.h"
+
+#define DRIVER_NAME "exynos"
+#define DRIVER_DESC "Samsung SoC DRM"
+
+/*
+ * Interface history:
+ *
+ * 1.0 - Original version
+ * 1.1 - Upgrade IPP driver to version 2.0
+ */
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 1
+
+static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
+{
+ struct drm_exynos_file_private *file_priv;
+ int ret;
+
+ file_priv = kzalloc_obj(*file_priv);
+ if (!file_priv)
+ return -ENOMEM;
+
+ file->driver_priv = file_priv;
+ ret = g2d_open(dev, file);
+ if (ret)
+ goto err_file_priv_free;
+
+ return ret;
+
+err_file_priv_free:
+ kfree(file_priv);
+ file->driver_priv = NULL;
+ return ret;
+}
+
+static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file)
+{
+ g2d_close(dev, file);
+ kfree(file->driver_priv);
+ file->driver_priv = NULL;
+}
+
+static const struct drm_ioctl_desc exynos_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(EXYNOS_GEM_CREATE, exynos_drm_gem_create_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MAP, exynos_drm_gem_map_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_GEM_GET, exynos_drm_gem_get_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_VIDI_CONNECTION, vidi_connection_ioctl,
+ DRM_AUTH),
+ DRM_IOCTL_DEF_DRV(EXYNOS_G2D_GET_VER, exynos_g2d_get_ver_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_G2D_SET_CMDLIST, exynos_g2d_set_cmdlist_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_G2D_EXEC, exynos_g2d_exec_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_IPP_GET_RESOURCES,
+ exynos_drm_ipp_get_res_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_IPP_GET_CAPS, exynos_drm_ipp_get_caps_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_IPP_GET_LIMITS,
+ exynos_drm_ipp_get_limits_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(EXYNOS_IPP_COMMIT, exynos_drm_ipp_commit_ioctl,
+ DRM_RENDER_ALLOW),
+};
+
+DEFINE_DRM_GEM_FOPS(exynos_drm_driver_fops);
+
+static const struct drm_driver exynos_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM
+ | DRIVER_ATOMIC | DRIVER_RENDER,
+ .open = exynos_drm_open,
+ .postclose = exynos_drm_postclose,
+ .dumb_create = exynos_drm_gem_dumb_create,
+ .gem_prime_import_sg_table = exynos_drm_gem_prime_import_sg_table,
+ EXYNOS_DRM_FBDEV_DRIVER_OPS,
+ .ioctls = exynos_ioctls,
+ .num_ioctls = ARRAY_SIZE(exynos_ioctls),
+ .fops = &exynos_drm_driver_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+static int exynos_drm_suspend(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_suspend(drm_dev);
+}
+
+static void exynos_drm_resume(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+
+ drm_mode_config_helper_resume(drm_dev);
+}
+
+static const struct dev_pm_ops exynos_drm_pm_ops = {
+ .prepare = exynos_drm_suspend,
+ .complete = exynos_drm_resume,
+};
+
+/* forward declaration */
+static struct platform_driver exynos_drm_platform_driver;
+
+struct exynos_drm_driver_info {
+ struct platform_driver *driver;
+ unsigned int flags;
+};
+
+#define DRM_COMPONENT_DRIVER BIT(0) /* supports component framework */
+#define DRM_VIRTUAL_DEVICE BIT(1) /* create virtual platform device */
+#define DRM_FIMC_DEVICE BIT(2) /* devices shared with V4L2 subsystem */
+
+#define DRV_PTR(drv, cond) (IS_ENABLED(cond) ? &drv : NULL)
+
+/*
+ * Connector drivers should not be placed before associated crtc drivers,
+ * because connector requires pipe number of its crtc during initialization.
+ */
+static struct exynos_drm_driver_info exynos_drm_drivers[] = {
+ {
+ DRV_PTR(fimd_driver, CONFIG_DRM_EXYNOS_FIMD),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(exynos5433_decon_driver, CONFIG_DRM_EXYNOS5433_DECON),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(decon_driver, CONFIG_DRM_EXYNOS7_DECON),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(mixer_driver, CONFIG_DRM_EXYNOS_MIXER),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(dp_driver, CONFIG_DRM_EXYNOS_DP),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(dsi_driver, CONFIG_DRM_EXYNOS_DSI),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(mic_driver, CONFIG_DRM_EXYNOS_MIC),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(hdmi_driver, CONFIG_DRM_EXYNOS_HDMI),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(vidi_driver, CONFIG_DRM_EXYNOS_VIDI),
+ DRM_COMPONENT_DRIVER | DRM_VIRTUAL_DEVICE
+ }, {
+ DRV_PTR(g2d_driver, CONFIG_DRM_EXYNOS_G2D),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(fimc_driver, CONFIG_DRM_EXYNOS_FIMC),
+ DRM_COMPONENT_DRIVER | DRM_FIMC_DEVICE,
+ }, {
+ DRV_PTR(rotator_driver, CONFIG_DRM_EXYNOS_ROTATOR),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(scaler_driver, CONFIG_DRM_EXYNOS_SCALER),
+ DRM_COMPONENT_DRIVER
+ }, {
+ DRV_PTR(gsc_driver, CONFIG_DRM_EXYNOS_GSC),
+ DRM_COMPONENT_DRIVER
+ }, {
+ &exynos_drm_platform_driver,
+ DRM_VIRTUAL_DEVICE
+ }
+};
+
+static struct component_match *exynos_drm_match_add(struct device *dev)
+{
+ struct component_match *match = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(exynos_drm_drivers); ++i) {
+ struct exynos_drm_driver_info *info = &exynos_drm_drivers[i];
+ struct device *p = NULL, *d;
+
+ if (!info->driver || !(info->flags & DRM_COMPONENT_DRIVER))
+ continue;
+
+ while ((d = platform_find_device_by_driver(p, &info->driver->driver))) {
+ put_device(p);
+
+ if (!(info->flags & DRM_FIMC_DEVICE) ||
+ exynos_drm_check_fimc_device(d) == 0)
+ component_match_add(dev, &match, component_compare_dev, d);
+ p = d;
+ }
+ put_device(p);
+ }
+
+ return match ?: ERR_PTR(-ENODEV);
+}
+
+static int exynos_drm_bind(struct device *dev)
+{
+ struct exynos_drm_private *private;
+ struct drm_encoder *encoder;
+ struct drm_device *drm;
+ unsigned int clone_mask;
+ int ret;
+
+ drm = drm_dev_alloc(&exynos_drm_driver, dev);
+ if (IS_ERR(drm))
+ return PTR_ERR(drm);
+
+ private = kzalloc_obj(struct exynos_drm_private);
+ if (!private) {
+ ret = -ENOMEM;
+ goto err_free_drm;
+ }
+
+ init_waitqueue_head(&private->wait);
+ spin_lock_init(&private->lock);
+
+ dev_set_drvdata(dev, drm);
+ drm->dev_private = (void *)private;
+
+ drm_mode_config_init(drm);
+
+ exynos_drm_mode_config_init(drm);
+
+ /* setup possible_clones. */
+ clone_mask = 0;
+ list_for_each_entry(encoder, &drm->mode_config.encoder_list, head)
+ clone_mask |= drm_encoder_mask(encoder);
+
+ list_for_each_entry(encoder, &drm->mode_config.encoder_list, head)
+ encoder->possible_clones = clone_mask;
+
+ /* Try to bind all sub drivers. */
+ ret = component_bind_all(drm->dev, drm);
+ if (ret)
+ goto err_mode_config_cleanup;
+
+ ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+ if (ret)
+ goto err_unbind_all;
+
+ drm_mode_config_reset(drm);
+
+ /* init kms poll for handling hpd */
+ drm_kms_helper_poll_init(drm);
+
+ /* register the DRM device */
+ ret = drm_dev_register(drm, 0);
+ if (ret < 0)
+ goto err_cleanup_poll;
+
+ drm_client_setup(drm, NULL);
+
+ return 0;
+
+err_cleanup_poll:
+ drm_kms_helper_poll_fini(drm);
+err_unbind_all:
+ component_unbind_all(drm->dev, drm);
+err_mode_config_cleanup:
+ drm_mode_config_cleanup(drm);
+ exynos_drm_cleanup_dma(drm);
+ kfree(private);
+ dev_set_drvdata(dev, NULL);
+err_free_drm:
+ drm_dev_put(drm);
+
+ return ret;
+}
+
+static void exynos_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ drm_dev_unregister(drm);
+
+ drm_kms_helper_poll_fini(drm);
+ drm_atomic_helper_shutdown(drm);
+
+ component_unbind_all(drm->dev, drm);
+ drm_mode_config_cleanup(drm);
+ exynos_drm_cleanup_dma(drm);
+
+ kfree(drm->dev_private);
+ drm->dev_private = NULL;
+ dev_set_drvdata(dev, NULL);
+
+ drm_dev_put(drm);
+}
+
+static const struct component_master_ops exynos_drm_ops = {
+ .bind = exynos_drm_bind,
+ .unbind = exynos_drm_unbind,
+};
+
+static int exynos_drm_platform_probe(struct platform_device *pdev)
+{
+ struct component_match *match;
+
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+ match = exynos_drm_match_add(&pdev->dev);
+ if (IS_ERR(match))
+ return PTR_ERR(match);
+
+ return component_master_add_with_match(&pdev->dev, &exynos_drm_ops,
+ match);
+}
+
+static void exynos_drm_platform_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &exynos_drm_ops);
+}
+
+static void exynos_drm_platform_shutdown(struct platform_device *pdev)
+{
+ struct drm_device *drm = platform_get_drvdata(pdev);
+
+ drm_atomic_helper_shutdown(drm);
+}
+
+static struct platform_driver exynos_drm_platform_driver = {
+ .probe = exynos_drm_platform_probe,
+ .remove = exynos_drm_platform_remove,
+ .shutdown = exynos_drm_platform_shutdown,
+ .driver = {
+ .name = "exynos-drm",
+ .pm = &exynos_drm_pm_ops,
+ },
+};
+
+static void exynos_drm_unregister_devices(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(exynos_drm_drivers) - 1; i >= 0; --i) {
+ struct exynos_drm_driver_info *info = &exynos_drm_drivers[i];
+ struct device *dev;
+
+ if (!info->driver || !(info->flags & DRM_VIRTUAL_DEVICE))
+ continue;
+
+ while ((dev = platform_find_device_by_driver(NULL,
+ &info->driver->driver))) {
+ put_device(dev);
+ platform_device_unregister(to_platform_device(dev));
+ }
+ }
+}
+
+static int exynos_drm_register_devices(void)
+{
+ struct platform_device *pdev;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(exynos_drm_drivers); ++i) {
+ struct exynos_drm_driver_info *info = &exynos_drm_drivers[i];
+
+ if (!info->driver || !(info->flags & DRM_VIRTUAL_DEVICE))
+ continue;
+
+ pdev = platform_device_register_simple(
+ info->driver->driver.name, -1, NULL, 0);
+ if (IS_ERR(pdev))
+ goto fail;
+ }
+
+ return 0;
+fail:
+ exynos_drm_unregister_devices();
+ return PTR_ERR(pdev);
+}
+
+static void exynos_drm_unregister_drivers(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(exynos_drm_drivers) - 1; i >= 0; --i) {
+ struct exynos_drm_driver_info *info = &exynos_drm_drivers[i];
+
+ if (!info->driver)
+ continue;
+
+ platform_driver_unregister(info->driver);
+ }
+}
+
+static int exynos_drm_register_drivers(void)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(exynos_drm_drivers); ++i) {
+ struct exynos_drm_driver_info *info = &exynos_drm_drivers[i];
+
+ if (!info->driver)
+ continue;
+
+ ret = platform_driver_register(info->driver);
+ if (ret)
+ goto fail;
+ }
+ return 0;
+fail:
+ exynos_drm_unregister_drivers();
+ return ret;
+}
+
+static int exynos_drm_init(void)
+{
+ int ret;
+
+ if (drm_firmware_drivers_only())
+ return -ENODEV;
+
+ ret = exynos_drm_register_devices();
+ if (ret)
+ return ret;
+
+ ret = exynos_drm_register_drivers();
+ if (ret)
+ goto err_unregister_pdevs;
+
+ return 0;
+
+err_unregister_pdevs:
+ exynos_drm_unregister_devices();
+
+ return ret;
+}
+
+static void exynos_drm_exit(void)
+{
+ exynos_drm_unregister_drivers();
+ exynos_drm_unregister_devices();
+}
+
+module_init(exynos_drm_init);
+module_exit(exynos_drm_exit);
+
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_AUTHOR("Seung-Woo Kim <sw0312.kim@samsung.com>");
+MODULE_DESCRIPTION("Samsung SoC DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h
new file mode 100644
index 000000000..b126cd129
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h
@@ -0,0 +1,263 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* exynos_drm_drv.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_DRV_H_
+#define _EXYNOS_DRM_DRV_H_
+
+#include <linux/module.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_plane.h>
+
+#define MAX_CRTC 3
+#define MAX_PLANE 5
+
+#define DEFAULT_WIN 0
+
+struct drm_crtc_state;
+struct drm_display_mode;
+
+#define to_exynos_crtc(x) container_of(x, struct exynos_drm_crtc, base)
+#define to_exynos_plane(x) container_of(x, struct exynos_drm_plane, base)
+
+/* this enumerates display type. */
+enum exynos_drm_output_type {
+ EXYNOS_DISPLAY_TYPE_NONE,
+ /* RGB or CPU Interface. */
+ EXYNOS_DISPLAY_TYPE_LCD,
+ /* HDMI Interface. */
+ EXYNOS_DISPLAY_TYPE_HDMI,
+ /* Virtual Display Interface. */
+ EXYNOS_DISPLAY_TYPE_VIDI,
+};
+
+struct exynos_drm_rect {
+ unsigned int x, y;
+ unsigned int w, h;
+};
+
+/*
+ * Exynos drm plane state structure.
+ *
+ * @base: plane_state object (contains drm_framebuffer pointer)
+ * @src: rectangle of the source image data to be displayed (clipped to
+ * visible part).
+ * @crtc: rectangle of the target image position on hardware screen
+ * (clipped to visible part).
+ * @h_ratio: horizontal scaling ratio, 16.16 fixed point
+ * @v_ratio: vertical scaling ratio, 16.16 fixed point
+ *
+ * this structure consists plane state data that will be applied to hardware
+ * specific overlay info.
+ */
+
+struct exynos_drm_plane_state {
+ struct drm_plane_state base;
+ struct exynos_drm_rect crtc;
+ struct exynos_drm_rect src;
+ unsigned int h_ratio;
+ unsigned int v_ratio;
+};
+
+static inline struct exynos_drm_plane_state *
+to_exynos_plane_state(struct drm_plane_state *state)
+{
+ return container_of(state, struct exynos_drm_plane_state, base);
+}
+
+/*
+ * Exynos drm common overlay structure.
+ *
+ * @base: plane object
+ * @index: hardware index of the overlay layer
+ *
+ * this structure is common to exynos SoC and its contents would be copied
+ * to hardware specific overlay info.
+ */
+
+struct exynos_drm_plane {
+ struct drm_plane base;
+ const struct exynos_drm_plane_config *config;
+ unsigned int index;
+};
+
+#define EXYNOS_DRM_PLANE_CAP_DOUBLE (1 << 0)
+#define EXYNOS_DRM_PLANE_CAP_SCALE (1 << 1)
+#define EXYNOS_DRM_PLANE_CAP_ZPOS (1 << 2)
+#define EXYNOS_DRM_PLANE_CAP_TILE (1 << 3)
+#define EXYNOS_DRM_PLANE_CAP_PIX_BLEND (1 << 4)
+#define EXYNOS_DRM_PLANE_CAP_WIN_BLEND (1 << 5)
+
+/*
+ * Exynos DRM plane configuration structure.
+ *
+ * @zpos: initial z-position of the plane.
+ * @type: type of the plane (primary, cursor or overlay).
+ * @pixel_formats: supported pixel formats.
+ * @num_pixel_formats: number of elements in 'pixel_formats'.
+ * @capabilities: supported features (see EXYNOS_DRM_PLANE_CAP_*)
+ */
+
+struct exynos_drm_plane_config {
+ unsigned int zpos;
+ enum drm_plane_type type;
+ const uint32_t *pixel_formats;
+ unsigned int num_pixel_formats;
+ unsigned int capabilities;
+};
+
+/*
+ * Exynos drm crtc ops
+ *
+ * @atomic_enable: enable the device
+ * @atomic_disable: disable the device
+ * @enable_vblank: specific driver callback for enabling vblank interrupt.
+ * @disable_vblank: specific driver callback for disabling vblank interrupt.
+ * @mode_valid: specific driver callback for mode validation
+ * @atomic_check: validate state
+ * @atomic_begin: prepare device to receive an update
+ * @atomic_flush: mark the end of device update
+ * @update_plane: apply hardware specific overlay data to registers.
+ * @disable_plane: disable hardware specific overlay.
+ * @te_handler: trigger to transfer video image at the tearing effect
+ * synchronization signal if there is a page flip request.
+ */
+struct exynos_drm_crtc;
+struct exynos_drm_crtc_ops {
+ void (*atomic_enable)(struct exynos_drm_crtc *crtc);
+ void (*atomic_disable)(struct exynos_drm_crtc *crtc);
+ int (*enable_vblank)(struct exynos_drm_crtc *crtc);
+ void (*disable_vblank)(struct exynos_drm_crtc *crtc);
+ enum drm_mode_status (*mode_valid)(struct exynos_drm_crtc *crtc,
+ const struct drm_display_mode *mode);
+ bool (*mode_fixup)(struct exynos_drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode);
+ int (*atomic_check)(struct exynos_drm_crtc *crtc,
+ struct drm_crtc_state *state);
+ void (*atomic_begin)(struct exynos_drm_crtc *crtc);
+ void (*update_plane)(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane);
+ void (*disable_plane)(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane);
+ void (*atomic_flush)(struct exynos_drm_crtc *crtc);
+ void (*te_handler)(struct exynos_drm_crtc *crtc);
+};
+
+struct exynos_drm_clk {
+ void (*enable)(struct exynos_drm_clk *clk, bool enable);
+};
+
+/*
+ * Exynos specific crtc structure.
+ *
+ * @base: crtc object.
+ * @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
+ * @ops: pointer to callbacks for exynos drm specific functionality
+ * @ctx: A pointer to the crtc's implementation specific context
+ * @pipe_clk: A pointer to the crtc's pipeline clock.
+ */
+struct exynos_drm_crtc {
+ struct drm_crtc base;
+ enum exynos_drm_output_type type;
+ const struct exynos_drm_crtc_ops *ops;
+ void *ctx;
+ struct exynos_drm_clk *pipe_clk;
+ bool i80_mode : 1;
+};
+
+static inline void exynos_drm_pipe_clk_enable(struct exynos_drm_crtc *crtc,
+ bool enable)
+{
+ if (crtc->pipe_clk)
+ crtc->pipe_clk->enable(crtc->pipe_clk, enable);
+}
+
+struct drm_exynos_file_private {
+ /* for g2d api */
+ struct list_head inuse_cmdlist;
+ struct list_head event_list;
+ struct list_head userptr_list;
+};
+
+/*
+ * Exynos drm private structure.
+ *
+ * @pending: the crtcs that have pending updates to finish
+ * @lock: protect access to @pending
+ * @wait: wait an atomic commit to finish
+ */
+struct exynos_drm_private {
+ struct device *g2d_dev;
+ struct device *vidi_dev;
+ void *mapping;
+
+ /* for atomic commit */
+ u32 pending;
+ spinlock_t lock;
+ wait_queue_head_t wait;
+};
+
+static inline bool is_drm_iommu_supported(struct drm_device *drm_dev)
+{
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+
+ return priv->mapping ? true : false;
+}
+
+int exynos_drm_register_dma(struct drm_device *drm, struct device *dev,
+ void **dma_priv);
+void exynos_drm_unregister_dma(struct drm_device *drm, struct device *dev,
+ void **dma_priv);
+void exynos_drm_cleanup_dma(struct drm_device *drm);
+
+#ifdef CONFIG_DRM_EXYNOS_DPI
+struct drm_encoder *exynos_dpi_probe(struct device *dev);
+int exynos_dpi_remove(struct drm_encoder *encoder);
+int exynos_dpi_bind(struct drm_device *dev, struct drm_encoder *encoder);
+#else
+static inline struct drm_encoder *
+exynos_dpi_probe(struct device *dev) { return NULL; }
+static inline int exynos_dpi_remove(struct drm_encoder *encoder)
+{
+ return 0;
+}
+static inline int exynos_dpi_bind(struct drm_device *dev,
+ struct drm_encoder *encoder)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_DRM_EXYNOS_FIMC
+int exynos_drm_check_fimc_device(struct device *dev);
+#else
+static inline int exynos_drm_check_fimc_device(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+extern struct platform_driver fimd_driver;
+extern struct platform_driver exynos5433_decon_driver;
+extern struct platform_driver decon_driver;
+extern struct platform_driver dp_driver;
+extern struct platform_driver dsi_driver;
+extern struct platform_driver mixer_driver;
+extern struct platform_driver hdmi_driver;
+extern struct platform_driver vidi_driver;
+extern struct platform_driver g2d_driver;
+extern struct platform_driver fimc_driver;
+extern struct platform_driver rotator_driver;
+extern struct platform_driver scaler_driver;
+extern struct platform_driver gsc_driver;
+extern struct platform_driver mic_driver;
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
new file mode 100644
index 000000000..2f83f3249
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung MIPI DSIM glue for Exynos SoCs.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * Contacts: Tomasz Figa <t.figa@samsung.com>
+ */
+
+#include <linux/component.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <drm/bridge/samsung-dsim.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_probe_helper.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+
+struct exynos_dsi {
+ struct drm_encoder encoder;
+};
+
+static const struct drm_encoder_funcs exynos_drm_dsi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static irqreturn_t exynos_dsi_te_irq_handler(struct samsung_dsim *dsim)
+{
+ struct exynos_dsi *dsi = dsim->priv;
+ struct drm_encoder *encoder = &dsi->encoder;
+
+ if (dsim->state & DSIM_STATE_VIDOUT_AVAILABLE)
+ exynos_drm_crtc_te_handler(encoder->crtc);
+
+ return IRQ_HANDLED;
+}
+
+static int exynos_dsi_host_attach(struct samsung_dsim *dsim,
+ struct mipi_dsi_device *device)
+{
+ struct exynos_dsi *dsi = dsim->priv;
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_device *drm = encoder->dev;
+
+ drm_bridge_attach(encoder, &dsim->bridge,
+ list_first_entry_or_null(&encoder->bridge_chain,
+ struct drm_bridge,
+ chain_node), 0);
+
+ mutex_lock(&drm->mode_config.mutex);
+
+ dsim->lanes = device->lanes;
+ dsim->format = device->format;
+ dsim->mode_flags = device->mode_flags;
+ exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD)->i80_mode =
+ !(dsim->mode_flags & MIPI_DSI_MODE_VIDEO);
+
+ mutex_unlock(&drm->mode_config.mutex);
+
+ if (drm->mode_config.poll_enabled)
+ drm_kms_helper_hotplug_event(drm);
+
+ return 0;
+}
+
+static void exynos_dsi_host_detach(struct samsung_dsim *dsim,
+ struct mipi_dsi_device *device)
+{
+ struct exynos_dsi *dsi = dsim->priv;
+ struct drm_device *drm = dsi->encoder.dev;
+
+ if (drm->mode_config.poll_enabled)
+ drm_kms_helper_hotplug_event(drm);
+}
+
+static int exynos_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct samsung_dsim *dsim = dev_get_drvdata(dev);
+ struct exynos_dsi *dsi = dsim->priv;
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_device *drm_dev = data;
+ int ret;
+
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_drm_dsi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ return ret;
+
+ ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
+ if (ret < 0)
+ return ret;
+
+ return mipi_dsi_host_register(&dsim->dsi_host);
+}
+
+static void exynos_dsi_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct samsung_dsim *dsim = dev_get_drvdata(dev);
+
+ dsim->bridge.funcs->atomic_disable(&dsim->bridge, NULL);
+
+ mipi_dsi_host_unregister(&dsim->dsi_host);
+}
+
+static const struct component_ops exynos_dsi_component_ops = {
+ .bind = exynos_dsi_bind,
+ .unbind = exynos_dsi_unbind,
+};
+
+static int exynos_dsi_register_host(struct samsung_dsim *dsim)
+{
+ struct exynos_dsi *dsi;
+
+ dsi = devm_kzalloc(dsim->dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ dsim->priv = dsi;
+ dsim->bridge.pre_enable_prev_first = true;
+
+ return component_add(dsim->dev, &exynos_dsi_component_ops);
+}
+
+static void exynos_dsi_unregister_host(struct samsung_dsim *dsim)
+{
+ component_del(dsim->dev, &exynos_dsi_component_ops);
+}
+
+static const struct samsung_dsim_host_ops exynos_dsi_exynos_host_ops = {
+ .register_host = exynos_dsi_register_host,
+ .unregister_host = exynos_dsi_unregister_host,
+ .attach = exynos_dsi_host_attach,
+ .detach = exynos_dsi_host_detach,
+ .te_irq_handler = exynos_dsi_te_irq_handler,
+};
+
+static const struct samsung_dsim_plat_data exynos3250_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS3250,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct samsung_dsim_plat_data exynos4210_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS4210,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct samsung_dsim_plat_data exynos5410_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS5410,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct samsung_dsim_plat_data exynos5422_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS5422,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct samsung_dsim_plat_data exynos5433_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS5433,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct samsung_dsim_plat_data exynos7870_dsi_pdata = {
+ .hw_type = DSIM_TYPE_EXYNOS7870,
+ .host_ops = &exynos_dsi_exynos_host_ops,
+};
+
+static const struct of_device_id exynos_dsi_of_match[] = {
+ {
+ .compatible = "samsung,exynos3250-mipi-dsi",
+ .data = &exynos3250_dsi_pdata,
+ },
+ {
+ .compatible = "samsung,exynos4210-mipi-dsi",
+ .data = &exynos4210_dsi_pdata,
+ },
+ {
+ .compatible = "samsung,exynos5410-mipi-dsi",
+ .data = &exynos5410_dsi_pdata,
+ },
+ {
+ .compatible = "samsung,exynos5422-mipi-dsi",
+ .data = &exynos5422_dsi_pdata,
+ },
+ {
+ .compatible = "samsung,exynos5433-mipi-dsi",
+ .data = &exynos5433_dsi_pdata,
+ },
+ {
+ .compatible = "samsung,exynos7870-mipi-dsi",
+ .data = &exynos7870_dsi_pdata,
+ },
+ { /* sentinel. */ }
+};
+MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
+
+struct platform_driver dsi_driver = {
+ .probe = samsung_dsim_probe,
+ .remove = samsung_dsim_remove,
+ .driver = {
+ .name = "exynos-dsi",
+ .pm = &samsung_dsim_pm_ops,
+ .of_match_table = exynos_dsi_of_match,
+ },
+};
+
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c
new file mode 100644
index 000000000..4cff2b2b6
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_fb.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_fbdev.h"
+#include "exynos_drm_gem.h"
+
+static int check_fb_gem_memory_type(struct drm_device *drm_dev,
+ struct exynos_drm_gem *exynos_gem)
+{
+ unsigned int flags;
+
+ /*
+ * if exynos drm driver supports iommu then framebuffer can use
+ * all the buffer types.
+ */
+ if (is_drm_iommu_supported(drm_dev))
+ return 0;
+
+ flags = exynos_gem->flags;
+
+ /*
+ * Physically non-contiguous memory type for framebuffer is not
+ * supported without IOMMU.
+ */
+ if (IS_NONCONTIG_BUFFER(flags)) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "Non-contiguous GEM memory is not supported.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct drm_framebuffer_funcs exynos_drm_fb_funcs = {
+ .destroy = drm_gem_fb_destroy,
+ .create_handle = drm_gem_fb_create_handle,
+};
+
+static struct drm_framebuffer *
+exynos_drm_framebuffer_init(struct drm_device *dev,
+ const struct drm_format_info *info,
+ const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct exynos_drm_gem **exynos_gem,
+ int count)
+{
+ struct drm_framebuffer *fb;
+ int i;
+ int ret;
+
+ fb = kzalloc_obj(*fb);
+ if (!fb)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < count; i++) {
+ ret = check_fb_gem_memory_type(dev, exynos_gem[i]);
+ if (ret < 0)
+ goto err;
+
+ fb->obj[i] = &exynos_gem[i]->base;
+ }
+
+ drm_helper_mode_fill_fb_struct(dev, fb, info, mode_cmd);
+
+ ret = drm_framebuffer_init(dev, fb, &exynos_drm_fb_funcs);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev->dev,
+ "failed to initialize framebuffer\n");
+ goto err;
+ }
+
+ return fb;
+
+err:
+ kfree(fb);
+ return ERR_PTR(ret);
+}
+
+static struct drm_framebuffer *
+exynos_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
+ const struct drm_format_info *info,
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct exynos_drm_gem *exynos_gem[DRM_FORMAT_MAX_PLANES];
+ struct drm_framebuffer *fb;
+ int i;
+ int ret;
+
+ for (i = 0; i < info->num_planes; i++) {
+ unsigned int height = (i == 0) ? mode_cmd->height :
+ DIV_ROUND_UP(mode_cmd->height, info->vsub);
+ unsigned long size = height * mode_cmd->pitches[i] +
+ mode_cmd->offsets[i];
+
+ exynos_gem[i] = exynos_drm_gem_get(file_priv,
+ mode_cmd->handles[i]);
+ if (!exynos_gem[i]) {
+ DRM_DEV_ERROR(dev->dev,
+ "failed to lookup gem object\n");
+ ret = -ENOENT;
+ goto err;
+ }
+
+ if (size > exynos_gem[i]->base.size) {
+ i++;
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ fb = exynos_drm_framebuffer_init(dev, info, mode_cmd, exynos_gem, i);
+ if (IS_ERR(fb)) {
+ ret = PTR_ERR(fb);
+ goto err;
+ }
+
+ return fb;
+
+err:
+ while (i--)
+ exynos_drm_gem_put(exynos_gem[i]);
+
+ return ERR_PTR(ret);
+}
+
+dma_addr_t exynos_drm_fb_dma_addr(struct drm_framebuffer *fb, int index)
+{
+ struct exynos_drm_gem *exynos_gem;
+
+ if (WARN_ON_ONCE(index >= DRM_FORMAT_MAX_PLANES))
+ return 0;
+
+ exynos_gem = to_exynos_gem(fb->obj[index]);
+ return exynos_gem->dma_addr + fb->offsets[index];
+}
+
+static struct drm_mode_config_helper_funcs exynos_drm_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = {
+ .fb_create = exynos_user_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+void exynos_drm_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+
+ /*
+ * set max width and height as default value(4096x4096).
+ * this value would be used to check framebuffer size limitation
+ * at drm_mode_addfb().
+ */
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+
+ dev->mode_config.funcs = &exynos_drm_mode_config_funcs;
+ dev->mode_config.helper_private = &exynos_drm_mode_config_helpers;
+
+ dev->mode_config.normalize_zpos = true;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.h b/drivers/gpu/drm/exynos/exynos_drm_fb.h
new file mode 100644
index 000000000..74300ad9b
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fb.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_FB_H_
+#define _EXYNOS_DRM_FB_H_
+
+#include <linux/types.h>
+
+struct drm_device;
+struct drm_framebuffer;
+
+dma_addr_t exynos_drm_fb_dma_addr(struct drm_framebuffer *fb, int index);
+
+void exynos_drm_mode_config_init(struct drm_device *dev);
+
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
new file mode 100644
index 000000000..8641c9e84
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_fbdev.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#include <linux/fb.h>
+
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_fbdev.h"
+#include "exynos_drm_gem.h"
+
+#define MAX_CONNECTOR 4
+
+static int exynos_drm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct drm_gem_object *obj = drm_gem_fb_get_obj(helper->fb, 0);
+
+ return drm_gem_prime_mmap(obj, vma);
+}
+
+static void exynos_drm_fb_destroy(struct fb_info *info)
+{
+ struct drm_fb_helper *fb_helper = info->par;
+
+ drm_fb_helper_fini(fb_helper);
+
+ drm_client_buffer_delete(fb_helper->buffer);
+ drm_client_release(&fb_helper->client);
+}
+
+static const struct fb_ops exynos_drm_fb_ops = {
+ .owner = THIS_MODULE,
+ __FB_DEFAULT_DMAMEM_OPS_RDWR,
+ DRM_FB_HELPER_DEFAULT_OPS,
+ __FB_DEFAULT_DMAMEM_OPS_DRAW,
+ .fb_mmap = exynos_drm_fb_mmap,
+ .fb_destroy = exynos_drm_fb_destroy,
+};
+
+static const struct drm_fb_helper_funcs exynos_drm_fbdev_helper_funcs = {
+};
+
+int exynos_drm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct drm_client_dev *client = &helper->client;
+ struct drm_device *dev = client->dev;
+ struct drm_file *file = client->file;
+ struct fb_info *info = helper->info;
+ u32 fourcc, pitch;
+ u64 size;
+ const struct drm_format_info *format;
+ struct exynos_drm_gem *exynos_gem;
+ struct drm_gem_object *obj;
+ struct drm_client_buffer *buffer;
+ u32 handle;
+ int ret;
+
+ DRM_DEV_DEBUG_KMS(dev->dev,
+ "surface width(%d), height(%d) and bpp(%d\n",
+ sizes->surface_width, sizes->surface_height,
+ sizes->surface_bpp);
+
+ fourcc = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth);
+ if (fourcc == DRM_FORMAT_INVALID)
+ return -EINVAL;
+ format = drm_get_format_info(dev, fourcc, DRM_FORMAT_MOD_LINEAR);
+ if (!format)
+ return -EINVAL;
+ pitch = drm_format_info_min_pitch(format, 0, sizes->surface_width);
+ if (!pitch)
+ return -EINVAL;
+ if (check_mul_overflow(pitch, sizes->surface_height, &size))
+ return -EINVAL;
+ size = ALIGN(size, PAGE_SIZE);
+ if (size < PAGE_SIZE)
+ return -EINVAL;
+
+ exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_WC, size, true);
+ if (IS_ERR(exynos_gem))
+ return PTR_ERR(exynos_gem);
+ obj = &exynos_gem->base;
+
+ ret = drm_gem_handle_create(file, obj, &handle);
+ if (ret)
+ goto err_drm_gem_object_put;
+
+ buffer = drm_client_buffer_create(client, sizes->surface_width, sizes->surface_height,
+ fourcc, handle, pitch);
+ if (IS_ERR(buffer)) {
+ ret = PTR_ERR(buffer);
+ goto err_drm_gem_handle_delete;
+ }
+
+ helper->funcs = &exynos_drm_fbdev_helper_funcs;
+ helper->buffer = buffer;
+ helper->fb = buffer->fb;
+
+ info->fbops = &exynos_drm_fb_ops;
+
+ drm_fb_helper_fill_info(info, helper, sizes);
+
+ info->flags |= FBINFO_VIRTFB;
+ info->screen_buffer = exynos_gem->kvaddr;
+ info->screen_size = obj->size;
+ info->fix.smem_len = obj->size;
+
+ /* The handle is only needed for creating the framebuffer. */
+ drm_gem_handle_delete(file, handle);
+
+ /* The framebuffer still holds a reference on the GEM object. */
+ drm_gem_object_put(obj);
+
+ return 0;
+
+err_drm_gem_handle_delete:
+ drm_gem_handle_delete(file, handle);
+err_drm_gem_object_put:
+ drm_gem_object_put(obj);
+ return ret;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.h b/drivers/gpu/drm/exynos/exynos_drm_fbdev.h
new file mode 100644
index 000000000..02a9201ab
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_FBDEV_H_
+#define _EXYNOS_DRM_FBDEV_H_
+
+struct drm_fb_helper;
+struct drm_fb_helper_surface_size;
+
+#if defined(CONFIG_DRM_FBDEV_EMULATION)
+int exynos_drm_fbdev_driver_fbdev_probe(struct drm_fb_helper *fbh,
+ struct drm_fb_helper_surface_size *sizes);
+#define EXYNOS_DRM_FBDEV_DRIVER_OPS \
+ .fbdev_probe = exynos_drm_fbdev_driver_fbdev_probe
+#else
+#define EXYNOS_DRM_FBDEV_DRIVER_OPS \
+ .fbdev_probe = NULL
+#endif
+
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimc.c b/drivers/gpu/drm/exynos/exynos_drm_fimc.c
new file mode 100644
index 000000000..09e33a26c
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fimc.c
@@ -0,0 +1,1417 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ * Jinyoung Jeon <jy0.jeon@samsung.com>
+ * Sangmin Lee <lsmin.lee@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_ipp.h"
+#include "regs-fimc.h"
+
+/*
+ * FIMC stands for Fully Interactive Mobile Camera and
+ * supports image scaler/rotator and input/output DMA operations.
+ * input DMA reads image data from the memory.
+ * output DMA writes image data to memory.
+ * FIMC supports image rotation and image effect functions.
+ */
+
+#define FIMC_MAX_DEVS 4
+#define FIMC_MAX_SRC 2
+#define FIMC_MAX_DST 32
+#define FIMC_SHFACTOR 10
+#define FIMC_BUF_STOP 1
+#define FIMC_BUF_START 2
+#define FIMC_WIDTH_ITU_709 1280
+#define FIMC_AUTOSUSPEND_DELAY 2000
+
+static unsigned int fimc_mask = 0xc;
+module_param_named(fimc_devs, fimc_mask, uint, 0644);
+MODULE_PARM_DESC(fimc_devs, "Alias mask for assigning FIMC devices to Exynos DRM");
+
+#define get_fimc_context(dev) dev_get_drvdata(dev)
+
+enum {
+ FIMC_CLK_LCLK,
+ FIMC_CLK_GATE,
+ FIMC_CLK_WB_A,
+ FIMC_CLK_WB_B,
+ FIMC_CLKS_MAX
+};
+
+static const char * const fimc_clock_names[] = {
+ [FIMC_CLK_LCLK] = "sclk_fimc",
+ [FIMC_CLK_GATE] = "fimc",
+ [FIMC_CLK_WB_A] = "pxl_async0",
+ [FIMC_CLK_WB_B] = "pxl_async1",
+};
+
+/*
+ * A structure of scaler.
+ *
+ * @range: narrow, wide.
+ * @bypass: unused scaler path.
+ * @up_h: horizontal scale up.
+ * @up_v: vertical scale up.
+ * @hratio: horizontal ratio.
+ * @vratio: vertical ratio.
+ */
+struct fimc_scaler {
+ bool range;
+ bool bypass;
+ bool up_h;
+ bool up_v;
+ u32 hratio;
+ u32 vratio;
+};
+
+/*
+ * A structure of fimc context.
+ *
+ * @regs: memory mapped io registers.
+ * @lock: locking of operations.
+ * @clocks: fimc clocks.
+ * @sc: scaler infomations.
+ * @pol: porarity of writeback.
+ * @id: fimc id.
+ * @irq: irq number.
+ */
+struct fimc_context {
+ struct exynos_drm_ipp ipp;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct device *dev;
+ struct exynos_drm_ipp_task *task;
+ struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+
+ void __iomem *regs;
+ spinlock_t lock;
+ struct clk *clocks[FIMC_CLKS_MAX];
+ struct fimc_scaler sc;
+ int id;
+ int irq;
+};
+
+static u32 fimc_read(struct fimc_context *ctx, u32 reg)
+{
+ return readl(ctx->regs + reg);
+}
+
+static void fimc_write(struct fimc_context *ctx, u32 val, u32 reg)
+{
+ writel(val, ctx->regs + reg);
+}
+
+static void fimc_set_bits(struct fimc_context *ctx, u32 reg, u32 bits)
+{
+ void __iomem *r = ctx->regs + reg;
+
+ writel(readl(r) | bits, r);
+}
+
+static void fimc_clear_bits(struct fimc_context *ctx, u32 reg, u32 bits)
+{
+ void __iomem *r = ctx->regs + reg;
+
+ writel(readl(r) & ~bits, r);
+}
+
+static void fimc_sw_reset(struct fimc_context *ctx)
+{
+ u32 cfg;
+
+ /* stop dma operation */
+ cfg = fimc_read(ctx, EXYNOS_CISTATUS);
+ if (EXYNOS_CISTATUS_GET_ENVID_STATUS(cfg))
+ fimc_clear_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID);
+
+ fimc_set_bits(ctx, EXYNOS_CISRCFMT, EXYNOS_CISRCFMT_ITU601_8BIT);
+
+ /* disable image capture */
+ fimc_clear_bits(ctx, EXYNOS_CIIMGCPT,
+ EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN);
+
+ /* s/w reset */
+ fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST);
+
+ /* s/w reset complete */
+ fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST);
+
+ /* reset sequence */
+ fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ);
+}
+
+static void fimc_set_type_ctrl(struct fimc_context *ctx)
+{
+ u32 cfg;
+
+ cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
+ cfg &= ~(EXYNOS_CIGCTRL_TESTPATTERN_MASK |
+ EXYNOS_CIGCTRL_SELCAM_ITU_MASK |
+ EXYNOS_CIGCTRL_SELCAM_MIPI_MASK |
+ EXYNOS_CIGCTRL_SELCAM_FIMC_MASK |
+ EXYNOS_CIGCTRL_SELWB_CAMIF_MASK |
+ EXYNOS_CIGCTRL_SELWRITEBACK_MASK);
+
+ cfg |= (EXYNOS_CIGCTRL_SELCAM_ITU_A |
+ EXYNOS_CIGCTRL_SELWRITEBACK_A |
+ EXYNOS_CIGCTRL_SELCAM_MIPI_A |
+ EXYNOS_CIGCTRL_SELCAM_FIMC_ITU);
+
+ fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
+}
+
+static void fimc_handle_jpeg(struct fimc_context *ctx, bool enable)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
+
+ cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
+ if (enable)
+ cfg |= EXYNOS_CIGCTRL_CAM_JPEG;
+ else
+ cfg &= ~EXYNOS_CIGCTRL_CAM_JPEG;
+
+ fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
+}
+
+static void fimc_mask_irq(struct fimc_context *ctx, bool enable)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
+
+ cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
+ if (enable) {
+ cfg &= ~EXYNOS_CIGCTRL_IRQ_OVFEN;
+ cfg |= EXYNOS_CIGCTRL_IRQ_ENABLE | EXYNOS_CIGCTRL_IRQ_LEVEL;
+ } else
+ cfg &= ~EXYNOS_CIGCTRL_IRQ_ENABLE;
+ fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
+}
+
+static void fimc_clear_irq(struct fimc_context *ctx)
+{
+ fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_CLR);
+}
+
+static bool fimc_check_ovf(struct fimc_context *ctx)
+{
+ u32 status, flag;
+
+ status = fimc_read(ctx, EXYNOS_CISTATUS);
+ flag = EXYNOS_CISTATUS_OVFIY | EXYNOS_CISTATUS_OVFICB |
+ EXYNOS_CISTATUS_OVFICR;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "flag[0x%x]\n", flag);
+
+ if (status & flag) {
+ fimc_set_bits(ctx, EXYNOS_CIWDOFST,
+ EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB |
+ EXYNOS_CIWDOFST_CLROVFICR);
+
+ DRM_DEV_ERROR(ctx->dev,
+ "occurred overflow at %d, status 0x%x.\n",
+ ctx->id, status);
+ return true;
+ }
+
+ return false;
+}
+
+static bool fimc_check_frame_end(struct fimc_context *ctx)
+{
+ u32 cfg;
+
+ cfg = fimc_read(ctx, EXYNOS_CISTATUS);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "cfg[0x%x]\n", cfg);
+
+ if (!(cfg & EXYNOS_CISTATUS_FRAMEEND))
+ return false;
+
+ cfg &= ~(EXYNOS_CISTATUS_FRAMEEND);
+ fimc_write(ctx, cfg, EXYNOS_CISTATUS);
+
+ return true;
+}
+
+static int fimc_get_buf_id(struct fimc_context *ctx)
+{
+ u32 cfg;
+ int frame_cnt, buf_id;
+
+ cfg = fimc_read(ctx, EXYNOS_CISTATUS2);
+ frame_cnt = EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(cfg);
+
+ if (frame_cnt == 0)
+ frame_cnt = EXYNOS_CISTATUS2_GET_FRAMECOUNT_PRESENT(cfg);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "present[%d]before[%d]\n",
+ EXYNOS_CISTATUS2_GET_FRAMECOUNT_PRESENT(cfg),
+ EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(cfg));
+
+ if (frame_cnt == 0) {
+ DRM_DEV_ERROR(ctx->dev, "failed to get frame count.\n");
+ return -EIO;
+ }
+
+ buf_id = frame_cnt - 1;
+ DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]\n", buf_id);
+
+ return buf_id;
+}
+
+static void fimc_handle_lastend(struct fimc_context *ctx, bool enable)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
+
+ cfg = fimc_read(ctx, EXYNOS_CIOCTRL);
+ if (enable)
+ cfg |= EXYNOS_CIOCTRL_LASTENDEN;
+ else
+ cfg &= ~EXYNOS_CIOCTRL_LASTENDEN;
+
+ fimc_write(ctx, cfg, EXYNOS_CIOCTRL);
+}
+
+static void fimc_src_set_fmt_order(struct fimc_context *ctx, u32 fmt)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ /* RGB */
+ cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
+ cfg &= ~EXYNOS_CISCCTRL_INRGB_FMT_RGB_MASK;
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB565;
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+ return;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_XRGB8888:
+ cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB888;
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+ return;
+ default:
+ /* bypass */
+ break;
+ }
+
+ /* YUV */
+ cfg = fimc_read(ctx, EXYNOS_MSCTRL);
+ cfg &= ~(EXYNOS_MSCTRL_ORDER2P_SHIFT_MASK |
+ EXYNOS_MSCTRL_C_INT_IN_2PLANE |
+ EXYNOS_MSCTRL_ORDER422_YCBYCR);
+
+ switch (fmt) {
+ case DRM_FORMAT_YUYV:
+ cfg |= EXYNOS_MSCTRL_ORDER422_YCBYCR;
+ break;
+ case DRM_FORMAT_YVYU:
+ cfg |= EXYNOS_MSCTRL_ORDER422_YCRYCB;
+ break;
+ case DRM_FORMAT_UYVY:
+ cfg |= EXYNOS_MSCTRL_ORDER422_CBYCRY;
+ break;
+ case DRM_FORMAT_VYUY:
+ case DRM_FORMAT_YUV444:
+ cfg |= EXYNOS_MSCTRL_ORDER422_CRYCBY;
+ break;
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV61:
+ cfg |= (EXYNOS_MSCTRL_ORDER2P_LSB_CRCB |
+ EXYNOS_MSCTRL_C_INT_IN_2PLANE);
+ break;
+ case DRM_FORMAT_YUV422:
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ cfg |= EXYNOS_MSCTRL_C_INT_IN_3PLANE;
+ break;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ cfg |= (EXYNOS_MSCTRL_ORDER2P_LSB_CBCR |
+ EXYNOS_MSCTRL_C_INT_IN_2PLANE);
+ break;
+ }
+
+ fimc_write(ctx, cfg, EXYNOS_MSCTRL);
+}
+
+static void fimc_src_set_fmt(struct fimc_context *ctx, u32 fmt, bool tiled)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ cfg = fimc_read(ctx, EXYNOS_MSCTRL);
+ cfg &= ~EXYNOS_MSCTRL_INFORMAT_RGB;
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_XRGB8888:
+ cfg |= EXYNOS_MSCTRL_INFORMAT_RGB;
+ break;
+ case DRM_FORMAT_YUV444:
+ cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR420;
+ break;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_YVYU:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_VYUY:
+ cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR422_1PLANE;
+ break;
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_YUV422:
+ cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR422;
+ break;
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR420;
+ break;
+ }
+
+ fimc_write(ctx, cfg, EXYNOS_MSCTRL);
+
+ cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM);
+ cfg &= ~EXYNOS_CIDMAPARAM_R_MODE_MASK;
+
+ if (tiled)
+ cfg |= EXYNOS_CIDMAPARAM_R_MODE_64X32;
+ else
+ cfg |= EXYNOS_CIDMAPARAM_R_MODE_LINEAR;
+
+ fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM);
+
+ fimc_src_set_fmt_order(ctx, fmt);
+}
+
+static void fimc_src_set_transf(struct fimc_context *ctx, unsigned int rotation)
+{
+ unsigned int degree = rotation & DRM_MODE_ROTATE_MASK;
+ u32 cfg1, cfg2;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "rotation[%x]\n", rotation);
+
+ cfg1 = fimc_read(ctx, EXYNOS_MSCTRL);
+ cfg1 &= ~(EXYNOS_MSCTRL_FLIP_X_MIRROR |
+ EXYNOS_MSCTRL_FLIP_Y_MIRROR);
+
+ cfg2 = fimc_read(ctx, EXYNOS_CITRGFMT);
+ cfg2 &= ~EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
+
+ switch (degree) {
+ case DRM_MODE_ROTATE_0:
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg1 |= EXYNOS_MSCTRL_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg1 |= EXYNOS_MSCTRL_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_90:
+ cfg2 |= EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg1 |= EXYNOS_MSCTRL_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg1 |= EXYNOS_MSCTRL_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_180:
+ cfg1 |= (EXYNOS_MSCTRL_FLIP_X_MIRROR |
+ EXYNOS_MSCTRL_FLIP_Y_MIRROR);
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg1 &= ~EXYNOS_MSCTRL_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg1 &= ~EXYNOS_MSCTRL_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_270:
+ cfg1 |= (EXYNOS_MSCTRL_FLIP_X_MIRROR |
+ EXYNOS_MSCTRL_FLIP_Y_MIRROR);
+ cfg2 |= EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg1 &= ~EXYNOS_MSCTRL_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg1 &= ~EXYNOS_MSCTRL_FLIP_Y_MIRROR;
+ break;
+ }
+
+ fimc_write(ctx, cfg1, EXYNOS_MSCTRL);
+ fimc_write(ctx, cfg2, EXYNOS_CITRGFMT);
+}
+
+static void fimc_set_window(struct fimc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
+ u32 cfg, h1, h2, v1, v2;
+
+ /* cropped image */
+ h1 = buf->rect.x;
+ h2 = real_width - buf->rect.w - buf->rect.x;
+ v1 = buf->rect.y;
+ v2 = buf->buf.height - buf->rect.h - buf->rect.y;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]hsize[%d]vsize[%d]\n",
+ buf->rect.x, buf->rect.y, buf->rect.w, buf->rect.h,
+ real_width, buf->buf.height);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "h1[%d]h2[%d]v1[%d]v2[%d]\n", h1, h2, v1,
+ v2);
+
+ /*
+ * set window offset 1, 2 size
+ * check figure 43-21 in user manual
+ */
+ cfg = fimc_read(ctx, EXYNOS_CIWDOFST);
+ cfg &= ~(EXYNOS_CIWDOFST_WINHOROFST_MASK |
+ EXYNOS_CIWDOFST_WINVEROFST_MASK);
+ cfg |= (EXYNOS_CIWDOFST_WINHOROFST(h1) |
+ EXYNOS_CIWDOFST_WINVEROFST(v1));
+ cfg |= EXYNOS_CIWDOFST_WINOFSEN;
+ fimc_write(ctx, cfg, EXYNOS_CIWDOFST);
+
+ cfg = (EXYNOS_CIWDOFST2_WINHOROFST2(h2) |
+ EXYNOS_CIWDOFST2_WINVEROFST2(v2));
+ fimc_write(ctx, cfg, EXYNOS_CIWDOFST2);
+}
+
+static void fimc_src_set_size(struct fimc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "hsize[%d]vsize[%d]\n", real_width,
+ buf->buf.height);
+
+ /* original size */
+ cfg = (EXYNOS_ORGISIZE_HORIZONTAL(real_width) |
+ EXYNOS_ORGISIZE_VERTICAL(buf->buf.height));
+
+ fimc_write(ctx, cfg, EXYNOS_ORGISIZE);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]\n", buf->rect.x,
+ buf->rect.y, buf->rect.w, buf->rect.h);
+
+ /* set input DMA image size */
+ cfg = fimc_read(ctx, EXYNOS_CIREAL_ISIZE);
+ cfg &= ~(EXYNOS_CIREAL_ISIZE_HEIGHT_MASK |
+ EXYNOS_CIREAL_ISIZE_WIDTH_MASK);
+ cfg |= (EXYNOS_CIREAL_ISIZE_WIDTH(buf->rect.w) |
+ EXYNOS_CIREAL_ISIZE_HEIGHT(buf->rect.h));
+ fimc_write(ctx, cfg, EXYNOS_CIREAL_ISIZE);
+
+ /*
+ * set input FIFO image size
+ * for now, we support only ITU601 8 bit mode
+ */
+ cfg = (EXYNOS_CISRCFMT_ITU601_8BIT |
+ EXYNOS_CISRCFMT_SOURCEHSIZE(real_width) |
+ EXYNOS_CISRCFMT_SOURCEVSIZE(buf->buf.height));
+ fimc_write(ctx, cfg, EXYNOS_CISRCFMT);
+
+ /* offset Y(RGB), Cb, Cr */
+ cfg = (EXYNOS_CIIYOFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIIYOFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIIYOFF);
+ cfg = (EXYNOS_CIICBOFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIICBOFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIICBOFF);
+ cfg = (EXYNOS_CIICROFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIICROFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIICROFF);
+
+ fimc_set_window(ctx, buf);
+}
+
+static void fimc_src_set_addr(struct fimc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ fimc_write(ctx, buf->dma_addr[0], EXYNOS_CIIYSA(0));
+ fimc_write(ctx, buf->dma_addr[1], EXYNOS_CIICBSA(0));
+ fimc_write(ctx, buf->dma_addr[2], EXYNOS_CIICRSA(0));
+}
+
+static void fimc_dst_set_fmt_order(struct fimc_context *ctx, u32 fmt)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ /* RGB */
+ cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
+ cfg &= ~EXYNOS_CISCCTRL_OUTRGB_FMT_RGB_MASK;
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB565;
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+ return;
+ case DRM_FORMAT_RGB888:
+ cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888;
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+ return;
+ case DRM_FORMAT_XRGB8888:
+ cfg |= (EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888 |
+ EXYNOS_CISCCTRL_EXTRGB_EXTENSION);
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+ break;
+ default:
+ /* bypass */
+ break;
+ }
+
+ /* YUV */
+ cfg = fimc_read(ctx, EXYNOS_CIOCTRL);
+ cfg &= ~(EXYNOS_CIOCTRL_ORDER2P_MASK |
+ EXYNOS_CIOCTRL_ORDER422_MASK |
+ EXYNOS_CIOCTRL_YCBCR_PLANE_MASK);
+
+ switch (fmt) {
+ case DRM_FORMAT_XRGB8888:
+ cfg |= EXYNOS_CIOCTRL_ALPHA_OUT;
+ break;
+ case DRM_FORMAT_YUYV:
+ cfg |= EXYNOS_CIOCTRL_ORDER422_YCBYCR;
+ break;
+ case DRM_FORMAT_YVYU:
+ cfg |= EXYNOS_CIOCTRL_ORDER422_YCRYCB;
+ break;
+ case DRM_FORMAT_UYVY:
+ cfg |= EXYNOS_CIOCTRL_ORDER422_CBYCRY;
+ break;
+ case DRM_FORMAT_VYUY:
+ cfg |= EXYNOS_CIOCTRL_ORDER422_CRYCBY;
+ break;
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV61:
+ cfg |= EXYNOS_CIOCTRL_ORDER2P_LSB_CRCB;
+ cfg |= EXYNOS_CIOCTRL_YCBCR_2PLANE;
+ break;
+ case DRM_FORMAT_YUV422:
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ cfg |= EXYNOS_CIOCTRL_YCBCR_3PLANE;
+ break;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ cfg |= EXYNOS_CIOCTRL_ORDER2P_LSB_CBCR;
+ cfg |= EXYNOS_CIOCTRL_YCBCR_2PLANE;
+ break;
+ }
+
+ fimc_write(ctx, cfg, EXYNOS_CIOCTRL);
+}
+
+static void fimc_dst_set_fmt(struct fimc_context *ctx, u32 fmt, bool tiled)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ cfg = fimc_read(ctx, EXYNOS_CIEXTEN);
+
+ if (fmt == DRM_FORMAT_AYUV) {
+ cfg |= EXYNOS_CIEXTEN_YUV444_OUT;
+ fimc_write(ctx, cfg, EXYNOS_CIEXTEN);
+ } else {
+ cfg &= ~EXYNOS_CIEXTEN_YUV444_OUT;
+ fimc_write(ctx, cfg, EXYNOS_CIEXTEN);
+
+ cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
+ cfg &= ~EXYNOS_CITRGFMT_OUTFORMAT_MASK;
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_XRGB8888:
+ cfg |= EXYNOS_CITRGFMT_OUTFORMAT_RGB;
+ break;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_YVYU:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_VYUY:
+ cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422_1PLANE;
+ break;
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_YUV422:
+ cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422;
+ break;
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR420;
+ break;
+ }
+
+ fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
+ }
+
+ cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM);
+ cfg &= ~EXYNOS_CIDMAPARAM_W_MODE_MASK;
+
+ if (tiled)
+ cfg |= EXYNOS_CIDMAPARAM_W_MODE_64X32;
+ else
+ cfg |= EXYNOS_CIDMAPARAM_W_MODE_LINEAR;
+
+ fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM);
+
+ fimc_dst_set_fmt_order(ctx, fmt);
+}
+
+static void fimc_dst_set_transf(struct fimc_context *ctx, unsigned int rotation)
+{
+ unsigned int degree = rotation & DRM_MODE_ROTATE_MASK;
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "rotation[0x%x]\n", rotation);
+
+ cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
+ cfg &= ~EXYNOS_CITRGFMT_FLIP_MASK;
+ cfg &= ~EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE;
+
+ switch (degree) {
+ case DRM_MODE_ROTATE_0:
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg |= EXYNOS_CITRGFMT_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg |= EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_90:
+ cfg |= EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg |= EXYNOS_CITRGFMT_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg |= EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_180:
+ cfg |= (EXYNOS_CITRGFMT_FLIP_X_MIRROR |
+ EXYNOS_CITRGFMT_FLIP_Y_MIRROR);
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg &= ~EXYNOS_CITRGFMT_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg &= ~EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
+ break;
+ case DRM_MODE_ROTATE_270:
+ cfg |= (EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE |
+ EXYNOS_CITRGFMT_FLIP_X_MIRROR |
+ EXYNOS_CITRGFMT_FLIP_Y_MIRROR);
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg &= ~EXYNOS_CITRGFMT_FLIP_X_MIRROR;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg &= ~EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
+ break;
+ }
+
+ fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
+}
+
+static int fimc_set_prescaler(struct fimc_context *ctx, struct fimc_scaler *sc,
+ struct drm_exynos_ipp_task_rect *src,
+ struct drm_exynos_ipp_task_rect *dst)
+{
+ u32 cfg, cfg_ext, shfactor;
+ u32 pre_dst_width, pre_dst_height;
+ u32 hfactor, vfactor;
+ int ret = 0;
+ u32 src_w, src_h, dst_w, dst_h;
+
+ cfg_ext = fimc_read(ctx, EXYNOS_CITRGFMT);
+ if (cfg_ext & EXYNOS_CITRGFMT_INROT90_CLOCKWISE) {
+ src_w = src->h;
+ src_h = src->w;
+ } else {
+ src_w = src->w;
+ src_h = src->h;
+ }
+
+ if (cfg_ext & EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE) {
+ dst_w = dst->h;
+ dst_h = dst->w;
+ } else {
+ dst_w = dst->w;
+ dst_h = dst->h;
+ }
+
+ /* fimc_ippdrv_check_property assures that dividers are not null */
+ hfactor = fls(src_w / dst_w / 2);
+ if (hfactor > FIMC_SHFACTOR / 2) {
+ dev_err(ctx->dev, "failed to get ratio horizontal.\n");
+ return -EINVAL;
+ }
+
+ vfactor = fls(src_h / dst_h / 2);
+ if (vfactor > FIMC_SHFACTOR / 2) {
+ dev_err(ctx->dev, "failed to get ratio vertical.\n");
+ return -EINVAL;
+ }
+
+ pre_dst_width = src_w >> hfactor;
+ pre_dst_height = src_h >> vfactor;
+ DRM_DEV_DEBUG_KMS(ctx->dev, "pre_dst_width[%d]pre_dst_height[%d]\n",
+ pre_dst_width, pre_dst_height);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "hfactor[%d]vfactor[%d]\n", hfactor,
+ vfactor);
+
+ sc->hratio = (src_w << 14) / (dst_w << hfactor);
+ sc->vratio = (src_h << 14) / (dst_h << vfactor);
+ sc->up_h = (dst_w >= src_w);
+ sc->up_v = (dst_h >= src_h);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "hratio[%d]vratio[%d]up_h[%d]up_v[%d]\n",
+ sc->hratio, sc->vratio, sc->up_h, sc->up_v);
+
+ shfactor = FIMC_SHFACTOR - (hfactor + vfactor);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "shfactor[%d]\n", shfactor);
+
+ cfg = (EXYNOS_CISCPRERATIO_SHFACTOR(shfactor) |
+ EXYNOS_CISCPRERATIO_PREHORRATIO(1 << hfactor) |
+ EXYNOS_CISCPRERATIO_PREVERRATIO(1 << vfactor));
+ fimc_write(ctx, cfg, EXYNOS_CISCPRERATIO);
+
+ cfg = (EXYNOS_CISCPREDST_PREDSTWIDTH(pre_dst_width) |
+ EXYNOS_CISCPREDST_PREDSTHEIGHT(pre_dst_height));
+ fimc_write(ctx, cfg, EXYNOS_CISCPREDST);
+
+ return ret;
+}
+
+static void fimc_set_scaler(struct fimc_context *ctx, struct fimc_scaler *sc)
+{
+ u32 cfg, cfg_ext;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "range[%d]bypass[%d]up_h[%d]up_v[%d]\n",
+ sc->range, sc->bypass, sc->up_h, sc->up_v);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "hratio[%d]vratio[%d]\n",
+ sc->hratio, sc->vratio);
+
+ cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
+ cfg &= ~(EXYNOS_CISCCTRL_SCALERBYPASS |
+ EXYNOS_CISCCTRL_SCALEUP_H | EXYNOS_CISCCTRL_SCALEUP_V |
+ EXYNOS_CISCCTRL_MAIN_V_RATIO_MASK |
+ EXYNOS_CISCCTRL_MAIN_H_RATIO_MASK |
+ EXYNOS_CISCCTRL_CSCR2Y_WIDE |
+ EXYNOS_CISCCTRL_CSCY2R_WIDE);
+
+ if (sc->range)
+ cfg |= (EXYNOS_CISCCTRL_CSCR2Y_WIDE |
+ EXYNOS_CISCCTRL_CSCY2R_WIDE);
+ if (sc->bypass)
+ cfg |= EXYNOS_CISCCTRL_SCALERBYPASS;
+ if (sc->up_h)
+ cfg |= EXYNOS_CISCCTRL_SCALEUP_H;
+ if (sc->up_v)
+ cfg |= EXYNOS_CISCCTRL_SCALEUP_V;
+
+ cfg |= (EXYNOS_CISCCTRL_MAINHORRATIO((sc->hratio >> 6)) |
+ EXYNOS_CISCCTRL_MAINVERRATIO((sc->vratio >> 6)));
+ fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
+
+ cfg_ext = fimc_read(ctx, EXYNOS_CIEXTEN);
+ cfg_ext &= ~EXYNOS_CIEXTEN_MAINHORRATIO_EXT_MASK;
+ cfg_ext &= ~EXYNOS_CIEXTEN_MAINVERRATIO_EXT_MASK;
+ cfg_ext |= (EXYNOS_CIEXTEN_MAINHORRATIO_EXT(sc->hratio) |
+ EXYNOS_CIEXTEN_MAINVERRATIO_EXT(sc->vratio));
+ fimc_write(ctx, cfg_ext, EXYNOS_CIEXTEN);
+}
+
+static void fimc_dst_set_size(struct fimc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
+ u32 cfg, cfg_ext;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "hsize[%d]vsize[%d]\n", real_width,
+ buf->buf.height);
+
+ /* original size */
+ cfg = (EXYNOS_ORGOSIZE_HORIZONTAL(real_width) |
+ EXYNOS_ORGOSIZE_VERTICAL(buf->buf.height));
+
+ fimc_write(ctx, cfg, EXYNOS_ORGOSIZE);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]\n", buf->rect.x,
+ buf->rect.y,
+ buf->rect.w, buf->rect.h);
+
+ /* CSC ITU */
+ cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
+ cfg &= ~EXYNOS_CIGCTRL_CSC_MASK;
+
+ if (buf->buf.width >= FIMC_WIDTH_ITU_709)
+ cfg |= EXYNOS_CIGCTRL_CSC_ITU709;
+ else
+ cfg |= EXYNOS_CIGCTRL_CSC_ITU601;
+
+ fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
+
+ cfg_ext = fimc_read(ctx, EXYNOS_CITRGFMT);
+
+ /* target image size */
+ cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
+ cfg &= ~(EXYNOS_CITRGFMT_TARGETH_MASK |
+ EXYNOS_CITRGFMT_TARGETV_MASK);
+ if (cfg_ext & EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE)
+ cfg |= (EXYNOS_CITRGFMT_TARGETHSIZE(buf->rect.h) |
+ EXYNOS_CITRGFMT_TARGETVSIZE(buf->rect.w));
+ else
+ cfg |= (EXYNOS_CITRGFMT_TARGETHSIZE(buf->rect.w) |
+ EXYNOS_CITRGFMT_TARGETVSIZE(buf->rect.h));
+ fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
+
+ /* target area */
+ cfg = EXYNOS_CITAREA_TARGET_AREA(buf->rect.w * buf->rect.h);
+ fimc_write(ctx, cfg, EXYNOS_CITAREA);
+
+ /* offset Y(RGB), Cb, Cr */
+ cfg = (EXYNOS_CIOYOFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIOYOFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIOYOFF);
+ cfg = (EXYNOS_CIOCBOFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIOCBOFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIOCBOFF);
+ cfg = (EXYNOS_CIOCROFF_HORIZONTAL(buf->rect.x) |
+ EXYNOS_CIOCROFF_VERTICAL(buf->rect.y));
+ fimc_write(ctx, cfg, EXYNOS_CIOCROFF);
+}
+
+static void fimc_dst_set_buf_seq(struct fimc_context *ctx, u32 buf_id,
+ bool enqueue)
+{
+ unsigned long flags;
+ u32 buf_num;
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]enqueue[%d]\n", buf_id, enqueue);
+
+ spin_lock_irqsave(&ctx->lock, flags);
+
+ cfg = fimc_read(ctx, EXYNOS_CIFCNTSEQ);
+
+ if (enqueue)
+ cfg |= (1 << buf_id);
+ else
+ cfg &= ~(1 << buf_id);
+
+ fimc_write(ctx, cfg, EXYNOS_CIFCNTSEQ);
+
+ buf_num = hweight32(cfg);
+
+ if (enqueue && buf_num >= FIMC_BUF_START)
+ fimc_mask_irq(ctx, true);
+ else if (!enqueue && buf_num <= FIMC_BUF_STOP)
+ fimc_mask_irq(ctx, false);
+
+ spin_unlock_irqrestore(&ctx->lock, flags);
+}
+
+static void fimc_dst_set_addr(struct fimc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ fimc_write(ctx, buf->dma_addr[0], EXYNOS_CIOYSA(0));
+ fimc_write(ctx, buf->dma_addr[1], EXYNOS_CIOCBSA(0));
+ fimc_write(ctx, buf->dma_addr[2], EXYNOS_CIOCRSA(0));
+
+ fimc_dst_set_buf_seq(ctx, 0, true);
+}
+
+static void fimc_stop(struct fimc_context *ctx);
+
+static irqreturn_t fimc_irq_handler(int irq, void *dev_id)
+{
+ struct fimc_context *ctx = dev_id;
+ int buf_id;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fimc id[%d]\n", ctx->id);
+
+ fimc_clear_irq(ctx);
+ if (fimc_check_ovf(ctx))
+ return IRQ_NONE;
+
+ if (!fimc_check_frame_end(ctx))
+ return IRQ_NONE;
+
+ buf_id = fimc_get_buf_id(ctx);
+ if (buf_id < 0)
+ return IRQ_HANDLED;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]\n", buf_id);
+
+ if (ctx->task) {
+ struct exynos_drm_ipp_task *task = ctx->task;
+
+ ctx->task = NULL;
+ pm_runtime_mark_last_busy(ctx->dev);
+ pm_runtime_put_autosuspend(ctx->dev);
+ exynos_drm_ipp_task_done(task, 0);
+ }
+
+ fimc_dst_set_buf_seq(ctx, buf_id, false);
+ fimc_stop(ctx);
+
+ return IRQ_HANDLED;
+}
+
+static void fimc_clear_addr(struct fimc_context *ctx)
+{
+ int i;
+
+ for (i = 0; i < FIMC_MAX_SRC; i++) {
+ fimc_write(ctx, 0, EXYNOS_CIIYSA(i));
+ fimc_write(ctx, 0, EXYNOS_CIICBSA(i));
+ fimc_write(ctx, 0, EXYNOS_CIICRSA(i));
+ }
+
+ for (i = 0; i < FIMC_MAX_DST; i++) {
+ fimc_write(ctx, 0, EXYNOS_CIOYSA(i));
+ fimc_write(ctx, 0, EXYNOS_CIOCBSA(i));
+ fimc_write(ctx, 0, EXYNOS_CIOCRSA(i));
+ }
+}
+
+static void fimc_reset(struct fimc_context *ctx)
+{
+ /* reset h/w block */
+ fimc_sw_reset(ctx);
+
+ /* reset scaler capability */
+ memset(&ctx->sc, 0x0, sizeof(ctx->sc));
+
+ fimc_clear_addr(ctx);
+}
+
+static void fimc_start(struct fimc_context *ctx)
+{
+ u32 cfg0, cfg1;
+
+ fimc_mask_irq(ctx, true);
+
+ /* If set true, we can save jpeg about screen */
+ fimc_handle_jpeg(ctx, false);
+ fimc_set_scaler(ctx, &ctx->sc);
+
+ fimc_set_type_ctrl(ctx);
+ fimc_handle_lastend(ctx, false);
+
+ /* setup dma */
+ cfg0 = fimc_read(ctx, EXYNOS_MSCTRL);
+ cfg0 &= ~EXYNOS_MSCTRL_INPUT_MASK;
+ cfg0 |= EXYNOS_MSCTRL_INPUT_MEMORY;
+ fimc_write(ctx, cfg0, EXYNOS_MSCTRL);
+
+ /* Reset status */
+ fimc_write(ctx, 0x0, EXYNOS_CISTATUS);
+
+ cfg0 = fimc_read(ctx, EXYNOS_CIIMGCPT);
+ cfg0 &= ~EXYNOS_CIIMGCPT_IMGCPTEN_SC;
+ cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN_SC;
+
+ /* Scaler */
+ cfg1 = fimc_read(ctx, EXYNOS_CISCCTRL);
+ cfg1 &= ~EXYNOS_CISCCTRL_SCAN_MASK;
+ cfg1 |= (EXYNOS_CISCCTRL_PROGRESSIVE |
+ EXYNOS_CISCCTRL_SCALERSTART);
+
+ fimc_write(ctx, cfg1, EXYNOS_CISCCTRL);
+
+ /* Enable image capture*/
+ cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN;
+ fimc_write(ctx, cfg0, EXYNOS_CIIMGCPT);
+
+ /* Disable frame end irq */
+ fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE);
+
+ fimc_clear_bits(ctx, EXYNOS_CIOCTRL, EXYNOS_CIOCTRL_WEAVE_MASK);
+
+ fimc_set_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID);
+}
+
+static void fimc_stop(struct fimc_context *ctx)
+{
+ u32 cfg;
+
+ /* Source clear */
+ cfg = fimc_read(ctx, EXYNOS_MSCTRL);
+ cfg &= ~EXYNOS_MSCTRL_INPUT_MASK;
+ cfg &= ~EXYNOS_MSCTRL_ENVID;
+ fimc_write(ctx, cfg, EXYNOS_MSCTRL);
+
+ fimc_mask_irq(ctx, false);
+
+ /* reset sequence */
+ fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ);
+
+ /* Scaler disable */
+ fimc_clear_bits(ctx, EXYNOS_CISCCTRL, EXYNOS_CISCCTRL_SCALERSTART);
+
+ /* Disable image capture */
+ fimc_clear_bits(ctx, EXYNOS_CIIMGCPT,
+ EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN);
+
+ /* Enable frame end irq */
+ fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE);
+}
+
+static int fimc_commit(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct fimc_context *ctx =
+ container_of(ipp, struct fimc_context, ipp);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable FIMC device.\n");
+ return ret;
+ }
+
+ ctx->task = task;
+
+ fimc_src_set_fmt(ctx, task->src.buf.fourcc, task->src.buf.modifier);
+ fimc_src_set_size(ctx, &task->src);
+ fimc_src_set_transf(ctx, DRM_MODE_ROTATE_0);
+ fimc_src_set_addr(ctx, &task->src);
+ fimc_dst_set_fmt(ctx, task->dst.buf.fourcc, task->dst.buf.modifier);
+ fimc_dst_set_transf(ctx, task->transform.rotation);
+ fimc_dst_set_size(ctx, &task->dst);
+ fimc_dst_set_addr(ctx, &task->dst);
+ fimc_set_prescaler(ctx, &ctx->sc, &task->src.rect, &task->dst.rect);
+ fimc_start(ctx);
+
+ return 0;
+}
+
+static void fimc_abort(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct fimc_context *ctx =
+ container_of(ipp, struct fimc_context, ipp);
+
+ fimc_reset(ctx);
+
+ if (ctx->task) {
+ struct exynos_drm_ipp_task *task = ctx->task;
+
+ ctx->task = NULL;
+ pm_runtime_mark_last_busy(ctx->dev);
+ pm_runtime_put_autosuspend(ctx->dev);
+ exynos_drm_ipp_task_done(task, -EIO);
+ }
+}
+
+static const struct exynos_drm_ipp_funcs ipp_funcs = {
+ .commit = fimc_commit,
+ .abort = fimc_abort,
+};
+
+static int fimc_bind(struct device *dev, struct device *master, void *data)
+{
+ struct fimc_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &ctx->ipp;
+
+ ctx->drm_dev = drm_dev;
+ ipp->drm_dev = drm_dev;
+ exynos_drm_register_dma(drm_dev, dev, &ctx->dma_priv);
+
+ exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
+ DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE |
+ DRM_EXYNOS_IPP_CAP_SCALE | DRM_EXYNOS_IPP_CAP_CONVERT,
+ ctx->formats, ctx->num_formats, "fimc");
+
+ dev_info(dev, "The exynos fimc has been probed successfully\n");
+
+ return 0;
+}
+
+static void fimc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct fimc_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &ctx->ipp;
+
+ exynos_drm_ipp_unregister(dev, ipp);
+ exynos_drm_unregister_dma(drm_dev, dev, &ctx->dma_priv);
+}
+
+static const struct component_ops fimc_component_ops = {
+ .bind = fimc_bind,
+ .unbind = fimc_unbind,
+};
+
+static void fimc_put_clocks(struct fimc_context *ctx)
+{
+ int i;
+
+ for (i = 0; i < FIMC_CLKS_MAX; i++) {
+ if (IS_ERR(ctx->clocks[i]))
+ continue;
+ clk_put(ctx->clocks[i]);
+ ctx->clocks[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int fimc_setup_clocks(struct fimc_context *ctx)
+{
+ struct device *fimc_dev = ctx->dev;
+ struct device *dev;
+ int ret, i;
+
+ for (i = 0; i < FIMC_CLKS_MAX; i++)
+ ctx->clocks[i] = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < FIMC_CLKS_MAX; i++) {
+ if (i == FIMC_CLK_WB_A || i == FIMC_CLK_WB_B)
+ dev = fimc_dev->parent;
+ else
+ dev = fimc_dev;
+
+ ctx->clocks[i] = clk_get(dev, fimc_clock_names[i]);
+ if (IS_ERR(ctx->clocks[i])) {
+ ret = PTR_ERR(ctx->clocks[i]);
+ dev_err(fimc_dev, "failed to get clock: %s\n",
+ fimc_clock_names[i]);
+ goto e_clk_free;
+ }
+ }
+
+ ret = clk_prepare_enable(ctx->clocks[FIMC_CLK_LCLK]);
+ if (!ret)
+ return ret;
+e_clk_free:
+ fimc_put_clocks(ctx);
+ return ret;
+}
+
+int exynos_drm_check_fimc_device(struct device *dev)
+{
+ int id = of_alias_get_id(dev->of_node, "fimc");
+
+ if (id >= 0 && (BIT(id) & fimc_mask))
+ return 0;
+ return -ENODEV;
+}
+
+static const unsigned int fimc_formats[] = {
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565,
+ DRM_FORMAT_NV12, DRM_FORMAT_NV16, DRM_FORMAT_NV21, DRM_FORMAT_NV61,
+ DRM_FORMAT_UYVY, DRM_FORMAT_VYUY, DRM_FORMAT_YUYV, DRM_FORMAT_YVYU,
+ DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV444,
+};
+
+static const unsigned int fimc_tiled_formats[] = {
+ DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+};
+
+static const struct drm_exynos_ipp_limit fimc_4210_limits_v1[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, 8192, 8 }, .v = { 16, 8192, 2 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 16, 4224, 2 }, .v = { 16, 0, 2 }) },
+ { IPP_SIZE_LIMIT(ROTATED, .h = { 128, 1920 }, .v = { 128, 0 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
+ .v = { (1 << 16) / 64, (1 << 16) * 64 }) },
+};
+
+static const struct drm_exynos_ipp_limit fimc_4210_limits_v2[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, 8192, 8 }, .v = { 16, 8192, 2 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 16, 1920, 2 }, .v = { 16, 0, 2 }) },
+ { IPP_SIZE_LIMIT(ROTATED, .h = { 128, 1366 }, .v = { 128, 0 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
+ .v = { (1 << 16) / 64, (1 << 16) * 64 }) },
+};
+
+static const struct drm_exynos_ipp_limit fimc_4210_limits_tiled_v1[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 128, 1920, 128 }, .v = { 32, 1920, 32 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 128, 1920, 2 }, .v = { 128, 0, 2 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
+ .v = { (1 << 16) / 64, (1 << 16) * 64 }) },
+};
+
+static const struct drm_exynos_ipp_limit fimc_4210_limits_tiled_v2[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 128, 1920, 128 }, .v = { 32, 1920, 32 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 128, 1366, 2 }, .v = { 128, 0, 2 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
+ .v = { (1 << 16) / 64, (1 << 16) * 64 }) },
+};
+
+static int fimc_probe(struct platform_device *pdev)
+{
+ const struct drm_exynos_ipp_limit *limits;
+ struct exynos_drm_ipp_formats *formats;
+ struct device *dev = &pdev->dev;
+ struct fimc_context *ctx;
+ int ret;
+ int i, j, num_limits, num_formats;
+
+ if (exynos_drm_check_fimc_device(dev) != 0)
+ return -ENODEV;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->id = of_alias_get_id(dev->of_node, "fimc");
+
+ /* construct formats/limits array */
+ num_formats = ARRAY_SIZE(fimc_formats) + ARRAY_SIZE(fimc_tiled_formats);
+ formats = devm_kcalloc(dev, num_formats, sizeof(*formats),
+ GFP_KERNEL);
+ if (!formats)
+ return -ENOMEM;
+
+ /* linear formats */
+ if (ctx->id < 3) {
+ limits = fimc_4210_limits_v1;
+ num_limits = ARRAY_SIZE(fimc_4210_limits_v1);
+ } else {
+ limits = fimc_4210_limits_v2;
+ num_limits = ARRAY_SIZE(fimc_4210_limits_v2);
+ }
+ for (i = 0; i < ARRAY_SIZE(fimc_formats); i++) {
+ formats[i].fourcc = fimc_formats[i];
+ formats[i].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION;
+ formats[i].limits = limits;
+ formats[i].num_limits = num_limits;
+ }
+
+ /* tiled formats */
+ if (ctx->id < 3) {
+ limits = fimc_4210_limits_tiled_v1;
+ num_limits = ARRAY_SIZE(fimc_4210_limits_tiled_v1);
+ } else {
+ limits = fimc_4210_limits_tiled_v2;
+ num_limits = ARRAY_SIZE(fimc_4210_limits_tiled_v2);
+ }
+ for (j = i, i = 0; i < ARRAY_SIZE(fimc_tiled_formats); j++, i++) {
+ formats[j].fourcc = fimc_tiled_formats[i];
+ formats[j].modifier = DRM_FORMAT_MOD_SAMSUNG_64_32_TILE;
+ formats[j].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION;
+ formats[j].limits = limits;
+ formats[j].num_limits = num_limits;
+ }
+
+ ctx->formats = formats;
+ ctx->num_formats = num_formats;
+
+ /* resource memory */
+ ctx->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->regs))
+ return PTR_ERR(ctx->regs);
+
+ /* resource irq */
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(dev, ret, fimc_irq_handler,
+ 0, dev_name(dev), ctx);
+ if (ret < 0) {
+ dev_err(dev, "failed to request irq.\n");
+ return ret;
+ }
+
+ ret = fimc_setup_clocks(ctx);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_init(&ctx->lock);
+ platform_set_drvdata(pdev, ctx);
+
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, FIMC_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &fimc_component_ops);
+ if (ret)
+ goto err_pm_dis;
+
+ dev_info(dev, "drm fimc registered successfully.\n");
+
+ return 0;
+
+err_pm_dis:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ fimc_put_clocks(ctx);
+
+ return ret;
+}
+
+static void fimc_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fimc_context *ctx = get_fimc_context(dev);
+
+ component_del(dev, &fimc_component_ops);
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+
+ fimc_put_clocks(ctx);
+}
+
+static int fimc_runtime_suspend(struct device *dev)
+{
+ struct fimc_context *ctx = get_fimc_context(dev);
+
+ DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
+ clk_disable_unprepare(ctx->clocks[FIMC_CLK_GATE]);
+ return 0;
+}
+
+static int fimc_runtime_resume(struct device *dev)
+{
+ struct fimc_context *ctx = get_fimc_context(dev);
+
+ DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
+ return clk_prepare_enable(ctx->clocks[FIMC_CLK_GATE]);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(fimc_pm_ops, fimc_runtime_suspend,
+ fimc_runtime_resume, NULL);
+
+static const struct of_device_id fimc_of_match[] = {
+ { .compatible = "samsung,exynos4210-fimc" },
+ { .compatible = "samsung,exynos4212-fimc" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, fimc_of_match);
+
+struct platform_driver fimc_driver = {
+ .probe = fimc_probe,
+ .remove = fimc_remove,
+ .driver = {
+ .of_match_table = fimc_of_match,
+ .name = "exynos-drm-fimc",
+ .pm = pm_ptr(&fimc_pm_ops),
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c
new file mode 100644
index 000000000..b6abdc4f2
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c
@@ -0,0 +1,1345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_fimd.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Authors:
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ * Inki Dae <inki.dae@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+#include <video/samsung_fimd.h>
+
+#include <drm/drm_blend.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_plane.h"
+
+/*
+ * FIMD stands for Fully Interactive Mobile Display and
+ * as a display controller, it transfers contents drawn on memory
+ * to a LCD Panel through Display Interfaces such as RGB or
+ * CPU Interface.
+ */
+
+#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
+
+/* position control register for hardware window 0, 2 ~ 4.*/
+#define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16)
+#define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16)
+/*
+ * size control register for hardware windows 0 and alpha control register
+ * for hardware windows 1 ~ 4
+ */
+#define VIDOSD_C(win) (VIDOSD_BASE + 0x08 + (win) * 16)
+/* size control register for hardware windows 1 ~ 2. */
+#define VIDOSD_D(win) (VIDOSD_BASE + 0x0C + (win) * 16)
+
+#define VIDWnALPHA0(win) (VIDW_ALPHA + 0x00 + (win) * 8)
+#define VIDWnALPHA1(win) (VIDW_ALPHA + 0x04 + (win) * 8)
+
+#define VIDWx_BUF_START(win, buf) (VIDW_BUF_START(buf) + (win) * 8)
+#define VIDWx_BUF_START_S(win, buf) (VIDW_BUF_START_S(buf) + (win) * 8)
+#define VIDWx_BUF_END(win, buf) (VIDW_BUF_END(buf) + (win) * 8)
+#define VIDWx_BUF_SIZE(win, buf) (VIDW_BUF_SIZE(buf) + (win) * 4)
+
+/* color key control register for hardware window 1 ~ 4. */
+#define WKEYCON0_BASE(x) ((WKEYCON0 + 0x140) + ((x - 1) * 8))
+/* color key value register for hardware window 1 ~ 4. */
+#define WKEYCON1_BASE(x) ((WKEYCON1 + 0x140) + ((x - 1) * 8))
+
+/* I80 trigger control register */
+#define TRIGCON 0x1A4
+#define TRGMODE_ENABLE (1 << 0)
+#define SWTRGCMD_ENABLE (1 << 1)
+/* Exynos3250, 3472, 5260 5410, 5420 and 5422 only supported. */
+#define HWTRGEN_ENABLE (1 << 3)
+#define HWTRGMASK_ENABLE (1 << 4)
+/* Exynos3250, 3472, 5260, 5420 and 5422 only supported. */
+#define HWTRIGEN_PER_ENABLE (1 << 31)
+
+/* display mode change control register except exynos4 */
+#define VIDOUT_CON 0x000
+#define VIDOUT_CON_F_I80_LDI0 (0x2 << 8)
+
+/* I80 interface control for main LDI register */
+#define I80IFCONFAx(x) (0x1B0 + (x) * 4)
+#define I80IFCONFBx(x) (0x1B8 + (x) * 4)
+#define LCD_CS_SETUP(x) ((x) << 16)
+#define LCD_WR_SETUP(x) ((x) << 12)
+#define LCD_WR_ACTIVE(x) ((x) << 8)
+#define LCD_WR_HOLD(x) ((x) << 4)
+#define I80IFEN_ENABLE (1 << 0)
+
+/* FIMD has totally five hardware windows. */
+#define WINDOWS_NR 5
+
+/* HW trigger flag on i80 panel. */
+#define I80_HW_TRG (1 << 1)
+
+struct fimd_driver_data {
+ unsigned int timing_base;
+ unsigned int lcdblk_offset;
+ unsigned int lcdblk_vt_shift;
+ unsigned int lcdblk_bypass_shift;
+ unsigned int lcdblk_mic_bypass_shift;
+ unsigned int trg_type;
+
+ unsigned int has_shadowcon:1;
+ unsigned int has_clksel:1;
+ unsigned int has_limited_fmt:1;
+ unsigned int has_vidoutcon:1;
+ unsigned int has_vtsel:1;
+ unsigned int has_mic_bypass:1;
+ unsigned int has_dp_clk:1;
+ unsigned int has_hw_trigger:1;
+ unsigned int has_trigger_per_te:1;
+ unsigned int has_bgr_support:1;
+};
+
+static struct fimd_driver_data s3c64xx_fimd_driver_data = {
+ .timing_base = 0x0,
+ .has_clksel = 1,
+ .has_limited_fmt = 1,
+};
+
+static struct fimd_driver_data s5pv210_fimd_driver_data = {
+ .timing_base = 0x0,
+ .has_shadowcon = 1,
+ .has_clksel = 1,
+};
+
+static struct fimd_driver_data exynos3_fimd_driver_data = {
+ .timing_base = 0x20000,
+ .lcdblk_offset = 0x210,
+ .lcdblk_bypass_shift = 1,
+ .has_shadowcon = 1,
+ .has_vidoutcon = 1,
+};
+
+static struct fimd_driver_data exynos4_fimd_driver_data = {
+ .timing_base = 0x0,
+ .lcdblk_offset = 0x210,
+ .lcdblk_vt_shift = 10,
+ .lcdblk_bypass_shift = 1,
+ .has_shadowcon = 1,
+ .has_vtsel = 1,
+ .has_bgr_support = 1,
+};
+
+static struct fimd_driver_data exynos5_fimd_driver_data = {
+ .timing_base = 0x20000,
+ .lcdblk_offset = 0x214,
+ .lcdblk_vt_shift = 24,
+ .lcdblk_bypass_shift = 15,
+ .has_shadowcon = 1,
+ .has_vidoutcon = 1,
+ .has_vtsel = 1,
+ .has_dp_clk = 1,
+ .has_bgr_support = 1,
+};
+
+static struct fimd_driver_data exynos5420_fimd_driver_data = {
+ .timing_base = 0x20000,
+ .lcdblk_offset = 0x214,
+ .lcdblk_vt_shift = 24,
+ .lcdblk_bypass_shift = 15,
+ .lcdblk_mic_bypass_shift = 11,
+ .has_shadowcon = 1,
+ .has_vidoutcon = 1,
+ .has_vtsel = 1,
+ .has_mic_bypass = 1,
+ .has_dp_clk = 1,
+ .has_bgr_support = 1,
+};
+
+struct fimd_context {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct exynos_drm_crtc *crtc;
+ struct exynos_drm_plane planes[WINDOWS_NR];
+ struct exynos_drm_plane_config configs[WINDOWS_NR];
+ struct clk *bus_clk;
+ struct clk *lcd_clk;
+ void __iomem *regs;
+ struct regmap *sysreg;
+ unsigned long irq_flags;
+ u32 vidcon0;
+ u32 vidcon1;
+ u32 vidout_con;
+ u32 i80ifcon;
+ bool i80_if;
+ bool suspended;
+ bool dp_clk_enabled;
+ wait_queue_head_t wait_vsync_queue;
+ atomic_t wait_vsync_event;
+ atomic_t win_updated;
+ atomic_t triggering;
+ u32 clkdiv;
+
+ const struct fimd_driver_data *driver_data;
+ struct drm_encoder *encoder;
+ struct exynos_drm_clk dp_clk;
+};
+
+static const struct of_device_id fimd_driver_dt_match[] = {
+ { .compatible = "samsung,s3c6400-fimd",
+ .data = &s3c64xx_fimd_driver_data },
+ { .compatible = "samsung,s5pv210-fimd",
+ .data = &s5pv210_fimd_driver_data },
+ { .compatible = "samsung,exynos3250-fimd",
+ .data = &exynos3_fimd_driver_data },
+ { .compatible = "samsung,exynos4210-fimd",
+ .data = &exynos4_fimd_driver_data },
+ { .compatible = "samsung,exynos5250-fimd",
+ .data = &exynos5_fimd_driver_data },
+ { .compatible = "samsung,exynos5420-fimd",
+ .data = &exynos5420_fimd_driver_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, fimd_driver_dt_match);
+
+static const enum drm_plane_type fimd_win_types[WINDOWS_NR] = {
+ DRM_PLANE_TYPE_PRIMARY,
+ DRM_PLANE_TYPE_OVERLAY,
+ DRM_PLANE_TYPE_OVERLAY,
+ DRM_PLANE_TYPE_OVERLAY,
+ DRM_PLANE_TYPE_CURSOR,
+};
+
+static const uint32_t fimd_formats[] = {
+ DRM_FORMAT_C8,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+};
+
+static const uint32_t fimd_extended_formats[] = {
+ DRM_FORMAT_C8,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_ABGR8888,
+};
+
+static const unsigned int capabilities[WINDOWS_NR] = {
+ 0,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND,
+};
+
+static inline void fimd_set_bits(struct fimd_context *ctx, u32 reg, u32 mask,
+ u32 val)
+{
+ val = (val & mask) | (readl(ctx->regs + reg) & ~mask);
+ writel(val, ctx->regs + reg);
+}
+
+static int fimd_enable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ u32 val;
+
+ if (ctx->suspended)
+ return -EPERM;
+
+ if (!test_and_set_bit(0, &ctx->irq_flags)) {
+ val = readl(ctx->regs + VIDINTCON0);
+
+ val |= VIDINTCON0_INT_ENABLE;
+
+ if (ctx->i80_if) {
+ val |= VIDINTCON0_INT_I80IFDONE;
+ val |= VIDINTCON0_INT_SYSMAINCON;
+ val &= ~VIDINTCON0_INT_SYSSUBCON;
+ } else {
+ val |= VIDINTCON0_INT_FRAME;
+
+ val &= ~VIDINTCON0_FRAMESEL0_MASK;
+ val |= VIDINTCON0_FRAMESEL0_FRONTPORCH;
+ val &= ~VIDINTCON0_FRAMESEL1_MASK;
+ val |= VIDINTCON0_FRAMESEL1_NONE;
+ }
+
+ writel(val, ctx->regs + VIDINTCON0);
+ }
+
+ return 0;
+}
+
+static void fimd_disable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ u32 val;
+
+ if (ctx->suspended)
+ return;
+
+ if (test_and_clear_bit(0, &ctx->irq_flags)) {
+ val = readl(ctx->regs + VIDINTCON0);
+
+ val &= ~VIDINTCON0_INT_ENABLE;
+
+ if (ctx->i80_if) {
+ val &= ~VIDINTCON0_INT_I80IFDONE;
+ val &= ~VIDINTCON0_INT_SYSMAINCON;
+ val &= ~VIDINTCON0_INT_SYSSUBCON;
+ } else
+ val &= ~VIDINTCON0_INT_FRAME;
+
+ writel(val, ctx->regs + VIDINTCON0);
+ }
+}
+
+static void fimd_wait_for_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+
+ if (ctx->suspended)
+ return;
+
+ atomic_set(&ctx->wait_vsync_event, 1);
+
+ /*
+ * wait for FIMD to signal VSYNC interrupt or return after
+ * timeout which is set to 50ms (refresh rate of 20).
+ */
+ if (!wait_event_timeout(ctx->wait_vsync_queue,
+ !atomic_read(&ctx->wait_vsync_event),
+ HZ/20))
+ DRM_DEV_DEBUG_KMS(ctx->dev, "vblank wait timed out.\n");
+}
+
+static void fimd_enable_video_output(struct fimd_context *ctx, unsigned int win,
+ bool enable)
+{
+ u32 val = readl(ctx->regs + WINCON(win));
+
+ if (enable)
+ val |= WINCONx_ENWIN;
+ else
+ val &= ~WINCONx_ENWIN;
+
+ writel(val, ctx->regs + WINCON(win));
+}
+
+static void fimd_enable_shadow_channel_path(struct fimd_context *ctx,
+ unsigned int win,
+ bool enable)
+{
+ u32 val = readl(ctx->regs + SHADOWCON);
+
+ if (enable)
+ val |= SHADOWCON_CHx_ENABLE(win);
+ else
+ val &= ~SHADOWCON_CHx_ENABLE(win);
+
+ writel(val, ctx->regs + SHADOWCON);
+}
+
+static int fimd_clear_channels(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ unsigned int win, ch_enabled = 0;
+ int ret;
+
+ /* Hardware is in unknown state, so ensure it gets enabled properly */
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable FIMD device.\n");
+ return ret;
+ }
+
+ clk_prepare_enable(ctx->bus_clk);
+ clk_prepare_enable(ctx->lcd_clk);
+
+ /* Check if any channel is enabled. */
+ for (win = 0; win < WINDOWS_NR; win++) {
+ u32 val = readl(ctx->regs + WINCON(win));
+
+ if (val & WINCONx_ENWIN) {
+ fimd_enable_video_output(ctx, win, false);
+
+ if (ctx->driver_data->has_shadowcon)
+ fimd_enable_shadow_channel_path(ctx, win,
+ false);
+
+ ch_enabled = 1;
+ }
+ }
+
+ /* Wait for vsync, as disable channel takes effect at next vsync */
+ if (ch_enabled) {
+ ctx->suspended = false;
+
+ fimd_enable_vblank(ctx->crtc);
+ fimd_wait_for_vblank(ctx->crtc);
+ fimd_disable_vblank(ctx->crtc);
+
+ ctx->suspended = true;
+ }
+
+ clk_disable_unprepare(ctx->lcd_clk);
+ clk_disable_unprepare(ctx->bus_clk);
+
+ pm_runtime_put(ctx->dev);
+
+ return 0;
+}
+
+
+static int fimd_atomic_check(struct exynos_drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct drm_display_mode *mode = &state->adjusted_mode;
+ struct fimd_context *ctx = crtc->ctx;
+ unsigned long ideal_clk, lcd_rate;
+ u32 clkdiv;
+
+ if (mode->clock == 0) {
+ DRM_DEV_ERROR(ctx->dev, "Mode has zero clock value.\n");
+ return -EINVAL;
+ }
+
+ ideal_clk = mode->clock * 1000;
+
+ if (ctx->i80_if) {
+ /*
+ * The frame done interrupt should be occurred prior to the
+ * next TE signal.
+ */
+ ideal_clk *= 2;
+ }
+
+ lcd_rate = clk_get_rate(ctx->lcd_clk);
+ if (2 * lcd_rate < ideal_clk) {
+ DRM_DEV_ERROR(ctx->dev,
+ "sclk_fimd clock too low(%lu) for requested pixel clock(%lu)\n",
+ lcd_rate, ideal_clk);
+ return -EINVAL;
+ }
+
+ /* Find the clock divider value that gets us closest to ideal_clk */
+ clkdiv = DIV_ROUND_CLOSEST(lcd_rate, ideal_clk);
+ if (clkdiv >= 0x200) {
+ DRM_DEV_ERROR(ctx->dev, "requested pixel clock(%lu) too low\n",
+ ideal_clk);
+ return -EINVAL;
+ }
+
+ ctx->clkdiv = (clkdiv < 0x100) ? clkdiv : 0xff;
+
+ return 0;
+}
+
+static void fimd_setup_trigger(struct fimd_context *ctx)
+{
+ void __iomem *timing_base = ctx->regs + ctx->driver_data->timing_base;
+ u32 trg_type = ctx->driver_data->trg_type;
+ u32 val = readl(timing_base + TRIGCON);
+
+ val &= ~(TRGMODE_ENABLE);
+
+ if (trg_type == I80_HW_TRG) {
+ if (ctx->driver_data->has_hw_trigger)
+ val |= HWTRGEN_ENABLE | HWTRGMASK_ENABLE;
+ if (ctx->driver_data->has_trigger_per_te)
+ val |= HWTRIGEN_PER_ENABLE;
+ } else {
+ val |= TRGMODE_ENABLE;
+ }
+
+ writel(val, timing_base + TRIGCON);
+}
+
+static void fimd_commit(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ struct drm_display_mode *mode = &crtc->base.state->adjusted_mode;
+ const struct fimd_driver_data *driver_data = ctx->driver_data;
+ void __iomem *timing_base = ctx->regs + driver_data->timing_base;
+ u32 val;
+
+ if (ctx->suspended)
+ return;
+
+ /* nothing to do if we haven't set the mode yet */
+ if (mode->htotal == 0 || mode->vtotal == 0)
+ return;
+
+ if (ctx->i80_if) {
+ val = ctx->i80ifcon | I80IFEN_ENABLE;
+ writel(val, timing_base + I80IFCONFAx(0));
+
+ /* disable auto frame rate */
+ writel(0, timing_base + I80IFCONFBx(0));
+
+ /* set video type selection to I80 interface */
+ if (driver_data->has_vtsel && ctx->sysreg &&
+ regmap_update_bits(ctx->sysreg,
+ driver_data->lcdblk_offset,
+ 0x3 << driver_data->lcdblk_vt_shift,
+ 0x1 << driver_data->lcdblk_vt_shift)) {
+ DRM_DEV_ERROR(ctx->dev,
+ "Failed to update sysreg for I80 i/f.\n");
+ return;
+ }
+ } else {
+ int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
+ u32 vidcon1;
+
+ /* setup polarity values */
+ vidcon1 = ctx->vidcon1;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ vidcon1 |= VIDCON1_INV_VSYNC;
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ vidcon1 |= VIDCON1_INV_HSYNC;
+ writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);
+
+ /* setup vertical timing values. */
+ vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+ vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
+ vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
+
+ val = VIDTCON0_VBPD(vbpd - 1) |
+ VIDTCON0_VFPD(vfpd - 1) |
+ VIDTCON0_VSPW(vsync_len - 1);
+ writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);
+
+ /* setup horizontal timing values. */
+ hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+ hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
+ hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
+
+ val = VIDTCON1_HBPD(hbpd - 1) |
+ VIDTCON1_HFPD(hfpd - 1) |
+ VIDTCON1_HSPW(hsync_len - 1);
+ writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
+ }
+
+ if (driver_data->has_vidoutcon)
+ writel(ctx->vidout_con, timing_base + VIDOUT_CON);
+
+ /* set bypass selection */
+ if (ctx->sysreg && regmap_update_bits(ctx->sysreg,
+ driver_data->lcdblk_offset,
+ 0x1 << driver_data->lcdblk_bypass_shift,
+ 0x1 << driver_data->lcdblk_bypass_shift)) {
+ DRM_DEV_ERROR(ctx->dev,
+ "Failed to update sysreg for bypass setting.\n");
+ return;
+ }
+
+ /* TODO: When MIC is enabled for display path, the lcdblk_mic_bypass
+ * bit should be cleared.
+ */
+ if (driver_data->has_mic_bypass && ctx->sysreg &&
+ regmap_update_bits(ctx->sysreg,
+ driver_data->lcdblk_offset,
+ 0x1 << driver_data->lcdblk_mic_bypass_shift,
+ 0x1 << driver_data->lcdblk_mic_bypass_shift)) {
+ DRM_DEV_ERROR(ctx->dev,
+ "Failed to update sysreg for bypass mic.\n");
+ return;
+ }
+
+ /* setup horizontal and vertical display size. */
+ val = VIDTCON2_LINEVAL(mode->vdisplay - 1) |
+ VIDTCON2_HOZVAL(mode->hdisplay - 1) |
+ VIDTCON2_LINEVAL_E(mode->vdisplay - 1) |
+ VIDTCON2_HOZVAL_E(mode->hdisplay - 1);
+ writel(val, ctx->regs + driver_data->timing_base + VIDTCON2);
+
+ fimd_setup_trigger(ctx);
+
+ /*
+ * fields of register with prefix '_F' would be updated
+ * at vsync(same as dma start)
+ */
+ val = ctx->vidcon0;
+ val |= VIDCON0_ENVID | VIDCON0_ENVID_F;
+
+ if (ctx->driver_data->has_clksel)
+ val |= VIDCON0_CLKSEL_LCD;
+
+ if (ctx->clkdiv > 1)
+ val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR;
+
+ writel(val, ctx->regs + VIDCON0);
+}
+
+static void fimd_win_set_bldeq(struct fimd_context *ctx, unsigned int win,
+ unsigned int alpha, unsigned int pixel_alpha)
+{
+ u32 mask = BLENDEQ_A_FUNC_F(0xf) | BLENDEQ_B_FUNC_F(0xf);
+ u32 val = 0;
+
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ case DRM_MODE_BLEND_COVERAGE:
+ val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA_A);
+ val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A);
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ default:
+ if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
+ val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA0);
+ val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A);
+ } else {
+ val |= BLENDEQ_A_FUNC_F(BLENDEQ_ONE);
+ val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A);
+ }
+ break;
+ }
+ fimd_set_bits(ctx, BLENDEQx(win), mask, val);
+}
+
+static void fimd_win_set_bldmod(struct fimd_context *ctx, unsigned int win,
+ unsigned int alpha, unsigned int pixel_alpha)
+{
+ u32 win_alpha_l = (alpha >> 8) & 0xf;
+ u32 win_alpha_h = alpha >> 12;
+ u32 val = 0;
+
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ break;
+ case DRM_MODE_BLEND_COVERAGE:
+ case DRM_MODE_BLEND_PREMULTI:
+ default:
+ val |= WINCON1_ALPHA_SEL;
+ val |= WINCON1_BLD_PIX;
+ val |= WINCON1_ALPHA_MUL;
+ break;
+ }
+ fimd_set_bits(ctx, WINCON(win), WINCONx_BLEND_MODE_MASK, val);
+
+ /* OSD alpha */
+ val = VIDISD14C_ALPHA0_R(win_alpha_h) |
+ VIDISD14C_ALPHA0_G(win_alpha_h) |
+ VIDISD14C_ALPHA0_B(win_alpha_h) |
+ VIDISD14C_ALPHA1_R(0x0) |
+ VIDISD14C_ALPHA1_G(0x0) |
+ VIDISD14C_ALPHA1_B(0x0);
+ writel(val, ctx->regs + VIDOSD_C(win));
+
+ val = VIDW_ALPHA_R(win_alpha_l) | VIDW_ALPHA_G(win_alpha_l) |
+ VIDW_ALPHA_B(win_alpha_l);
+ writel(val, ctx->regs + VIDWnALPHA0(win));
+
+ val = VIDW_ALPHA_R(0x0) | VIDW_ALPHA_G(0x0) |
+ VIDW_ALPHA_B(0x0);
+ writel(val, ctx->regs + VIDWnALPHA1(win));
+
+ fimd_set_bits(ctx, BLENDCON, BLENDCON_NEW_MASK,
+ BLENDCON_NEW_8BIT_ALPHA_VALUE);
+}
+
+static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win,
+ struct drm_framebuffer *fb, int width)
+{
+ struct exynos_drm_plane *plane = &ctx->planes[win];
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ uint32_t pixel_format = fb->format->format;
+ unsigned int alpha = state->base.alpha;
+ u32 val = WINCONx_ENWIN;
+ unsigned int pixel_alpha;
+
+ if (fb->format->has_alpha)
+ pixel_alpha = state->base.pixel_blend_mode;
+ else
+ pixel_alpha = DRM_MODE_BLEND_PIXEL_NONE;
+
+ /*
+ * In case of s3c64xx, window 0 doesn't support alpha channel.
+ * So the request format is ARGB8888 then change it to XRGB8888.
+ */
+ if (ctx->driver_data->has_limited_fmt && !win) {
+ if (pixel_format == DRM_FORMAT_ARGB8888)
+ pixel_format = DRM_FORMAT_XRGB8888;
+ }
+
+ switch (pixel_format) {
+ case DRM_FORMAT_C8:
+ val |= WINCON0_BPPMODE_8BPP_PALETTE;
+ val |= WINCONx_BURSTLEN_8WORD;
+ val |= WINCONx_BYTSWP;
+ break;
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_XBGR1555:
+ val |= WINCON0_BPPMODE_16BPP_1555;
+ val |= WINCONx_HAWSWP;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ val |= WINCON0_BPPMODE_16BPP_565;
+ val |= WINCONx_HAWSWP;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_XBGR8888:
+ val |= WINCON0_BPPMODE_24BPP_888;
+ val |= WINCONx_WSWP;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_ABGR8888:
+ default:
+ val |= WINCON1_BPPMODE_25BPP_A1888;
+ val |= WINCONx_WSWP;
+ val |= WINCONx_BURSTLEN_16WORD;
+ break;
+ }
+
+ switch (pixel_format) {
+ case DRM_FORMAT_XBGR1555:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGR565:
+ writel(WIN_RGB_ORDER_REVERSE, ctx->regs + WIN_RGB_ORDER(win));
+ break;
+ default:
+ writel(WIN_RGB_ORDER_FORWARD, ctx->regs + WIN_RGB_ORDER(win));
+ break;
+ }
+
+ /*
+ * Setting dma-burst to 16Word causes permanent tearing for very small
+ * buffers, e.g. cursor buffer. Burst Mode switching which based on
+ * plane size is not recommended as plane size varies a lot towards the
+ * end of the screen and rapid movement causes unstable DMA, but it is
+ * still better to change dma-burst than displaying garbage.
+ */
+
+ if (width < MIN_FB_WIDTH_FOR_16WORD_BURST) {
+ val &= ~WINCONx_BURSTLEN_MASK;
+ val |= WINCONx_BURSTLEN_4WORD;
+ }
+ fimd_set_bits(ctx, WINCON(win), ~WINCONx_BLEND_MODE_MASK, val);
+
+ /* hardware window 0 doesn't support alpha channel. */
+ if (win != 0) {
+ fimd_win_set_bldmod(ctx, win, alpha, pixel_alpha);
+ fimd_win_set_bldeq(ctx, win, alpha, pixel_alpha);
+ }
+}
+
+static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win)
+{
+ unsigned int keycon0 = 0, keycon1 = 0;
+
+ keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
+ WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);
+
+ keycon1 = WxKEYCON1_COLVAL(0xffffffff);
+
+ writel(keycon0, ctx->regs + WKEYCON0_BASE(win));
+ writel(keycon1, ctx->regs + WKEYCON1_BASE(win));
+}
+
+/**
+ * fimd_shadow_protect_win() - disable updating values from shadow registers at vsync
+ *
+ * @ctx: local driver data
+ * @win: window to protect registers for
+ * @protect: 1 to protect (disable updates)
+ */
+static void fimd_shadow_protect_win(struct fimd_context *ctx,
+ unsigned int win, bool protect)
+{
+ u32 reg, bits, val;
+
+ /*
+ * SHADOWCON/PRTCON register is used for enabling timing.
+ *
+ * for example, once only width value of a register is set,
+ * if the dma is started then fimd hardware could malfunction so
+ * with protect window setting, the register fields with prefix '_F'
+ * wouldn't be updated at vsync also but updated once unprotect window
+ * is set.
+ */
+
+ if (ctx->driver_data->has_shadowcon) {
+ reg = SHADOWCON;
+ bits = SHADOWCON_WINx_PROTECT(win);
+ } else {
+ reg = PRTCON;
+ bits = PRTCON_PROTECT;
+ }
+
+ val = readl(ctx->regs + reg);
+ if (protect)
+ val |= bits;
+ else
+ val &= ~bits;
+ writel(val, ctx->regs + reg);
+}
+
+static void fimd_atomic_begin(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ int i;
+
+ if (ctx->suspended)
+ return;
+
+ for (i = 0; i < WINDOWS_NR; i++)
+ fimd_shadow_protect_win(ctx, i, true);
+}
+
+static void fimd_atomic_flush(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ int i;
+
+ if (ctx->suspended)
+ return;
+
+ for (i = 0; i < WINDOWS_NR; i++)
+ fimd_shadow_protect_win(ctx, i, false);
+
+ exynos_crtc_handle_event(crtc);
+}
+
+static void fimd_update_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ struct fimd_context *ctx = crtc->ctx;
+ struct drm_framebuffer *fb = state->base.fb;
+ dma_addr_t dma_addr;
+ unsigned long val, size, offset;
+ unsigned int last_x, last_y, buf_offsize, line_size;
+ unsigned int win = plane->index;
+ unsigned int cpp = fb->format->cpp[0];
+ unsigned int pitch = fb->pitches[0];
+
+ if (ctx->suspended)
+ return;
+
+ offset = state->src.x * cpp;
+ offset += state->src.y * pitch;
+
+ /* buffer start address */
+ dma_addr = exynos_drm_fb_dma_addr(fb, 0) + offset;
+ val = (unsigned long)dma_addr;
+ writel(val, ctx->regs + VIDWx_BUF_START(win, 0));
+
+ /* buffer end address */
+ size = pitch * state->crtc.h;
+ val = (unsigned long)(dma_addr + size);
+ writel(val, ctx->regs + VIDWx_BUF_END(win, 0));
+
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx\n",
+ (unsigned long)dma_addr, val, size);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "ovl_width = %d, ovl_height = %d\n",
+ state->crtc.w, state->crtc.h);
+
+ /* buffer size */
+ buf_offsize = pitch - (state->crtc.w * cpp);
+ line_size = state->crtc.w * cpp;
+ val = VIDW_BUF_SIZE_OFFSET(buf_offsize) |
+ VIDW_BUF_SIZE_PAGEWIDTH(line_size) |
+ VIDW_BUF_SIZE_OFFSET_E(buf_offsize) |
+ VIDW_BUF_SIZE_PAGEWIDTH_E(line_size);
+ writel(val, ctx->regs + VIDWx_BUF_SIZE(win, 0));
+
+ /* OSD position */
+ val = VIDOSDxA_TOPLEFT_X(state->crtc.x) |
+ VIDOSDxA_TOPLEFT_Y(state->crtc.y) |
+ VIDOSDxA_TOPLEFT_X_E(state->crtc.x) |
+ VIDOSDxA_TOPLEFT_Y_E(state->crtc.y);
+ writel(val, ctx->regs + VIDOSD_A(win));
+
+ last_x = state->crtc.x + state->crtc.w;
+ if (last_x)
+ last_x--;
+ last_y = state->crtc.y + state->crtc.h;
+ if (last_y)
+ last_y--;
+
+ val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y) |
+ VIDOSDxB_BOTRIGHT_X_E(last_x) | VIDOSDxB_BOTRIGHT_Y_E(last_y);
+
+ writel(val, ctx->regs + VIDOSD_B(win));
+
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "osd pos: tx = %d, ty = %d, bx = %d, by = %d\n",
+ state->crtc.x, state->crtc.y, last_x, last_y);
+
+ /* OSD size */
+ if (win != 3 && win != 4) {
+ u32 offset = VIDOSD_D(win);
+ if (win == 0)
+ offset = VIDOSD_C(win);
+ val = state->crtc.w * state->crtc.h;
+ writel(val, ctx->regs + offset);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "osd size = 0x%x\n",
+ (unsigned int)val);
+ }
+
+ fimd_win_set_pixfmt(ctx, win, fb, state->src.w);
+
+ /* hardware window 0 doesn't support color key. */
+ if (win != 0)
+ fimd_win_set_colkey(ctx, win);
+
+ fimd_enable_video_output(ctx, win, true);
+
+ if (ctx->driver_data->has_shadowcon)
+ fimd_enable_shadow_channel_path(ctx, win, true);
+
+ if (ctx->i80_if)
+ atomic_set(&ctx->win_updated, 1);
+}
+
+static void fimd_disable_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ unsigned int win = plane->index;
+
+ if (ctx->suspended)
+ return;
+
+ fimd_enable_video_output(ctx, win, false);
+
+ if (ctx->driver_data->has_shadowcon)
+ fimd_enable_shadow_channel_path(ctx, win, false);
+}
+
+static void fimd_atomic_enable(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+
+ if (!ctx->suspended)
+ return;
+
+ ctx->suspended = false;
+
+ if (pm_runtime_resume_and_get(ctx->dev) < 0) {
+ dev_warn(ctx->dev, "failed to enable FIMD device.\n");
+ return;
+ }
+
+ /* if vblank was enabled status, enable it again. */
+ if (test_and_clear_bit(0, &ctx->irq_flags))
+ fimd_enable_vblank(ctx->crtc);
+
+ fimd_commit(ctx->crtc);
+}
+
+static void fimd_atomic_disable(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ int i;
+
+ if (ctx->suspended)
+ return;
+
+ /*
+ * We need to make sure that all windows are disabled before we
+ * suspend that connector. Otherwise we might try to scan from
+ * a destroyed buffer later.
+ */
+ for (i = 0; i < WINDOWS_NR; i++)
+ fimd_disable_plane(crtc, &ctx->planes[i]);
+
+ fimd_enable_vblank(crtc);
+ fimd_wait_for_vblank(crtc);
+ fimd_disable_vblank(crtc);
+
+ writel(0, ctx->regs + VIDCON0);
+
+ pm_runtime_put_sync(ctx->dev);
+ ctx->suspended = true;
+}
+
+static void fimd_trigger(struct device *dev)
+{
+ struct fimd_context *ctx = dev_get_drvdata(dev);
+ const struct fimd_driver_data *driver_data = ctx->driver_data;
+ void *timing_base = ctx->regs + driver_data->timing_base;
+ u32 reg;
+
+ /*
+ * Skips triggering if in triggering state, because multiple triggering
+ * requests can cause panel reset.
+ */
+ if (atomic_read(&ctx->triggering))
+ return;
+
+ /* Enters triggering mode */
+ atomic_set(&ctx->triggering, 1);
+
+ reg = readl(timing_base + TRIGCON);
+ reg |= (TRGMODE_ENABLE | SWTRGCMD_ENABLE);
+ writel(reg, timing_base + TRIGCON);
+
+ /*
+ * Exits triggering mode if vblank is not enabled yet, because when the
+ * VIDINTCON0 register is not set, it can not exit from triggering mode.
+ */
+ if (!test_bit(0, &ctx->irq_flags))
+ atomic_set(&ctx->triggering, 0);
+}
+
+static void fimd_te_handler(struct exynos_drm_crtc *crtc)
+{
+ struct fimd_context *ctx = crtc->ctx;
+ u32 trg_type = ctx->driver_data->trg_type;
+
+ /* Checks the crtc is detached already from encoder */
+ if (!ctx->drm_dev)
+ return;
+
+ if (trg_type == I80_HW_TRG)
+ goto out;
+
+ /*
+ * If there is a page flip request, triggers and handles the page flip
+ * event so that current fb can be updated into panel GRAM.
+ */
+ if (atomic_add_unless(&ctx->win_updated, -1, 0))
+ fimd_trigger(ctx->dev);
+
+out:
+ /* Wakes up vsync event queue */
+ if (atomic_read(&ctx->wait_vsync_event)) {
+ atomic_set(&ctx->wait_vsync_event, 0);
+ wake_up(&ctx->wait_vsync_queue);
+ }
+
+ if (test_bit(0, &ctx->irq_flags))
+ drm_crtc_handle_vblank(&ctx->crtc->base);
+}
+
+static void fimd_dp_clock_enable(struct exynos_drm_clk *clk, bool enable)
+{
+ struct fimd_context *ctx = container_of(clk, struct fimd_context,
+ dp_clk);
+ u32 val = enable ? DP_MIE_CLK_DP_ENABLE : DP_MIE_CLK_DISABLE;
+
+ if (enable == ctx->dp_clk_enabled)
+ return;
+
+ if (enable)
+ pm_runtime_resume_and_get(ctx->dev);
+
+ ctx->dp_clk_enabled = enable;
+ writel(val, ctx->regs + DP_MIE_CLKCON);
+
+ if (!enable)
+ pm_runtime_put(ctx->dev);
+}
+
+static const struct exynos_drm_crtc_ops fimd_crtc_ops = {
+ .atomic_enable = fimd_atomic_enable,
+ .atomic_disable = fimd_atomic_disable,
+ .enable_vblank = fimd_enable_vblank,
+ .disable_vblank = fimd_disable_vblank,
+ .atomic_begin = fimd_atomic_begin,
+ .update_plane = fimd_update_plane,
+ .disable_plane = fimd_disable_plane,
+ .atomic_flush = fimd_atomic_flush,
+ .atomic_check = fimd_atomic_check,
+ .te_handler = fimd_te_handler,
+};
+
+static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
+{
+ struct fimd_context *ctx = (struct fimd_context *)dev_id;
+ u32 val, clear_bit;
+
+ val = readl(ctx->regs + VIDINTCON1);
+
+ clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME;
+ if (val & clear_bit)
+ writel(clear_bit, ctx->regs + VIDINTCON1);
+
+ /* check the crtc is detached already from encoder */
+ if (!ctx->drm_dev)
+ goto out;
+
+ if (!ctx->i80_if)
+ drm_crtc_handle_vblank(&ctx->crtc->base);
+
+ if (ctx->i80_if) {
+ /* Exits triggering mode */
+ atomic_set(&ctx->triggering, 0);
+ } else {
+ /* set wait vsync event to zero and wake up queue. */
+ if (atomic_read(&ctx->wait_vsync_event)) {
+ atomic_set(&ctx->wait_vsync_event, 0);
+ wake_up(&ctx->wait_vsync_queue);
+ }
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int fimd_bind(struct device *dev, struct device *master, void *data)
+{
+ struct fimd_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_plane *exynos_plane;
+ unsigned int i;
+ int ret;
+
+ ctx->drm_dev = drm_dev;
+
+ for (i = 0; i < WINDOWS_NR; i++) {
+ if (ctx->driver_data->has_bgr_support) {
+ ctx->configs[i].pixel_formats = fimd_extended_formats;
+ ctx->configs[i].num_pixel_formats = ARRAY_SIZE(fimd_extended_formats);
+ } else {
+ ctx->configs[i].pixel_formats = fimd_formats;
+ ctx->configs[i].num_pixel_formats = ARRAY_SIZE(fimd_formats);
+ }
+
+ ctx->configs[i].zpos = i;
+ ctx->configs[i].type = fimd_win_types[i];
+ ctx->configs[i].capabilities = capabilities[i];
+ ret = exynos_plane_init(drm_dev, &ctx->planes[i], i,
+ &ctx->configs[i]);
+ if (ret)
+ return ret;
+ }
+
+ exynos_plane = &ctx->planes[DEFAULT_WIN];
+ ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
+ EXYNOS_DISPLAY_TYPE_LCD, &fimd_crtc_ops, ctx);
+ if (IS_ERR(ctx->crtc))
+ return PTR_ERR(ctx->crtc);
+
+ if (ctx->driver_data->has_dp_clk) {
+ ctx->dp_clk.enable = fimd_dp_clock_enable;
+ ctx->crtc->pipe_clk = &ctx->dp_clk;
+ }
+
+ if (ctx->encoder)
+ exynos_dpi_bind(drm_dev, ctx->encoder);
+
+ if (is_drm_iommu_supported(drm_dev)) {
+ int ret;
+
+ ret = fimd_clear_channels(ctx->crtc);
+ if (ret < 0)
+ return ret;
+ }
+
+ return exynos_drm_register_dma(drm_dev, dev, &ctx->dma_priv);
+}
+
+static void fimd_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct fimd_context *ctx = dev_get_drvdata(dev);
+
+ fimd_atomic_disable(ctx->crtc);
+
+ exynos_drm_unregister_dma(ctx->drm_dev, ctx->dev, &ctx->dma_priv);
+
+ if (ctx->encoder)
+ exynos_dpi_remove(ctx->encoder);
+}
+
+static const struct component_ops fimd_component_ops = {
+ .bind = fimd_bind,
+ .unbind = fimd_unbind,
+};
+
+static int fimd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fimd_context *ctx;
+ struct device_node *i80_if_timings;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->suspended = true;
+ ctx->driver_data = of_device_get_match_data(dev);
+
+ if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
+ ctx->vidcon1 |= VIDCON1_INV_VDEN;
+ if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
+ ctx->vidcon1 |= VIDCON1_INV_VCLK;
+
+ i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings");
+ if (i80_if_timings) {
+ u32 val;
+
+ ctx->i80_if = true;
+
+ if (ctx->driver_data->has_vidoutcon)
+ ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0;
+ else
+ ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0;
+ /*
+ * The user manual describes that this "DSI_EN" bit is required
+ * to enable I80 24-bit data interface.
+ */
+ ctx->vidcon0 |= VIDCON0_DSI_EN;
+
+ if (of_property_read_u32(i80_if_timings, "cs-setup", &val))
+ val = 0;
+ ctx->i80ifcon = LCD_CS_SETUP(val);
+ if (of_property_read_u32(i80_if_timings, "wr-setup", &val))
+ val = 0;
+ ctx->i80ifcon |= LCD_WR_SETUP(val);
+ if (of_property_read_u32(i80_if_timings, "wr-active", &val))
+ val = 1;
+ ctx->i80ifcon |= LCD_WR_ACTIVE(val);
+ if (of_property_read_u32(i80_if_timings, "wr-hold", &val))
+ val = 0;
+ ctx->i80ifcon |= LCD_WR_HOLD(val);
+ }
+ of_node_put(i80_if_timings);
+
+ ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,sysreg");
+ if (IS_ERR(ctx->sysreg)) {
+ dev_warn(dev, "failed to get system register.\n");
+ ctx->sysreg = NULL;
+ }
+
+ ctx->bus_clk = devm_clk_get(dev, "fimd");
+ if (IS_ERR(ctx->bus_clk)) {
+ dev_err(dev, "failed to get bus clock\n");
+ return PTR_ERR(ctx->bus_clk);
+ }
+
+ ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
+ if (IS_ERR(ctx->lcd_clk)) {
+ dev_err(dev, "failed to get lcd clock\n");
+ return PTR_ERR(ctx->lcd_clk);
+ }
+
+ ctx->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->regs))
+ return PTR_ERR(ctx->regs);
+
+ ret = platform_get_irq_byname(pdev, ctx->i80_if ? "lcd_sys" : "vsync");
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(dev, ret, fimd_irq_handler, 0, "drm_fimd", ctx);
+ if (ret) {
+ dev_err(dev, "irq request failed.\n");
+ return ret;
+ }
+
+ init_waitqueue_head(&ctx->wait_vsync_queue);
+ atomic_set(&ctx->wait_vsync_event, 0);
+
+ platform_set_drvdata(pdev, ctx);
+
+ ctx->encoder = exynos_dpi_probe(dev);
+ if (IS_ERR(ctx->encoder))
+ return PTR_ERR(ctx->encoder);
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &fimd_component_ops);
+ if (ret)
+ goto err_disable_pm_runtime;
+
+ return ret;
+
+err_disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void fimd_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ component_del(&pdev->dev, &fimd_component_ops);
+}
+
+static int exynos_fimd_suspend(struct device *dev)
+{
+ struct fimd_context *ctx = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(ctx->lcd_clk);
+ clk_disable_unprepare(ctx->bus_clk);
+
+ return 0;
+}
+
+static int exynos_fimd_resume(struct device *dev)
+{
+ struct fimd_context *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(ctx->bus_clk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Failed to prepare_enable the bus clk [%d]\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ctx->lcd_clk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Failed to prepare_enable the lcd clk [%d]\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(exynos_fimd_pm_ops, exynos_fimd_suspend,
+ exynos_fimd_resume, NULL);
+
+struct platform_driver fimd_driver = {
+ .probe = fimd_probe,
+ .remove = fimd_remove,
+ .driver = {
+ .name = "exynos4-fb",
+ .pm = pm_ptr(&exynos_fimd_pm_ops),
+ .of_match_table = fimd_driver_dt_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c
new file mode 100644
index 000000000..e92a4d872
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c
@@ -0,0 +1,1617 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors: Joonyoung Shim <jy0922.shim@samsung.com>
+ */
+
+#include <linux/refcount.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_g2d.h"
+#include "exynos_drm_gem.h"
+
+#define G2D_HW_MAJOR_VER 4
+#define G2D_HW_MINOR_VER 1
+
+/* vaild register range set from user: 0x0104 ~ 0x0880 */
+#define G2D_VALID_START 0x0104
+#define G2D_VALID_END 0x0880
+
+/* general registers */
+#define G2D_SOFT_RESET 0x0000
+#define G2D_INTEN 0x0004
+#define G2D_INTC_PEND 0x000C
+#define G2D_DMA_SFR_BASE_ADDR 0x0080
+#define G2D_DMA_COMMAND 0x0084
+#define G2D_DMA_STATUS 0x008C
+#define G2D_DMA_HOLD_CMD 0x0090
+
+/* command registers */
+#define G2D_BITBLT_START 0x0100
+
+/* registers for base address */
+#define G2D_SRC_BASE_ADDR 0x0304
+#define G2D_SRC_STRIDE 0x0308
+#define G2D_SRC_COLOR_MODE 0x030C
+#define G2D_SRC_LEFT_TOP 0x0310
+#define G2D_SRC_RIGHT_BOTTOM 0x0314
+#define G2D_SRC_PLANE2_BASE_ADDR 0x0318
+#define G2D_DST_BASE_ADDR 0x0404
+#define G2D_DST_STRIDE 0x0408
+#define G2D_DST_COLOR_MODE 0x040C
+#define G2D_DST_LEFT_TOP 0x0410
+#define G2D_DST_RIGHT_BOTTOM 0x0414
+#define G2D_DST_PLANE2_BASE_ADDR 0x0418
+#define G2D_PAT_BASE_ADDR 0x0500
+#define G2D_MSK_BASE_ADDR 0x0520
+
+/* G2D_SOFT_RESET */
+#define G2D_SFRCLEAR (1 << 1)
+#define G2D_R (1 << 0)
+
+/* G2D_INTEN */
+#define G2D_INTEN_ACF (1 << 3)
+#define G2D_INTEN_UCF (1 << 2)
+#define G2D_INTEN_GCF (1 << 1)
+#define G2D_INTEN_SCF (1 << 0)
+
+/* G2D_INTC_PEND */
+#define G2D_INTP_ACMD_FIN (1 << 3)
+#define G2D_INTP_UCMD_FIN (1 << 2)
+#define G2D_INTP_GCMD_FIN (1 << 1)
+#define G2D_INTP_SCMD_FIN (1 << 0)
+
+/* G2D_DMA_COMMAND */
+#define G2D_DMA_HALT (1 << 2)
+#define G2D_DMA_CONTINUE (1 << 1)
+#define G2D_DMA_START (1 << 0)
+
+/* G2D_DMA_STATUS */
+#define G2D_DMA_LIST_DONE_COUNT (0xFF << 17)
+#define G2D_DMA_BITBLT_DONE_COUNT (0xFFFF << 1)
+#define G2D_DMA_DONE (1 << 0)
+#define G2D_DMA_LIST_DONE_COUNT_OFFSET 17
+
+/* G2D_DMA_HOLD_CMD */
+#define G2D_USER_HOLD (1 << 2)
+#define G2D_LIST_HOLD (1 << 1)
+#define G2D_BITBLT_HOLD (1 << 0)
+
+/* G2D_BITBLT_START */
+#define G2D_START_CASESEL (1 << 2)
+#define G2D_START_NHOLT (1 << 1)
+#define G2D_START_BITBLT (1 << 0)
+
+/* buffer color format */
+#define G2D_FMT_XRGB8888 0
+#define G2D_FMT_ARGB8888 1
+#define G2D_FMT_RGB565 2
+#define G2D_FMT_XRGB1555 3
+#define G2D_FMT_ARGB1555 4
+#define G2D_FMT_XRGB4444 5
+#define G2D_FMT_ARGB4444 6
+#define G2D_FMT_PACKED_RGB888 7
+#define G2D_FMT_A8 11
+#define G2D_FMT_L8 12
+
+/* buffer valid length */
+#define G2D_LEN_MIN 1
+#define G2D_LEN_MAX 8000
+
+#define G2D_CMDLIST_SIZE (PAGE_SIZE / 4)
+#define G2D_CMDLIST_NUM 64
+#define G2D_CMDLIST_POOL_SIZE (G2D_CMDLIST_SIZE * G2D_CMDLIST_NUM)
+#define G2D_CMDLIST_DATA_NUM (G2D_CMDLIST_SIZE / sizeof(u32) - 2)
+
+/* maximum buffer pool size of userptr is 64MB as default */
+#define MAX_POOL (64 * 1024 * 1024)
+
+enum {
+ BUF_TYPE_GEM = 1,
+ BUF_TYPE_USERPTR,
+};
+
+enum g2d_reg_type {
+ REG_TYPE_NONE = -1,
+ REG_TYPE_SRC,
+ REG_TYPE_SRC_PLANE2,
+ REG_TYPE_DST,
+ REG_TYPE_DST_PLANE2,
+ REG_TYPE_PAT,
+ REG_TYPE_MSK,
+ MAX_REG_TYPE_NR
+};
+
+enum g2d_flag_bits {
+ /*
+ * If set, suspends the runqueue worker after the currently
+ * processed node is finished.
+ */
+ G2D_BIT_SUSPEND_RUNQUEUE,
+ /*
+ * If set, indicates that the engine is currently busy.
+ */
+ G2D_BIT_ENGINE_BUSY,
+};
+
+/* cmdlist data structure */
+struct g2d_cmdlist {
+ u32 head;
+ unsigned long data[G2D_CMDLIST_DATA_NUM];
+ u32 last; /* last data offset */
+};
+
+/*
+ * A structure of buffer description
+ *
+ * @format: color format
+ * @stride: buffer stride/pitch in bytes
+ * @left_x: the x coordinates of left top corner
+ * @top_y: the y coordinates of left top corner
+ * @right_x: the x coordinates of right bottom corner
+ * @bottom_y: the y coordinates of right bottom corner
+ *
+ */
+struct g2d_buf_desc {
+ unsigned int format;
+ unsigned int stride;
+ unsigned int left_x;
+ unsigned int top_y;
+ unsigned int right_x;
+ unsigned int bottom_y;
+};
+
+/*
+ * A structure of buffer information
+ *
+ * @map_nr: manages the number of mapped buffers
+ * @reg_types: stores regitster type in the order of requested command
+ * @handles: stores buffer handle in its reg_type position
+ * @types: stores buffer type in its reg_type position
+ * @descs: stores buffer description in its reg_type position
+ *
+ */
+struct g2d_buf_info {
+ unsigned int map_nr;
+ enum g2d_reg_type reg_types[MAX_REG_TYPE_NR];
+ void *obj[MAX_REG_TYPE_NR];
+ unsigned int types[MAX_REG_TYPE_NR];
+ struct g2d_buf_desc descs[MAX_REG_TYPE_NR];
+};
+
+struct drm_exynos_pending_g2d_event {
+ struct drm_pending_event base;
+ struct drm_exynos_g2d_event event;
+};
+
+struct g2d_cmdlist_userptr {
+ struct list_head list;
+ dma_addr_t dma_addr;
+ unsigned long userptr;
+ unsigned long size;
+ struct page **pages;
+ unsigned int npages;
+ struct sg_table *sgt;
+ refcount_t refcount;
+ bool in_pool;
+ bool out_of_list;
+};
+struct g2d_cmdlist_node {
+ struct list_head list;
+ struct g2d_cmdlist *cmdlist;
+ dma_addr_t dma_addr;
+ struct g2d_buf_info buf_info;
+
+ struct drm_exynos_pending_g2d_event *event;
+};
+
+struct g2d_runqueue_node {
+ struct list_head list;
+ struct list_head run_cmdlist;
+ struct list_head event_list;
+ struct drm_file *filp;
+ pid_t pid;
+ struct completion complete;
+ int async;
+};
+
+struct g2d_data {
+ struct device *dev;
+ void *dma_priv;
+ struct clk *gate_clk;
+ void __iomem *regs;
+ int irq;
+ struct workqueue_struct *g2d_workq;
+ struct work_struct runqueue_work;
+ struct drm_device *drm_dev;
+ unsigned long flags;
+
+ /* cmdlist */
+ struct g2d_cmdlist_node *cmdlist_node;
+ struct list_head free_cmdlist;
+ struct mutex cmdlist_mutex;
+ dma_addr_t cmdlist_pool;
+ void *cmdlist_pool_virt;
+ unsigned long cmdlist_dma_attrs;
+
+ /* runqueue*/
+ struct g2d_runqueue_node *runqueue_node;
+ struct list_head runqueue;
+ struct mutex runqueue_mutex;
+ struct kmem_cache *runqueue_slab;
+
+ unsigned long current_pool;
+ unsigned long max_pool;
+};
+
+static inline void g2d_hw_reset(struct g2d_data *g2d)
+{
+ writel(G2D_R | G2D_SFRCLEAR, g2d->regs + G2D_SOFT_RESET);
+ clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags);
+}
+
+static int g2d_init_cmdlist(struct g2d_data *g2d)
+{
+ struct device *dev = g2d->dev;
+ struct g2d_cmdlist_node *node;
+ int nr;
+ int ret;
+ struct g2d_buf_info *buf_info;
+
+ g2d->cmdlist_dma_attrs = DMA_ATTR_WRITE_COMBINE;
+
+ g2d->cmdlist_pool_virt = dma_alloc_attrs(drm_dev_dma_dev(g2d->drm_dev),
+ G2D_CMDLIST_POOL_SIZE,
+ &g2d->cmdlist_pool, GFP_KERNEL,
+ g2d->cmdlist_dma_attrs);
+ if (!g2d->cmdlist_pool_virt) {
+ dev_err(dev, "failed to allocate dma memory\n");
+ return -ENOMEM;
+ }
+
+ node = kzalloc_objs(*node, G2D_CMDLIST_NUM);
+ if (!node) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ for (nr = 0; nr < G2D_CMDLIST_NUM; nr++) {
+ unsigned int i;
+
+ node[nr].cmdlist =
+ g2d->cmdlist_pool_virt + nr * G2D_CMDLIST_SIZE;
+ node[nr].dma_addr =
+ g2d->cmdlist_pool + nr * G2D_CMDLIST_SIZE;
+
+ buf_info = &node[nr].buf_info;
+ for (i = 0; i < MAX_REG_TYPE_NR; i++)
+ buf_info->reg_types[i] = REG_TYPE_NONE;
+
+ list_add_tail(&node[nr].list, &g2d->free_cmdlist);
+ }
+
+ return 0;
+
+err:
+ dma_free_attrs(drm_dev_dma_dev(g2d->drm_dev), G2D_CMDLIST_POOL_SIZE,
+ g2d->cmdlist_pool_virt,
+ g2d->cmdlist_pool, g2d->cmdlist_dma_attrs);
+ return ret;
+}
+
+static void g2d_fini_cmdlist(struct g2d_data *g2d)
+{
+ kfree(g2d->cmdlist_node);
+
+ if (g2d->cmdlist_pool_virt && g2d->cmdlist_pool) {
+ dma_free_attrs(drm_dev_dma_dev(g2d->drm_dev),
+ G2D_CMDLIST_POOL_SIZE,
+ g2d->cmdlist_pool_virt,
+ g2d->cmdlist_pool, g2d->cmdlist_dma_attrs);
+ }
+}
+
+static struct g2d_cmdlist_node *g2d_get_cmdlist(struct g2d_data *g2d)
+{
+ struct device *dev = g2d->dev;
+ struct g2d_cmdlist_node *node;
+
+ mutex_lock(&g2d->cmdlist_mutex);
+ if (list_empty(&g2d->free_cmdlist)) {
+ dev_err(dev, "there is no free cmdlist\n");
+ mutex_unlock(&g2d->cmdlist_mutex);
+ return NULL;
+ }
+
+ node = list_first_entry(&g2d->free_cmdlist, struct g2d_cmdlist_node,
+ list);
+ list_del_init(&node->list);
+ mutex_unlock(&g2d->cmdlist_mutex);
+
+ return node;
+}
+
+static void g2d_put_cmdlist(struct g2d_data *g2d, struct g2d_cmdlist_node *node)
+{
+ mutex_lock(&g2d->cmdlist_mutex);
+ list_move_tail(&node->list, &g2d->free_cmdlist);
+ mutex_unlock(&g2d->cmdlist_mutex);
+}
+
+static void g2d_add_cmdlist_to_inuse(struct drm_exynos_file_private *file_priv,
+ struct g2d_cmdlist_node *node)
+{
+ struct g2d_cmdlist_node *lnode;
+
+ if (list_empty(&file_priv->inuse_cmdlist))
+ goto add_to_list;
+
+ /* this links to base address of new cmdlist */
+ lnode = list_entry(file_priv->inuse_cmdlist.prev,
+ struct g2d_cmdlist_node, list);
+ lnode->cmdlist->data[lnode->cmdlist->last] = node->dma_addr;
+
+add_to_list:
+ list_add_tail(&node->list, &file_priv->inuse_cmdlist);
+
+ if (node->event)
+ list_add_tail(&node->event->base.link, &file_priv->event_list);
+}
+
+static void g2d_userptr_put_dma_addr(struct g2d_data *g2d,
+ void *obj,
+ bool force)
+{
+ struct g2d_cmdlist_userptr *g2d_userptr = obj;
+
+ if (!obj)
+ return;
+
+ if (force)
+ goto out;
+
+ refcount_dec(&g2d_userptr->refcount);
+
+ if (refcount_read(&g2d_userptr->refcount) > 0)
+ return;
+
+ if (g2d_userptr->in_pool)
+ return;
+
+out:
+ dma_unmap_sgtable(drm_dev_dma_dev(g2d->drm_dev), g2d_userptr->sgt,
+ DMA_BIDIRECTIONAL, 0);
+
+ unpin_user_pages_dirty_lock(g2d_userptr->pages, g2d_userptr->npages,
+ true);
+ kvfree(g2d_userptr->pages);
+
+ if (!g2d_userptr->out_of_list)
+ list_del_init(&g2d_userptr->list);
+
+ sg_free_table(g2d_userptr->sgt);
+ kfree(g2d_userptr->sgt);
+ kfree(g2d_userptr);
+}
+
+static dma_addr_t *g2d_userptr_get_dma_addr(struct g2d_data *g2d,
+ unsigned long userptr,
+ unsigned long size,
+ struct drm_file *filp,
+ void **obj)
+{
+ struct drm_exynos_file_private *file_priv = filp->driver_priv;
+ struct g2d_cmdlist_userptr *g2d_userptr;
+ struct sg_table *sgt;
+ unsigned long start, end;
+ unsigned int npages, offset;
+ int ret;
+
+ if (!size) {
+ DRM_DEV_ERROR(g2d->dev, "invalid userptr size.\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* check if userptr already exists in userptr_list. */
+ list_for_each_entry(g2d_userptr, &file_priv->userptr_list, list) {
+ if (g2d_userptr->userptr == userptr) {
+ /*
+ * also check size because there could be same address
+ * and different size.
+ */
+ if (g2d_userptr->size == size) {
+ refcount_inc(&g2d_userptr->refcount);
+ *obj = g2d_userptr;
+
+ return &g2d_userptr->dma_addr;
+ }
+
+ /*
+ * at this moment, maybe g2d dma is accessing this
+ * g2d_userptr memory region so just remove this
+ * g2d_userptr object from userptr_list not to be
+ * referred again and also except it the userptr
+ * pool to be released after the dma access completion.
+ */
+ g2d_userptr->out_of_list = true;
+ g2d_userptr->in_pool = false;
+ list_del_init(&g2d_userptr->list);
+
+ break;
+ }
+ }
+
+ g2d_userptr = kzalloc_obj(*g2d_userptr);
+ if (!g2d_userptr)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&g2d_userptr->refcount, 1);
+ g2d_userptr->size = size;
+
+ start = userptr & PAGE_MASK;
+ offset = userptr & ~PAGE_MASK;
+ end = PAGE_ALIGN(userptr + size);
+ npages = (end - start) >> PAGE_SHIFT;
+ g2d_userptr->pages = kvmalloc_objs(*g2d_userptr->pages, npages);
+ if (!g2d_userptr->pages) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ ret = pin_user_pages_fast(start, npages,
+ FOLL_WRITE | FOLL_LONGTERM,
+ g2d_userptr->pages);
+ if (ret != npages) {
+ DRM_DEV_ERROR(g2d->dev,
+ "failed to get user pages from userptr.\n");
+ if (ret < 0)
+ goto err_destroy_pages;
+ npages = ret;
+ ret = -EFAULT;
+ goto err_unpin_pages;
+ }
+ g2d_userptr->npages = npages;
+
+ sgt = kzalloc_obj(*sgt);
+ if (!sgt) {
+ ret = -ENOMEM;
+ goto err_unpin_pages;
+ }
+
+ ret = sg_alloc_table_from_pages(sgt,
+ g2d_userptr->pages,
+ npages, offset, size, GFP_KERNEL);
+ if (ret < 0) {
+ DRM_DEV_ERROR(g2d->dev, "failed to get sgt from pages.\n");
+ goto err_free_sgt;
+ }
+
+ g2d_userptr->sgt = sgt;
+
+ ret = dma_map_sgtable(drm_dev_dma_dev(g2d->drm_dev), sgt,
+ DMA_BIDIRECTIONAL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(g2d->dev, "failed to map sgt with dma region.\n");
+ goto err_sg_free_table;
+ }
+
+ g2d_userptr->dma_addr = sgt->sgl[0].dma_address;
+ g2d_userptr->userptr = userptr;
+
+ list_add_tail(&g2d_userptr->list, &file_priv->userptr_list);
+
+ if (g2d->current_pool + (npages << PAGE_SHIFT) < g2d->max_pool) {
+ g2d->current_pool += npages << PAGE_SHIFT;
+ g2d_userptr->in_pool = true;
+ }
+
+ *obj = g2d_userptr;
+
+ return &g2d_userptr->dma_addr;
+
+err_sg_free_table:
+ sg_free_table(sgt);
+
+err_free_sgt:
+ kfree(sgt);
+
+err_unpin_pages:
+ unpin_user_pages(g2d_userptr->pages, npages);
+
+err_destroy_pages:
+ kvfree(g2d_userptr->pages);
+
+err_free:
+ kfree(g2d_userptr);
+
+ return ERR_PTR(ret);
+}
+
+static void g2d_userptr_free_all(struct g2d_data *g2d, struct drm_file *filp)
+{
+ struct drm_exynos_file_private *file_priv = filp->driver_priv;
+ struct g2d_cmdlist_userptr *g2d_userptr, *n;
+
+ list_for_each_entry_safe(g2d_userptr, n, &file_priv->userptr_list, list)
+ if (g2d_userptr->in_pool)
+ g2d_userptr_put_dma_addr(g2d, g2d_userptr, true);
+
+ g2d->current_pool = 0;
+}
+
+static enum g2d_reg_type g2d_get_reg_type(struct g2d_data *g2d, int reg_offset)
+{
+ enum g2d_reg_type reg_type;
+
+ switch (reg_offset) {
+ case G2D_SRC_BASE_ADDR:
+ case G2D_SRC_STRIDE:
+ case G2D_SRC_COLOR_MODE:
+ case G2D_SRC_LEFT_TOP:
+ case G2D_SRC_RIGHT_BOTTOM:
+ reg_type = REG_TYPE_SRC;
+ break;
+ case G2D_SRC_PLANE2_BASE_ADDR:
+ reg_type = REG_TYPE_SRC_PLANE2;
+ break;
+ case G2D_DST_BASE_ADDR:
+ case G2D_DST_STRIDE:
+ case G2D_DST_COLOR_MODE:
+ case G2D_DST_LEFT_TOP:
+ case G2D_DST_RIGHT_BOTTOM:
+ reg_type = REG_TYPE_DST;
+ break;
+ case G2D_DST_PLANE2_BASE_ADDR:
+ reg_type = REG_TYPE_DST_PLANE2;
+ break;
+ case G2D_PAT_BASE_ADDR:
+ reg_type = REG_TYPE_PAT;
+ break;
+ case G2D_MSK_BASE_ADDR:
+ reg_type = REG_TYPE_MSK;
+ break;
+ default:
+ reg_type = REG_TYPE_NONE;
+ DRM_DEV_ERROR(g2d->dev, "Unknown register offset![%d]\n",
+ reg_offset);
+ break;
+ }
+
+ return reg_type;
+}
+
+static unsigned long g2d_get_buf_bpp(unsigned int format)
+{
+ unsigned long bpp;
+
+ switch (format) {
+ case G2D_FMT_XRGB8888:
+ case G2D_FMT_ARGB8888:
+ bpp = 4;
+ break;
+ case G2D_FMT_RGB565:
+ case G2D_FMT_XRGB1555:
+ case G2D_FMT_ARGB1555:
+ case G2D_FMT_XRGB4444:
+ case G2D_FMT_ARGB4444:
+ bpp = 2;
+ break;
+ case G2D_FMT_PACKED_RGB888:
+ bpp = 3;
+ break;
+ default:
+ bpp = 1;
+ break;
+ }
+
+ return bpp;
+}
+
+static bool g2d_check_buf_desc_is_valid(struct g2d_data *g2d,
+ struct g2d_buf_desc *buf_desc,
+ enum g2d_reg_type reg_type,
+ unsigned long size)
+{
+ int width, height;
+ unsigned long bpp, last_pos;
+
+ /*
+ * check source and destination buffers only.
+ * so the others are always valid.
+ */
+ if (reg_type != REG_TYPE_SRC && reg_type != REG_TYPE_DST)
+ return true;
+
+ /* This check also makes sure that right_x > left_x. */
+ width = (int)buf_desc->right_x - (int)buf_desc->left_x;
+ if (width < G2D_LEN_MIN || width > G2D_LEN_MAX) {
+ DRM_DEV_ERROR(g2d->dev, "width[%d] is out of range!\n", width);
+ return false;
+ }
+
+ /* This check also makes sure that bottom_y > top_y. */
+ height = (int)buf_desc->bottom_y - (int)buf_desc->top_y;
+ if (height < G2D_LEN_MIN || height > G2D_LEN_MAX) {
+ DRM_DEV_ERROR(g2d->dev,
+ "height[%d] is out of range!\n", height);
+ return false;
+ }
+
+ bpp = g2d_get_buf_bpp(buf_desc->format);
+
+ /* Compute the position of the last byte that the engine accesses. */
+ last_pos = ((unsigned long)buf_desc->bottom_y - 1) *
+ (unsigned long)buf_desc->stride +
+ (unsigned long)buf_desc->right_x * bpp - 1;
+
+ /*
+ * Since right_x > left_x and bottom_y > top_y we already know
+ * that the first_pos < last_pos (first_pos being the position
+ * of the first byte the engine accesses), it just remains to
+ * check if last_pos is smaller then the buffer size.
+ */
+
+ if (last_pos >= size) {
+ DRM_DEV_ERROR(g2d->dev, "last engine access position [%lu] "
+ "is out of range [%lu]!\n", last_pos, size);
+ return false;
+ }
+
+ return true;
+}
+
+static int g2d_map_cmdlist_gem(struct g2d_data *g2d,
+ struct g2d_cmdlist_node *node,
+ struct drm_device *drm_dev,
+ struct drm_file *file)
+{
+ struct g2d_cmdlist *cmdlist = node->cmdlist;
+ struct g2d_buf_info *buf_info = &node->buf_info;
+ int offset;
+ int ret;
+ int i;
+
+ for (i = 0; i < buf_info->map_nr; i++) {
+ struct g2d_buf_desc *buf_desc;
+ enum g2d_reg_type reg_type;
+ int reg_pos;
+ unsigned long handle;
+ dma_addr_t *addr;
+
+ reg_pos = cmdlist->last - 2 * (i + 1);
+
+ offset = cmdlist->data[reg_pos];
+ handle = cmdlist->data[reg_pos + 1];
+
+ reg_type = g2d_get_reg_type(g2d, offset);
+ if (reg_type == REG_TYPE_NONE) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ buf_desc = &buf_info->descs[reg_type];
+
+ if (buf_info->types[reg_type] == BUF_TYPE_GEM) {
+ struct exynos_drm_gem *exynos_gem;
+
+ exynos_gem = exynos_drm_gem_get(file, handle);
+ if (!exynos_gem) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ if (!g2d_check_buf_desc_is_valid(g2d, buf_desc,
+ reg_type, exynos_gem->base.size)) {
+ exynos_drm_gem_put(exynos_gem);
+ ret = -EFAULT;
+ goto err;
+ }
+
+ addr = &exynos_gem->dma_addr;
+ buf_info->obj[reg_type] = exynos_gem;
+ } else {
+ struct drm_exynos_g2d_userptr g2d_userptr;
+
+ if (copy_from_user(&g2d_userptr, (void __user *)handle,
+ sizeof(struct drm_exynos_g2d_userptr))) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ if (!g2d_check_buf_desc_is_valid(g2d, buf_desc,
+ reg_type,
+ g2d_userptr.size)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ addr = g2d_userptr_get_dma_addr(g2d,
+ g2d_userptr.userptr,
+ g2d_userptr.size,
+ file,
+ &buf_info->obj[reg_type]);
+ if (IS_ERR(addr)) {
+ ret = -EFAULT;
+ goto err;
+ }
+ }
+
+ cmdlist->data[reg_pos + 1] = *addr;
+ buf_info->reg_types[i] = reg_type;
+ }
+
+ return 0;
+
+err:
+ buf_info->map_nr = i;
+ return ret;
+}
+
+static void g2d_unmap_cmdlist_gem(struct g2d_data *g2d,
+ struct g2d_cmdlist_node *node,
+ struct drm_file *filp)
+{
+ struct g2d_buf_info *buf_info = &node->buf_info;
+ int i;
+
+ for (i = 0; i < buf_info->map_nr; i++) {
+ struct g2d_buf_desc *buf_desc;
+ enum g2d_reg_type reg_type;
+ void *obj;
+
+ reg_type = buf_info->reg_types[i];
+
+ buf_desc = &buf_info->descs[reg_type];
+ obj = buf_info->obj[reg_type];
+
+ if (buf_info->types[reg_type] == BUF_TYPE_GEM)
+ exynos_drm_gem_put(obj);
+ else
+ g2d_userptr_put_dma_addr(g2d, obj, false);
+
+ buf_info->reg_types[i] = REG_TYPE_NONE;
+ buf_info->obj[reg_type] = NULL;
+ buf_info->types[reg_type] = 0;
+ memset(buf_desc, 0x00, sizeof(*buf_desc));
+ }
+
+ buf_info->map_nr = 0;
+}
+
+static void g2d_dma_start(struct g2d_data *g2d,
+ struct g2d_runqueue_node *runqueue_node)
+{
+ struct g2d_cmdlist_node *node =
+ list_first_entry(&runqueue_node->run_cmdlist,
+ struct g2d_cmdlist_node, list);
+
+ set_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags);
+ writel_relaxed(node->dma_addr, g2d->regs + G2D_DMA_SFR_BASE_ADDR);
+ writel_relaxed(G2D_DMA_START, g2d->regs + G2D_DMA_COMMAND);
+}
+
+static struct g2d_runqueue_node *g2d_get_runqueue_node(struct g2d_data *g2d)
+{
+ struct g2d_runqueue_node *runqueue_node;
+
+ if (list_empty(&g2d->runqueue))
+ return NULL;
+
+ runqueue_node = list_first_entry(&g2d->runqueue,
+ struct g2d_runqueue_node, list);
+ list_del_init(&runqueue_node->list);
+ return runqueue_node;
+}
+
+static void g2d_free_runqueue_node(struct g2d_data *g2d,
+ struct g2d_runqueue_node *runqueue_node)
+{
+ struct g2d_cmdlist_node *node;
+
+ mutex_lock(&g2d->cmdlist_mutex);
+ /*
+ * commands in run_cmdlist have been completed so unmap all gem
+ * objects in each command node so that they are unreferenced.
+ */
+ list_for_each_entry(node, &runqueue_node->run_cmdlist, list)
+ g2d_unmap_cmdlist_gem(g2d, node, runqueue_node->filp);
+ list_splice_tail_init(&runqueue_node->run_cmdlist, &g2d->free_cmdlist);
+ mutex_unlock(&g2d->cmdlist_mutex);
+
+ kmem_cache_free(g2d->runqueue_slab, runqueue_node);
+}
+
+/**
+ * g2d_remove_runqueue_nodes - remove items from the list of runqueue nodes
+ * @g2d: G2D state object
+ * @file: if not zero, only remove items with this DRM file
+ *
+ * Has to be called under runqueue lock.
+ */
+static void g2d_remove_runqueue_nodes(struct g2d_data *g2d, struct drm_file *file)
+{
+ struct g2d_runqueue_node *node, *n;
+
+ if (list_empty(&g2d->runqueue))
+ return;
+
+ list_for_each_entry_safe(node, n, &g2d->runqueue, list) {
+ if (file && node->filp != file)
+ continue;
+
+ list_del_init(&node->list);
+ g2d_free_runqueue_node(g2d, node);
+ }
+}
+
+static void g2d_runqueue_worker(struct work_struct *work)
+{
+ struct g2d_data *g2d = container_of(work, struct g2d_data,
+ runqueue_work);
+ struct g2d_runqueue_node *runqueue_node;
+
+ /*
+ * The engine is busy and the completion of the current node is going
+ * to poke the runqueue worker, so nothing to do here.
+ */
+ if (test_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags))
+ return;
+
+ mutex_lock(&g2d->runqueue_mutex);
+
+ runqueue_node = g2d->runqueue_node;
+ g2d->runqueue_node = NULL;
+
+ if (runqueue_node) {
+ pm_runtime_mark_last_busy(g2d->dev);
+ pm_runtime_put_autosuspend(g2d->dev);
+
+ complete(&runqueue_node->complete);
+ if (runqueue_node->async)
+ g2d_free_runqueue_node(g2d, runqueue_node);
+ }
+
+ if (!test_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags)) {
+ g2d->runqueue_node = g2d_get_runqueue_node(g2d);
+
+ if (g2d->runqueue_node) {
+ int ret;
+
+ ret = pm_runtime_resume_and_get(g2d->dev);
+ if (ret < 0) {
+ dev_err(g2d->dev, "failed to enable G2D device.\n");
+ goto out;
+ }
+
+ g2d_dma_start(g2d, g2d->runqueue_node);
+ }
+ }
+
+out:
+ mutex_unlock(&g2d->runqueue_mutex);
+}
+
+static void g2d_finish_event(struct g2d_data *g2d, u32 cmdlist_no)
+{
+ struct drm_device *drm_dev = g2d->drm_dev;
+ struct g2d_runqueue_node *runqueue_node = g2d->runqueue_node;
+ struct drm_exynos_pending_g2d_event *e;
+ struct timespec64 now;
+
+ if (list_empty(&runqueue_node->event_list))
+ return;
+
+ e = list_first_entry(&runqueue_node->event_list,
+ struct drm_exynos_pending_g2d_event, base.link);
+
+ ktime_get_ts64(&now);
+ e->event.tv_sec = now.tv_sec;
+ e->event.tv_usec = now.tv_nsec / NSEC_PER_USEC;
+ e->event.cmdlist_no = cmdlist_no;
+
+ drm_send_event(drm_dev, &e->base);
+}
+
+static irqreturn_t g2d_irq_handler(int irq, void *dev_id)
+{
+ struct g2d_data *g2d = dev_id;
+ u32 pending;
+
+ pending = readl_relaxed(g2d->regs + G2D_INTC_PEND);
+ if (pending)
+ writel_relaxed(pending, g2d->regs + G2D_INTC_PEND);
+
+ if (pending & G2D_INTP_GCMD_FIN) {
+ u32 cmdlist_no = readl_relaxed(g2d->regs + G2D_DMA_STATUS);
+
+ cmdlist_no = (cmdlist_no & G2D_DMA_LIST_DONE_COUNT) >>
+ G2D_DMA_LIST_DONE_COUNT_OFFSET;
+
+ g2d_finish_event(g2d, cmdlist_no);
+
+ writel_relaxed(0, g2d->regs + G2D_DMA_HOLD_CMD);
+ if (!(pending & G2D_INTP_ACMD_FIN)) {
+ writel_relaxed(G2D_DMA_CONTINUE,
+ g2d->regs + G2D_DMA_COMMAND);
+ }
+ }
+
+ if (pending & G2D_INTP_ACMD_FIN) {
+ clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags);
+ queue_work(g2d->g2d_workq, &g2d->runqueue_work);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * g2d_wait_finish - wait for the G2D engine to finish the current runqueue node
+ * @g2d: G2D state object
+ * @file: if not zero, only wait if the current runqueue node belongs
+ * to the DRM file
+ *
+ * Should the engine not become idle after a 100ms timeout, a hardware
+ * reset is issued.
+ */
+static void g2d_wait_finish(struct g2d_data *g2d, struct drm_file *file)
+{
+ struct device *dev = g2d->dev;
+
+ struct g2d_runqueue_node *runqueue_node = NULL;
+ unsigned int tries = 10;
+
+ mutex_lock(&g2d->runqueue_mutex);
+
+ /* If no node is currently processed, we have nothing to do. */
+ if (!g2d->runqueue_node)
+ goto out;
+
+ runqueue_node = g2d->runqueue_node;
+
+ /* Check if the currently processed item belongs to us. */
+ if (file && runqueue_node->filp != file)
+ goto out;
+
+ mutex_unlock(&g2d->runqueue_mutex);
+
+ /* Wait for the G2D engine to finish. */
+ while (tries-- && (g2d->runqueue_node == runqueue_node))
+ mdelay(10);
+
+ mutex_lock(&g2d->runqueue_mutex);
+
+ if (g2d->runqueue_node != runqueue_node)
+ goto out;
+
+ dev_err(dev, "wait timed out, resetting engine...\n");
+ g2d_hw_reset(g2d);
+
+ /*
+ * After the hardware reset of the engine we are going to loose
+ * the IRQ which triggers the PM runtime put().
+ * So do this manually here.
+ */
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ complete(&runqueue_node->complete);
+ if (runqueue_node->async)
+ g2d_free_runqueue_node(g2d, runqueue_node);
+
+out:
+ mutex_unlock(&g2d->runqueue_mutex);
+}
+
+static int g2d_check_reg_offset(struct g2d_data *g2d,
+ struct g2d_cmdlist_node *node,
+ int nr, bool for_addr)
+{
+ struct g2d_cmdlist *cmdlist = node->cmdlist;
+ int reg_offset;
+ int index;
+ int i;
+
+ for (i = 0; i < nr; i++) {
+ struct g2d_buf_info *buf_info = &node->buf_info;
+ struct g2d_buf_desc *buf_desc;
+ enum g2d_reg_type reg_type;
+ unsigned long value;
+
+ index = cmdlist->last - 2 * (i + 1);
+
+ reg_offset = cmdlist->data[index] & ~0xfffff000;
+ if (reg_offset < G2D_VALID_START || reg_offset > G2D_VALID_END)
+ goto err;
+ if (reg_offset % 4)
+ goto err;
+
+ switch (reg_offset) {
+ case G2D_SRC_BASE_ADDR:
+ case G2D_SRC_PLANE2_BASE_ADDR:
+ case G2D_DST_BASE_ADDR:
+ case G2D_DST_PLANE2_BASE_ADDR:
+ case G2D_PAT_BASE_ADDR:
+ case G2D_MSK_BASE_ADDR:
+ if (!for_addr)
+ goto err;
+
+ reg_type = g2d_get_reg_type(g2d, reg_offset);
+
+ /* check userptr buffer type. */
+ if ((cmdlist->data[index] & ~0x7fffffff) >> 31) {
+ buf_info->types[reg_type] = BUF_TYPE_USERPTR;
+ cmdlist->data[index] &= ~G2D_BUF_USERPTR;
+ } else
+ buf_info->types[reg_type] = BUF_TYPE_GEM;
+ break;
+ case G2D_SRC_STRIDE:
+ case G2D_DST_STRIDE:
+ if (for_addr)
+ goto err;
+
+ reg_type = g2d_get_reg_type(g2d, reg_offset);
+
+ buf_desc = &buf_info->descs[reg_type];
+ buf_desc->stride = cmdlist->data[index + 1];
+ break;
+ case G2D_SRC_COLOR_MODE:
+ case G2D_DST_COLOR_MODE:
+ if (for_addr)
+ goto err;
+
+ reg_type = g2d_get_reg_type(g2d, reg_offset);
+
+ buf_desc = &buf_info->descs[reg_type];
+ value = cmdlist->data[index + 1];
+
+ buf_desc->format = value & 0xf;
+ break;
+ case G2D_SRC_LEFT_TOP:
+ case G2D_DST_LEFT_TOP:
+ if (for_addr)
+ goto err;
+
+ reg_type = g2d_get_reg_type(g2d, reg_offset);
+
+ buf_desc = &buf_info->descs[reg_type];
+ value = cmdlist->data[index + 1];
+
+ buf_desc->left_x = value & 0x1fff;
+ buf_desc->top_y = (value & 0x1fff0000) >> 16;
+ break;
+ case G2D_SRC_RIGHT_BOTTOM:
+ case G2D_DST_RIGHT_BOTTOM:
+ if (for_addr)
+ goto err;
+
+ reg_type = g2d_get_reg_type(g2d, reg_offset);
+
+ buf_desc = &buf_info->descs[reg_type];
+ value = cmdlist->data[index + 1];
+
+ buf_desc->right_x = value & 0x1fff;
+ buf_desc->bottom_y = (value & 0x1fff0000) >> 16;
+ break;
+ default:
+ if (for_addr)
+ goto err;
+ break;
+ }
+ }
+
+ return 0;
+
+err:
+ dev_err(g2d->dev, "Bad register offset: 0x%lx\n", cmdlist->data[index]);
+ return -EINVAL;
+}
+
+/* ioctl functions */
+int exynos_g2d_get_ver_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ struct drm_exynos_g2d_get_ver *ver = data;
+
+ ver->major = G2D_HW_MAJOR_VER;
+ ver->minor = G2D_HW_MINOR_VER;
+
+ return 0;
+}
+
+int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ struct drm_exynos_file_private *file_priv = file->driver_priv;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ struct g2d_data *g2d = dev_get_drvdata(priv->g2d_dev);
+ struct drm_exynos_g2d_set_cmdlist *req = data;
+ struct drm_exynos_g2d_cmd *cmd;
+ struct drm_exynos_pending_g2d_event *e;
+ struct g2d_cmdlist_node *node;
+ struct g2d_cmdlist *cmdlist;
+ int size;
+ int ret;
+
+ node = g2d_get_cmdlist(g2d);
+ if (!node)
+ return -ENOMEM;
+
+ /*
+ * To avoid an integer overflow for the later size computations, we
+ * enforce a maximum number of submitted commands here. This limit is
+ * sufficient for all conceivable usage cases of the G2D.
+ */
+ if (req->cmd_nr > G2D_CMDLIST_DATA_NUM ||
+ req->cmd_buf_nr > G2D_CMDLIST_DATA_NUM) {
+ dev_err(g2d->dev, "number of submitted G2D commands exceeds limit\n");
+ return -EINVAL;
+ }
+
+ node->event = NULL;
+
+ if (req->event_type != G2D_EVENT_NOT) {
+ e = kzalloc_obj(*node->event);
+ if (!e) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ e->event.base.type = DRM_EXYNOS_G2D_EVENT;
+ e->event.base.length = sizeof(e->event);
+ e->event.user_data = req->user_data;
+
+ ret = drm_event_reserve_init(drm_dev, file, &e->base, &e->event.base);
+ if (ret) {
+ kfree(e);
+ goto err;
+ }
+
+ node->event = e;
+ }
+
+ cmdlist = node->cmdlist;
+
+ cmdlist->last = 0;
+
+ /*
+ * If don't clear SFR registers, the cmdlist is affected by register
+ * values of previous cmdlist. G2D hw executes SFR clear command and
+ * a next command at the same time then the next command is ignored and
+ * is executed rightly from next next command, so needs a dummy command
+ * to next command of SFR clear command.
+ */
+ cmdlist->data[cmdlist->last++] = G2D_SOFT_RESET;
+ cmdlist->data[cmdlist->last++] = G2D_SFRCLEAR;
+ cmdlist->data[cmdlist->last++] = G2D_SRC_BASE_ADDR;
+ cmdlist->data[cmdlist->last++] = 0;
+
+ /*
+ * 'LIST_HOLD' command should be set to the DMA_HOLD_CMD_REG
+ * and GCF bit should be set to INTEN register if user wants
+ * G2D interrupt event once current command list execution is
+ * finished.
+ * Otherwise only ACF bit should be set to INTEN register so
+ * that one interrupt is occurred after all command lists
+ * have been completed.
+ */
+ if (node->event) {
+ cmdlist->data[cmdlist->last++] = G2D_INTEN;
+ cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF | G2D_INTEN_GCF;
+ cmdlist->data[cmdlist->last++] = G2D_DMA_HOLD_CMD;
+ cmdlist->data[cmdlist->last++] = G2D_LIST_HOLD;
+ } else {
+ cmdlist->data[cmdlist->last++] = G2D_INTEN;
+ cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF;
+ }
+
+ /*
+ * Check the size of cmdlist. The 2 that is added last comes from
+ * the implicit G2D_BITBLT_START that is appended once we have
+ * checked all the submitted commands.
+ */
+ size = cmdlist->last + req->cmd_nr * 2 + req->cmd_buf_nr * 2 + 2;
+ if (size > G2D_CMDLIST_DATA_NUM) {
+ dev_err(g2d->dev, "cmdlist size is too big\n");
+ ret = -EINVAL;
+ goto err_free_event;
+ }
+
+ cmd = (struct drm_exynos_g2d_cmd *)(unsigned long)req->cmd;
+
+ if (copy_from_user(cmdlist->data + cmdlist->last,
+ (void __user *)cmd,
+ sizeof(*cmd) * req->cmd_nr)) {
+ ret = -EFAULT;
+ goto err_free_event;
+ }
+ cmdlist->last += req->cmd_nr * 2;
+
+ ret = g2d_check_reg_offset(g2d, node, req->cmd_nr, false);
+ if (ret < 0)
+ goto err_free_event;
+
+ node->buf_info.map_nr = req->cmd_buf_nr;
+ if (req->cmd_buf_nr) {
+ struct drm_exynos_g2d_cmd *cmd_buf;
+
+ cmd_buf = (struct drm_exynos_g2d_cmd *)
+ (unsigned long)req->cmd_buf;
+
+ if (copy_from_user(cmdlist->data + cmdlist->last,
+ (void __user *)cmd_buf,
+ sizeof(*cmd_buf) * req->cmd_buf_nr)) {
+ ret = -EFAULT;
+ goto err_free_event;
+ }
+ cmdlist->last += req->cmd_buf_nr * 2;
+
+ ret = g2d_check_reg_offset(g2d, node, req->cmd_buf_nr, true);
+ if (ret < 0)
+ goto err_free_event;
+
+ ret = g2d_map_cmdlist_gem(g2d, node, drm_dev, file);
+ if (ret < 0)
+ goto err_unmap;
+ }
+
+ cmdlist->data[cmdlist->last++] = G2D_BITBLT_START;
+ cmdlist->data[cmdlist->last++] = G2D_START_BITBLT;
+
+ /* head */
+ cmdlist->head = cmdlist->last / 2;
+
+ /* tail */
+ cmdlist->data[cmdlist->last] = 0;
+
+ g2d_add_cmdlist_to_inuse(file_priv, node);
+
+ return 0;
+
+err_unmap:
+ g2d_unmap_cmdlist_gem(g2d, node, file);
+err_free_event:
+ if (node->event)
+ drm_event_cancel_free(drm_dev, &node->event->base);
+err:
+ g2d_put_cmdlist(g2d, node);
+ return ret;
+}
+
+int exynos_g2d_exec_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ struct drm_exynos_file_private *file_priv = file->driver_priv;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ struct g2d_data *g2d = dev_get_drvdata(priv->g2d_dev);
+ struct drm_exynos_g2d_exec *req = data;
+ struct g2d_runqueue_node *runqueue_node;
+ struct list_head *run_cmdlist;
+ struct list_head *event_list;
+
+ runqueue_node = kmem_cache_alloc(g2d->runqueue_slab, GFP_KERNEL);
+ if (!runqueue_node)
+ return -ENOMEM;
+
+ run_cmdlist = &runqueue_node->run_cmdlist;
+ event_list = &runqueue_node->event_list;
+ INIT_LIST_HEAD(run_cmdlist);
+ INIT_LIST_HEAD(event_list);
+ init_completion(&runqueue_node->complete);
+ runqueue_node->async = req->async;
+
+ list_splice_init(&file_priv->inuse_cmdlist, run_cmdlist);
+ list_splice_init(&file_priv->event_list, event_list);
+
+ if (list_empty(run_cmdlist)) {
+ dev_err(g2d->dev, "there is no inuse cmdlist\n");
+ kmem_cache_free(g2d->runqueue_slab, runqueue_node);
+ return -EPERM;
+ }
+
+ mutex_lock(&g2d->runqueue_mutex);
+ runqueue_node->pid = current->pid;
+ runqueue_node->filp = file;
+ list_add_tail(&runqueue_node->list, &g2d->runqueue);
+ mutex_unlock(&g2d->runqueue_mutex);
+
+ /* Let the runqueue know that there is work to do. */
+ queue_work(g2d->g2d_workq, &g2d->runqueue_work);
+
+ if (req->async)
+ goto out;
+
+ wait_for_completion(&runqueue_node->complete);
+ g2d_free_runqueue_node(g2d, runqueue_node);
+
+out:
+ return 0;
+}
+
+int g2d_open(struct drm_device *drm_dev, struct drm_file *file)
+{
+ struct drm_exynos_file_private *file_priv = file->driver_priv;
+
+ INIT_LIST_HEAD(&file_priv->inuse_cmdlist);
+ INIT_LIST_HEAD(&file_priv->event_list);
+ INIT_LIST_HEAD(&file_priv->userptr_list);
+
+ return 0;
+}
+
+void g2d_close(struct drm_device *drm_dev, struct drm_file *file)
+{
+ struct drm_exynos_file_private *file_priv = file->driver_priv;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ struct g2d_data *g2d;
+ struct g2d_cmdlist_node *node, *n;
+
+ if (!priv->g2d_dev)
+ return;
+
+ g2d = dev_get_drvdata(priv->g2d_dev);
+
+ /* Remove the runqueue nodes that belong to us. */
+ mutex_lock(&g2d->runqueue_mutex);
+ g2d_remove_runqueue_nodes(g2d, file);
+ mutex_unlock(&g2d->runqueue_mutex);
+
+ /*
+ * Wait for the runqueue worker to finish its current node.
+ * After this the engine should no longer be accessing any
+ * memory belonging to us.
+ */
+ g2d_wait_finish(g2d, file);
+
+ /*
+ * Even after the engine is idle, there might still be stale cmdlists
+ * (i.e. cmdlisst which we submitted but never executed) around, with
+ * their corresponding GEM/userptr buffers.
+ * Properly unmap these buffers here.
+ */
+ mutex_lock(&g2d->cmdlist_mutex);
+ list_for_each_entry_safe(node, n, &file_priv->inuse_cmdlist, list) {
+ g2d_unmap_cmdlist_gem(g2d, node, file);
+ list_move_tail(&node->list, &g2d->free_cmdlist);
+ }
+ mutex_unlock(&g2d->cmdlist_mutex);
+
+ /* release all g2d_userptr in pool. */
+ g2d_userptr_free_all(g2d, file);
+}
+
+static int g2d_bind(struct device *dev, struct device *master, void *data)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ int ret;
+
+ g2d->drm_dev = drm_dev;
+
+ /* allocate dma-aware cmdlist buffer. */
+ ret = g2d_init_cmdlist(g2d);
+ if (ret < 0) {
+ dev_err(dev, "cmdlist init failed\n");
+ return ret;
+ }
+
+ ret = exynos_drm_register_dma(drm_dev, dev, &g2d->dma_priv);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable iommu.\n");
+ g2d_fini_cmdlist(g2d);
+ return ret;
+ }
+ priv->g2d_dev = dev;
+
+ dev_info(dev, "The Exynos G2D (ver %d.%d) successfully registered.\n",
+ G2D_HW_MAJOR_VER, G2D_HW_MINOR_VER);
+ return 0;
+}
+
+static void g2d_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+
+ /* Suspend operation and wait for engine idle. */
+ set_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags);
+ g2d_wait_finish(g2d, NULL);
+ priv->g2d_dev = NULL;
+
+ cancel_work_sync(&g2d->runqueue_work);
+ exynos_drm_unregister_dma(g2d->drm_dev, dev, &g2d->dma_priv);
+}
+
+static const struct component_ops g2d_component_ops = {
+ .bind = g2d_bind,
+ .unbind = g2d_unbind,
+};
+
+static int g2d_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct g2d_data *g2d;
+ int ret;
+
+ g2d = devm_kzalloc(dev, sizeof(*g2d), GFP_KERNEL);
+ if (!g2d)
+ return -ENOMEM;
+
+ g2d->runqueue_slab = kmem_cache_create("g2d_runqueue_slab",
+ sizeof(struct g2d_runqueue_node), 0, 0, NULL);
+ if (!g2d->runqueue_slab)
+ return -ENOMEM;
+
+ g2d->dev = dev;
+
+ g2d->g2d_workq = create_singlethread_workqueue("g2d");
+ if (!g2d->g2d_workq) {
+ dev_err(dev, "failed to create workqueue\n");
+ ret = -EINVAL;
+ goto err_destroy_slab;
+ }
+
+ INIT_WORK(&g2d->runqueue_work, g2d_runqueue_worker);
+ INIT_LIST_HEAD(&g2d->free_cmdlist);
+ INIT_LIST_HEAD(&g2d->runqueue);
+
+ mutex_init(&g2d->cmdlist_mutex);
+ mutex_init(&g2d->runqueue_mutex);
+
+ g2d->gate_clk = devm_clk_get(dev, "fimg2d");
+ if (IS_ERR(g2d->gate_clk)) {
+ dev_err(dev, "failed to get gate clock\n");
+ ret = PTR_ERR(g2d->gate_clk);
+ goto err_destroy_workqueue;
+ }
+
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, 2000);
+ pm_runtime_enable(dev);
+ clear_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags);
+ clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags);
+
+ g2d->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(g2d->regs)) {
+ ret = PTR_ERR(g2d->regs);
+ goto err_put_clk;
+ }
+
+ g2d->irq = platform_get_irq(pdev, 0);
+ if (g2d->irq < 0) {
+ ret = g2d->irq;
+ goto err_put_clk;
+ }
+
+ ret = devm_request_irq(dev, g2d->irq, g2d_irq_handler, 0,
+ "drm_g2d", g2d);
+ if (ret < 0) {
+ dev_err(dev, "irq request failed\n");
+ goto err_put_clk;
+ }
+
+ g2d->max_pool = MAX_POOL;
+
+ platform_set_drvdata(pdev, g2d);
+
+ ret = component_add(dev, &g2d_component_ops);
+ if (ret < 0) {
+ dev_err(dev, "failed to register drm g2d device\n");
+ goto err_put_clk;
+ }
+
+ return 0;
+
+err_put_clk:
+ pm_runtime_disable(dev);
+err_destroy_workqueue:
+ destroy_workqueue(g2d->g2d_workq);
+err_destroy_slab:
+ kmem_cache_destroy(g2d->runqueue_slab);
+ return ret;
+}
+
+static void g2d_remove(struct platform_device *pdev)
+{
+ struct g2d_data *g2d = platform_get_drvdata(pdev);
+
+ component_del(&pdev->dev, &g2d_component_ops);
+
+ /* There should be no locking needed here. */
+ g2d_remove_runqueue_nodes(g2d, NULL);
+
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ g2d_fini_cmdlist(g2d);
+ destroy_workqueue(g2d->g2d_workq);
+ kmem_cache_destroy(g2d->runqueue_slab);
+}
+
+static int g2d_suspend(struct device *dev)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+
+ /*
+ * Suspend the runqueue worker operation and wait until the G2D
+ * engine is idle.
+ */
+ set_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags);
+ g2d_wait_finish(g2d, NULL);
+ flush_work(&g2d->runqueue_work);
+
+ return 0;
+}
+
+static int g2d_resume(struct device *dev)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+
+ clear_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags);
+ queue_work(g2d->g2d_workq, &g2d->runqueue_work);
+
+ return 0;
+}
+
+static int g2d_runtime_suspend(struct device *dev)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(g2d->gate_clk);
+
+ return 0;
+}
+
+static int g2d_runtime_resume(struct device *dev)
+{
+ struct g2d_data *g2d = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(g2d->gate_clk);
+ if (ret < 0)
+ dev_warn(dev, "failed to enable clock.\n");
+
+ return ret;
+}
+
+static const struct dev_pm_ops g2d_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(g2d_suspend, g2d_resume)
+ RUNTIME_PM_OPS(g2d_runtime_suspend, g2d_runtime_resume, NULL)
+};
+
+static const struct of_device_id exynos_g2d_match[] = {
+ { .compatible = "samsung,exynos5250-g2d" },
+ { .compatible = "samsung,exynos4212-g2d" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_g2d_match);
+
+struct platform_driver g2d_driver = {
+ .probe = g2d_probe,
+ .remove = g2d_remove,
+ .driver = {
+ .name = "exynos-drm-g2d",
+ .pm = pm_ptr(&g2d_pm_ops),
+ .of_match_table = exynos_g2d_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.h b/drivers/gpu/drm/exynos/exynos_drm_g2d.h
new file mode 100644
index 000000000..1a5ae781b
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors: Joonyoung Shim <jy0922.shim@samsung.com>
+ */
+
+#ifdef CONFIG_DRM_EXYNOS_G2D
+extern int exynos_g2d_get_ver_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+extern int exynos_g2d_set_cmdlist_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+extern int exynos_g2d_exec_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+extern int g2d_open(struct drm_device *drm_dev, struct drm_file *file);
+extern void g2d_close(struct drm_device *drm_dev, struct drm_file *file);
+#else
+static inline int exynos_g2d_get_ver_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+
+static inline int exynos_g2d_set_cmdlist_ioctl(struct drm_device *dev,
+ void *data,
+ struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+
+static inline int exynos_g2d_exec_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+
+static inline int g2d_open(struct drm_device *drm_dev, struct drm_file *file)
+{
+ return 0;
+}
+
+static inline void g2d_close(struct drm_device *drm_dev, struct drm_file *file)
+{ }
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c
new file mode 100644
index 000000000..0208c9259
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_gem.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Author: Inki Dae <inki.dae@samsung.com>
+ */
+
+
+#include <linux/dma-buf.h>
+#include <linux/shmem_fs.h>
+#include <linux/module.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_dumb_buffers.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vma_manager.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_gem.h"
+
+MODULE_IMPORT_NS("DMA_BUF");
+
+static int exynos_drm_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
+
+static int exynos_drm_alloc_buf(struct exynos_drm_gem *exynos_gem, bool kvmap)
+{
+ struct drm_device *dev = exynos_gem->base.dev;
+ unsigned long attr = 0;
+
+ if (exynos_gem->dma_addr) {
+ DRM_DEV_DEBUG_KMS(drm_dev_dma_dev(dev), "already allocated.\n");
+ return 0;
+ }
+
+ /*
+ * if EXYNOS_BO_CONTIG, fully physically contiguous memory
+ * region will be allocated else physically contiguous
+ * as possible.
+ */
+ if (!(exynos_gem->flags & EXYNOS_BO_NONCONTIG))
+ attr |= DMA_ATTR_FORCE_CONTIGUOUS;
+
+ /*
+ * if EXYNOS_BO_WC or EXYNOS_BO_NONCACHABLE, writecombine mapping
+ * else cachable mapping.
+ */
+ if (exynos_gem->flags & EXYNOS_BO_WC ||
+ !(exynos_gem->flags & EXYNOS_BO_CACHABLE))
+ attr |= DMA_ATTR_WRITE_COMBINE;
+
+ /* FBDev emulation requires kernel mapping */
+ if (!kvmap)
+ attr |= DMA_ATTR_NO_KERNEL_MAPPING;
+
+ exynos_gem->dma_attrs = attr;
+ exynos_gem->cookie = dma_alloc_attrs(drm_dev_dma_dev(dev), exynos_gem->base.size,
+ &exynos_gem->dma_addr, GFP_KERNEL,
+ exynos_gem->dma_attrs);
+ if (!exynos_gem->cookie) {
+ DRM_DEV_ERROR(drm_dev_dma_dev(dev), "failed to allocate buffer.\n");
+ return -ENOMEM;
+ }
+
+ if (kvmap)
+ exynos_gem->kvaddr = exynos_gem->cookie;
+
+ DRM_DEV_DEBUG_KMS(drm_dev_dma_dev(dev), "dma_addr(%pad), size(0x%zx)\n",
+ &exynos_gem->dma_addr, exynos_gem->base.size);
+ return 0;
+}
+
+static void exynos_drm_free_buf(struct exynos_drm_gem *exynos_gem)
+{
+ struct drm_device *dev = exynos_gem->base.dev;
+
+ if (!exynos_gem->dma_addr) {
+ DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr is invalid.\n");
+ return;
+ }
+
+ DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr(0x%pad), size(0x%zx)\n",
+ &exynos_gem->dma_addr, exynos_gem->base.size);
+
+ dma_free_attrs(drm_dev_dma_dev(dev), exynos_gem->base.size, exynos_gem->cookie,
+ (dma_addr_t)exynos_gem->dma_addr,
+ exynos_gem->dma_attrs);
+}
+
+static int exynos_drm_gem_handle_create(struct drm_gem_object *obj,
+ struct drm_file *file_priv,
+ unsigned int *handle)
+{
+ int ret;
+
+ /*
+ * allocate a id of idr table where the obj is registered
+ * and handle has the id what user can see.
+ */
+ ret = drm_gem_handle_create(file_priv, obj, handle);
+ if (ret)
+ return ret;
+
+ DRM_DEV_DEBUG_KMS(drm_dev_dma_dev(obj->dev), "gem handle = 0x%x\n", *handle);
+
+ /* drop reference from allocate - handle holds it now. */
+ drm_gem_object_put(obj);
+
+ return 0;
+}
+
+void exynos_drm_gem_destroy(struct exynos_drm_gem *exynos_gem)
+{
+ struct drm_gem_object *obj = &exynos_gem->base;
+
+ DRM_DEV_DEBUG_KMS(drm_dev_dma_dev(obj->dev), "handle count = %d\n",
+ obj->handle_count);
+
+ /*
+ * do not release memory region from exporter.
+ *
+ * the region will be released by exporter
+ * once dmabuf's refcount becomes 0.
+ */
+ if (obj->import_attach)
+ drm_prime_gem_destroy(obj, exynos_gem->sgt);
+ else
+ exynos_drm_free_buf(exynos_gem);
+
+ /* release file pointer to gem object. */
+ drm_gem_object_release(obj);
+
+ kfree(exynos_gem);
+}
+
+static void exynos_drm_gem_free_object(struct drm_gem_object *obj)
+{
+ exynos_drm_gem_destroy(to_exynos_gem(obj));
+}
+
+static const struct vm_operations_struct exynos_drm_gem_vm_ops = {
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static const struct drm_gem_object_funcs exynos_drm_gem_object_funcs = {
+ .free = exynos_drm_gem_free_object,
+ .get_sg_table = exynos_drm_gem_prime_get_sg_table,
+ .mmap = exynos_drm_gem_mmap,
+ .vm_ops = &exynos_drm_gem_vm_ops,
+};
+
+static struct exynos_drm_gem *exynos_drm_gem_init(struct drm_device *dev,
+ unsigned long size)
+{
+ struct exynos_drm_gem *exynos_gem;
+ struct drm_gem_object *obj;
+ int ret;
+
+ exynos_gem = kzalloc_obj(*exynos_gem);
+ if (!exynos_gem)
+ return ERR_PTR(-ENOMEM);
+
+ obj = &exynos_gem->base;
+
+ obj->funcs = &exynos_drm_gem_object_funcs;
+
+ ret = drm_gem_object_init(dev, obj, size);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev->dev, "failed to initialize gem object\n");
+ kfree(exynos_gem);
+ return ERR_PTR(ret);
+ }
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret < 0) {
+ drm_gem_object_release(obj);
+ kfree(exynos_gem);
+ return ERR_PTR(ret);
+ }
+
+ DRM_DEV_DEBUG_KMS(dev->dev, "created file object = %p\n", obj->filp);
+
+ return exynos_gem;
+}
+
+struct exynos_drm_gem *exynos_drm_gem_create(struct drm_device *dev,
+ unsigned int flags,
+ unsigned long size,
+ bool kvmap)
+{
+ struct exynos_drm_gem *exynos_gem;
+ int ret;
+
+ if (flags & ~(EXYNOS_BO_MASK)) {
+ DRM_DEV_ERROR(dev->dev,
+ "invalid GEM buffer flags: %u\n", flags);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!size) {
+ DRM_DEV_ERROR(dev->dev, "invalid GEM buffer size: %lu\n", size);
+ return ERR_PTR(-EINVAL);
+ }
+
+ size = roundup(size, PAGE_SIZE);
+
+ exynos_gem = exynos_drm_gem_init(dev, size);
+ if (IS_ERR(exynos_gem))
+ return exynos_gem;
+
+ if (!is_drm_iommu_supported(dev) && (flags & EXYNOS_BO_NONCONTIG)) {
+ /*
+ * when no IOMMU is available, all allocated buffers are
+ * contiguous anyway, so drop EXYNOS_BO_NONCONTIG flag
+ */
+ flags &= ~EXYNOS_BO_NONCONTIG;
+ DRM_WARN("Non-contiguous allocation is not supported without IOMMU, falling back to contiguous buffer\n");
+ }
+
+ /* set memory type and cache attribute from user side. */
+ exynos_gem->flags = flags;
+
+ ret = exynos_drm_alloc_buf(exynos_gem, kvmap);
+ if (ret < 0) {
+ drm_gem_object_release(&exynos_gem->base);
+ kfree(exynos_gem);
+ return ERR_PTR(ret);
+ }
+
+ return exynos_gem;
+}
+
+int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_gem_create *args = data;
+ struct exynos_drm_gem *exynos_gem;
+ int ret;
+
+ exynos_gem = exynos_drm_gem_create(dev, args->flags, args->size, false);
+ if (IS_ERR(exynos_gem))
+ return PTR_ERR(exynos_gem);
+
+ ret = exynos_drm_gem_handle_create(&exynos_gem->base, file_priv,
+ &args->handle);
+ if (ret) {
+ exynos_drm_gem_destroy(exynos_gem);
+ return ret;
+ }
+
+ return 0;
+}
+
+int exynos_drm_gem_map_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_gem_map *args = data;
+
+ return drm_gem_dumb_map_offset(file_priv, dev, args->handle,
+ &args->offset);
+}
+
+struct exynos_drm_gem *exynos_drm_gem_get(struct drm_file *filp,
+ unsigned int gem_handle)
+{
+ struct drm_gem_object *obj;
+
+ obj = drm_gem_object_lookup(filp, gem_handle);
+ if (!obj)
+ return NULL;
+ return to_exynos_gem(obj);
+}
+
+static int exynos_drm_gem_mmap_buffer(struct exynos_drm_gem *exynos_gem,
+ struct vm_area_struct *vma)
+{
+ struct drm_device *drm_dev = exynos_gem->base.dev;
+ unsigned long vm_size;
+ int ret;
+
+ vm_flags_clear(vma, VM_PFNMAP);
+ vma->vm_pgoff = 0;
+
+ vm_size = vma->vm_end - vma->vm_start;
+
+ /* check if user-requested size is valid. */
+ if (vm_size > exynos_gem->base.size)
+ return -EINVAL;
+
+ ret = dma_mmap_attrs(drm_dev_dma_dev(drm_dev), vma, exynos_gem->cookie,
+ exynos_gem->dma_addr, exynos_gem->base.size,
+ exynos_gem->dma_attrs);
+ if (ret < 0) {
+ DRM_ERROR("failed to mmap.\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct exynos_drm_gem *exynos_gem;
+ struct drm_exynos_gem_info *args = data;
+ struct drm_gem_object *obj;
+
+ obj = drm_gem_object_lookup(file_priv, args->handle);
+ if (!obj) {
+ DRM_DEV_ERROR(dev->dev, "failed to lookup gem object.\n");
+ return -EINVAL;
+ }
+
+ exynos_gem = to_exynos_gem(obj);
+
+ args->flags = exynos_gem->flags;
+ args->size = obj->size;
+
+ drm_gem_object_put(obj);
+
+ return 0;
+}
+
+int exynos_drm_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct exynos_drm_gem *exynos_gem;
+ unsigned int flags;
+ int ret;
+
+ ret = drm_mode_size_dumb(dev, args, 0, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * allocate memory to be used for framebuffer.
+ * - this callback would be called by user application
+ * with DRM_IOCTL_MODE_CREATE_DUMB command.
+ */
+
+ if (is_drm_iommu_supported(dev))
+ flags = EXYNOS_BO_NONCONTIG | EXYNOS_BO_WC;
+ else
+ flags = EXYNOS_BO_CONTIG | EXYNOS_BO_WC;
+
+ exynos_gem = exynos_drm_gem_create(dev, flags, args->size, false);
+ if (IS_ERR(exynos_gem)) {
+ dev_warn(dev->dev, "FB allocation failed.\n");
+ return PTR_ERR(exynos_gem);
+ }
+
+ ret = exynos_drm_gem_handle_create(&exynos_gem->base, file_priv,
+ &args->handle);
+ if (ret) {
+ exynos_drm_gem_destroy(exynos_gem);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int exynos_drm_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+ struct exynos_drm_gem *exynos_gem = to_exynos_gem(obj);
+ int ret;
+
+ if (obj->import_attach)
+ return dma_buf_mmap(obj->dma_buf, vma, 0);
+
+ vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP);
+
+ DRM_DEV_DEBUG_KMS(drm_dev_dma_dev(obj->dev), "flags = 0x%x\n",
+ exynos_gem->flags);
+
+ /* non-cachable as default. */
+ if (exynos_gem->flags & EXYNOS_BO_CACHABLE)
+ vma->vm_page_prot = vma_get_page_prot(vma);
+ else if (exynos_gem->flags & EXYNOS_BO_WC)
+ vma->vm_page_prot =
+ pgprot_writecombine(vma_get_page_prot(vma));
+ else
+ vma->vm_page_prot =
+ pgprot_noncached(vma_get_page_prot(vma));
+
+ ret = exynos_drm_gem_mmap_buffer(exynos_gem, vma);
+ if (ret)
+ goto err_close_vm;
+
+ return ret;
+
+err_close_vm:
+ drm_gem_vm_close(vma);
+
+ return ret;
+}
+
+/* low-level interface prime helpers */
+struct sg_table *exynos_drm_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct exynos_drm_gem *exynos_gem = to_exynos_gem(obj);
+ struct drm_device *drm_dev = obj->dev;
+ struct sg_table *sgt;
+ int ret;
+
+ sgt = kzalloc_obj(*sgt);
+ if (!sgt)
+ return ERR_PTR(-ENOMEM);
+
+ ret = dma_get_sgtable_attrs(drm_dev_dma_dev(drm_dev), sgt, exynos_gem->cookie,
+ exynos_gem->dma_addr, obj->size,
+ exynos_gem->dma_attrs);
+ if (ret) {
+ DRM_ERROR("failed to get sgtable, %d\n", ret);
+ kfree(sgt);
+ return ERR_PTR(ret);
+ }
+
+ return sgt;
+}
+
+struct drm_gem_object *
+exynos_drm_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sgt)
+{
+ struct exynos_drm_gem *exynos_gem;
+
+ /* check if the entries in the sg_table are contiguous */
+ if (drm_prime_get_contiguous_size(sgt) < attach->dmabuf->size) {
+ DRM_ERROR("buffer chunks must be mapped contiguously");
+ return ERR_PTR(-EINVAL);
+ }
+
+ exynos_gem = exynos_drm_gem_init(dev, attach->dmabuf->size);
+ if (IS_ERR(exynos_gem))
+ return ERR_CAST(exynos_gem);
+
+ /*
+ * Buffer has been mapped as contiguous into DMA address space,
+ * but if there is IOMMU, it can be either CONTIG or NONCONTIG.
+ * We assume a simplified logic below:
+ */
+ if (is_drm_iommu_supported(dev))
+ exynos_gem->flags |= EXYNOS_BO_NONCONTIG;
+ else
+ exynos_gem->flags |= EXYNOS_BO_CONTIG;
+
+ exynos_gem->dma_addr = sg_dma_address(sgt->sgl);
+ exynos_gem->sgt = sgt;
+ return &exynos_gem->base;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h
new file mode 100644
index 000000000..3ff9fa995
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* exynos_drm_gem.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Authoer: Inki Dae <inki.dae@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_GEM_H_
+#define _EXYNOS_DRM_GEM_H_
+
+#include <drm/drm_gem.h>
+#include <linux/mm_types.h>
+
+#define to_exynos_gem(x) container_of(x, struct exynos_drm_gem, base)
+
+#define IS_NONCONTIG_BUFFER(f) (f & EXYNOS_BO_NONCONTIG)
+
+/*
+ * exynos drm buffer structure.
+ *
+ * @base: a gem object.
+ * - a new handle to this gem object would be created
+ * by drm_gem_handle_create().
+ * @flags: indicate memory type to allocated buffer and cache attruibute.
+ * @cookie: cookie returned by dma_alloc_attrs
+ * @kvaddr: kernel virtual address to allocated memory region (for fbdev)
+ * @dma_addr: bus address(accessed by dma) to allocated memory region.
+ * - this address could be physical address without IOMMU and
+ * device address with IOMMU.
+ * @dma_attrs: attrs passed dma mapping framework
+ * @sgt: Imported sg_table.
+ *
+ * P.S. this object would be transferred to user as kms_bo.handle so
+ * user can access the buffer through kms_bo.handle.
+ */
+struct exynos_drm_gem {
+ struct drm_gem_object base;
+ unsigned int flags;
+ void *cookie;
+ void *kvaddr;
+ dma_addr_t dma_addr;
+ unsigned long dma_attrs;
+ struct sg_table *sgt;
+};
+
+/* destroy a buffer with gem object */
+void exynos_drm_gem_destroy(struct exynos_drm_gem *exynos_gem);
+
+/* create a new buffer with gem object */
+struct exynos_drm_gem *exynos_drm_gem_create(struct drm_device *dev,
+ unsigned int flags,
+ unsigned long size,
+ bool kvmap);
+
+/*
+ * request gem object creation and buffer allocation as the size
+ * that it is calculated with framebuffer information such as width,
+ * height and bpp.
+ */
+int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+/* get fake-offset of gem object that can be used with mmap. */
+int exynos_drm_gem_map_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+/*
+ * get exynos drm object from gem handle, this function could be used for
+ * other drivers such as 2d/3d acceleration drivers.
+ * with this function call, gem object reference count would be increased.
+ */
+struct exynos_drm_gem *exynos_drm_gem_get(struct drm_file *filp,
+ unsigned int gem_handle);
+
+/*
+ * put exynos drm object acquired from exynos_drm_gem_get(),
+ * gem object reference count would be decreased.
+ */
+static inline void exynos_drm_gem_put(struct exynos_drm_gem *exynos_gem)
+{
+ drm_gem_object_put(&exynos_gem->base);
+}
+
+/* get buffer information to memory region allocated by gem. */
+int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+/* create memory region for drm framebuffer. */
+int exynos_drm_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+
+/* low-level interface prime helpers */
+struct sg_table *exynos_drm_gem_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *
+exynos_drm_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sgt);
+
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gsc.c b/drivers/gpu/drm/exynos/exynos_drm_gsc.c
new file mode 100644
index 000000000..d9637ddfc
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_gsc.c
@@ -0,0 +1,1428 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ * Jinyoung Jeon <jy0.jeon@samsung.com>
+ * Sangmin Lee <lsmin.lee@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_ipp.h"
+#include "regs-gsc.h"
+
+/*
+ * GSC stands for General SCaler and
+ * supports image scaler/rotator and input/output DMA operations.
+ * input DMA reads image data from the memory.
+ * output DMA writes image data to memory.
+ * GSC supports image rotation and image effect functions.
+ */
+
+
+#define GSC_MAX_CLOCKS 8
+#define GSC_MAX_SRC 4
+#define GSC_MAX_DST 16
+#define GSC_RESET_TIMEOUT 50
+#define GSC_BUF_STOP 1
+#define GSC_BUF_START 2
+#define GSC_REG_SZ 16
+#define GSC_WIDTH_ITU_709 1280
+#define GSC_SC_UP_MAX_RATIO 65536
+#define GSC_SC_DOWN_RATIO_7_8 74898
+#define GSC_SC_DOWN_RATIO_6_8 87381
+#define GSC_SC_DOWN_RATIO_5_8 104857
+#define GSC_SC_DOWN_RATIO_4_8 131072
+#define GSC_SC_DOWN_RATIO_3_8 174762
+#define GSC_SC_DOWN_RATIO_2_8 262144
+#define GSC_CROP_MAX 8192
+#define GSC_CROP_MIN 32
+#define GSC_SCALE_MAX 4224
+#define GSC_SCALE_MIN 32
+#define GSC_COEF_RATIO 7
+#define GSC_COEF_PHASE 9
+#define GSC_COEF_ATTR 16
+#define GSC_COEF_H_8T 8
+#define GSC_COEF_V_4T 4
+#define GSC_COEF_DEPTH 3
+#define GSC_AUTOSUSPEND_DELAY 2000
+
+#define get_gsc_context(dev) dev_get_drvdata(dev)
+#define gsc_read(offset) readl(ctx->regs + (offset))
+#define gsc_write(cfg, offset) writel(cfg, ctx->regs + (offset))
+
+/*
+ * A structure of scaler.
+ *
+ * @range: narrow, wide.
+ * @pre_shfactor: pre sclaer shift factor.
+ * @pre_hratio: horizontal ratio of the prescaler.
+ * @pre_vratio: vertical ratio of the prescaler.
+ * @main_hratio: the main scaler's horizontal ratio.
+ * @main_vratio: the main scaler's vertical ratio.
+ */
+struct gsc_scaler {
+ bool range;
+ u32 pre_shfactor;
+ u32 pre_hratio;
+ u32 pre_vratio;
+ unsigned long main_hratio;
+ unsigned long main_vratio;
+};
+
+/*
+ * A structure of gsc context.
+ *
+ * @regs: memory mapped io registers.
+ * @gsc_clk: gsc gate clock.
+ * @sc: scaler infomations.
+ * @id: gsc id.
+ * @irq: irq number.
+ * @rotation: supports rotation of src.
+ */
+struct gsc_context {
+ struct exynos_drm_ipp ipp;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct device *dev;
+ struct exynos_drm_ipp_task *task;
+ struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+
+ void __iomem *regs;
+ const char *const *clk_names;
+ struct clk *clocks[GSC_MAX_CLOCKS];
+ int num_clocks;
+ struct gsc_scaler sc;
+ int id;
+ int irq;
+ bool rotation;
+};
+
+/**
+ * struct gsc_driverdata - per device type driver data for init time.
+ *
+ * @limits: picture size limits array
+ * @num_limits: number of items in the aforementioned array
+ * @clk_names: names of clocks needed by this variant
+ * @num_clocks: the number of clocks needed by this variant
+ */
+struct gsc_driverdata {
+ const struct drm_exynos_ipp_limit *limits;
+ int num_limits;
+ const char *clk_names[GSC_MAX_CLOCKS];
+ int num_clocks;
+};
+
+/* 8-tap Filter Coefficient */
+static const int h_coef_8t[GSC_COEF_RATIO][GSC_COEF_ATTR][GSC_COEF_H_8T] = {
+ { /* Ratio <= 65536 (~8:8) */
+ { 0, 0, 0, 128, 0, 0, 0, 0 },
+ { -1, 2, -6, 127, 7, -2, 1, 0 },
+ { -1, 4, -12, 125, 16, -5, 1, 0 },
+ { -1, 5, -15, 120, 25, -8, 2, 0 },
+ { -1, 6, -18, 114, 35, -10, 3, -1 },
+ { -1, 6, -20, 107, 46, -13, 4, -1 },
+ { -2, 7, -21, 99, 57, -16, 5, -1 },
+ { -1, 6, -20, 89, 68, -18, 5, -1 },
+ { -1, 6, -20, 79, 79, -20, 6, -1 },
+ { -1, 5, -18, 68, 89, -20, 6, -1 },
+ { -1, 5, -16, 57, 99, -21, 7, -2 },
+ { -1, 4, -13, 46, 107, -20, 6, -1 },
+ { -1, 3, -10, 35, 114, -18, 6, -1 },
+ { 0, 2, -8, 25, 120, -15, 5, -1 },
+ { 0, 1, -5, 16, 125, -12, 4, -1 },
+ { 0, 1, -2, 7, 127, -6, 2, -1 }
+ }, { /* 65536 < Ratio <= 74898 (~8:7) */
+ { 3, -8, 14, 111, 13, -8, 3, 0 },
+ { 2, -6, 7, 112, 21, -10, 3, -1 },
+ { 2, -4, 1, 110, 28, -12, 4, -1 },
+ { 1, -2, -3, 106, 36, -13, 4, -1 },
+ { 1, -1, -7, 103, 44, -15, 4, -1 },
+ { 1, 1, -11, 97, 53, -16, 4, -1 },
+ { 0, 2, -13, 91, 61, -16, 4, -1 },
+ { 0, 3, -15, 85, 69, -17, 4, -1 },
+ { 0, 3, -16, 77, 77, -16, 3, 0 },
+ { -1, 4, -17, 69, 85, -15, 3, 0 },
+ { -1, 4, -16, 61, 91, -13, 2, 0 },
+ { -1, 4, -16, 53, 97, -11, 1, 1 },
+ { -1, 4, -15, 44, 103, -7, -1, 1 },
+ { -1, 4, -13, 36, 106, -3, -2, 1 },
+ { -1, 4, -12, 28, 110, 1, -4, 2 },
+ { -1, 3, -10, 21, 112, 7, -6, 2 }
+ }, { /* 74898 < Ratio <= 87381 (~8:6) */
+ { 2, -11, 25, 96, 25, -11, 2, 0 },
+ { 2, -10, 19, 96, 31, -12, 2, 0 },
+ { 2, -9, 14, 94, 37, -12, 2, 0 },
+ { 2, -8, 10, 92, 43, -12, 1, 0 },
+ { 2, -7, 5, 90, 49, -12, 1, 0 },
+ { 2, -5, 1, 86, 55, -12, 0, 1 },
+ { 2, -4, -2, 82, 61, -11, -1, 1 },
+ { 1, -3, -5, 77, 67, -9, -1, 1 },
+ { 1, -2, -7, 72, 72, -7, -2, 1 },
+ { 1, -1, -9, 67, 77, -5, -3, 1 },
+ { 1, -1, -11, 61, 82, -2, -4, 2 },
+ { 1, 0, -12, 55, 86, 1, -5, 2 },
+ { 0, 1, -12, 49, 90, 5, -7, 2 },
+ { 0, 1, -12, 43, 92, 10, -8, 2 },
+ { 0, 2, -12, 37, 94, 14, -9, 2 },
+ { 0, 2, -12, 31, 96, 19, -10, 2 }
+ }, { /* 87381 < Ratio <= 104857 (~8:5) */
+ { -1, -8, 33, 80, 33, -8, -1, 0 },
+ { -1, -8, 28, 80, 37, -7, -2, 1 },
+ { 0, -8, 24, 79, 41, -7, -2, 1 },
+ { 0, -8, 20, 78, 46, -6, -3, 1 },
+ { 0, -8, 16, 76, 50, -4, -3, 1 },
+ { 0, -7, 13, 74, 54, -3, -4, 1 },
+ { 1, -7, 10, 71, 58, -1, -5, 1 },
+ { 1, -6, 6, 68, 62, 1, -5, 1 },
+ { 1, -6, 4, 65, 65, 4, -6, 1 },
+ { 1, -5, 1, 62, 68, 6, -6, 1 },
+ { 1, -5, -1, 58, 71, 10, -7, 1 },
+ { 1, -4, -3, 54, 74, 13, -7, 0 },
+ { 1, -3, -4, 50, 76, 16, -8, 0 },
+ { 1, -3, -6, 46, 78, 20, -8, 0 },
+ { 1, -2, -7, 41, 79, 24, -8, 0 },
+ { 1, -2, -7, 37, 80, 28, -8, -1 }
+ }, { /* 104857 < Ratio <= 131072 (~8:4) */
+ { -3, 0, 35, 64, 35, 0, -3, 0 },
+ { -3, -1, 32, 64, 38, 1, -3, 0 },
+ { -2, -2, 29, 63, 41, 2, -3, 0 },
+ { -2, -3, 27, 63, 43, 4, -4, 0 },
+ { -2, -3, 24, 61, 46, 6, -4, 0 },
+ { -2, -3, 21, 60, 49, 7, -4, 0 },
+ { -1, -4, 19, 59, 51, 9, -4, -1 },
+ { -1, -4, 16, 57, 53, 12, -4, -1 },
+ { -1, -4, 14, 55, 55, 14, -4, -1 },
+ { -1, -4, 12, 53, 57, 16, -4, -1 },
+ { -1, -4, 9, 51, 59, 19, -4, -1 },
+ { 0, -4, 7, 49, 60, 21, -3, -2 },
+ { 0, -4, 6, 46, 61, 24, -3, -2 },
+ { 0, -4, 4, 43, 63, 27, -3, -2 },
+ { 0, -3, 2, 41, 63, 29, -2, -2 },
+ { 0, -3, 1, 38, 64, 32, -1, -3 }
+ }, { /* 131072 < Ratio <= 174762 (~8:3) */
+ { -1, 8, 33, 48, 33, 8, -1, 0 },
+ { -1, 7, 31, 49, 35, 9, -1, -1 },
+ { -1, 6, 30, 49, 36, 10, -1, -1 },
+ { -1, 5, 28, 48, 38, 12, -1, -1 },
+ { -1, 4, 26, 48, 39, 13, 0, -1 },
+ { -1, 3, 24, 47, 41, 15, 0, -1 },
+ { -1, 2, 23, 47, 42, 16, 0, -1 },
+ { -1, 2, 21, 45, 43, 18, 1, -1 },
+ { -1, 1, 19, 45, 45, 19, 1, -1 },
+ { -1, 1, 18, 43, 45, 21, 2, -1 },
+ { -1, 0, 16, 42, 47, 23, 2, -1 },
+ { -1, 0, 15, 41, 47, 24, 3, -1 },
+ { -1, 0, 13, 39, 48, 26, 4, -1 },
+ { -1, -1, 12, 38, 48, 28, 5, -1 },
+ { -1, -1, 10, 36, 49, 30, 6, -1 },
+ { -1, -1, 9, 35, 49, 31, 7, -1 }
+ }, { /* 174762 < Ratio <= 262144 (~8:2) */
+ { 2, 13, 30, 38, 30, 13, 2, 0 },
+ { 2, 12, 29, 38, 30, 14, 3, 0 },
+ { 2, 11, 28, 38, 31, 15, 3, 0 },
+ { 2, 10, 26, 38, 32, 16, 4, 0 },
+ { 1, 10, 26, 37, 33, 17, 4, 0 },
+ { 1, 9, 24, 37, 34, 18, 5, 0 },
+ { 1, 8, 24, 37, 34, 19, 5, 0 },
+ { 1, 7, 22, 36, 35, 20, 6, 1 },
+ { 1, 6, 21, 36, 36, 21, 6, 1 },
+ { 1, 6, 20, 35, 36, 22, 7, 1 },
+ { 0, 5, 19, 34, 37, 24, 8, 1 },
+ { 0, 5, 18, 34, 37, 24, 9, 1 },
+ { 0, 4, 17, 33, 37, 26, 10, 1 },
+ { 0, 4, 16, 32, 38, 26, 10, 2 },
+ { 0, 3, 15, 31, 38, 28, 11, 2 },
+ { 0, 3, 14, 30, 38, 29, 12, 2 }
+ }
+};
+
+/* 4-tap Filter Coefficient */
+static const int v_coef_4t[GSC_COEF_RATIO][GSC_COEF_ATTR][GSC_COEF_V_4T] = {
+ { /* Ratio <= 65536 (~8:8) */
+ { 0, 128, 0, 0 },
+ { -4, 127, 5, 0 },
+ { -6, 124, 11, -1 },
+ { -8, 118, 19, -1 },
+ { -8, 111, 27, -2 },
+ { -8, 102, 37, -3 },
+ { -8, 92, 48, -4 },
+ { -7, 81, 59, -5 },
+ { -6, 70, 70, -6 },
+ { -5, 59, 81, -7 },
+ { -4, 48, 92, -8 },
+ { -3, 37, 102, -8 },
+ { -2, 27, 111, -8 },
+ { -1, 19, 118, -8 },
+ { -1, 11, 124, -6 },
+ { 0, 5, 127, -4 }
+ }, { /* 65536 < Ratio <= 74898 (~8:7) */
+ { 8, 112, 8, 0 },
+ { 4, 111, 14, -1 },
+ { 1, 109, 20, -2 },
+ { -2, 105, 27, -2 },
+ { -3, 100, 34, -3 },
+ { -5, 93, 43, -3 },
+ { -5, 86, 51, -4 },
+ { -5, 77, 60, -4 },
+ { -5, 69, 69, -5 },
+ { -4, 60, 77, -5 },
+ { -4, 51, 86, -5 },
+ { -3, 43, 93, -5 },
+ { -3, 34, 100, -3 },
+ { -2, 27, 105, -2 },
+ { -2, 20, 109, 1 },
+ { -1, 14, 111, 4 }
+ }, { /* 74898 < Ratio <= 87381 (~8:6) */
+ { 16, 96, 16, 0 },
+ { 12, 97, 21, -2 },
+ { 8, 96, 26, -2 },
+ { 5, 93, 32, -2 },
+ { 2, 89, 39, -2 },
+ { 0, 84, 46, -2 },
+ { -1, 79, 53, -3 },
+ { -2, 73, 59, -2 },
+ { -2, 66, 66, -2 },
+ { -2, 59, 73, -2 },
+ { -3, 53, 79, -1 },
+ { -2, 46, 84, 0 },
+ { -2, 39, 89, 2 },
+ { -2, 32, 93, 5 },
+ { -2, 26, 96, 8 },
+ { -2, 21, 97, 12 }
+ }, { /* 87381 < Ratio <= 104857 (~8:5) */
+ { 22, 84, 22, 0 },
+ { 18, 85, 26, -1 },
+ { 14, 84, 31, -1 },
+ { 11, 82, 36, -1 },
+ { 8, 79, 42, -1 },
+ { 6, 76, 47, -1 },
+ { 4, 72, 52, 0 },
+ { 2, 68, 58, 0 },
+ { 1, 63, 63, 1 },
+ { 0, 58, 68, 2 },
+ { 0, 52, 72, 4 },
+ { -1, 47, 76, 6 },
+ { -1, 42, 79, 8 },
+ { -1, 36, 82, 11 },
+ { -1, 31, 84, 14 },
+ { -1, 26, 85, 18 }
+ }, { /* 104857 < Ratio <= 131072 (~8:4) */
+ { 26, 76, 26, 0 },
+ { 22, 76, 30, 0 },
+ { 19, 75, 34, 0 },
+ { 16, 73, 38, 1 },
+ { 13, 71, 43, 1 },
+ { 10, 69, 47, 2 },
+ { 8, 66, 51, 3 },
+ { 6, 63, 55, 4 },
+ { 5, 59, 59, 5 },
+ { 4, 55, 63, 6 },
+ { 3, 51, 66, 8 },
+ { 2, 47, 69, 10 },
+ { 1, 43, 71, 13 },
+ { 1, 38, 73, 16 },
+ { 0, 34, 75, 19 },
+ { 0, 30, 76, 22 }
+ }, { /* 131072 < Ratio <= 174762 (~8:3) */
+ { 29, 70, 29, 0 },
+ { 26, 68, 32, 2 },
+ { 23, 67, 36, 2 },
+ { 20, 66, 39, 3 },
+ { 17, 65, 43, 3 },
+ { 15, 63, 46, 4 },
+ { 12, 61, 50, 5 },
+ { 10, 58, 53, 7 },
+ { 8, 56, 56, 8 },
+ { 7, 53, 58, 10 },
+ { 5, 50, 61, 12 },
+ { 4, 46, 63, 15 },
+ { 3, 43, 65, 17 },
+ { 3, 39, 66, 20 },
+ { 2, 36, 67, 23 },
+ { 2, 32, 68, 26 }
+ }, { /* 174762 < Ratio <= 262144 (~8:2) */
+ { 32, 64, 32, 0 },
+ { 28, 63, 34, 3 },
+ { 25, 62, 37, 4 },
+ { 22, 62, 40, 4 },
+ { 19, 61, 43, 5 },
+ { 17, 59, 46, 6 },
+ { 15, 58, 48, 7 },
+ { 13, 55, 51, 9 },
+ { 11, 53, 53, 11 },
+ { 9, 51, 55, 13 },
+ { 7, 48, 58, 15 },
+ { 6, 46, 59, 17 },
+ { 5, 43, 61, 19 },
+ { 4, 40, 62, 22 },
+ { 4, 37, 62, 25 },
+ { 3, 34, 63, 28 }
+ }
+};
+
+static int gsc_sw_reset(struct gsc_context *ctx)
+{
+ u32 cfg;
+ int count = GSC_RESET_TIMEOUT;
+
+ /* s/w reset */
+ cfg = (GSC_SW_RESET_SRESET);
+ gsc_write(cfg, GSC_SW_RESET);
+
+ /* wait s/w reset complete */
+ while (count--) {
+ cfg = gsc_read(GSC_SW_RESET);
+ if (!cfg)
+ break;
+ usleep_range(1000, 2000);
+ }
+
+ if (cfg) {
+ DRM_DEV_ERROR(ctx->dev, "failed to reset gsc h/w.\n");
+ return -EBUSY;
+ }
+
+ /* reset sequence */
+ cfg = gsc_read(GSC_IN_BASE_ADDR_Y_MASK);
+ cfg |= (GSC_IN_BASE_ADDR_MASK |
+ GSC_IN_BASE_ADDR_PINGPONG(0));
+ gsc_write(cfg, GSC_IN_BASE_ADDR_Y_MASK);
+ gsc_write(cfg, GSC_IN_BASE_ADDR_CB_MASK);
+ gsc_write(cfg, GSC_IN_BASE_ADDR_CR_MASK);
+
+ cfg = gsc_read(GSC_OUT_BASE_ADDR_Y_MASK);
+ cfg |= (GSC_OUT_BASE_ADDR_MASK |
+ GSC_OUT_BASE_ADDR_PINGPONG(0));
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_Y_MASK);
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_CB_MASK);
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_CR_MASK);
+
+ return 0;
+}
+
+static void gsc_handle_irq(struct gsc_context *ctx, bool enable,
+ bool overflow, bool done)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]overflow[%d]level[%d]\n",
+ enable, overflow, done);
+
+ cfg = gsc_read(GSC_IRQ);
+ cfg |= (GSC_IRQ_OR_MASK | GSC_IRQ_FRMDONE_MASK);
+
+ if (enable)
+ cfg |= GSC_IRQ_ENABLE;
+ else
+ cfg &= ~GSC_IRQ_ENABLE;
+
+ if (overflow)
+ cfg &= ~GSC_IRQ_OR_MASK;
+ else
+ cfg |= GSC_IRQ_OR_MASK;
+
+ if (done)
+ cfg &= ~GSC_IRQ_FRMDONE_MASK;
+ else
+ cfg |= GSC_IRQ_FRMDONE_MASK;
+
+ gsc_write(cfg, GSC_IRQ);
+}
+
+
+static void gsc_src_set_fmt(struct gsc_context *ctx, u32 fmt, bool tiled)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ cfg = gsc_read(GSC_IN_CON);
+ cfg &= ~(GSC_IN_RGB_TYPE_MASK | GSC_IN_YUV422_1P_ORDER_MASK |
+ GSC_IN_CHROMA_ORDER_MASK | GSC_IN_FORMAT_MASK |
+ GSC_IN_TILE_TYPE_MASK | GSC_IN_TILE_MODE |
+ GSC_IN_CHROM_STRIDE_SEL_MASK | GSC_IN_RB_SWAP_MASK);
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ cfg |= GSC_IN_RGB565;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ cfg |= GSC_IN_XRGB8888;
+ break;
+ case DRM_FORMAT_BGRX8888:
+ cfg |= (GSC_IN_XRGB8888 | GSC_IN_RB_SWAP);
+ break;
+ case DRM_FORMAT_YUYV:
+ cfg |= (GSC_IN_YUV422_1P |
+ GSC_IN_YUV422_1P_ORDER_LSB_Y |
+ GSC_IN_CHROMA_ORDER_CBCR);
+ break;
+ case DRM_FORMAT_YVYU:
+ cfg |= (GSC_IN_YUV422_1P |
+ GSC_IN_YUV422_1P_ORDER_LSB_Y |
+ GSC_IN_CHROMA_ORDER_CRCB);
+ break;
+ case DRM_FORMAT_UYVY:
+ cfg |= (GSC_IN_YUV422_1P |
+ GSC_IN_YUV422_1P_OEDER_LSB_C |
+ GSC_IN_CHROMA_ORDER_CBCR);
+ break;
+ case DRM_FORMAT_VYUY:
+ cfg |= (GSC_IN_YUV422_1P |
+ GSC_IN_YUV422_1P_OEDER_LSB_C |
+ GSC_IN_CHROMA_ORDER_CRCB);
+ break;
+ case DRM_FORMAT_NV21:
+ cfg |= (GSC_IN_CHROMA_ORDER_CRCB | GSC_IN_YUV420_2P);
+ break;
+ case DRM_FORMAT_NV61:
+ cfg |= (GSC_IN_CHROMA_ORDER_CRCB | GSC_IN_YUV422_2P);
+ break;
+ case DRM_FORMAT_YUV422:
+ cfg |= GSC_IN_YUV422_3P;
+ break;
+ case DRM_FORMAT_YUV420:
+ cfg |= (GSC_IN_CHROMA_ORDER_CBCR | GSC_IN_YUV420_3P);
+ break;
+ case DRM_FORMAT_YVU420:
+ cfg |= (GSC_IN_CHROMA_ORDER_CRCB | GSC_IN_YUV420_3P);
+ break;
+ case DRM_FORMAT_NV12:
+ cfg |= (GSC_IN_CHROMA_ORDER_CBCR | GSC_IN_YUV420_2P);
+ break;
+ case DRM_FORMAT_NV16:
+ cfg |= (GSC_IN_CHROMA_ORDER_CBCR | GSC_IN_YUV422_2P);
+ break;
+ }
+
+ if (tiled)
+ cfg |= (GSC_IN_TILE_C_16x8 | GSC_IN_TILE_MODE);
+
+ gsc_write(cfg, GSC_IN_CON);
+}
+
+static void gsc_src_set_transf(struct gsc_context *ctx, unsigned int rotation)
+{
+ unsigned int degree = rotation & DRM_MODE_ROTATE_MASK;
+ u32 cfg;
+
+ cfg = gsc_read(GSC_IN_CON);
+ cfg &= ~GSC_IN_ROT_MASK;
+
+ switch (degree) {
+ case DRM_MODE_ROTATE_0:
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg |= GSC_IN_ROT_XFLIP;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg |= GSC_IN_ROT_YFLIP;
+ break;
+ case DRM_MODE_ROTATE_90:
+ cfg |= GSC_IN_ROT_90;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg |= GSC_IN_ROT_XFLIP;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg |= GSC_IN_ROT_YFLIP;
+ break;
+ case DRM_MODE_ROTATE_180:
+ cfg |= GSC_IN_ROT_180;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg &= ~GSC_IN_ROT_XFLIP;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg &= ~GSC_IN_ROT_YFLIP;
+ break;
+ case DRM_MODE_ROTATE_270:
+ cfg |= GSC_IN_ROT_270;
+ if (rotation & DRM_MODE_REFLECT_X)
+ cfg &= ~GSC_IN_ROT_XFLIP;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ cfg &= ~GSC_IN_ROT_YFLIP;
+ break;
+ }
+
+ gsc_write(cfg, GSC_IN_CON);
+
+ ctx->rotation = (cfg & GSC_IN_ROT_90) ? 1 : 0;
+}
+
+static void gsc_src_set_size(struct gsc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ struct gsc_scaler *sc = &ctx->sc;
+ u32 cfg;
+
+ /* pixel offset */
+ cfg = (GSC_SRCIMG_OFFSET_X(buf->rect.x) |
+ GSC_SRCIMG_OFFSET_Y(buf->rect.y));
+ gsc_write(cfg, GSC_SRCIMG_OFFSET);
+
+ /* cropped size */
+ cfg = (GSC_CROPPED_WIDTH(buf->rect.w) |
+ GSC_CROPPED_HEIGHT(buf->rect.h));
+ gsc_write(cfg, GSC_CROPPED_SIZE);
+
+ /* original size */
+ cfg = gsc_read(GSC_SRCIMG_SIZE);
+ cfg &= ~(GSC_SRCIMG_HEIGHT_MASK |
+ GSC_SRCIMG_WIDTH_MASK);
+
+ cfg |= (GSC_SRCIMG_WIDTH(buf->buf.pitch[0] / buf->format->cpp[0]) |
+ GSC_SRCIMG_HEIGHT(buf->buf.height));
+
+ gsc_write(cfg, GSC_SRCIMG_SIZE);
+
+ cfg = gsc_read(GSC_IN_CON);
+ cfg &= ~GSC_IN_RGB_TYPE_MASK;
+
+ if (buf->rect.w >= GSC_WIDTH_ITU_709)
+ if (sc->range)
+ cfg |= GSC_IN_RGB_HD_WIDE;
+ else
+ cfg |= GSC_IN_RGB_HD_NARROW;
+ else
+ if (sc->range)
+ cfg |= GSC_IN_RGB_SD_WIDE;
+ else
+ cfg |= GSC_IN_RGB_SD_NARROW;
+
+ gsc_write(cfg, GSC_IN_CON);
+}
+
+static void gsc_src_set_buf_seq(struct gsc_context *ctx, u32 buf_id,
+ bool enqueue)
+{
+ bool masked = !enqueue;
+ u32 cfg;
+ u32 mask = 0x00000001 << buf_id;
+
+ /* mask register set */
+ cfg = gsc_read(GSC_IN_BASE_ADDR_Y_MASK);
+
+ /* sequence id */
+ cfg &= ~mask;
+ cfg |= masked << buf_id;
+ gsc_write(cfg, GSC_IN_BASE_ADDR_Y_MASK);
+ gsc_write(cfg, GSC_IN_BASE_ADDR_CB_MASK);
+ gsc_write(cfg, GSC_IN_BASE_ADDR_CR_MASK);
+}
+
+static void gsc_src_set_addr(struct gsc_context *ctx, u32 buf_id,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ /* address register set */
+ gsc_write(buf->dma_addr[0], GSC_IN_BASE_ADDR_Y(buf_id));
+ gsc_write(buf->dma_addr[1], GSC_IN_BASE_ADDR_CB(buf_id));
+ gsc_write(buf->dma_addr[2], GSC_IN_BASE_ADDR_CR(buf_id));
+
+ gsc_src_set_buf_seq(ctx, buf_id, true);
+}
+
+static void gsc_dst_set_fmt(struct gsc_context *ctx, u32 fmt, bool tiled)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
+
+ cfg = gsc_read(GSC_OUT_CON);
+ cfg &= ~(GSC_OUT_RGB_TYPE_MASK | GSC_OUT_YUV422_1P_ORDER_MASK |
+ GSC_OUT_CHROMA_ORDER_MASK | GSC_OUT_FORMAT_MASK |
+ GSC_OUT_CHROM_STRIDE_SEL_MASK | GSC_OUT_RB_SWAP_MASK |
+ GSC_OUT_GLOBAL_ALPHA_MASK);
+
+ switch (fmt) {
+ case DRM_FORMAT_RGB565:
+ cfg |= GSC_OUT_RGB565;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ cfg |= (GSC_OUT_XRGB8888 | GSC_OUT_GLOBAL_ALPHA(0xff));
+ break;
+ case DRM_FORMAT_BGRX8888:
+ cfg |= (GSC_OUT_XRGB8888 | GSC_OUT_RB_SWAP);
+ break;
+ case DRM_FORMAT_YUYV:
+ cfg |= (GSC_OUT_YUV422_1P |
+ GSC_OUT_YUV422_1P_ORDER_LSB_Y |
+ GSC_OUT_CHROMA_ORDER_CBCR);
+ break;
+ case DRM_FORMAT_YVYU:
+ cfg |= (GSC_OUT_YUV422_1P |
+ GSC_OUT_YUV422_1P_ORDER_LSB_Y |
+ GSC_OUT_CHROMA_ORDER_CRCB);
+ break;
+ case DRM_FORMAT_UYVY:
+ cfg |= (GSC_OUT_YUV422_1P |
+ GSC_OUT_YUV422_1P_OEDER_LSB_C |
+ GSC_OUT_CHROMA_ORDER_CBCR);
+ break;
+ case DRM_FORMAT_VYUY:
+ cfg |= (GSC_OUT_YUV422_1P |
+ GSC_OUT_YUV422_1P_OEDER_LSB_C |
+ GSC_OUT_CHROMA_ORDER_CRCB);
+ break;
+ case DRM_FORMAT_NV21:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CRCB | GSC_OUT_YUV420_2P);
+ break;
+ case DRM_FORMAT_NV61:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CRCB | GSC_OUT_YUV422_2P);
+ break;
+ case DRM_FORMAT_YUV422:
+ cfg |= GSC_OUT_YUV422_3P;
+ break;
+ case DRM_FORMAT_YUV420:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CBCR | GSC_OUT_YUV420_3P);
+ break;
+ case DRM_FORMAT_YVU420:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CRCB | GSC_OUT_YUV420_3P);
+ break;
+ case DRM_FORMAT_NV12:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CBCR | GSC_OUT_YUV420_2P);
+ break;
+ case DRM_FORMAT_NV16:
+ cfg |= (GSC_OUT_CHROMA_ORDER_CBCR | GSC_OUT_YUV422_2P);
+ break;
+ }
+
+ if (tiled)
+ cfg |= (GSC_IN_TILE_C_16x8 | GSC_OUT_TILE_MODE);
+
+ gsc_write(cfg, GSC_OUT_CON);
+}
+
+static int gsc_get_ratio_shift(struct gsc_context *ctx, u32 src, u32 dst,
+ u32 *ratio)
+{
+ DRM_DEV_DEBUG_KMS(ctx->dev, "src[%d]dst[%d]\n", src, dst);
+
+ if (src >= dst * 8) {
+ DRM_DEV_ERROR(ctx->dev, "failed to make ratio and shift.\n");
+ return -EINVAL;
+ } else if (src >= dst * 4)
+ *ratio = 4;
+ else if (src >= dst * 2)
+ *ratio = 2;
+ else
+ *ratio = 1;
+
+ return 0;
+}
+
+static void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *shfactor)
+{
+ if (hratio == 4 && vratio == 4)
+ *shfactor = 4;
+ else if ((hratio == 4 && vratio == 2) ||
+ (hratio == 2 && vratio == 4))
+ *shfactor = 3;
+ else if ((hratio == 4 && vratio == 1) ||
+ (hratio == 1 && vratio == 4) ||
+ (hratio == 2 && vratio == 2))
+ *shfactor = 2;
+ else if (hratio == 1 && vratio == 1)
+ *shfactor = 0;
+ else
+ *shfactor = 1;
+}
+
+static int gsc_set_prescaler(struct gsc_context *ctx, struct gsc_scaler *sc,
+ struct drm_exynos_ipp_task_rect *src,
+ struct drm_exynos_ipp_task_rect *dst)
+{
+ u32 cfg;
+ u32 src_w, src_h, dst_w, dst_h;
+ int ret = 0;
+
+ src_w = src->w;
+ src_h = src->h;
+
+ if (ctx->rotation) {
+ dst_w = dst->h;
+ dst_h = dst->w;
+ } else {
+ dst_w = dst->w;
+ dst_h = dst->h;
+ }
+
+ ret = gsc_get_ratio_shift(ctx, src_w, dst_w, &sc->pre_hratio);
+ if (ret) {
+ DRM_DEV_ERROR(ctx->dev, "failed to get ratio horizontal.\n");
+ return ret;
+ }
+
+ ret = gsc_get_ratio_shift(ctx, src_h, dst_h, &sc->pre_vratio);
+ if (ret) {
+ DRM_DEV_ERROR(ctx->dev, "failed to get ratio vertical.\n");
+ return ret;
+ }
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "pre_hratio[%d]pre_vratio[%d]\n",
+ sc->pre_hratio, sc->pre_vratio);
+
+ sc->main_hratio = (src_w << 16) / dst_w;
+ sc->main_vratio = (src_h << 16) / dst_h;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "main_hratio[%ld]main_vratio[%ld]\n",
+ sc->main_hratio, sc->main_vratio);
+
+ gsc_get_prescaler_shfactor(sc->pre_hratio, sc->pre_vratio,
+ &sc->pre_shfactor);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "pre_shfactor[%d]\n", sc->pre_shfactor);
+
+ cfg = (GSC_PRESC_SHFACTOR(sc->pre_shfactor) |
+ GSC_PRESC_H_RATIO(sc->pre_hratio) |
+ GSC_PRESC_V_RATIO(sc->pre_vratio));
+ gsc_write(cfg, GSC_PRE_SCALE_RATIO);
+
+ return ret;
+}
+
+static void gsc_set_h_coef(struct gsc_context *ctx, unsigned long main_hratio)
+{
+ int i, j, k, sc_ratio;
+
+ if (main_hratio <= GSC_SC_UP_MAX_RATIO)
+ sc_ratio = 0;
+ else if (main_hratio <= GSC_SC_DOWN_RATIO_7_8)
+ sc_ratio = 1;
+ else if (main_hratio <= GSC_SC_DOWN_RATIO_6_8)
+ sc_ratio = 2;
+ else if (main_hratio <= GSC_SC_DOWN_RATIO_5_8)
+ sc_ratio = 3;
+ else if (main_hratio <= GSC_SC_DOWN_RATIO_4_8)
+ sc_ratio = 4;
+ else if (main_hratio <= GSC_SC_DOWN_RATIO_3_8)
+ sc_ratio = 5;
+ else
+ sc_ratio = 6;
+
+ for (i = 0; i < GSC_COEF_PHASE; i++)
+ for (j = 0; j < GSC_COEF_H_8T; j++)
+ for (k = 0; k < GSC_COEF_DEPTH; k++)
+ gsc_write(h_coef_8t[sc_ratio][i][j],
+ GSC_HCOEF(i, j, k));
+}
+
+static void gsc_set_v_coef(struct gsc_context *ctx, unsigned long main_vratio)
+{
+ int i, j, k, sc_ratio;
+
+ if (main_vratio <= GSC_SC_UP_MAX_RATIO)
+ sc_ratio = 0;
+ else if (main_vratio <= GSC_SC_DOWN_RATIO_7_8)
+ sc_ratio = 1;
+ else if (main_vratio <= GSC_SC_DOWN_RATIO_6_8)
+ sc_ratio = 2;
+ else if (main_vratio <= GSC_SC_DOWN_RATIO_5_8)
+ sc_ratio = 3;
+ else if (main_vratio <= GSC_SC_DOWN_RATIO_4_8)
+ sc_ratio = 4;
+ else if (main_vratio <= GSC_SC_DOWN_RATIO_3_8)
+ sc_ratio = 5;
+ else
+ sc_ratio = 6;
+
+ for (i = 0; i < GSC_COEF_PHASE; i++)
+ for (j = 0; j < GSC_COEF_V_4T; j++)
+ for (k = 0; k < GSC_COEF_DEPTH; k++)
+ gsc_write(v_coef_4t[sc_ratio][i][j],
+ GSC_VCOEF(i, j, k));
+}
+
+static void gsc_set_scaler(struct gsc_context *ctx, struct gsc_scaler *sc)
+{
+ u32 cfg;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "main_hratio[%ld]main_vratio[%ld]\n",
+ sc->main_hratio, sc->main_vratio);
+
+ gsc_set_h_coef(ctx, sc->main_hratio);
+ cfg = GSC_MAIN_H_RATIO_VALUE(sc->main_hratio);
+ gsc_write(cfg, GSC_MAIN_H_RATIO);
+
+ gsc_set_v_coef(ctx, sc->main_vratio);
+ cfg = GSC_MAIN_V_RATIO_VALUE(sc->main_vratio);
+ gsc_write(cfg, GSC_MAIN_V_RATIO);
+}
+
+static void gsc_dst_set_size(struct gsc_context *ctx,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ struct gsc_scaler *sc = &ctx->sc;
+ u32 cfg;
+
+ /* pixel offset */
+ cfg = (GSC_DSTIMG_OFFSET_X(buf->rect.x) |
+ GSC_DSTIMG_OFFSET_Y(buf->rect.y));
+ gsc_write(cfg, GSC_DSTIMG_OFFSET);
+
+ /* scaled size */
+ if (ctx->rotation)
+ cfg = (GSC_SCALED_WIDTH(buf->rect.h) |
+ GSC_SCALED_HEIGHT(buf->rect.w));
+ else
+ cfg = (GSC_SCALED_WIDTH(buf->rect.w) |
+ GSC_SCALED_HEIGHT(buf->rect.h));
+ gsc_write(cfg, GSC_SCALED_SIZE);
+
+ /* original size */
+ cfg = gsc_read(GSC_DSTIMG_SIZE);
+ cfg &= ~(GSC_DSTIMG_HEIGHT_MASK | GSC_DSTIMG_WIDTH_MASK);
+ cfg |= GSC_DSTIMG_WIDTH(buf->buf.pitch[0] / buf->format->cpp[0]) |
+ GSC_DSTIMG_HEIGHT(buf->buf.height);
+ gsc_write(cfg, GSC_DSTIMG_SIZE);
+
+ cfg = gsc_read(GSC_OUT_CON);
+ cfg &= ~GSC_OUT_RGB_TYPE_MASK;
+
+ if (buf->rect.w >= GSC_WIDTH_ITU_709)
+ if (sc->range)
+ cfg |= GSC_OUT_RGB_HD_WIDE;
+ else
+ cfg |= GSC_OUT_RGB_HD_NARROW;
+ else
+ if (sc->range)
+ cfg |= GSC_OUT_RGB_SD_WIDE;
+ else
+ cfg |= GSC_OUT_RGB_SD_NARROW;
+
+ gsc_write(cfg, GSC_OUT_CON);
+}
+
+static int gsc_dst_get_buf_seq(struct gsc_context *ctx)
+{
+ u32 cfg, i, buf_num = GSC_REG_SZ;
+ u32 mask = 0x00000001;
+
+ cfg = gsc_read(GSC_OUT_BASE_ADDR_Y_MASK);
+
+ for (i = 0; i < GSC_REG_SZ; i++)
+ if (cfg & (mask << i))
+ buf_num--;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "buf_num[%d]\n", buf_num);
+
+ return buf_num;
+}
+
+static void gsc_dst_set_buf_seq(struct gsc_context *ctx, u32 buf_id,
+ bool enqueue)
+{
+ bool masked = !enqueue;
+ u32 cfg;
+ u32 mask = 0x00000001 << buf_id;
+
+ /* mask register set */
+ cfg = gsc_read(GSC_OUT_BASE_ADDR_Y_MASK);
+
+ /* sequence id */
+ cfg &= ~mask;
+ cfg |= masked << buf_id;
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_Y_MASK);
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_CB_MASK);
+ gsc_write(cfg, GSC_OUT_BASE_ADDR_CR_MASK);
+
+ /* interrupt enable */
+ if (enqueue && gsc_dst_get_buf_seq(ctx) >= GSC_BUF_START)
+ gsc_handle_irq(ctx, true, false, true);
+
+ /* interrupt disable */
+ if (!enqueue && gsc_dst_get_buf_seq(ctx) <= GSC_BUF_STOP)
+ gsc_handle_irq(ctx, false, false, true);
+}
+
+static void gsc_dst_set_addr(struct gsc_context *ctx,
+ u32 buf_id, struct exynos_drm_ipp_buffer *buf)
+{
+ /* address register set */
+ gsc_write(buf->dma_addr[0], GSC_OUT_BASE_ADDR_Y(buf_id));
+ gsc_write(buf->dma_addr[1], GSC_OUT_BASE_ADDR_CB(buf_id));
+ gsc_write(buf->dma_addr[2], GSC_OUT_BASE_ADDR_CR(buf_id));
+
+ gsc_dst_set_buf_seq(ctx, buf_id, true);
+}
+
+static int gsc_get_src_buf_index(struct gsc_context *ctx)
+{
+ u32 cfg, curr_index, i;
+ u32 buf_id = GSC_MAX_SRC;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "gsc id[%d]\n", ctx->id);
+
+ cfg = gsc_read(GSC_IN_BASE_ADDR_Y_MASK);
+ curr_index = GSC_IN_CURR_GET_INDEX(cfg);
+
+ for (i = curr_index; i < GSC_MAX_SRC; i++) {
+ if (!((cfg >> i) & 0x1)) {
+ buf_id = i;
+ break;
+ }
+ }
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "cfg[0x%x]curr_index[%d]buf_id[%d]\n", cfg,
+ curr_index, buf_id);
+
+ if (buf_id == GSC_MAX_SRC) {
+ DRM_DEV_ERROR(ctx->dev, "failed to get in buffer index.\n");
+ return -EINVAL;
+ }
+
+ gsc_src_set_buf_seq(ctx, buf_id, false);
+
+ return buf_id;
+}
+
+static int gsc_get_dst_buf_index(struct gsc_context *ctx)
+{
+ u32 cfg, curr_index, i;
+ u32 buf_id = GSC_MAX_DST;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "gsc id[%d]\n", ctx->id);
+
+ cfg = gsc_read(GSC_OUT_BASE_ADDR_Y_MASK);
+ curr_index = GSC_OUT_CURR_GET_INDEX(cfg);
+
+ for (i = curr_index; i < GSC_MAX_DST; i++) {
+ if (!((cfg >> i) & 0x1)) {
+ buf_id = i;
+ break;
+ }
+ }
+
+ if (buf_id == GSC_MAX_DST) {
+ DRM_DEV_ERROR(ctx->dev, "failed to get out buffer index.\n");
+ return -EINVAL;
+ }
+
+ gsc_dst_set_buf_seq(ctx, buf_id, false);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "cfg[0x%x]curr_index[%d]buf_id[%d]\n", cfg,
+ curr_index, buf_id);
+
+ return buf_id;
+}
+
+static irqreturn_t gsc_irq_handler(int irq, void *dev_id)
+{
+ struct gsc_context *ctx = dev_id;
+ u32 status;
+ int err = 0;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "gsc id[%d]\n", ctx->id);
+
+ status = gsc_read(GSC_IRQ);
+ if (status & GSC_IRQ_STATUS_OR_IRQ) {
+ dev_err(ctx->dev, "occurred overflow at %d, status 0x%x.\n",
+ ctx->id, status);
+ err = -EINVAL;
+ }
+
+ if (status & GSC_IRQ_STATUS_OR_FRM_DONE) {
+ int src_buf_id, dst_buf_id;
+
+ dev_dbg(ctx->dev, "occurred frame done at %d, status 0x%x.\n",
+ ctx->id, status);
+
+ src_buf_id = gsc_get_src_buf_index(ctx);
+ dst_buf_id = gsc_get_dst_buf_index(ctx);
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id_src[%d]buf_id_dst[%d]\n",
+ src_buf_id, dst_buf_id);
+
+ if (src_buf_id < 0 || dst_buf_id < 0)
+ err = -EINVAL;
+ }
+
+ if (ctx->task) {
+ struct exynos_drm_ipp_task *task = ctx->task;
+
+ ctx->task = NULL;
+ pm_runtime_mark_last_busy(ctx->dev);
+ pm_runtime_put_autosuspend(ctx->dev);
+ exynos_drm_ipp_task_done(task, err);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int gsc_reset(struct gsc_context *ctx)
+{
+ struct gsc_scaler *sc = &ctx->sc;
+ int ret;
+
+ /* reset h/w block */
+ ret = gsc_sw_reset(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to reset hardware.\n");
+ return ret;
+ }
+
+ /* scaler setting */
+ memset(&ctx->sc, 0x0, sizeof(ctx->sc));
+ sc->range = true;
+
+ return 0;
+}
+
+static void gsc_start(struct gsc_context *ctx)
+{
+ u32 cfg;
+
+ gsc_handle_irq(ctx, true, false, true);
+
+ /* enable one shot */
+ cfg = gsc_read(GSC_ENABLE);
+ cfg &= ~(GSC_ENABLE_ON_CLEAR_MASK |
+ GSC_ENABLE_CLK_GATE_MODE_MASK);
+ cfg |= GSC_ENABLE_ON_CLEAR_ONESHOT;
+ gsc_write(cfg, GSC_ENABLE);
+
+ /* src dma memory */
+ cfg = gsc_read(GSC_IN_CON);
+ cfg &= ~(GSC_IN_PATH_MASK | GSC_IN_LOCAL_SEL_MASK);
+ cfg |= GSC_IN_PATH_MEMORY;
+ gsc_write(cfg, GSC_IN_CON);
+
+ /* dst dma memory */
+ cfg = gsc_read(GSC_OUT_CON);
+ cfg |= GSC_OUT_PATH_MEMORY;
+ gsc_write(cfg, GSC_OUT_CON);
+
+ gsc_set_scaler(ctx, &ctx->sc);
+
+ cfg = gsc_read(GSC_ENABLE);
+ cfg |= GSC_ENABLE_ON;
+ gsc_write(cfg, GSC_ENABLE);
+}
+
+static int gsc_commit(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct gsc_context *ctx = container_of(ipp, struct gsc_context, ipp);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable GScaler device.\n");
+ return ret;
+ }
+
+ ctx->task = task;
+
+ ret = gsc_reset(ctx);
+ if (ret) {
+ pm_runtime_put_autosuspend(ctx->dev);
+ ctx->task = NULL;
+ return ret;
+ }
+
+ gsc_src_set_fmt(ctx, task->src.buf.fourcc, task->src.buf.modifier);
+ gsc_src_set_transf(ctx, task->transform.rotation);
+ gsc_src_set_size(ctx, &task->src);
+ gsc_src_set_addr(ctx, 0, &task->src);
+ gsc_dst_set_fmt(ctx, task->dst.buf.fourcc, task->dst.buf.modifier);
+ gsc_dst_set_size(ctx, &task->dst);
+ gsc_dst_set_addr(ctx, 0, &task->dst);
+ gsc_set_prescaler(ctx, &ctx->sc, &task->src.rect, &task->dst.rect);
+ gsc_start(ctx);
+
+ return 0;
+}
+
+static void gsc_abort(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct gsc_context *ctx =
+ container_of(ipp, struct gsc_context, ipp);
+
+ gsc_reset(ctx);
+ if (ctx->task) {
+ struct exynos_drm_ipp_task *task = ctx->task;
+
+ ctx->task = NULL;
+ pm_runtime_mark_last_busy(ctx->dev);
+ pm_runtime_put_autosuspend(ctx->dev);
+ exynos_drm_ipp_task_done(task, -EIO);
+ }
+}
+
+static const struct exynos_drm_ipp_funcs ipp_funcs = {
+ .commit = gsc_commit,
+ .abort = gsc_abort,
+};
+
+static int gsc_bind(struct device *dev, struct device *master, void *data)
+{
+ struct gsc_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &ctx->ipp;
+
+ ctx->drm_dev = drm_dev;
+ ipp->drm_dev = drm_dev;
+ exynos_drm_register_dma(drm_dev, dev, &ctx->dma_priv);
+
+ exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
+ DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE |
+ DRM_EXYNOS_IPP_CAP_SCALE | DRM_EXYNOS_IPP_CAP_CONVERT,
+ ctx->formats, ctx->num_formats, "gsc");
+
+ dev_info(dev, "The exynos gscaler has been probed successfully\n");
+
+ return 0;
+}
+
+static void gsc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct gsc_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &ctx->ipp;
+
+ exynos_drm_ipp_unregister(dev, ipp);
+ exynos_drm_unregister_dma(drm_dev, dev, &ctx->dma_priv);
+}
+
+static const struct component_ops gsc_component_ops = {
+ .bind = gsc_bind,
+ .unbind = gsc_unbind,
+};
+
+static const unsigned int gsc_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_NV12, DRM_FORMAT_NV16, DRM_FORMAT_NV21, DRM_FORMAT_NV61,
+ DRM_FORMAT_UYVY, DRM_FORMAT_VYUY, DRM_FORMAT_YUYV, DRM_FORMAT_YVYU,
+ DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422,
+};
+
+static const unsigned int gsc_tiled_formats[] = {
+ DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+};
+
+static int gsc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct gsc_driverdata *driver_data;
+ struct exynos_drm_ipp_formats *formats;
+ struct gsc_context *ctx;
+ int num_formats, ret, i, j;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ driver_data = device_get_match_data(dev);
+ ctx->dev = dev;
+ ctx->num_clocks = driver_data->num_clocks;
+ ctx->clk_names = driver_data->clk_names;
+
+ /* construct formats/limits array */
+ num_formats = ARRAY_SIZE(gsc_formats) + ARRAY_SIZE(gsc_tiled_formats);
+ formats = devm_kcalloc(dev, num_formats, sizeof(*formats), GFP_KERNEL);
+ if (!formats)
+ return -ENOMEM;
+
+ /* linear formats */
+ for (i = 0; i < ARRAY_SIZE(gsc_formats); i++) {
+ formats[i].fourcc = gsc_formats[i];
+ formats[i].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION;
+ formats[i].limits = driver_data->limits;
+ formats[i].num_limits = driver_data->num_limits;
+ }
+
+ /* tiled formats */
+ for (j = i, i = 0; i < ARRAY_SIZE(gsc_tiled_formats); j++, i++) {
+ formats[j].fourcc = gsc_tiled_formats[i];
+ formats[j].modifier = DRM_FORMAT_MOD_SAMSUNG_16_16_TILE;
+ formats[j].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION;
+ formats[j].limits = driver_data->limits;
+ formats[j].num_limits = driver_data->num_limits;
+ }
+
+ ctx->formats = formats;
+ ctx->num_formats = num_formats;
+
+ /* clock control */
+ for (i = 0; i < ctx->num_clocks; i++) {
+ ctx->clocks[i] = devm_clk_get(dev, ctx->clk_names[i]);
+ if (IS_ERR(ctx->clocks[i])) {
+ dev_err(dev, "failed to get clock: %s\n",
+ ctx->clk_names[i]);
+ return PTR_ERR(ctx->clocks[i]);
+ }
+ }
+
+ ctx->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->regs))
+ return PTR_ERR(ctx->regs);
+
+ /* resource irq */
+ ctx->irq = platform_get_irq(pdev, 0);
+ if (ctx->irq < 0)
+ return ctx->irq;
+
+ ret = devm_request_irq(dev, ctx->irq, gsc_irq_handler, 0,
+ dev_name(dev), ctx);
+ if (ret < 0) {
+ dev_err(dev, "failed to request irq.\n");
+ return ret;
+ }
+
+ /* context initialization */
+ ctx->id = pdev->id;
+
+ platform_set_drvdata(pdev, ctx);
+
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, GSC_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &gsc_component_ops);
+ if (ret)
+ goto err_pm_dis;
+
+ dev_info(dev, "drm gsc registered successfully.\n");
+
+ return 0;
+
+err_pm_dis:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void gsc_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &gsc_component_ops);
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+}
+
+static int __maybe_unused gsc_runtime_suspend(struct device *dev)
+{
+ struct gsc_context *ctx = get_gsc_context(dev);
+ int i;
+
+ DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
+
+ for (i = ctx->num_clocks - 1; i >= 0; i--)
+ clk_disable_unprepare(ctx->clocks[i]);
+
+ return 0;
+}
+
+static int __maybe_unused gsc_runtime_resume(struct device *dev)
+{
+ struct gsc_context *ctx = get_gsc_context(dev);
+ int i, ret;
+
+ DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
+
+ for (i = 0; i < ctx->num_clocks; i++) {
+ ret = clk_prepare_enable(ctx->clocks[i]);
+ if (ret) {
+ while (--i >= 0)
+ clk_disable_unprepare(ctx->clocks[i]);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops gsc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(gsc_runtime_suspend, gsc_runtime_resume, NULL)
+};
+
+static const struct drm_exynos_ipp_limit gsc_5250_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, 4800, 8 }, .v = { 16, 3344, 8 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 16, 4800, 2 }, .v = { 8, 3344, 2 }) },
+ { IPP_SIZE_LIMIT(ROTATED, .h = { 32, 2048 }, .v = { 16, 2048 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 16, (1 << 16) * 8 },
+ .v = { (1 << 16) / 16, (1 << 16) * 8 }) },
+};
+
+static const struct drm_exynos_ipp_limit gsc_5420_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, 4800, 8 }, .v = { 16, 3344, 8 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 16, 4800, 2 }, .v = { 8, 3344, 2 }) },
+ { IPP_SIZE_LIMIT(ROTATED, .h = { 16, 2016 }, .v = { 8, 2016 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 16, (1 << 16) * 8 },
+ .v = { (1 << 16) / 16, (1 << 16) * 8 }) },
+};
+
+static const struct drm_exynos_ipp_limit gsc_5433_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, 8191, 16 }, .v = { 16, 8191, 2 }) },
+ { IPP_SIZE_LIMIT(AREA, .h = { 16, 4800, 1 }, .v = { 8, 3344, 1 }) },
+ { IPP_SIZE_LIMIT(ROTATED, .h = { 32, 2047 }, .v = { 8, 8191 }) },
+ { IPP_SCALE_LIMIT(.h = { (1 << 16) / 16, (1 << 16) * 8 },
+ .v = { (1 << 16) / 16, (1 << 16) * 8 }) },
+};
+
+static struct gsc_driverdata gsc_exynos5250_drvdata = {
+ .clk_names = {"gscl"},
+ .num_clocks = 1,
+ .limits = gsc_5250_limits,
+ .num_limits = ARRAY_SIZE(gsc_5250_limits),
+};
+
+static struct gsc_driverdata gsc_exynos5420_drvdata = {
+ .clk_names = {"gscl"},
+ .num_clocks = 1,
+ .limits = gsc_5420_limits,
+ .num_limits = ARRAY_SIZE(gsc_5420_limits),
+};
+
+static struct gsc_driverdata gsc_exynos5433_drvdata = {
+ .clk_names = {"pclk", "aclk", "aclk_xiu", "aclk_gsclbend"},
+ .num_clocks = 4,
+ .limits = gsc_5433_limits,
+ .num_limits = ARRAY_SIZE(gsc_5433_limits),
+};
+
+static const struct of_device_id exynos_drm_gsc_of_match[] = {
+ {
+ .compatible = "samsung,exynos5-gsc",
+ .data = &gsc_exynos5250_drvdata,
+ }, {
+ .compatible = "samsung,exynos5250-gsc",
+ .data = &gsc_exynos5250_drvdata,
+ }, {
+ .compatible = "samsung,exynos5420-gsc",
+ .data = &gsc_exynos5420_drvdata,
+ }, {
+ .compatible = "samsung,exynos5433-gsc",
+ .data = &gsc_exynos5433_drvdata,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, exynos_drm_gsc_of_match);
+
+struct platform_driver gsc_driver = {
+ .probe = gsc_probe,
+ .remove = gsc_remove,
+ .driver = {
+ .name = "exynos-drm-gsc",
+ .pm = &gsc_pm_ops,
+ .of_match_table = exynos_drm_gsc_of_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.c b/drivers/gpu/drm/exynos/exynos_drm_ipp.c
new file mode 100644
index 000000000..43bc4f63b
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2017 Samsung Electronics Co.Ltd
+ * Authors:
+ * Marek Szyprowski <m.szyprowski@samsung.com>
+ *
+ * Exynos DRM Image Post Processing (IPP) related functions
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ */
+
+#include <linux/uaccess.h>
+
+#include <drm/drm_blend.h>
+#include <drm/drm_file.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_mode.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_gem.h"
+#include "exynos_drm_ipp.h"
+
+static int num_ipp;
+static LIST_HEAD(ipp_list);
+
+/**
+ * exynos_drm_ipp_register - Register a new picture processor hardware module
+ * @dev: DRM device
+ * @ipp: ipp module to init
+ * @funcs: callbacks for the new ipp object
+ * @caps: bitmask of ipp capabilities (%DRM_EXYNOS_IPP_CAP_*)
+ * @formats: array of supported formats
+ * @num_formats: size of the supported formats array
+ * @name: name (for debugging purposes)
+ *
+ * Initializes a ipp module.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int exynos_drm_ipp_register(struct device *dev, struct exynos_drm_ipp *ipp,
+ const struct exynos_drm_ipp_funcs *funcs, unsigned int caps,
+ const struct exynos_drm_ipp_formats *formats,
+ unsigned int num_formats, const char *name)
+{
+ WARN_ON(!ipp);
+ WARN_ON(!funcs);
+ WARN_ON(!formats);
+ WARN_ON(!num_formats);
+
+ spin_lock_init(&ipp->lock);
+ INIT_LIST_HEAD(&ipp->todo_list);
+ init_waitqueue_head(&ipp->done_wq);
+ ipp->dev = dev;
+ ipp->funcs = funcs;
+ ipp->capabilities = caps;
+ ipp->name = name;
+ ipp->formats = formats;
+ ipp->num_formats = num_formats;
+
+ /* ipp_list modification is serialized by component framework */
+ list_add_tail(&ipp->head, &ipp_list);
+ ipp->id = num_ipp++;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "Registered ipp %d\n", ipp->id);
+
+ return 0;
+}
+
+/**
+ * exynos_drm_ipp_unregister - Unregister the picture processor module
+ * @dev: DRM device
+ * @ipp: ipp module
+ */
+void exynos_drm_ipp_unregister(struct device *dev,
+ struct exynos_drm_ipp *ipp)
+{
+ WARN_ON(ipp->task);
+ WARN_ON(!list_empty(&ipp->todo_list));
+ list_del(&ipp->head);
+}
+
+/**
+ * exynos_drm_ipp_get_res_ioctl - enumerate all ipp modules
+ * @dev: DRM device
+ * @data: ioctl data
+ * @file_priv: DRM file info
+ *
+ * Construct a list of ipp ids.
+ *
+ * Called by the user via ioctl.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int exynos_drm_ipp_get_res_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_ioctl_ipp_get_res *resp = data;
+ struct exynos_drm_ipp *ipp;
+ uint32_t __user *ipp_ptr = (uint32_t __user *)
+ (unsigned long)resp->ipp_id_ptr;
+ unsigned int count = num_ipp, copied = 0;
+
+ /*
+ * This ioctl is called twice, once to determine how much space is
+ * needed, and the 2nd time to fill it.
+ */
+ if (count && resp->count_ipps >= count) {
+ list_for_each_entry(ipp, &ipp_list, head) {
+ if (put_user(ipp->id, ipp_ptr + copied))
+ return -EFAULT;
+ copied++;
+ }
+ }
+ resp->count_ipps = count;
+
+ return 0;
+}
+
+static inline struct exynos_drm_ipp *__ipp_get(uint32_t id)
+{
+ struct exynos_drm_ipp *ipp;
+
+ list_for_each_entry(ipp, &ipp_list, head)
+ if (ipp->id == id)
+ return ipp;
+ return NULL;
+}
+
+/**
+ * exynos_drm_ipp_get_caps_ioctl - get ipp module capabilities and formats
+ * @dev: DRM device
+ * @data: ioctl data
+ * @file_priv: DRM file info
+ *
+ * Construct a structure describing ipp module capabilities.
+ *
+ * Called by the user via ioctl.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int exynos_drm_ipp_get_caps_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_ioctl_ipp_get_caps *resp = data;
+ void __user *ptr = (void __user *)(unsigned long)resp->formats_ptr;
+ struct exynos_drm_ipp *ipp;
+ int i;
+
+ ipp = __ipp_get(resp->ipp_id);
+ if (!ipp)
+ return -ENOENT;
+
+ resp->ipp_id = ipp->id;
+ resp->capabilities = ipp->capabilities;
+
+ /*
+ * This ioctl is called twice, once to determine how much space is
+ * needed, and the 2nd time to fill it.
+ */
+ if (resp->formats_count >= ipp->num_formats) {
+ for (i = 0; i < ipp->num_formats; i++) {
+ struct drm_exynos_ipp_format tmp = {
+ .fourcc = ipp->formats[i].fourcc,
+ .type = ipp->formats[i].type,
+ .modifier = ipp->formats[i].modifier,
+ };
+
+ if (copy_to_user(ptr, &tmp, sizeof(tmp)))
+ return -EFAULT;
+ ptr += sizeof(tmp);
+ }
+ }
+ resp->formats_count = ipp->num_formats;
+
+ return 0;
+}
+
+static inline const struct exynos_drm_ipp_formats *__ipp_format_get(
+ struct exynos_drm_ipp *ipp, uint32_t fourcc,
+ uint64_t mod, unsigned int type)
+{
+ int i;
+
+ for (i = 0; i < ipp->num_formats; i++) {
+ if ((ipp->formats[i].type & type) &&
+ ipp->formats[i].fourcc == fourcc &&
+ ipp->formats[i].modifier == mod)
+ return &ipp->formats[i];
+ }
+ return NULL;
+}
+
+/**
+ * exynos_drm_ipp_get_limits_ioctl - get ipp module limits
+ * @dev: DRM device
+ * @data: ioctl data
+ * @file_priv: DRM file info
+ *
+ * Construct a structure describing ipp module limitations for provided
+ * picture format.
+ *
+ * Called by the user via ioctl.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int exynos_drm_ipp_get_limits_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_ioctl_ipp_get_limits *resp = data;
+ void __user *ptr = (void __user *)(unsigned long)resp->limits_ptr;
+ const struct exynos_drm_ipp_formats *format;
+ struct exynos_drm_ipp *ipp;
+
+ if (resp->type != DRM_EXYNOS_IPP_FORMAT_SOURCE &&
+ resp->type != DRM_EXYNOS_IPP_FORMAT_DESTINATION)
+ return -EINVAL;
+
+ ipp = __ipp_get(resp->ipp_id);
+ if (!ipp)
+ return -ENOENT;
+
+ format = __ipp_format_get(ipp, resp->fourcc, resp->modifier,
+ resp->type);
+ if (!format)
+ return -EINVAL;
+
+ /*
+ * This ioctl is called twice, once to determine how much space is
+ * needed, and the 2nd time to fill it.
+ */
+ if (format->num_limits && resp->limits_count >= format->num_limits)
+ if (copy_to_user((void __user *)ptr, format->limits,
+ sizeof(*format->limits) * format->num_limits))
+ return -EFAULT;
+ resp->limits_count = format->num_limits;
+
+ return 0;
+}
+
+struct drm_pending_exynos_ipp_event {
+ struct drm_pending_event base;
+ struct drm_exynos_ipp_event event;
+};
+
+static inline struct exynos_drm_ipp_task *
+ exynos_drm_ipp_task_alloc(struct exynos_drm_ipp *ipp)
+{
+ struct exynos_drm_ipp_task *task;
+
+ task = kzalloc_obj(*task);
+ if (!task)
+ return NULL;
+
+ task->dev = ipp->dev;
+ task->ipp = ipp;
+
+ /* some defaults */
+ task->src.rect.w = task->dst.rect.w = UINT_MAX;
+ task->src.rect.h = task->dst.rect.h = UINT_MAX;
+ task->transform.rotation = DRM_MODE_ROTATE_0;
+
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Allocated task %p\n", task);
+
+ return task;
+}
+
+static const struct exynos_drm_param_map {
+ unsigned int id;
+ unsigned int size;
+ unsigned int offset;
+} exynos_drm_ipp_params_maps[] = {
+ {
+ DRM_EXYNOS_IPP_TASK_BUFFER | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE,
+ sizeof(struct drm_exynos_ipp_task_buffer),
+ offsetof(struct exynos_drm_ipp_task, src.buf),
+ }, {
+ DRM_EXYNOS_IPP_TASK_BUFFER |
+ DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION,
+ sizeof(struct drm_exynos_ipp_task_buffer),
+ offsetof(struct exynos_drm_ipp_task, dst.buf),
+ }, {
+ DRM_EXYNOS_IPP_TASK_RECTANGLE | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE,
+ sizeof(struct drm_exynos_ipp_task_rect),
+ offsetof(struct exynos_drm_ipp_task, src.rect),
+ }, {
+ DRM_EXYNOS_IPP_TASK_RECTANGLE |
+ DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION,
+ sizeof(struct drm_exynos_ipp_task_rect),
+ offsetof(struct exynos_drm_ipp_task, dst.rect),
+ }, {
+ DRM_EXYNOS_IPP_TASK_TRANSFORM,
+ sizeof(struct drm_exynos_ipp_task_transform),
+ offsetof(struct exynos_drm_ipp_task, transform),
+ }, {
+ DRM_EXYNOS_IPP_TASK_ALPHA,
+ sizeof(struct drm_exynos_ipp_task_alpha),
+ offsetof(struct exynos_drm_ipp_task, alpha),
+ },
+};
+
+static int exynos_drm_ipp_task_set(struct exynos_drm_ipp_task *task,
+ struct drm_exynos_ioctl_ipp_commit *arg)
+{
+ const struct exynos_drm_param_map *map = exynos_drm_ipp_params_maps;
+ void __user *params = (void __user *)(unsigned long)arg->params_ptr;
+ unsigned int size = arg->params_size;
+ uint32_t id;
+ int i;
+
+ while (size) {
+ if (get_user(id, (uint32_t __user *)params))
+ return -EFAULT;
+
+ for (i = 0; i < ARRAY_SIZE(exynos_drm_ipp_params_maps); i++)
+ if (map[i].id == id)
+ break;
+ if (i == ARRAY_SIZE(exynos_drm_ipp_params_maps) ||
+ map[i].size > size)
+ return -EINVAL;
+
+ if (copy_from_user((void *)task + map[i].offset, params,
+ map[i].size))
+ return -EFAULT;
+
+ params += map[i].size;
+ size -= map[i].size;
+ }
+
+ DRM_DEV_DEBUG_DRIVER(task->dev,
+ "Got task %p configuration from userspace\n",
+ task);
+ return 0;
+}
+
+static int exynos_drm_ipp_task_setup_buffer(struct exynos_drm_ipp_buffer *buf,
+ struct drm_file *filp)
+{
+ int ret = 0;
+ int i;
+
+ /* get GEM buffers and check their size */
+ for (i = 0; i < buf->format->num_planes; i++) {
+ unsigned int height = (i == 0) ? buf->buf.height :
+ DIV_ROUND_UP(buf->buf.height, buf->format->vsub);
+ unsigned long size = height * buf->buf.pitch[i];
+ struct exynos_drm_gem *gem = exynos_drm_gem_get(filp,
+ buf->buf.gem_id[i]);
+ if (!gem) {
+ ret = -ENOENT;
+ goto gem_free;
+ }
+ buf->exynos_gem[i] = gem;
+
+ if (size + buf->buf.offset[i] > buf->exynos_gem[i]->base.size) {
+ i++;
+ ret = -EINVAL;
+ goto gem_free;
+ }
+ buf->dma_addr[i] = buf->exynos_gem[i]->dma_addr +
+ buf->buf.offset[i];
+ }
+
+ return 0;
+gem_free:
+ while (i--) {
+ exynos_drm_gem_put(buf->exynos_gem[i]);
+ buf->exynos_gem[i] = NULL;
+ }
+ return ret;
+}
+
+static void exynos_drm_ipp_task_release_buf(struct exynos_drm_ipp_buffer *buf)
+{
+ int i;
+
+ if (!buf->exynos_gem[0])
+ return;
+ for (i = 0; i < buf->format->num_planes; i++)
+ exynos_drm_gem_put(buf->exynos_gem[i]);
+}
+
+static void exynos_drm_ipp_task_free(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Freeing task %p\n", task);
+
+ exynos_drm_ipp_task_release_buf(&task->src);
+ exynos_drm_ipp_task_release_buf(&task->dst);
+ if (task->event)
+ drm_event_cancel_free(ipp->drm_dev, &task->event->base);
+ kfree(task);
+}
+
+struct drm_ipp_limit {
+ struct drm_exynos_ipp_limit_val h;
+ struct drm_exynos_ipp_limit_val v;
+};
+
+enum drm_ipp_size_id {
+ IPP_LIMIT_BUFFER, IPP_LIMIT_AREA, IPP_LIMIT_ROTATED, IPP_LIMIT_MAX
+};
+
+static const enum drm_exynos_ipp_limit_type limit_id_fallback[IPP_LIMIT_MAX][4] = {
+ [IPP_LIMIT_BUFFER] = { DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
+ [IPP_LIMIT_AREA] = { DRM_EXYNOS_IPP_LIMIT_SIZE_AREA,
+ DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
+ [IPP_LIMIT_ROTATED] = { DRM_EXYNOS_IPP_LIMIT_SIZE_ROTATED,
+ DRM_EXYNOS_IPP_LIMIT_SIZE_AREA,
+ DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
+};
+
+static inline void __limit_set_val(unsigned int *ptr, unsigned int val)
+{
+ if (!*ptr)
+ *ptr = val;
+}
+
+static void __get_size_limit(const struct drm_exynos_ipp_limit *limits,
+ unsigned int num_limits, enum drm_ipp_size_id id,
+ struct drm_ipp_limit *res)
+{
+ const struct drm_exynos_ipp_limit *l = limits;
+ int i = 0;
+
+ memset(res, 0, sizeof(*res));
+ for (i = 0; limit_id_fallback[id][i]; i++)
+ for (l = limits; l - limits < num_limits; l++) {
+ if (((l->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) !=
+ DRM_EXYNOS_IPP_LIMIT_TYPE_SIZE) ||
+ ((l->type & DRM_EXYNOS_IPP_LIMIT_SIZE_MASK) !=
+ limit_id_fallback[id][i]))
+ continue;
+ __limit_set_val(&res->h.min, l->h.min);
+ __limit_set_val(&res->h.max, l->h.max);
+ __limit_set_val(&res->h.align, l->h.align);
+ __limit_set_val(&res->v.min, l->v.min);
+ __limit_set_val(&res->v.max, l->v.max);
+ __limit_set_val(&res->v.align, l->v.align);
+ }
+}
+
+static inline bool __align_check(unsigned int val, unsigned int align)
+{
+ if (align && (val & (align - 1))) {
+ DRM_DEBUG_DRIVER("Value %d exceeds HW limits (align %d)\n",
+ val, align);
+ return false;
+ }
+ return true;
+}
+
+static inline bool __size_limit_check(unsigned int val,
+ struct drm_exynos_ipp_limit_val *l)
+{
+ if ((l->min && val < l->min) || (l->max && val > l->max)) {
+ DRM_DEBUG_DRIVER("Value %d exceeds HW limits (min %d, max %d)\n",
+ val, l->min, l->max);
+ return false;
+ }
+ return __align_check(val, l->align);
+}
+
+static int exynos_drm_ipp_check_size_limits(struct exynos_drm_ipp_buffer *buf,
+ const struct drm_exynos_ipp_limit *limits, unsigned int num_limits,
+ bool rotate, bool swap)
+{
+ enum drm_ipp_size_id id = rotate ? IPP_LIMIT_ROTATED : IPP_LIMIT_AREA;
+ struct drm_ipp_limit l;
+ struct drm_exynos_ipp_limit_val *lh = &l.h, *lv = &l.v;
+ int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
+
+ if (!limits)
+ return 0;
+
+ __get_size_limit(limits, num_limits, IPP_LIMIT_BUFFER, &l);
+ if (!__size_limit_check(real_width, &l.h) ||
+ !__size_limit_check(buf->buf.height, &l.v))
+ return -EINVAL;
+
+ if (swap) {
+ lv = &l.h;
+ lh = &l.v;
+ }
+ __get_size_limit(limits, num_limits, id, &l);
+ if (!__size_limit_check(buf->rect.w, lh) ||
+ !__align_check(buf->rect.x, lh->align) ||
+ !__size_limit_check(buf->rect.h, lv) ||
+ !__align_check(buf->rect.y, lv->align))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline bool __scale_limit_check(unsigned int src, unsigned int dst,
+ unsigned int min, unsigned int max)
+{
+ if ((max && (dst << 16) > src * max) ||
+ (min && (dst << 16) < src * min)) {
+ DRM_DEBUG_DRIVER("Scale from %d to %d exceeds HW limits (ratio min %d.%05d, max %d.%05d)\n",
+ src, dst,
+ min >> 16, 100000 * (min & 0xffff) / (1 << 16),
+ max >> 16, 100000 * (max & 0xffff) / (1 << 16));
+ return false;
+ }
+ return true;
+}
+
+static int exynos_drm_ipp_check_scale_limits(
+ struct drm_exynos_ipp_task_rect *src,
+ struct drm_exynos_ipp_task_rect *dst,
+ const struct drm_exynos_ipp_limit *limits,
+ unsigned int num_limits, bool swap)
+{
+ const struct drm_exynos_ipp_limit_val *lh, *lv;
+ int dw, dh;
+
+ for (; num_limits; limits++, num_limits--)
+ if ((limits->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) ==
+ DRM_EXYNOS_IPP_LIMIT_TYPE_SCALE)
+ break;
+ if (!num_limits)
+ return 0;
+
+ lh = (!swap) ? &limits->h : &limits->v;
+ lv = (!swap) ? &limits->v : &limits->h;
+ dw = (!swap) ? dst->w : dst->h;
+ dh = (!swap) ? dst->h : dst->w;
+
+ if (!__scale_limit_check(src->w, dw, lh->min, lh->max) ||
+ !__scale_limit_check(src->h, dh, lv->min, lv->max))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int exynos_drm_ipp_check_format(struct exynos_drm_ipp_task *task,
+ struct exynos_drm_ipp_buffer *buf,
+ struct exynos_drm_ipp_buffer *src,
+ struct exynos_drm_ipp_buffer *dst,
+ bool rotate, bool swap)
+{
+ const struct exynos_drm_ipp_formats *fmt;
+ int ret, i;
+
+ fmt = __ipp_format_get(task->ipp, buf->buf.fourcc, buf->buf.modifier,
+ buf == src ? DRM_EXYNOS_IPP_FORMAT_SOURCE :
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION);
+ if (!fmt) {
+ DRM_DEV_DEBUG_DRIVER(task->dev,
+ "Task %p: %s format not supported\n",
+ task, buf == src ? "src" : "dst");
+ return -EINVAL;
+ }
+
+ /* basic checks */
+ if (buf->buf.width == 0 || buf->buf.height == 0)
+ return -EINVAL;
+
+ buf->format = drm_format_info(buf->buf.fourcc);
+ for (i = 0; i < buf->format->num_planes; i++) {
+ unsigned int width = (i == 0) ? buf->buf.width :
+ DIV_ROUND_UP(buf->buf.width, buf->format->hsub);
+
+ if (buf->buf.pitch[i] == 0)
+ buf->buf.pitch[i] = width * buf->format->cpp[i];
+ if (buf->buf.pitch[i] < width * buf->format->cpp[i])
+ return -EINVAL;
+ if (!buf->buf.gem_id[i])
+ return -ENOENT;
+ }
+
+ /* pitch for additional planes must match */
+ if (buf->format->num_planes > 2 &&
+ buf->buf.pitch[1] != buf->buf.pitch[2])
+ return -EINVAL;
+
+ /* check driver limits */
+ ret = exynos_drm_ipp_check_size_limits(buf, fmt->limits,
+ fmt->num_limits,
+ rotate,
+ buf == dst ? swap : false);
+ if (ret)
+ return ret;
+ ret = exynos_drm_ipp_check_scale_limits(&src->rect, &dst->rect,
+ fmt->limits,
+ fmt->num_limits, swap);
+ return ret;
+}
+
+static int exynos_drm_ipp_task_check(struct exynos_drm_ipp_task *task)
+{
+ struct exynos_drm_ipp *ipp = task->ipp;
+ struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst;
+ unsigned int rotation = task->transform.rotation;
+ int ret = 0;
+ bool swap = drm_rotation_90_or_270(rotation);
+ bool rotate = (rotation != DRM_MODE_ROTATE_0);
+ bool scale = false;
+
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Checking task %p\n", task);
+
+ if (src->rect.w == UINT_MAX)
+ src->rect.w = src->buf.width;
+ if (src->rect.h == UINT_MAX)
+ src->rect.h = src->buf.height;
+ if (dst->rect.w == UINT_MAX)
+ dst->rect.w = dst->buf.width;
+ if (dst->rect.h == UINT_MAX)
+ dst->rect.h = dst->buf.height;
+
+ if (src->rect.x + src->rect.w > (src->buf.width) ||
+ src->rect.y + src->rect.h > (src->buf.height) ||
+ dst->rect.x + dst->rect.w > (dst->buf.width) ||
+ dst->rect.y + dst->rect.h > (dst->buf.height)) {
+ DRM_DEV_DEBUG_DRIVER(task->dev,
+ "Task %p: defined area is outside provided buffers\n",
+ task);
+ return -EINVAL;
+ }
+
+ if ((!swap && (src->rect.w != dst->rect.w ||
+ src->rect.h != dst->rect.h)) ||
+ (swap && (src->rect.w != dst->rect.h ||
+ src->rect.h != dst->rect.w)))
+ scale = true;
+
+ if ((!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CROP) &&
+ (src->rect.x || src->rect.y || dst->rect.x || dst->rect.y)) ||
+ (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_ROTATE) && rotate) ||
+ (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_SCALE) && scale) ||
+ (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CONVERT) &&
+ src->buf.fourcc != dst->buf.fourcc)) {
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Task %p: hw capabilities exceeded\n",
+ task);
+ return -EINVAL;
+ }
+
+ ret = exynos_drm_ipp_check_format(task, src, src, dst, rotate, swap);
+ if (ret)
+ return ret;
+
+ ret = exynos_drm_ipp_check_format(task, dst, src, dst, false, swap);
+ if (ret)
+ return ret;
+
+ DRM_DEV_DEBUG_DRIVER(ipp->dev, "Task %p: all checks done.\n",
+ task);
+
+ return ret;
+}
+
+static int exynos_drm_ipp_task_setup_buffers(struct exynos_drm_ipp_task *task,
+ struct drm_file *filp)
+{
+ struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst;
+ int ret = 0;
+
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Setting buffer for task %p\n",
+ task);
+
+ ret = exynos_drm_ipp_task_setup_buffer(src, filp);
+ if (ret) {
+ DRM_DEV_DEBUG_DRIVER(task->dev,
+ "Task %p: src buffer setup failed\n",
+ task);
+ return ret;
+ }
+ ret = exynos_drm_ipp_task_setup_buffer(dst, filp);
+ if (ret) {
+ DRM_DEV_DEBUG_DRIVER(task->dev,
+ "Task %p: dst buffer setup failed\n",
+ task);
+ return ret;
+ }
+
+ DRM_DEV_DEBUG_DRIVER(task->dev, "Task %p: buffers prepared.\n",
+ task);
+
+ return ret;
+}
+
+
+static int exynos_drm_ipp_event_create(struct exynos_drm_ipp_task *task,
+ struct drm_file *file_priv, uint64_t user_data)
+{
+ struct drm_pending_exynos_ipp_event *e = NULL;
+ int ret;
+
+ e = kzalloc_obj(*e);
+ if (!e)
+ return -ENOMEM;
+
+ e->event.base.type = DRM_EXYNOS_IPP_EVENT;
+ e->event.base.length = sizeof(e->event);
+ e->event.user_data = user_data;
+
+ ret = drm_event_reserve_init(task->ipp->drm_dev, file_priv, &e->base,
+ &e->event.base);
+ if (ret)
+ goto free;
+
+ task->event = e;
+ return 0;
+free:
+ kfree(e);
+ return ret;
+}
+
+static void exynos_drm_ipp_event_send(struct exynos_drm_ipp_task *task)
+{
+ struct timespec64 now;
+
+ ktime_get_ts64(&now);
+ task->event->event.tv_sec = now.tv_sec;
+ task->event->event.tv_usec = now.tv_nsec / NSEC_PER_USEC;
+ task->event->event.sequence = atomic_inc_return(&task->ipp->sequence);
+
+ drm_send_event(task->ipp->drm_dev, &task->event->base);
+}
+
+static int exynos_drm_ipp_task_cleanup(struct exynos_drm_ipp_task *task)
+{
+ int ret = task->ret;
+
+ if (ret == 0 && task->event) {
+ exynos_drm_ipp_event_send(task);
+ /* ensure event won't be canceled on task free */
+ task->event = NULL;
+ }
+
+ exynos_drm_ipp_task_free(task->ipp, task);
+ return ret;
+}
+
+static void exynos_drm_ipp_cleanup_work(struct work_struct *work)
+{
+ struct exynos_drm_ipp_task *task = container_of(work,
+ struct exynos_drm_ipp_task, cleanup_work);
+
+ exynos_drm_ipp_task_cleanup(task);
+}
+
+static void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp);
+
+/**
+ * exynos_drm_ipp_task_done - finish given task and set return code
+ * @task: ipp task to finish
+ * @ret: error code or 0 if operation has been performed successfully
+ */
+void exynos_drm_ipp_task_done(struct exynos_drm_ipp_task *task, int ret)
+{
+ struct exynos_drm_ipp *ipp = task->ipp;
+ unsigned long flags;
+
+ DRM_DEV_DEBUG_DRIVER(task->dev, "ipp: %d, task %p done: %d\n",
+ ipp->id, task, ret);
+
+ spin_lock_irqsave(&ipp->lock, flags);
+ if (ipp->task == task)
+ ipp->task = NULL;
+ task->flags |= DRM_EXYNOS_IPP_TASK_DONE;
+ task->ret = ret;
+ spin_unlock_irqrestore(&ipp->lock, flags);
+
+ exynos_drm_ipp_next_task(ipp);
+ wake_up(&ipp->done_wq);
+
+ if (task->flags & DRM_EXYNOS_IPP_TASK_ASYNC) {
+ INIT_WORK(&task->cleanup_work, exynos_drm_ipp_cleanup_work);
+ schedule_work(&task->cleanup_work);
+ }
+}
+
+static void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp)
+{
+ struct exynos_drm_ipp_task *task;
+ unsigned long flags;
+ int ret;
+
+ DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, try to run new task\n",
+ ipp->id);
+
+ spin_lock_irqsave(&ipp->lock, flags);
+
+ if (ipp->task || list_empty(&ipp->todo_list)) {
+ spin_unlock_irqrestore(&ipp->lock, flags);
+ return;
+ }
+
+ task = list_first_entry(&ipp->todo_list, struct exynos_drm_ipp_task,
+ head);
+ list_del_init(&task->head);
+ ipp->task = task;
+
+ spin_unlock_irqrestore(&ipp->lock, flags);
+
+ DRM_DEV_DEBUG_DRIVER(ipp->dev,
+ "ipp: %d, selected task %p to run\n", ipp->id,
+ task);
+
+ ret = ipp->funcs->commit(ipp, task);
+ if (ret)
+ exynos_drm_ipp_task_done(task, ret);
+}
+
+static void exynos_drm_ipp_schedule_task(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipp->lock, flags);
+ list_add(&task->head, &ipp->todo_list);
+ spin_unlock_irqrestore(&ipp->lock, flags);
+
+ exynos_drm_ipp_next_task(ipp);
+}
+
+static void exynos_drm_ipp_task_abort(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipp->lock, flags);
+ if (task->flags & DRM_EXYNOS_IPP_TASK_DONE) {
+ /* already completed task */
+ exynos_drm_ipp_task_cleanup(task);
+ } else if (ipp->task != task) {
+ /* task has not been scheduled for execution yet */
+ list_del_init(&task->head);
+ exynos_drm_ipp_task_cleanup(task);
+ } else {
+ /*
+ * currently processed task, call abort() and perform
+ * cleanup with async worker
+ */
+ task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC;
+ spin_unlock_irqrestore(&ipp->lock, flags);
+ if (ipp->funcs->abort)
+ ipp->funcs->abort(ipp, task);
+ return;
+ }
+ spin_unlock_irqrestore(&ipp->lock, flags);
+}
+
+/**
+ * exynos_drm_ipp_commit_ioctl - perform image processing operation
+ * @dev: DRM device
+ * @data: ioctl data
+ * @file_priv: DRM file info
+ *
+ * Construct a ipp task from the set of properties provided from the user
+ * and try to schedule it to framebuffer processor hardware.
+ *
+ * Called by the user via ioctl.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int exynos_drm_ipp_commit_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_exynos_ioctl_ipp_commit *arg = data;
+ struct exynos_drm_ipp *ipp;
+ struct exynos_drm_ipp_task *task;
+ int ret = 0;
+
+ if ((arg->flags & ~DRM_EXYNOS_IPP_FLAGS) || arg->reserved)
+ return -EINVAL;
+
+ /* can't test and expect an event at the same time */
+ if ((arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY) &&
+ (arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT))
+ return -EINVAL;
+
+ ipp = __ipp_get(arg->ipp_id);
+ if (!ipp)
+ return -ENOENT;
+
+ task = exynos_drm_ipp_task_alloc(ipp);
+ if (!task)
+ return -ENOMEM;
+
+ ret = exynos_drm_ipp_task_set(task, arg);
+ if (ret)
+ goto free;
+
+ ret = exynos_drm_ipp_task_check(task);
+ if (ret)
+ goto free;
+
+ ret = exynos_drm_ipp_task_setup_buffers(task, file_priv);
+ if (ret || arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY)
+ goto free;
+
+ if (arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT) {
+ ret = exynos_drm_ipp_event_create(task, file_priv,
+ arg->user_data);
+ if (ret)
+ goto free;
+ }
+
+ /*
+ * Queue task for processing on the hardware. task object will be
+ * then freed after exynos_drm_ipp_task_done()
+ */
+ if (arg->flags & DRM_EXYNOS_IPP_FLAG_NONBLOCK) {
+ DRM_DEV_DEBUG_DRIVER(ipp->dev,
+ "ipp: %d, nonblocking processing task %p\n",
+ ipp->id, task);
+
+ task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC;
+ exynos_drm_ipp_schedule_task(task->ipp, task);
+ ret = 0;
+ } else {
+ DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, processing task %p\n",
+ ipp->id, task);
+ exynos_drm_ipp_schedule_task(ipp, task);
+ ret = wait_event_interruptible(ipp->done_wq,
+ task->flags & DRM_EXYNOS_IPP_TASK_DONE);
+ if (ret)
+ exynos_drm_ipp_task_abort(ipp, task);
+ else
+ ret = exynos_drm_ipp_task_cleanup(task);
+ }
+ return ret;
+free:
+ exynos_drm_ipp_task_free(ipp, task);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.h b/drivers/gpu/drm/exynos/exynos_drm_ipp.h
new file mode 100644
index 000000000..2842c8af0
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.h
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef _EXYNOS_DRM_IPP_H_
+#define _EXYNOS_DRM_IPP_H_
+
+struct exynos_drm_ipp;
+struct exynos_drm_ipp_task;
+
+/**
+ * struct exynos_drm_ipp_funcs - exynos_drm_ipp control functions
+ */
+struct exynos_drm_ipp_funcs {
+ /**
+ * @commit:
+ *
+ * This is the main entry point to start framebuffer processing
+ * in the hardware. The exynos_drm_ipp_task has been already validated.
+ * This function must not wait until the device finishes processing.
+ * When the driver finishes processing, it has to call
+ * exynos_exynos_drm_ipp_task_done() function.
+ *
+ * RETURNS:
+ *
+ * 0 on success or negative error codes in case of failure.
+ */
+ int (*commit)(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task);
+
+ /**
+ * @abort:
+ *
+ * Informs the driver that it has to abort the currently running
+ * task as soon as possible (i.e. as soon as it can stop the device
+ * safely), even if the task would not have been finished by then.
+ * After the driver performs the necessary steps, it has to call
+ * exynos_drm_ipp_task_done() (as if the task ended normally).
+ * This function does not have to (and will usually not) wait
+ * until the device enters a state when it can be stopped.
+ */
+ void (*abort)(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task);
+};
+
+/**
+ * struct exynos_drm_ipp - central picture processor module structure
+ */
+struct exynos_drm_ipp {
+ struct drm_device *drm_dev;
+ struct device *dev;
+ struct list_head head;
+ unsigned int id;
+
+ const char *name;
+ const struct exynos_drm_ipp_funcs *funcs;
+ unsigned int capabilities;
+ const struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+ atomic_t sequence;
+
+ spinlock_t lock;
+ struct exynos_drm_ipp_task *task;
+ struct list_head todo_list;
+ wait_queue_head_t done_wq;
+};
+
+struct exynos_drm_ipp_buffer {
+ struct drm_exynos_ipp_task_buffer buf;
+ struct drm_exynos_ipp_task_rect rect;
+
+ struct exynos_drm_gem *exynos_gem[DRM_FORMAT_MAX_PLANES];
+ const struct drm_format_info *format;
+ dma_addr_t dma_addr[DRM_FORMAT_MAX_PLANES];
+};
+
+/**
+ * struct exynos_drm_ipp_task - a structure describing transformation that
+ * has to be performed by the picture processor hardware module
+ */
+struct exynos_drm_ipp_task {
+ struct device *dev;
+ struct exynos_drm_ipp *ipp;
+ struct list_head head;
+
+ struct exynos_drm_ipp_buffer src;
+ struct exynos_drm_ipp_buffer dst;
+
+ struct drm_exynos_ipp_task_transform transform;
+ struct drm_exynos_ipp_task_alpha alpha;
+
+ struct work_struct cleanup_work;
+ unsigned int flags;
+ int ret;
+
+ struct drm_pending_exynos_ipp_event *event;
+};
+
+#define DRM_EXYNOS_IPP_TASK_DONE (1 << 0)
+#define DRM_EXYNOS_IPP_TASK_ASYNC (1 << 1)
+
+struct exynos_drm_ipp_formats {
+ uint32_t fourcc;
+ uint32_t type;
+ uint64_t modifier;
+ const struct drm_exynos_ipp_limit *limits;
+ unsigned int num_limits;
+};
+
+/* helper macros to set exynos_drm_ipp_formats structure and limits*/
+#define IPP_SRCDST_MFORMAT(f, m, l) \
+ .fourcc = DRM_FORMAT_##f, .modifier = m, .limits = l, \
+ .num_limits = ARRAY_SIZE(l), \
+ .type = (DRM_EXYNOS_IPP_FORMAT_SOURCE | \
+ DRM_EXYNOS_IPP_FORMAT_DESTINATION)
+
+#define IPP_SRCDST_FORMAT(f, l) IPP_SRCDST_MFORMAT(f, 0, l)
+
+#define IPP_SIZE_LIMIT(l, val...) \
+ .type = (DRM_EXYNOS_IPP_LIMIT_TYPE_SIZE | \
+ DRM_EXYNOS_IPP_LIMIT_SIZE_##l), val
+
+#define IPP_SCALE_LIMIT(val...) \
+ .type = (DRM_EXYNOS_IPP_LIMIT_TYPE_SCALE), val
+
+int exynos_drm_ipp_register(struct device *dev, struct exynos_drm_ipp *ipp,
+ const struct exynos_drm_ipp_funcs *funcs, unsigned int caps,
+ const struct exynos_drm_ipp_formats *formats,
+ unsigned int num_formats, const char *name);
+void exynos_drm_ipp_unregister(struct device *dev,
+ struct exynos_drm_ipp *ipp);
+
+void exynos_drm_ipp_task_done(struct exynos_drm_ipp_task *task, int ret);
+
+#ifdef CONFIG_DRM_EXYNOS_IPP
+int exynos_drm_ipp_get_res_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int exynos_drm_ipp_get_caps_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int exynos_drm_ipp_get_limits_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int exynos_drm_ipp_commit_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv);
+#else
+static inline int exynos_drm_ipp_get_res_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+{
+ struct drm_exynos_ioctl_ipp_get_res *resp = data;
+
+ resp->count_ipps = 0;
+ return 0;
+}
+static inline int exynos_drm_ipp_get_caps_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+static inline int exynos_drm_ipp_get_limits_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+static inline int exynos_drm_ipp_commit_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+{
+ return -ENODEV;
+}
+#endif
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_mic.c b/drivers/gpu/drm/exynos/exynos_drm_mic.c
new file mode 100644
index 000000000..3069f9581
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_mic.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ * Authors:
+ * Hyungwon Hwang <human.hwang@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_print.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_crtc.h"
+
+/* Sysreg registers for MIC */
+#define DSD_CFG_MUX 0x1004
+#define MIC0_RGB_MUX (1 << 0)
+#define MIC0_I80_MUX (1 << 1)
+#define MIC0_ON_MUX (1 << 5)
+
+/* MIC registers */
+#define MIC_OP 0x0
+#define MIC_IP_VER 0x0004
+#define MIC_V_TIMING_0 0x0008
+#define MIC_V_TIMING_1 0x000C
+#define MIC_IMG_SIZE 0x0010
+#define MIC_INPUT_TIMING_0 0x0014
+#define MIC_INPUT_TIMING_1 0x0018
+#define MIC_2D_OUTPUT_TIMING_0 0x001C
+#define MIC_2D_OUTPUT_TIMING_1 0x0020
+#define MIC_2D_OUTPUT_TIMING_2 0x0024
+#define MIC_3D_OUTPUT_TIMING_0 0x0028
+#define MIC_3D_OUTPUT_TIMING_1 0x002C
+#define MIC_3D_OUTPUT_TIMING_2 0x0030
+#define MIC_CORE_PARA_0 0x0034
+#define MIC_CORE_PARA_1 0x0038
+#define MIC_CTC_CTRL 0x0040
+#define MIC_RD_DATA 0x0044
+
+#define MIC_UPD_REG (1 << 31)
+#define MIC_ON_REG (1 << 30)
+#define MIC_TD_ON_REG (1 << 29)
+#define MIC_BS_CHG_OUT (1 << 16)
+#define MIC_VIDEO_TYPE(x) (((x) & 0xf) << 12)
+#define MIC_PSR_EN (1 << 5)
+#define MIC_SW_RST (1 << 4)
+#define MIC_ALL_RST (1 << 3)
+#define MIC_CORE_VER_CONTROL (1 << 2)
+#define MIC_MODE_SEL_COMMAND_MODE (1 << 1)
+#define MIC_MODE_SEL_MASK (1 << 1)
+#define MIC_CORE_EN (1 << 0)
+
+#define MIC_V_PULSE_WIDTH(x) (((x) & 0x3fff) << 16)
+#define MIC_V_PERIOD_LINE(x) ((x) & 0x3fff)
+
+#define MIC_VBP_SIZE(x) (((x) & 0x3fff) << 16)
+#define MIC_VFP_SIZE(x) ((x) & 0x3fff)
+
+#define MIC_IMG_V_SIZE(x) (((x) & 0x3fff) << 16)
+#define MIC_IMG_H_SIZE(x) ((x) & 0x3fff)
+
+#define MIC_H_PULSE_WIDTH_IN(x) (((x) & 0x3fff) << 16)
+#define MIC_H_PERIOD_PIXEL_IN(x) ((x) & 0x3fff)
+
+#define MIC_HBP_SIZE_IN(x) (((x) & 0x3fff) << 16)
+#define MIC_HFP_SIZE_IN(x) ((x) & 0x3fff)
+
+#define MIC_H_PULSE_WIDTH_2D(x) (((x) & 0x3fff) << 16)
+#define MIC_H_PERIOD_PIXEL_2D(x) ((x) & 0x3fff)
+
+#define MIC_HBP_SIZE_2D(x) (((x) & 0x3fff) << 16)
+#define MIC_HFP_SIZE_2D(x) ((x) & 0x3fff)
+
+#define MIC_BS_SIZE_2D(x) ((x) & 0x3fff)
+
+static const char *const clk_names[] = { "pclk_mic0", "sclk_rgb_vclk_to_mic0" };
+#define NUM_CLKS ARRAY_SIZE(clk_names)
+static DEFINE_MUTEX(mic_mutex);
+
+struct exynos_mic {
+ struct device *dev;
+ void __iomem *reg;
+ struct regmap *sysreg;
+ struct clk *clks[NUM_CLKS];
+
+ bool i80_mode;
+ struct videomode vm;
+ struct drm_bridge bridge;
+
+ bool enabled;
+};
+
+static void mic_set_path(struct exynos_mic *mic, bool enable)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(mic->sysreg, DSD_CFG_MUX, &val);
+ if (ret) {
+ DRM_DEV_ERROR(mic->dev,
+ "mic: Failed to read system register\n");
+ return;
+ }
+
+ if (enable) {
+ if (mic->i80_mode)
+ val |= MIC0_I80_MUX;
+ else
+ val |= MIC0_RGB_MUX;
+
+ val |= MIC0_ON_MUX;
+ } else
+ val &= ~(MIC0_RGB_MUX | MIC0_I80_MUX | MIC0_ON_MUX);
+
+ ret = regmap_write(mic->sysreg, DSD_CFG_MUX, val);
+ if (ret)
+ DRM_DEV_ERROR(mic->dev,
+ "mic: Failed to read system register\n");
+}
+
+static int mic_sw_reset(struct exynos_mic *mic)
+{
+ unsigned int retry = 100;
+ int ret;
+
+ writel(MIC_SW_RST, mic->reg + MIC_OP);
+
+ while (retry-- > 0) {
+ ret = readl(mic->reg + MIC_OP);
+ if (!(ret & MIC_SW_RST))
+ return 0;
+
+ udelay(10);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static void mic_set_porch_timing(struct exynos_mic *mic)
+{
+ struct videomode vm = mic->vm;
+ u32 reg;
+
+ reg = MIC_V_PULSE_WIDTH(vm.vsync_len) +
+ MIC_V_PERIOD_LINE(vm.vsync_len + vm.vactive +
+ vm.vback_porch + vm.vfront_porch);
+ writel(reg, mic->reg + MIC_V_TIMING_0);
+
+ reg = MIC_VBP_SIZE(vm.vback_porch) +
+ MIC_VFP_SIZE(vm.vfront_porch);
+ writel(reg, mic->reg + MIC_V_TIMING_1);
+
+ reg = MIC_V_PULSE_WIDTH(vm.hsync_len) +
+ MIC_V_PERIOD_LINE(vm.hsync_len + vm.hactive +
+ vm.hback_porch + vm.hfront_porch);
+ writel(reg, mic->reg + MIC_INPUT_TIMING_0);
+
+ reg = MIC_VBP_SIZE(vm.hback_porch) +
+ MIC_VFP_SIZE(vm.hfront_porch);
+ writel(reg, mic->reg + MIC_INPUT_TIMING_1);
+}
+
+static void mic_set_img_size(struct exynos_mic *mic)
+{
+ struct videomode *vm = &mic->vm;
+ u32 reg;
+
+ reg = MIC_IMG_H_SIZE(vm->hactive) +
+ MIC_IMG_V_SIZE(vm->vactive);
+
+ writel(reg, mic->reg + MIC_IMG_SIZE);
+}
+
+static void mic_set_output_timing(struct exynos_mic *mic)
+{
+ struct videomode vm = mic->vm;
+ u32 reg, bs_size_2d;
+
+ DRM_DEV_DEBUG(mic->dev, "w: %u, h: %u\n", vm.hactive, vm.vactive);
+ bs_size_2d = ((vm.hactive >> 2) << 1) + (vm.vactive % 4);
+ reg = MIC_BS_SIZE_2D(bs_size_2d);
+ writel(reg, mic->reg + MIC_2D_OUTPUT_TIMING_2);
+
+ if (!mic->i80_mode) {
+ reg = MIC_H_PULSE_WIDTH_2D(vm.hsync_len) +
+ MIC_H_PERIOD_PIXEL_2D(vm.hsync_len + bs_size_2d +
+ vm.hback_porch + vm.hfront_porch);
+ writel(reg, mic->reg + MIC_2D_OUTPUT_TIMING_0);
+
+ reg = MIC_HBP_SIZE_2D(vm.hback_porch) +
+ MIC_H_PERIOD_PIXEL_2D(vm.hfront_porch);
+ writel(reg, mic->reg + MIC_2D_OUTPUT_TIMING_1);
+ }
+}
+
+static void mic_set_reg_on(struct exynos_mic *mic, bool enable)
+{
+ u32 reg = readl(mic->reg + MIC_OP);
+
+ if (enable) {
+ reg &= ~(MIC_MODE_SEL_MASK | MIC_CORE_VER_CONTROL | MIC_PSR_EN);
+ reg |= (MIC_CORE_EN | MIC_BS_CHG_OUT | MIC_ON_REG);
+
+ reg &= ~MIC_MODE_SEL_COMMAND_MODE;
+ if (mic->i80_mode)
+ reg |= MIC_MODE_SEL_COMMAND_MODE;
+ } else {
+ reg &= ~MIC_CORE_EN;
+ }
+
+ reg |= MIC_UPD_REG;
+ writel(reg, mic->reg + MIC_OP);
+}
+
+static void mic_post_disable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *commit)
+{
+ struct exynos_mic *mic = bridge->driver_private;
+
+ mutex_lock(&mic_mutex);
+ if (!mic->enabled)
+ goto already_disabled;
+
+ mic_set_path(mic, 0);
+
+ pm_runtime_put(mic->dev);
+ mic->enabled = 0;
+
+already_disabled:
+ mutex_unlock(&mic_mutex);
+}
+
+static void mic_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct exynos_mic *mic = bridge->driver_private;
+
+ mutex_lock(&mic_mutex);
+ drm_display_mode_to_videomode(mode, &mic->vm);
+ mic->i80_mode = to_exynos_crtc(bridge->encoder->crtc)->i80_mode;
+ mutex_unlock(&mic_mutex);
+}
+
+static void mic_pre_enable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *commit)
+{
+ struct exynos_mic *mic = bridge->driver_private;
+ int ret;
+
+ mutex_lock(&mic_mutex);
+ if (mic->enabled)
+ goto unlock;
+
+ ret = pm_runtime_resume_and_get(mic->dev);
+ if (ret < 0)
+ goto unlock;
+
+ mic_set_path(mic, 1);
+
+ ret = mic_sw_reset(mic);
+ if (ret) {
+ DRM_DEV_ERROR(mic->dev, "Failed to reset\n");
+ goto turn_off;
+ }
+
+ if (!mic->i80_mode)
+ mic_set_porch_timing(mic);
+ mic_set_img_size(mic);
+ mic_set_output_timing(mic);
+ mic_set_reg_on(mic, 1);
+ mic->enabled = 1;
+ mutex_unlock(&mic_mutex);
+
+ return;
+
+turn_off:
+ pm_runtime_put(mic->dev);
+unlock:
+ mutex_unlock(&mic_mutex);
+}
+
+static const struct drm_bridge_funcs mic_bridge_funcs = {
+ .atomic_create_state = drm_atomic_helper_bridge_create_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_post_disable = mic_post_disable,
+ .mode_set = mic_mode_set,
+ .atomic_pre_enable = mic_pre_enable,
+};
+
+static int exynos_mic_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct exynos_mic *mic = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_crtc *crtc = exynos_drm_crtc_get_by_type(drm_dev,
+ EXYNOS_DISPLAY_TYPE_LCD);
+ struct drm_encoder *e, *encoder = NULL;
+
+ drm_for_each_encoder(e, drm_dev)
+ if (e->possible_crtcs == drm_crtc_mask(&crtc->base))
+ encoder = e;
+ if (!encoder)
+ return -ENODEV;
+
+ mic->bridge.driver_private = mic;
+
+ return drm_bridge_attach(encoder, &mic->bridge, NULL, 0);
+}
+
+static void exynos_mic_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct exynos_mic *mic = dev_get_drvdata(dev);
+
+ mutex_lock(&mic_mutex);
+ if (!mic->enabled)
+ goto already_disabled;
+
+ pm_runtime_put(mic->dev);
+
+already_disabled:
+ mutex_unlock(&mic_mutex);
+}
+
+static const struct component_ops exynos_mic_component_ops = {
+ .bind = exynos_mic_bind,
+ .unbind = exynos_mic_unbind,
+};
+
+static int exynos_mic_suspend(struct device *dev)
+{
+ struct exynos_mic *mic = dev_get_drvdata(dev);
+ int i;
+
+ for (i = NUM_CLKS - 1; i > -1; i--)
+ clk_disable_unprepare(mic->clks[i]);
+
+ return 0;
+}
+
+static int exynos_mic_resume(struct device *dev)
+{
+ struct exynos_mic *mic = dev_get_drvdata(dev);
+ int ret, i;
+
+ for (i = 0; i < NUM_CLKS; i++) {
+ ret = clk_prepare_enable(mic->clks[i]);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to enable clock (%s)\n",
+ clk_names[i]);
+ while (--i > -1)
+ clk_disable_unprepare(mic->clks[i]);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(exynos_mic_pm_ops, exynos_mic_suspend,
+ exynos_mic_resume, NULL);
+
+static int exynos_mic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct exynos_mic *mic;
+ struct resource res;
+ int ret, i;
+
+ mic = devm_drm_bridge_alloc(dev, struct exynos_mic, bridge, &mic_bridge_funcs);
+ if (IS_ERR(mic)) {
+ DRM_DEV_ERROR(dev,
+ "mic: Failed to allocate memory for MIC object\n");
+ ret = PTR_ERR(mic);
+ goto err;
+ }
+
+ mic->dev = dev;
+
+ ret = of_address_to_resource(dev->of_node, 0, &res);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "mic: Failed to get mem region for MIC\n");
+ goto err;
+ }
+ mic->reg = devm_ioremap(dev, res.start, resource_size(&res));
+ if (!mic->reg) {
+ DRM_DEV_ERROR(dev, "mic: Failed to remap for MIC\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ mic->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,disp-syscon");
+ if (IS_ERR(mic->sysreg)) {
+ DRM_DEV_ERROR(dev, "mic: Failed to get system register.\n");
+ ret = PTR_ERR(mic->sysreg);
+ goto err;
+ }
+
+ for (i = 0; i < NUM_CLKS; i++) {
+ mic->clks[i] = devm_clk_get(dev, clk_names[i]);
+ if (IS_ERR(mic->clks[i])) {
+ DRM_DEV_ERROR(dev, "mic: Failed to get clock (%s)\n",
+ clk_names[i]);
+ ret = PTR_ERR(mic->clks[i]);
+ goto err;
+ }
+ }
+
+ platform_set_drvdata(pdev, mic);
+
+ mic->bridge.of_node = dev->of_node;
+
+ ret = devm_drm_bridge_add(dev, &mic->bridge);
+ if (ret)
+ goto err;
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &exynos_mic_component_ops);
+ if (ret)
+ goto err_pm;
+
+ DRM_DEV_DEBUG_KMS(dev, "MIC has been probed\n");
+
+ return 0;
+
+err_pm:
+ pm_runtime_disable(dev);
+err:
+ return ret;
+}
+
+static void exynos_mic_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &exynos_mic_component_ops);
+ pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id exynos_mic_of_match[] = {
+ { .compatible = "samsung,exynos5433-mic" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, exynos_mic_of_match);
+
+struct platform_driver mic_driver = {
+ .probe = exynos_mic_probe,
+ .remove = exynos_mic_remove,
+ .driver = {
+ .name = "exynos-mic",
+ .pm = pm_ptr(&exynos_mic_pm_ops),
+ .of_match_table = exynos_mic_of_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c b/drivers/gpu/drm/exynos/exynos_drm_plane.c
new file mode 100644
index 000000000..5c04ef87e
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Authors: Joonyoung Shim <jy0922.shim@samsung.com>
+ */
+
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_gem.h"
+#include "exynos_drm_plane.h"
+
+/*
+ * This function is to get X or Y size shown via screen. This needs length and
+ * start position of CRTC.
+ *
+ * <--- length --->
+ * CRTC ----------------
+ * ^ start ^ end
+ *
+ * There are six cases from a to f.
+ *
+ * <----- SCREEN ----->
+ * 0 last
+ * ----------|------------------|----------
+ * CRTCs
+ * a -------
+ * b -------
+ * c --------------------------
+ * d --------
+ * e -------
+ * f -------
+ */
+static int exynos_plane_get_size(int start, unsigned length, unsigned last)
+{
+ int end = start + length;
+ int size = 0;
+
+ if (start <= 0) {
+ if (end > 0)
+ size = min_t(unsigned, end, last);
+ } else if (start <= last) {
+ size = min_t(unsigned, last - start, length);
+ }
+
+ return size;
+}
+
+static void exynos_plane_mode_set(struct exynos_drm_plane_state *exynos_state)
+{
+ struct drm_plane_state *state = &exynos_state->base;
+ struct drm_crtc *crtc = state->crtc;
+ struct drm_crtc_state *crtc_state =
+ drm_atomic_get_new_crtc_state(state->state, crtc);
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ int crtc_x, crtc_y;
+ unsigned int crtc_w, crtc_h;
+ unsigned int src_x, src_y;
+ unsigned int src_w, src_h;
+ unsigned int actual_w;
+ unsigned int actual_h;
+
+ /*
+ * The original src/dest coordinates are stored in exynos_state->base,
+ * but we want to keep another copy internal to our driver that we can
+ * clip/modify ourselves.
+ */
+
+ crtc_x = state->crtc_x;
+ crtc_y = state->crtc_y;
+ crtc_w = state->crtc_w;
+ crtc_h = state->crtc_h;
+
+ src_x = state->src_x >> 16;
+ src_y = state->src_y >> 16;
+ src_w = state->src_w >> 16;
+ src_h = state->src_h >> 16;
+
+ /* set ratio */
+ exynos_state->h_ratio = (src_w << 16) / crtc_w;
+ exynos_state->v_ratio = (src_h << 16) / crtc_h;
+
+ /* clip to visible area */
+ actual_w = exynos_plane_get_size(crtc_x, crtc_w, mode->hdisplay);
+ actual_h = exynos_plane_get_size(crtc_y, crtc_h, mode->vdisplay);
+
+ if (crtc_x < 0) {
+ if (actual_w)
+ src_x += ((-crtc_x) * exynos_state->h_ratio) >> 16;
+ crtc_x = 0;
+ }
+
+ if (crtc_y < 0) {
+ if (actual_h)
+ src_y += ((-crtc_y) * exynos_state->v_ratio) >> 16;
+ crtc_y = 0;
+ }
+
+ /* set drm framebuffer data. */
+ exynos_state->src.x = src_x;
+ exynos_state->src.y = src_y;
+ exynos_state->src.w = (actual_w * exynos_state->h_ratio) >> 16;
+ exynos_state->src.h = (actual_h * exynos_state->v_ratio) >> 16;
+
+ /* set plane range to be displayed. */
+ exynos_state->crtc.x = crtc_x;
+ exynos_state->crtc.y = crtc_y;
+ exynos_state->crtc.w = actual_w;
+ exynos_state->crtc.h = actual_h;
+
+ DRM_DEV_DEBUG_KMS(crtc->dev->dev,
+ "plane : offset_x/y(%d,%d), width/height(%d,%d)",
+ exynos_state->crtc.x, exynos_state->crtc.y,
+ exynos_state->crtc.w, exynos_state->crtc.h);
+}
+
+static void exynos_drm_plane_reset(struct drm_plane *plane)
+{
+ struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);
+ struct exynos_drm_plane_state *exynos_state;
+
+ if (plane->state) {
+ exynos_state = to_exynos_plane_state(plane->state);
+ __drm_atomic_helper_plane_destroy_state(plane->state);
+ kfree(exynos_state);
+ plane->state = NULL;
+ }
+
+ exynos_state = kzalloc_obj(*exynos_state);
+ if (exynos_state) {
+ __drm_atomic_helper_plane_reset(plane, &exynos_state->base);
+ plane->state->zpos = exynos_plane->config->zpos;
+ }
+}
+
+static struct drm_plane_state *
+exynos_drm_plane_duplicate_state(struct drm_plane *plane)
+{
+ struct exynos_drm_plane_state *exynos_state;
+ struct exynos_drm_plane_state *copy;
+
+ exynos_state = to_exynos_plane_state(plane->state);
+ copy = kzalloc_obj(*exynos_state);
+ if (!copy)
+ return NULL;
+
+ __drm_atomic_helper_plane_duplicate_state(plane, &copy->base);
+ return &copy->base;
+}
+
+static void exynos_drm_plane_destroy_state(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct exynos_drm_plane_state *old_exynos_state =
+ to_exynos_plane_state(old_state);
+ __drm_atomic_helper_plane_destroy_state(old_state);
+ kfree(old_exynos_state);
+}
+
+static struct drm_plane_funcs exynos_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = exynos_drm_plane_reset,
+ .atomic_duplicate_state = exynos_drm_plane_duplicate_state,
+ .atomic_destroy_state = exynos_drm_plane_destroy_state,
+};
+
+static int
+exynos_drm_plane_check_format(const struct exynos_drm_plane_config *config,
+ struct exynos_drm_plane_state *state)
+{
+ struct drm_framebuffer *fb = state->base.fb;
+ struct drm_device *dev = fb->dev;
+
+ switch (fb->modifier) {
+ case DRM_FORMAT_MOD_SAMSUNG_64_32_TILE:
+ if (!(config->capabilities & EXYNOS_DRM_PLANE_CAP_TILE))
+ return -ENOTSUPP;
+ break;
+
+ case DRM_FORMAT_MOD_LINEAR:
+ break;
+
+ default:
+ DRM_DEV_ERROR(dev->dev, "unsupported pixel format modifier");
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+exynos_drm_plane_check_size(const struct exynos_drm_plane_config *config,
+ struct exynos_drm_plane_state *state)
+{
+ struct drm_crtc *crtc = state->base.crtc;
+ bool width_ok = false, height_ok = false;
+
+ if (config->capabilities & EXYNOS_DRM_PLANE_CAP_SCALE)
+ return 0;
+
+ if (state->src.w == state->crtc.w)
+ width_ok = true;
+
+ if (state->src.h == state->crtc.h)
+ height_ok = true;
+
+ if ((config->capabilities & EXYNOS_DRM_PLANE_CAP_DOUBLE) &&
+ state->h_ratio == (1 << 15))
+ width_ok = true;
+
+ if ((config->capabilities & EXYNOS_DRM_PLANE_CAP_DOUBLE) &&
+ state->v_ratio == (1 << 15))
+ height_ok = true;
+
+ if (width_ok && height_ok)
+ return 0;
+
+ DRM_DEV_DEBUG_KMS(crtc->dev->dev, "scaling mode is not supported");
+ return -ENOTSUPP;
+}
+
+static int exynos_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);
+ struct exynos_drm_plane_state *exynos_state =
+ to_exynos_plane_state(new_plane_state);
+ int ret = 0;
+
+ if (!new_plane_state->crtc || !new_plane_state->fb)
+ return 0;
+
+ /* translate state into exynos_state */
+ exynos_plane_mode_set(exynos_state);
+
+ ret = exynos_drm_plane_check_format(exynos_plane->config, exynos_state);
+ if (ret)
+ return ret;
+
+ ret = exynos_drm_plane_check_size(exynos_plane->config, exynos_state);
+ return ret;
+}
+
+static void exynos_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(new_state->crtc);
+ struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);
+
+ if (!new_state->crtc)
+ return;
+
+ if (exynos_crtc->ops->update_plane)
+ exynos_crtc->ops->update_plane(exynos_crtc, exynos_plane);
+}
+
+static void exynos_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
+ struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);
+ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(old_state->crtc);
+
+ if (!old_state->crtc)
+ return;
+
+ if (exynos_crtc->ops->disable_plane)
+ exynos_crtc->ops->disable_plane(exynos_crtc, exynos_plane);
+}
+
+static const struct drm_plane_helper_funcs plane_helper_funcs = {
+ .atomic_check = exynos_plane_atomic_check,
+ .atomic_update = exynos_plane_atomic_update,
+ .atomic_disable = exynos_plane_atomic_disable,
+};
+
+static void exynos_plane_attach_zpos_property(struct drm_plane *plane,
+ int zpos, bool immutable)
+{
+ if (immutable)
+ drm_plane_create_zpos_immutable_property(plane, zpos);
+ else
+ drm_plane_create_zpos_property(plane, zpos, 0, MAX_PLANE - 1);
+}
+
+int exynos_plane_init(struct drm_device *dev,
+ struct exynos_drm_plane *exynos_plane, unsigned int index,
+ const struct exynos_drm_plane_config *config)
+{
+ int err;
+ unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE);
+ struct drm_plane *plane = &exynos_plane->base;
+
+ err = drm_universal_plane_init(dev, &exynos_plane->base,
+ 1 << dev->mode_config.num_crtc,
+ &exynos_plane_funcs,
+ config->pixel_formats,
+ config->num_pixel_formats,
+ NULL, config->type, NULL);
+ if (err) {
+ DRM_DEV_ERROR(dev->dev, "failed to initialize plane\n");
+ return err;
+ }
+
+ drm_plane_helper_add(&exynos_plane->base, &plane_helper_funcs);
+
+ exynos_plane->index = index;
+ exynos_plane->config = config;
+
+ exynos_plane_attach_zpos_property(&exynos_plane->base, config->zpos,
+ !(config->capabilities & EXYNOS_DRM_PLANE_CAP_ZPOS));
+
+ if (config->capabilities & EXYNOS_DRM_PLANE_CAP_PIX_BLEND)
+ drm_plane_create_blend_mode_property(plane, supported_modes);
+
+ if (config->capabilities & EXYNOS_DRM_PLANE_CAP_WIN_BLEND)
+ drm_plane_create_alpha_property(plane);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.h b/drivers/gpu/drm/exynos/exynos_drm_plane.h
new file mode 100644
index 000000000..c08528b79
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_plane.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Authors: Joonyoung Shim <jy0922.shim@samsung.com>
+ */
+
+int exynos_plane_init(struct drm_device *dev,
+ struct exynos_drm_plane *exynos_plane, unsigned int index,
+ const struct exynos_drm_plane_config *config);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c
new file mode 100644
index 000000000..7b0f4a98a
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ * YoungJun Cho <yj44.cho@samsung.com>
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/sizes.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_ipp.h"
+#include "regs-rotator.h"
+
+/*
+ * Rotator supports image crop/rotator and input/output DMA operations.
+ * input DMA reads image data from the memory.
+ * output DMA writes image data to memory.
+ */
+
+#define ROTATOR_AUTOSUSPEND_DELAY 2000
+
+#define rot_read(offset) readl(rot->regs + (offset))
+#define rot_write(cfg, offset) writel(cfg, rot->regs + (offset))
+
+enum rot_irq_status {
+ ROT_IRQ_STATUS_COMPLETE = 8,
+ ROT_IRQ_STATUS_ILLEGAL = 9,
+};
+
+struct rot_variant {
+ const struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+};
+
+/*
+ * A structure of rotator context.
+ * @ippdrv: prepare initialization using ippdrv.
+ * @regs: memory mapped io registers.
+ * @clock: rotator gate clock.
+ * @limit_tbl: limitation of rotator.
+ * @irq: irq number.
+ */
+struct rot_context {
+ struct exynos_drm_ipp ipp;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *clock;
+ const struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+ struct exynos_drm_ipp_task *task;
+};
+
+static void rotator_reg_set_irq(struct rot_context *rot, bool enable)
+{
+ u32 val = rot_read(ROT_CONFIG);
+
+ if (enable == true)
+ val |= ROT_CONFIG_IRQ;
+ else
+ val &= ~ROT_CONFIG_IRQ;
+
+ rot_write(val, ROT_CONFIG);
+}
+
+static enum rot_irq_status rotator_reg_get_irq_status(struct rot_context *rot)
+{
+ u32 val = rot_read(ROT_STATUS);
+
+ val = ROT_STATUS_IRQ(val);
+
+ if (val == ROT_STATUS_IRQ_VAL_COMPLETE)
+ return ROT_IRQ_STATUS_COMPLETE;
+
+ return ROT_IRQ_STATUS_ILLEGAL;
+}
+
+static irqreturn_t rotator_irq_handler(int irq, void *arg)
+{
+ struct rot_context *rot = arg;
+ enum rot_irq_status irq_status;
+ u32 val;
+
+ /* Get execution result */
+ irq_status = rotator_reg_get_irq_status(rot);
+
+ /* clear status */
+ val = rot_read(ROT_STATUS);
+ val |= ROT_STATUS_IRQ_PENDING((u32)irq_status);
+ rot_write(val, ROT_STATUS);
+
+ if (rot->task) {
+ struct exynos_drm_ipp_task *task = rot->task;
+
+ rot->task = NULL;
+ pm_runtime_mark_last_busy(rot->dev);
+ pm_runtime_put_autosuspend(rot->dev);
+ exynos_drm_ipp_task_done(task,
+ irq_status == ROT_IRQ_STATUS_COMPLETE ? 0 : -EINVAL);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void rotator_src_set_fmt(struct rot_context *rot, u32 fmt)
+{
+ u32 val;
+
+ val = rot_read(ROT_CONTROL);
+ val &= ~ROT_CONTROL_FMT_MASK;
+
+ switch (fmt) {
+ case DRM_FORMAT_NV12:
+ val |= ROT_CONTROL_FMT_YCBCR420_2P;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ val |= ROT_CONTROL_FMT_RGB888;
+ break;
+ }
+
+ rot_write(val, ROT_CONTROL);
+}
+
+static void rotator_src_set_buf(struct rot_context *rot,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ u32 val;
+
+ /* Set buffer size configuration */
+ val = ROT_SET_BUF_SIZE_H(buf->buf.height) |
+ ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]);
+ rot_write(val, ROT_SRC_BUF_SIZE);
+
+ /* Set crop image position configuration */
+ val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x);
+ rot_write(val, ROT_SRC_CROP_POS);
+ val = ROT_SRC_CROP_SIZE_H(buf->rect.h) |
+ ROT_SRC_CROP_SIZE_W(buf->rect.w);
+ rot_write(val, ROT_SRC_CROP_SIZE);
+
+ /* Set buffer DMA address */
+ rot_write(buf->dma_addr[0], ROT_SRC_BUF_ADDR(0));
+ rot_write(buf->dma_addr[1], ROT_SRC_BUF_ADDR(1));
+}
+
+static void rotator_dst_set_transf(struct rot_context *rot,
+ unsigned int rotation)
+{
+ u32 val;
+
+ /* Set transform configuration */
+ val = rot_read(ROT_CONTROL);
+ val &= ~ROT_CONTROL_FLIP_MASK;
+
+ if (rotation & DRM_MODE_REFLECT_X)
+ val |= ROT_CONTROL_FLIP_VERTICAL;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ val |= ROT_CONTROL_FLIP_HORIZONTAL;
+
+ val &= ~ROT_CONTROL_ROT_MASK;
+
+ if (rotation & DRM_MODE_ROTATE_90)
+ val |= ROT_CONTROL_ROT_90;
+ else if (rotation & DRM_MODE_ROTATE_180)
+ val |= ROT_CONTROL_ROT_180;
+ else if (rotation & DRM_MODE_ROTATE_270)
+ val |= ROT_CONTROL_ROT_270;
+
+ rot_write(val, ROT_CONTROL);
+}
+
+static void rotator_dst_set_buf(struct rot_context *rot,
+ struct exynos_drm_ipp_buffer *buf)
+{
+ u32 val;
+
+ /* Set buffer size configuration */
+ val = ROT_SET_BUF_SIZE_H(buf->buf.height) |
+ ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]);
+ rot_write(val, ROT_DST_BUF_SIZE);
+
+ /* Set crop image position configuration */
+ val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x);
+ rot_write(val, ROT_DST_CROP_POS);
+
+ /* Set buffer DMA address */
+ rot_write(buf->dma_addr[0], ROT_DST_BUF_ADDR(0));
+ rot_write(buf->dma_addr[1], ROT_DST_BUF_ADDR(1));
+}
+
+static void rotator_start(struct rot_context *rot)
+{
+ u32 val;
+
+ /* Set interrupt enable */
+ rotator_reg_set_irq(rot, true);
+
+ val = rot_read(ROT_CONTROL);
+ val |= ROT_CONTROL_START;
+ rot_write(val, ROT_CONTROL);
+}
+
+static int rotator_commit(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct rot_context *rot =
+ container_of(ipp, struct rot_context, ipp);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(rot->dev);
+ if (ret < 0) {
+ dev_err(rot->dev, "failed to enable ROTATOR device.\n");
+ return ret;
+ }
+ rot->task = task;
+
+ rotator_src_set_fmt(rot, task->src.buf.fourcc);
+ rotator_src_set_buf(rot, &task->src);
+ rotator_dst_set_transf(rot, task->transform.rotation);
+ rotator_dst_set_buf(rot, &task->dst);
+ rotator_start(rot);
+
+ return 0;
+}
+
+static const struct exynos_drm_ipp_funcs ipp_funcs = {
+ .commit = rotator_commit,
+};
+
+static int rotator_bind(struct device *dev, struct device *master, void *data)
+{
+ struct rot_context *rot = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &rot->ipp;
+
+ rot->drm_dev = drm_dev;
+ ipp->drm_dev = drm_dev;
+ exynos_drm_register_dma(drm_dev, dev, &rot->dma_priv);
+
+ exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
+ DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE,
+ rot->formats, rot->num_formats, "rotator");
+
+ dev_info(dev, "The exynos rotator has been probed successfully\n");
+
+ return 0;
+}
+
+static void rotator_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rot_context *rot = dev_get_drvdata(dev);
+ struct exynos_drm_ipp *ipp = &rot->ipp;
+
+ exynos_drm_ipp_unregister(dev, ipp);
+ exynos_drm_unregister_dma(rot->drm_dev, rot->dev, &rot->dma_priv);
+}
+
+static const struct component_ops rotator_component_ops = {
+ .bind = rotator_bind,
+ .unbind = rotator_unbind,
+};
+
+static int rotator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rot_context *rot;
+ const struct rot_variant *variant;
+ int irq;
+ int ret;
+
+ rot = devm_kzalloc(dev, sizeof(*rot), GFP_KERNEL);
+ if (!rot)
+ return -ENOMEM;
+
+ variant = of_device_get_match_data(dev);
+ rot->formats = variant->formats;
+ rot->num_formats = variant->num_formats;
+ rot->dev = dev;
+ rot->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rot->regs))
+ return PTR_ERR(rot->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, rotator_irq_handler, 0, dev_name(dev),
+ rot);
+ if (ret < 0) {
+ dev_err(dev, "failed to request irq\n");
+ return ret;
+ }
+
+ rot->clock = devm_clk_get(dev, "rotator");
+ if (IS_ERR(rot->clock)) {
+ dev_err(dev, "failed to get clock\n");
+ return PTR_ERR(rot->clock);
+ }
+
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, ROTATOR_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+ platform_set_drvdata(pdev, rot);
+
+ ret = component_add(dev, &rotator_component_ops);
+ if (ret)
+ goto err_component;
+
+ return 0;
+
+err_component:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void rotator_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &rotator_component_ops);
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+}
+
+static int rotator_runtime_suspend(struct device *dev)
+{
+ struct rot_context *rot = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(rot->clock);
+ return 0;
+}
+
+static int rotator_runtime_resume(struct device *dev)
+{
+ struct rot_context *rot = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(rot->clock);
+}
+
+static const struct drm_exynos_ipp_limit rotator_s5pv210_rbg888_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_4210_rbg888_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_4412_rbg888_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_5250_rbg888_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_s5pv210_yuv_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_4210_yuv_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
+};
+
+static const struct drm_exynos_ipp_limit rotator_4412_yuv_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_32K }, .v = { 32, SZ_32K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
+};
+
+static const struct exynos_drm_ipp_formats rotator_s5pv210_formats[] = {
+ { IPP_SRCDST_FORMAT(XRGB8888, rotator_s5pv210_rbg888_limits) },
+ { IPP_SRCDST_FORMAT(NV12, rotator_s5pv210_yuv_limits) },
+};
+
+static const struct exynos_drm_ipp_formats rotator_4210_formats[] = {
+ { IPP_SRCDST_FORMAT(XRGB8888, rotator_4210_rbg888_limits) },
+ { IPP_SRCDST_FORMAT(NV12, rotator_4210_yuv_limits) },
+};
+
+static const struct exynos_drm_ipp_formats rotator_4412_formats[] = {
+ { IPP_SRCDST_FORMAT(XRGB8888, rotator_4412_rbg888_limits) },
+ { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) },
+};
+
+static const struct exynos_drm_ipp_formats rotator_5250_formats[] = {
+ { IPP_SRCDST_FORMAT(XRGB8888, rotator_5250_rbg888_limits) },
+ { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) },
+};
+
+static const struct rot_variant rotator_s5pv210_data = {
+ .formats = rotator_s5pv210_formats,
+ .num_formats = ARRAY_SIZE(rotator_s5pv210_formats),
+};
+
+static const struct rot_variant rotator_4210_data = {
+ .formats = rotator_4210_formats,
+ .num_formats = ARRAY_SIZE(rotator_4210_formats),
+};
+
+static const struct rot_variant rotator_4412_data = {
+ .formats = rotator_4412_formats,
+ .num_formats = ARRAY_SIZE(rotator_4412_formats),
+};
+
+static const struct rot_variant rotator_5250_data = {
+ .formats = rotator_5250_formats,
+ .num_formats = ARRAY_SIZE(rotator_5250_formats),
+};
+
+static const struct of_device_id exynos_rotator_match[] = {
+ {
+ .compatible = "samsung,s5pv210-rotator",
+ .data = &rotator_s5pv210_data,
+ }, {
+ .compatible = "samsung,exynos4210-rotator",
+ .data = &rotator_4210_data,
+ }, {
+ .compatible = "samsung,exynos4212-rotator",
+ .data = &rotator_4412_data,
+ }, {
+ .compatible = "samsung,exynos5250-rotator",
+ .data = &rotator_5250_data,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, exynos_rotator_match);
+
+static DEFINE_RUNTIME_DEV_PM_OPS(rotator_pm_ops, rotator_runtime_suspend,
+ rotator_runtime_resume, NULL);
+
+struct platform_driver rotator_driver = {
+ .probe = rotator_probe,
+ .remove = rotator_remove,
+ .driver = {
+ .name = "exynos-rotator",
+ .pm = pm_ptr(&rotator_pm_ops),
+ .of_match_table = exynos_rotator_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_scaler.c b/drivers/gpu/drm/exynos/exynos_drm_scaler.c
new file mode 100644
index 000000000..c8a1b6b0a
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_scaler.c
@@ -0,0 +1,728 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Samsung Electronics Co.Ltd
+ * Author:
+ * Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_blend.h>
+#include <drm/drm_fourcc.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_ipp.h"
+#include "regs-scaler.h"
+
+#define scaler_read(offset) readl(scaler->regs + (offset))
+#define scaler_write(cfg, offset) writel(cfg, scaler->regs + (offset))
+#define SCALER_MAX_CLK 4
+#define SCALER_AUTOSUSPEND_DELAY 2000
+#define SCALER_RESET_WAIT_RETRIES 100
+
+struct scaler_data {
+ const char *clk_name[SCALER_MAX_CLK];
+ unsigned int num_clk;
+ const struct exynos_drm_ipp_formats *formats;
+ unsigned int num_formats;
+};
+
+struct scaler_context {
+ struct exynos_drm_ipp ipp;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *clock[SCALER_MAX_CLK];
+ struct exynos_drm_ipp_task *task;
+ const struct scaler_data *scaler_data;
+};
+
+struct scaler_format {
+ u32 drm_fmt;
+ u32 internal_fmt;
+ u32 chroma_tile_w;
+ u32 chroma_tile_h;
+};
+
+static const struct scaler_format scaler_formats[] = {
+ { DRM_FORMAT_NV12, SCALER_YUV420_2P_UV, 8, 8 },
+ { DRM_FORMAT_NV21, SCALER_YUV420_2P_VU, 8, 8 },
+ { DRM_FORMAT_YUV420, SCALER_YUV420_3P, 8, 8 },
+ { DRM_FORMAT_YUYV, SCALER_YUV422_1P_YUYV, 16, 16 },
+ { DRM_FORMAT_UYVY, SCALER_YUV422_1P_UYVY, 16, 16 },
+ { DRM_FORMAT_YVYU, SCALER_YUV422_1P_YVYU, 16, 16 },
+ { DRM_FORMAT_NV16, SCALER_YUV422_2P_UV, 8, 16 },
+ { DRM_FORMAT_NV61, SCALER_YUV422_2P_VU, 8, 16 },
+ { DRM_FORMAT_YUV422, SCALER_YUV422_3P, 8, 16 },
+ { DRM_FORMAT_NV24, SCALER_YUV444_2P_UV, 16, 16 },
+ { DRM_FORMAT_NV42, SCALER_YUV444_2P_VU, 16, 16 },
+ { DRM_FORMAT_YUV444, SCALER_YUV444_3P, 16, 16 },
+ { DRM_FORMAT_RGB565, SCALER_RGB_565, 0, 0 },
+ { DRM_FORMAT_XRGB1555, SCALER_ARGB1555, 0, 0 },
+ { DRM_FORMAT_ARGB1555, SCALER_ARGB1555, 0, 0 },
+ { DRM_FORMAT_XRGB4444, SCALER_ARGB4444, 0, 0 },
+ { DRM_FORMAT_ARGB4444, SCALER_ARGB4444, 0, 0 },
+ { DRM_FORMAT_XRGB8888, SCALER_ARGB8888, 0, 0 },
+ { DRM_FORMAT_ARGB8888, SCALER_ARGB8888, 0, 0 },
+ { DRM_FORMAT_RGBX8888, SCALER_RGBA8888, 0, 0 },
+ { DRM_FORMAT_RGBA8888, SCALER_RGBA8888, 0, 0 },
+};
+
+static const struct scaler_format *scaler_get_format(u32 drm_fmt)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(scaler_formats); i++)
+ if (scaler_formats[i].drm_fmt == drm_fmt)
+ return &scaler_formats[i];
+
+ return NULL;
+}
+
+static inline int scaler_reset(struct scaler_context *scaler)
+{
+ int retry = SCALER_RESET_WAIT_RETRIES;
+
+ scaler_write(SCALER_CFG_SOFT_RESET, SCALER_CFG);
+ do {
+ cpu_relax();
+ } while (--retry > 1 &&
+ scaler_read(SCALER_CFG) & SCALER_CFG_SOFT_RESET);
+ do {
+ cpu_relax();
+ scaler_write(1, SCALER_INT_EN);
+ } while (--retry > 0 && scaler_read(SCALER_INT_EN) != 1);
+
+ return retry ? 0 : -EIO;
+}
+
+static inline void scaler_enable_int(struct scaler_context *scaler)
+{
+ u32 val;
+
+ val = SCALER_INT_EN_TIMEOUT |
+ SCALER_INT_EN_ILLEGAL_BLEND |
+ SCALER_INT_EN_ILLEGAL_RATIO |
+ SCALER_INT_EN_ILLEGAL_DST_HEIGHT |
+ SCALER_INT_EN_ILLEGAL_DST_WIDTH |
+ SCALER_INT_EN_ILLEGAL_DST_V_POS |
+ SCALER_INT_EN_ILLEGAL_DST_H_POS |
+ SCALER_INT_EN_ILLEGAL_DST_C_SPAN |
+ SCALER_INT_EN_ILLEGAL_DST_Y_SPAN |
+ SCALER_INT_EN_ILLEGAL_DST_CR_BASE |
+ SCALER_INT_EN_ILLEGAL_DST_CB_BASE |
+ SCALER_INT_EN_ILLEGAL_DST_Y_BASE |
+ SCALER_INT_EN_ILLEGAL_DST_COLOR |
+ SCALER_INT_EN_ILLEGAL_SRC_HEIGHT |
+ SCALER_INT_EN_ILLEGAL_SRC_WIDTH |
+ SCALER_INT_EN_ILLEGAL_SRC_CV_POS |
+ SCALER_INT_EN_ILLEGAL_SRC_CH_POS |
+ SCALER_INT_EN_ILLEGAL_SRC_YV_POS |
+ SCALER_INT_EN_ILLEGAL_SRC_YH_POS |
+ SCALER_INT_EN_ILLEGAL_DST_SPAN |
+ SCALER_INT_EN_ILLEGAL_SRC_Y_SPAN |
+ SCALER_INT_EN_ILLEGAL_SRC_CR_BASE |
+ SCALER_INT_EN_ILLEGAL_SRC_CB_BASE |
+ SCALER_INT_EN_ILLEGAL_SRC_Y_BASE |
+ SCALER_INT_EN_ILLEGAL_SRC_COLOR |
+ SCALER_INT_EN_FRAME_END;
+ scaler_write(val, SCALER_INT_EN);
+}
+
+static inline void scaler_set_src_fmt(struct scaler_context *scaler,
+ u32 src_fmt, u32 tile)
+{
+ u32 val;
+
+ val = SCALER_SRC_CFG_SET_COLOR_FORMAT(src_fmt) | (tile << 10);
+ scaler_write(val, SCALER_SRC_CFG);
+}
+
+static inline void scaler_set_src_base(struct scaler_context *scaler,
+ struct exynos_drm_ipp_buffer *src_buf)
+{
+ static unsigned int bases[] = {
+ SCALER_SRC_Y_BASE,
+ SCALER_SRC_CB_BASE,
+ SCALER_SRC_CR_BASE,
+ };
+ int i;
+
+ for (i = 0; i < src_buf->format->num_planes; ++i)
+ scaler_write(src_buf->dma_addr[i], bases[i]);
+}
+
+static inline void scaler_set_src_span(struct scaler_context *scaler,
+ struct exynos_drm_ipp_buffer *src_buf)
+{
+ u32 val;
+
+ val = SCALER_SRC_SPAN_SET_Y_SPAN(src_buf->buf.pitch[0] /
+ src_buf->format->cpp[0]);
+
+ if (src_buf->format->num_planes > 1)
+ val |= SCALER_SRC_SPAN_SET_C_SPAN(src_buf->buf.pitch[1]);
+
+ scaler_write(val, SCALER_SRC_SPAN);
+}
+
+static inline void scaler_set_src_luma_chroma_pos(struct scaler_context *scaler,
+ struct drm_exynos_ipp_task_rect *src_pos,
+ const struct scaler_format *fmt)
+{
+ u32 val;
+
+ val = SCALER_SRC_Y_POS_SET_YH_POS(src_pos->x << 2);
+ val |= SCALER_SRC_Y_POS_SET_YV_POS(src_pos->y << 2);
+ scaler_write(val, SCALER_SRC_Y_POS);
+ val = SCALER_SRC_C_POS_SET_CH_POS(
+ (src_pos->x * fmt->chroma_tile_w / 16) << 2);
+ val |= SCALER_SRC_C_POS_SET_CV_POS(
+ (src_pos->y * fmt->chroma_tile_h / 16) << 2);
+ scaler_write(val, SCALER_SRC_C_POS);
+}
+
+static inline void scaler_set_src_wh(struct scaler_context *scaler,
+ struct drm_exynos_ipp_task_rect *src_pos)
+{
+ u32 val;
+
+ val = SCALER_SRC_WH_SET_WIDTH(src_pos->w);
+ val |= SCALER_SRC_WH_SET_HEIGHT(src_pos->h);
+ scaler_write(val, SCALER_SRC_WH);
+}
+
+static inline void scaler_set_dst_fmt(struct scaler_context *scaler,
+ u32 dst_fmt)
+{
+ u32 val;
+
+ val = SCALER_DST_CFG_SET_COLOR_FORMAT(dst_fmt);
+ scaler_write(val, SCALER_DST_CFG);
+}
+
+static inline void scaler_set_dst_base(struct scaler_context *scaler,
+ struct exynos_drm_ipp_buffer *dst_buf)
+{
+ static unsigned int bases[] = {
+ SCALER_DST_Y_BASE,
+ SCALER_DST_CB_BASE,
+ SCALER_DST_CR_BASE,
+ };
+ int i;
+
+ for (i = 0; i < dst_buf->format->num_planes; ++i)
+ scaler_write(dst_buf->dma_addr[i], bases[i]);
+}
+
+static inline void scaler_set_dst_span(struct scaler_context *scaler,
+ struct exynos_drm_ipp_buffer *dst_buf)
+{
+ u32 val;
+
+ val = SCALER_DST_SPAN_SET_Y_SPAN(dst_buf->buf.pitch[0] /
+ dst_buf->format->cpp[0]);
+
+ if (dst_buf->format->num_planes > 1)
+ val |= SCALER_DST_SPAN_SET_C_SPAN(dst_buf->buf.pitch[1]);
+
+ scaler_write(val, SCALER_DST_SPAN);
+}
+
+static inline void scaler_set_dst_luma_pos(struct scaler_context *scaler,
+ struct drm_exynos_ipp_task_rect *dst_pos)
+{
+ u32 val;
+
+ val = SCALER_DST_WH_SET_WIDTH(dst_pos->w);
+ val |= SCALER_DST_WH_SET_HEIGHT(dst_pos->h);
+ scaler_write(val, SCALER_DST_WH);
+}
+
+static inline void scaler_set_dst_wh(struct scaler_context *scaler,
+ struct drm_exynos_ipp_task_rect *dst_pos)
+{
+ u32 val;
+
+ val = SCALER_DST_POS_SET_H_POS(dst_pos->x);
+ val |= SCALER_DST_POS_SET_V_POS(dst_pos->y);
+ scaler_write(val, SCALER_DST_POS);
+}
+
+static inline void scaler_set_hv_ratio(struct scaler_context *scaler,
+ unsigned int rotation,
+ struct drm_exynos_ipp_task_rect *src_pos,
+ struct drm_exynos_ipp_task_rect *dst_pos)
+{
+ u32 val, h_ratio, v_ratio;
+
+ if (drm_rotation_90_or_270(rotation)) {
+ h_ratio = (src_pos->h << 16) / dst_pos->w;
+ v_ratio = (src_pos->w << 16) / dst_pos->h;
+ } else {
+ h_ratio = (src_pos->w << 16) / dst_pos->w;
+ v_ratio = (src_pos->h << 16) / dst_pos->h;
+ }
+
+ val = SCALER_H_RATIO_SET(h_ratio);
+ scaler_write(val, SCALER_H_RATIO);
+
+ val = SCALER_V_RATIO_SET(v_ratio);
+ scaler_write(val, SCALER_V_RATIO);
+}
+
+static inline void scaler_set_rotation(struct scaler_context *scaler,
+ unsigned int rotation)
+{
+ u32 val = 0;
+
+ if (rotation & DRM_MODE_ROTATE_90)
+ val |= SCALER_ROT_CFG_SET_ROTMODE(SCALER_ROT_MODE_90);
+ else if (rotation & DRM_MODE_ROTATE_180)
+ val |= SCALER_ROT_CFG_SET_ROTMODE(SCALER_ROT_MODE_180);
+ else if (rotation & DRM_MODE_ROTATE_270)
+ val |= SCALER_ROT_CFG_SET_ROTMODE(SCALER_ROT_MODE_270);
+ if (rotation & DRM_MODE_REFLECT_X)
+ val |= SCALER_ROT_CFG_FLIP_X_EN;
+ if (rotation & DRM_MODE_REFLECT_Y)
+ val |= SCALER_ROT_CFG_FLIP_Y_EN;
+ scaler_write(val, SCALER_ROT_CFG);
+}
+
+static inline void scaler_set_csc(struct scaler_context *scaler,
+ const struct drm_format_info *fmt)
+{
+ static const u32 csc_mtx[2][3][3] = {
+ { /* YCbCr to RGB */
+ {0x254, 0x000, 0x331},
+ {0x254, 0xf38, 0xe60},
+ {0x254, 0x409, 0x000},
+ },
+ { /* RGB to YCbCr */
+ {0x084, 0x102, 0x032},
+ {0xfb4, 0xf6b, 0x0e1},
+ {0x0e1, 0xf44, 0xfdc},
+ },
+ };
+ int i, j, dir;
+
+ switch (fmt->format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_ARGB4444:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_RGBA8888:
+ dir = 1;
+ break;
+ default:
+ dir = 0;
+ }
+
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ scaler_write(csc_mtx[dir][i][j], SCALER_CSC_COEF(j, i));
+}
+
+static inline void scaler_set_timer(struct scaler_context *scaler,
+ unsigned int timer, unsigned int divider)
+{
+ u32 val;
+
+ val = SCALER_TIMEOUT_CTRL_TIMER_ENABLE;
+ val |= SCALER_TIMEOUT_CTRL_SET_TIMER_VALUE(timer);
+ val |= SCALER_TIMEOUT_CTRL_SET_TIMER_DIV(divider);
+ scaler_write(val, SCALER_TIMEOUT_CTRL);
+}
+
+static inline void scaler_start_hw(struct scaler_context *scaler)
+{
+ scaler_write(SCALER_CFG_START_CMD, SCALER_CFG);
+}
+
+static int scaler_commit(struct exynos_drm_ipp *ipp,
+ struct exynos_drm_ipp_task *task)
+{
+ struct scaler_context *scaler =
+ container_of(ipp, struct scaler_context, ipp);
+
+ struct drm_exynos_ipp_task_rect *src_pos = &task->src.rect;
+ struct drm_exynos_ipp_task_rect *dst_pos = &task->dst.rect;
+ const struct scaler_format *src_fmt, *dst_fmt;
+ int ret = 0;
+
+ src_fmt = scaler_get_format(task->src.buf.fourcc);
+ dst_fmt = scaler_get_format(task->dst.buf.fourcc);
+
+ ret = pm_runtime_resume_and_get(scaler->dev);
+ if (ret < 0)
+ return ret;
+
+ if (scaler_reset(scaler))
+ return -EIO;
+
+ scaler->task = task;
+
+ scaler_set_src_fmt(
+ scaler, src_fmt->internal_fmt, task->src.buf.modifier != 0);
+ scaler_set_src_base(scaler, &task->src);
+ scaler_set_src_span(scaler, &task->src);
+ scaler_set_src_luma_chroma_pos(scaler, src_pos, src_fmt);
+ scaler_set_src_wh(scaler, src_pos);
+
+ scaler_set_dst_fmt(scaler, dst_fmt->internal_fmt);
+ scaler_set_dst_base(scaler, &task->dst);
+ scaler_set_dst_span(scaler, &task->dst);
+ scaler_set_dst_luma_pos(scaler, dst_pos);
+ scaler_set_dst_wh(scaler, dst_pos);
+
+ scaler_set_hv_ratio(scaler, task->transform.rotation, src_pos, dst_pos);
+ scaler_set_rotation(scaler, task->transform.rotation);
+
+ scaler_set_csc(scaler, task->src.format);
+
+ scaler_set_timer(scaler, 0xffff, 0xf);
+
+ scaler_enable_int(scaler);
+ scaler_start_hw(scaler);
+
+ return 0;
+}
+
+static const struct exynos_drm_ipp_funcs ipp_funcs = {
+ .commit = scaler_commit,
+};
+
+static inline void scaler_disable_int(struct scaler_context *scaler)
+{
+ scaler_write(0, SCALER_INT_EN);
+}
+
+static inline u32 scaler_get_int_status(struct scaler_context *scaler)
+{
+ u32 val = scaler_read(SCALER_INT_STATUS);
+
+ scaler_write(val, SCALER_INT_STATUS);
+
+ return val;
+}
+
+static inline int scaler_task_done(u32 val)
+{
+ return val & SCALER_INT_STATUS_FRAME_END ? 0 : -EINVAL;
+}
+
+static irqreturn_t scaler_irq_handler(int irq, void *arg)
+{
+ struct scaler_context *scaler = arg;
+
+ u32 val = scaler_get_int_status(scaler);
+
+ scaler_disable_int(scaler);
+
+ if (scaler->task) {
+ struct exynos_drm_ipp_task *task = scaler->task;
+
+ scaler->task = NULL;
+ pm_runtime_mark_last_busy(scaler->dev);
+ pm_runtime_put_autosuspend(scaler->dev);
+ exynos_drm_ipp_task_done(task, scaler_task_done(val));
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int scaler_bind(struct device *dev, struct device *master, void *data)
+{
+ struct scaler_context *scaler = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_ipp *ipp = &scaler->ipp;
+
+ scaler->drm_dev = drm_dev;
+ ipp->drm_dev = drm_dev;
+ exynos_drm_register_dma(drm_dev, dev, &scaler->dma_priv);
+
+ exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
+ DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE |
+ DRM_EXYNOS_IPP_CAP_SCALE | DRM_EXYNOS_IPP_CAP_CONVERT,
+ scaler->scaler_data->formats,
+ scaler->scaler_data->num_formats, "scaler");
+
+ dev_info(dev, "The exynos scaler has been probed successfully\n");
+
+ return 0;
+}
+
+static void scaler_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct scaler_context *scaler = dev_get_drvdata(dev);
+ struct exynos_drm_ipp *ipp = &scaler->ipp;
+
+ exynos_drm_ipp_unregister(dev, ipp);
+ exynos_drm_unregister_dma(scaler->drm_dev, scaler->dev,
+ &scaler->dma_priv);
+}
+
+static const struct component_ops scaler_component_ops = {
+ .bind = scaler_bind,
+ .unbind = scaler_unbind,
+};
+
+static int scaler_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct scaler_context *scaler;
+ int irq;
+ int ret, i;
+
+ scaler = devm_kzalloc(dev, sizeof(*scaler), GFP_KERNEL);
+ if (!scaler)
+ return -ENOMEM;
+
+ scaler->scaler_data =
+ (struct scaler_data *)of_device_get_match_data(dev);
+
+ scaler->dev = dev;
+ scaler->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(scaler->regs))
+ return PTR_ERR(scaler->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, scaler_irq_handler,
+ IRQF_ONESHOT, "drm_scaler", scaler);
+ if (ret < 0) {
+ dev_err(dev, "failed to request irq\n");
+ return ret;
+ }
+
+ for (i = 0; i < scaler->scaler_data->num_clk; ++i) {
+ scaler->clock[i] = devm_clk_get(dev,
+ scaler->scaler_data->clk_name[i]);
+ if (IS_ERR(scaler->clock[i])) {
+ dev_err(dev, "failed to get clock\n");
+ return PTR_ERR(scaler->clock[i]);
+ }
+ }
+
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, SCALER_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+ platform_set_drvdata(pdev, scaler);
+
+ ret = component_add(dev, &scaler_component_ops);
+ if (ret)
+ goto err_ippdrv_register;
+
+ return 0;
+
+err_ippdrv_register:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void scaler_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &scaler_component_ops);
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+}
+
+static int clk_disable_unprepare_wrapper(struct clk *clk)
+{
+ clk_disable_unprepare(clk);
+
+ return 0;
+}
+
+static int scaler_clk_ctrl(struct scaler_context *scaler, bool enable)
+{
+ int (*clk_fun)(struct clk *clk), i;
+
+ clk_fun = enable ? clk_prepare_enable : clk_disable_unprepare_wrapper;
+
+ for (i = 0; i < scaler->scaler_data->num_clk; ++i)
+ clk_fun(scaler->clock[i]);
+
+ return 0;
+}
+
+static int scaler_runtime_suspend(struct device *dev)
+{
+ struct scaler_context *scaler = dev_get_drvdata(dev);
+
+ return scaler_clk_ctrl(scaler, false);
+}
+
+static int scaler_runtime_resume(struct device *dev)
+{
+ struct scaler_context *scaler = dev_get_drvdata(dev);
+
+ return scaler_clk_ctrl(scaler, true);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(scaler_pm_ops, scaler_runtime_suspend,
+ scaler_runtime_resume, NULL);
+
+static const struct drm_exynos_ipp_limit scaler_5420_two_pixel_hv_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, SZ_8K }, .v = { 16, SZ_8K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) },
+ { IPP_SCALE_LIMIT(.h = { 65536 * 1 / 4, 65536 * 16 },
+ .v = { 65536 * 1 / 4, 65536 * 16 }) },
+};
+
+static const struct drm_exynos_ipp_limit scaler_5420_two_pixel_h_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, SZ_8K }, .v = { 16, SZ_8K }) },
+ { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 1) },
+ { IPP_SCALE_LIMIT(.h = { 65536 * 1 / 4, 65536 * 16 },
+ .v = { 65536 * 1 / 4, 65536 * 16 }) },
+};
+
+static const struct drm_exynos_ipp_limit scaler_5420_one_pixel_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, SZ_8K }, .v = { 16, SZ_8K }) },
+ { IPP_SCALE_LIMIT(.h = { 65536 * 1 / 4, 65536 * 16 },
+ .v = { 65536 * 1 / 4, 65536 * 16 }) },
+};
+
+static const struct drm_exynos_ipp_limit scaler_5420_tile_limits[] = {
+ { IPP_SIZE_LIMIT(BUFFER, .h = { 16, SZ_8K }, .v = { 16, SZ_8K })},
+ { IPP_SIZE_LIMIT(AREA, .h.align = 16, .v.align = 16) },
+ { IPP_SCALE_LIMIT(.h = {1, 1}, .v = {1, 1})},
+ { }
+};
+
+#define IPP_SRCDST_TILE_FORMAT(f, l) \
+ IPP_SRCDST_MFORMAT(f, DRM_FORMAT_MOD_SAMSUNG_16_16_TILE, (l))
+
+static const struct exynos_drm_ipp_formats exynos5420_formats[] = {
+ /* SCALER_YUV420_2P_UV */
+ { IPP_SRCDST_FORMAT(NV21, scaler_5420_two_pixel_hv_limits) },
+
+ /* SCALER_YUV420_2P_VU */
+ { IPP_SRCDST_FORMAT(NV12, scaler_5420_two_pixel_hv_limits) },
+
+ /* SCALER_YUV420_3P */
+ { IPP_SRCDST_FORMAT(YUV420, scaler_5420_two_pixel_hv_limits) },
+
+ /* SCALER_YUV422_1P_YUYV */
+ { IPP_SRCDST_FORMAT(YUYV, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV422_1P_UYVY */
+ { IPP_SRCDST_FORMAT(UYVY, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV422_1P_YVYU */
+ { IPP_SRCDST_FORMAT(YVYU, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV422_2P_UV */
+ { IPP_SRCDST_FORMAT(NV61, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV422_2P_VU */
+ { IPP_SRCDST_FORMAT(NV16, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV422_3P */
+ { IPP_SRCDST_FORMAT(YUV422, scaler_5420_two_pixel_h_limits) },
+
+ /* SCALER_YUV444_2P_UV */
+ { IPP_SRCDST_FORMAT(NV42, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_YUV444_2P_VU */
+ { IPP_SRCDST_FORMAT(NV24, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_YUV444_3P */
+ { IPP_SRCDST_FORMAT(YUV444, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_RGB_565 */
+ { IPP_SRCDST_FORMAT(RGB565, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB1555 */
+ { IPP_SRCDST_FORMAT(XRGB1555, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB1555 */
+ { IPP_SRCDST_FORMAT(ARGB1555, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB4444 */
+ { IPP_SRCDST_FORMAT(XRGB4444, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB4444 */
+ { IPP_SRCDST_FORMAT(ARGB4444, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB8888 */
+ { IPP_SRCDST_FORMAT(XRGB8888, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_ARGB8888 */
+ { IPP_SRCDST_FORMAT(ARGB8888, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_RGBA8888 */
+ { IPP_SRCDST_FORMAT(RGBX8888, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_RGBA8888 */
+ { IPP_SRCDST_FORMAT(RGBA8888, scaler_5420_one_pixel_limits) },
+
+ /* SCALER_YUV420_2P_UV TILE */
+ { IPP_SRCDST_TILE_FORMAT(NV21, scaler_5420_tile_limits) },
+
+ /* SCALER_YUV420_2P_VU TILE */
+ { IPP_SRCDST_TILE_FORMAT(NV12, scaler_5420_tile_limits) },
+
+ /* SCALER_YUV420_3P TILE */
+ { IPP_SRCDST_TILE_FORMAT(YUV420, scaler_5420_tile_limits) },
+
+ /* SCALER_YUV422_1P_YUYV TILE */
+ { IPP_SRCDST_TILE_FORMAT(YUYV, scaler_5420_tile_limits) },
+};
+
+static const struct scaler_data exynos5420_data = {
+ .clk_name = {"mscl"},
+ .num_clk = 1,
+ .formats = exynos5420_formats,
+ .num_formats = ARRAY_SIZE(exynos5420_formats),
+};
+
+static const struct scaler_data exynos5433_data = {
+ .clk_name = {"pclk", "aclk", "aclk_xiu"},
+ .num_clk = 3,
+ .formats = exynos5420_formats, /* intentional */
+ .num_formats = ARRAY_SIZE(exynos5420_formats),
+};
+
+static const struct of_device_id exynos_scaler_match[] = {
+ {
+ .compatible = "samsung,exynos5420-scaler",
+ .data = &exynos5420_data,
+ }, {
+ .compatible = "samsung,exynos5433-scaler",
+ .data = &exynos5433_data,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, exynos_scaler_match);
+
+struct platform_driver scaler_driver = {
+ .probe = scaler_probe,
+ .remove = scaler_remove,
+ .driver = {
+ .name = "exynos-scaler",
+ .pm = pm_ptr(&scaler_pm_ops),
+ .of_match_table = exynos_scaler_match,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
new file mode 100644
index 000000000..59dea853d
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* exynos_drm_vidi.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ * Inki Dae <inki.dae@samsung.com>
+ */
+
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_plane.h"
+#include "exynos_drm_vidi.h"
+
+/* VIDI uses fixed refresh rate of 50Hz */
+#define VIDI_REFRESH_TIME (1000 / 50)
+
+/* vidi has totally three virtual windows. */
+#define WINDOWS_NR 3
+
+#define ctx_from_connector(c) container_of(c, struct vidi_context, \
+ connector)
+
+struct vidi_context {
+ struct drm_encoder encoder;
+ struct drm_device *drm_dev;
+ struct device *dev;
+ struct exynos_drm_crtc *crtc;
+ struct drm_connector connector;
+ struct exynos_drm_plane planes[WINDOWS_NR];
+ const struct drm_edid *raw_edid;
+ unsigned int clkdiv;
+ unsigned int connected;
+ bool suspended;
+ struct timer_list timer;
+ struct mutex lock;
+};
+
+static inline struct vidi_context *encoder_to_vidi(struct drm_encoder *e)
+{
+ return container_of(e, struct vidi_context, encoder);
+}
+
+static const char fake_edid_info[] = {
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x4c, 0x2d, 0x05, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x12, 0x01, 0x03, 0x80, 0x10, 0x09, 0x78,
+ 0x0a, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0xbd,
+ 0xee, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x66, 0x21, 0x50, 0xb0, 0x51, 0x00,
+ 0x1b, 0x30, 0x40, 0x70, 0x36, 0x00, 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x1e,
+ 0x01, 0x1d, 0x00, 0x72, 0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28, 0x55, 0x00,
+ 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18,
+ 0x4b, 0x1a, 0x44, 0x17, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x00, 0x00, 0x00, 0xfc, 0x00, 0x53, 0x41, 0x4d, 0x53, 0x55, 0x4e, 0x47,
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0xbc, 0x02, 0x03, 0x1e, 0xf1,
+ 0x46, 0x84, 0x05, 0x03, 0x10, 0x20, 0x22, 0x23, 0x09, 0x07, 0x07, 0x83,
+ 0x01, 0x00, 0x00, 0xe2, 0x00, 0x0f, 0x67, 0x03, 0x0c, 0x00, 0x10, 0x00,
+ 0xb8, 0x2d, 0x01, 0x1d, 0x80, 0x18, 0x71, 0x1c, 0x16, 0x20, 0x58, 0x2c,
+ 0x25, 0x00, 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x9e, 0x8c, 0x0a, 0xd0, 0x8a,
+ 0x20, 0xe0, 0x2d, 0x10, 0x10, 0x3e, 0x96, 0x00, 0xa0, 0x5a, 0x00, 0x00,
+ 0x00, 0x18, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40, 0x58, 0x2c,
+ 0x45, 0x00, 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06
+};
+
+static const uint32_t formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_NV12,
+};
+
+static const enum drm_plane_type vidi_win_types[WINDOWS_NR] = {
+ DRM_PLANE_TYPE_PRIMARY,
+ DRM_PLANE_TYPE_OVERLAY,
+ DRM_PLANE_TYPE_CURSOR,
+};
+
+static int vidi_enable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct vidi_context *ctx = crtc->ctx;
+
+ if (ctx->suspended)
+ return -EPERM;
+
+ mod_timer(&ctx->timer,
+ jiffies + msecs_to_jiffies(VIDI_REFRESH_TIME) - 1);
+
+ return 0;
+}
+
+static void vidi_disable_vblank(struct exynos_drm_crtc *crtc)
+{
+}
+
+static void vidi_update_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct drm_plane_state *state = plane->base.state;
+ struct vidi_context *ctx = crtc->ctx;
+ dma_addr_t addr;
+
+ if (ctx->suspended)
+ return;
+
+ addr = exynos_drm_fb_dma_addr(state->fb, 0);
+ DRM_DEV_DEBUG_KMS(ctx->dev, "dma_addr = %pad\n", &addr);
+}
+
+static void vidi_atomic_enable(struct exynos_drm_crtc *crtc)
+{
+ struct vidi_context *ctx = crtc->ctx;
+
+ mutex_lock(&ctx->lock);
+
+ ctx->suspended = false;
+
+ mutex_unlock(&ctx->lock);
+
+ drm_crtc_vblank_on(&crtc->base);
+}
+
+static void vidi_atomic_disable(struct exynos_drm_crtc *crtc)
+{
+ struct vidi_context *ctx = crtc->ctx;
+
+ drm_crtc_vblank_off(&crtc->base);
+
+ mutex_lock(&ctx->lock);
+
+ ctx->suspended = true;
+
+ mutex_unlock(&ctx->lock);
+}
+
+static const struct exynos_drm_crtc_ops vidi_crtc_ops = {
+ .atomic_enable = vidi_atomic_enable,
+ .atomic_disable = vidi_atomic_disable,
+ .enable_vblank = vidi_enable_vblank,
+ .disable_vblank = vidi_disable_vblank,
+ .update_plane = vidi_update_plane,
+ .atomic_flush = exynos_crtc_handle_event,
+};
+
+static void vidi_fake_vblank_timer(struct timer_list *t)
+{
+ struct vidi_context *ctx = timer_container_of(ctx, t, timer);
+
+ if (drm_crtc_handle_vblank(&ctx->crtc->base))
+ mod_timer(&ctx->timer,
+ jiffies + msecs_to_jiffies(VIDI_REFRESH_TIME) - 1);
+}
+
+static ssize_t vidi_show_connection(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vidi_context *ctx = dev_get_drvdata(dev);
+ int rc;
+
+ mutex_lock(&ctx->lock);
+
+ rc = sprintf(buf, "%d\n", ctx->connected);
+
+ mutex_unlock(&ctx->lock);
+
+ return rc;
+}
+
+static ssize_t vidi_store_connection(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct vidi_context *ctx = dev_get_drvdata(dev);
+ int ret, new_connected;
+
+ ret = kstrtoint(buf, 0, &new_connected);
+ if (ret)
+ return ret;
+ if (new_connected > 1)
+ return -EINVAL;
+
+ mutex_lock(&ctx->lock);
+
+ /*
+ * Use fake edid data for test. If raw_edid is set then it can't be
+ * tested.
+ */
+ if (ctx->raw_edid) {
+ DRM_DEV_DEBUG_KMS(dev, "edid data is not fake data.\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ctx->connected = new_connected;
+ mutex_unlock(&ctx->lock);
+
+ DRM_DEV_DEBUG_KMS(dev, "requested connection.\n");
+
+ drm_helper_hpd_irq_event(ctx->drm_dev);
+
+ return len;
+fail:
+ mutex_unlock(&ctx->lock);
+ return ret;
+}
+
+static DEVICE_ATTR(connection, 0644, vidi_show_connection,
+ vidi_store_connection);
+
+static struct attribute *vidi_attrs[] = {
+ &dev_attr_connection.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(vidi);
+
+int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ struct device *dev = priv ? priv->vidi_dev : NULL;
+ struct vidi_context *ctx = dev ? dev_get_drvdata(dev) : NULL;
+ struct drm_exynos_vidi_connection *vidi = data;
+
+ if (!ctx)
+ return -ENODEV;
+
+ if (!vidi) {
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "user data for vidi is null.\n");
+ return -EINVAL;
+ }
+
+ if (vidi->connection > 1) {
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "connection should be 0 or 1.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&ctx->lock);
+ if (ctx->connected == vidi->connection) {
+ mutex_unlock(&ctx->lock);
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "same connection request.\n");
+ return -EINVAL;
+ }
+ mutex_unlock(&ctx->lock);
+
+ if (vidi->connection) {
+ const struct drm_edid *drm_edid;
+ const void __user *edid_userptr = u64_to_user_ptr(vidi->edid);
+ void *edid_buf;
+ struct edid hdr;
+ size_t size;
+
+ if (copy_from_user(&hdr, edid_userptr, sizeof(hdr)))
+ return -EFAULT;
+
+ size = (hdr.extensions + 1) * EDID_LENGTH;
+
+ edid_buf = kmalloc(size, GFP_KERNEL);
+ if (!edid_buf)
+ return -ENOMEM;
+
+ if (copy_from_user(edid_buf, edid_userptr, size)) {
+ kfree(edid_buf);
+ return -EFAULT;
+ }
+
+ drm_edid = drm_edid_alloc(edid_buf, size);
+ kfree(edid_buf);
+ if (!drm_edid)
+ return -ENOMEM;
+
+ if (!drm_edid_valid(drm_edid)) {
+ drm_edid_free(drm_edid);
+ DRM_DEV_DEBUG_KMS(ctx->dev,
+ "edid data is invalid.\n");
+ return -EINVAL;
+ }
+ mutex_lock(&ctx->lock);
+ ctx->raw_edid = drm_edid;
+ mutex_unlock(&ctx->lock);
+ } else {
+ /* with connection = 0, free raw_edid */
+ mutex_lock(&ctx->lock);
+ drm_edid_free(ctx->raw_edid);
+ ctx->raw_edid = NULL;
+ mutex_unlock(&ctx->lock);
+ }
+
+ mutex_lock(&ctx->lock);
+ ctx->connected = vidi->connection;
+ mutex_unlock(&ctx->lock);
+
+ drm_helper_hpd_irq_event(ctx->drm_dev);
+
+ return 0;
+}
+
+static enum drm_connector_status vidi_detect(struct drm_connector *connector,
+ bool force)
+{
+ struct vidi_context *ctx = ctx_from_connector(connector);
+
+ /*
+ * connection request would come from user side
+ * to do hotplug through specific ioctl.
+ */
+ return READ_ONCE(ctx->connected) ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static void vidi_connector_destroy(struct drm_connector *connector)
+{
+}
+
+static const struct drm_connector_funcs vidi_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = vidi_detect,
+ .destroy = vidi_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 vidi_get_modes(struct drm_connector *connector)
+{
+ struct vidi_context *ctx = ctx_from_connector(connector);
+ const struct drm_edid *drm_edid;
+ int count;
+
+ mutex_lock(&ctx->lock);
+
+ if (ctx->raw_edid)
+ drm_edid = drm_edid_dup(ctx->raw_edid);
+ else
+ drm_edid = drm_edid_alloc(fake_edid_info, sizeof(fake_edid_info));
+
+ mutex_unlock(&ctx->lock);
+
+ drm_edid_connector_update(connector, drm_edid);
+
+ count = drm_edid_connector_add_modes(connector);
+
+ drm_edid_free(drm_edid);
+
+ return count;
+}
+
+static const struct drm_connector_helper_funcs vidi_connector_helper_funcs = {
+ .get_modes = vidi_get_modes,
+};
+
+static int vidi_create_connector(struct drm_encoder *encoder)
+{
+ struct vidi_context *ctx = encoder_to_vidi(encoder);
+ struct drm_connector *connector = &ctx->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(ctx->drm_dev, connector,
+ &vidi_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret) {
+ DRM_DEV_ERROR(ctx->dev,
+ "Failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &vidi_connector_helper_funcs);
+ drm_connector_attach_encoder(connector, encoder);
+
+ return 0;
+}
+
+static void exynos_vidi_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void exynos_vidi_enable(struct drm_encoder *encoder)
+{
+}
+
+static void exynos_vidi_disable(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_funcs exynos_vidi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_encoder_helper_funcs exynos_vidi_encoder_helper_funcs = {
+ .mode_set = exynos_vidi_mode_set,
+ .enable = exynos_vidi_enable,
+ .disable = exynos_vidi_disable,
+};
+
+static int vidi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct vidi_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+ struct drm_encoder *encoder = &ctx->encoder;
+ struct exynos_drm_plane *exynos_plane;
+ struct exynos_drm_plane_config plane_config = { 0 };
+ unsigned int i;
+ int ret;
+
+ ctx->drm_dev = drm_dev;
+ if (priv)
+ priv->vidi_dev = dev;
+
+ plane_config.pixel_formats = formats;
+ plane_config.num_pixel_formats = ARRAY_SIZE(formats);
+
+ for (i = 0; i < WINDOWS_NR; i++) {
+ plane_config.zpos = i;
+ plane_config.type = vidi_win_types[i];
+
+ ret = exynos_plane_init(drm_dev, &ctx->planes[i], i,
+ &plane_config);
+ if (ret)
+ return ret;
+ }
+
+ exynos_plane = &ctx->planes[DEFAULT_WIN];
+ ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
+ EXYNOS_DISPLAY_TYPE_VIDI, &vidi_crtc_ops, ctx);
+ if (IS_ERR(ctx->crtc)) {
+ DRM_DEV_ERROR(dev, "failed to create crtc.\n");
+ return PTR_ERR(ctx->crtc);
+ }
+
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_vidi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to initialize encoder ret = %d\n",
+ ret);
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &exynos_vidi_encoder_helper_funcs);
+
+ ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_VIDI);
+ if (ret < 0)
+ return ret;
+
+ ret = vidi_create_connector(encoder);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to create connector ret = %d\n",
+ ret);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void vidi_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vidi_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_private *priv = drm_dev->dev_private;
+
+ timer_delete_sync(&ctx->timer);
+ if (priv)
+ priv->vidi_dev = NULL;
+}
+
+static const struct component_ops vidi_component_ops = {
+ .bind = vidi_bind,
+ .unbind = vidi_unbind,
+};
+
+static int vidi_probe(struct platform_device *pdev)
+{
+ struct vidi_context *ctx;
+ struct device *dev = &pdev->dev;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+
+ timer_setup(&ctx->timer, vidi_fake_vblank_timer, 0);
+
+ mutex_init(&ctx->lock);
+
+ platform_set_drvdata(pdev, ctx);
+
+ return component_add(dev, &vidi_component_ops);
+}
+
+static void vidi_remove(struct platform_device *pdev)
+{
+ struct vidi_context *ctx = platform_get_drvdata(pdev);
+
+ mutex_lock(&ctx->lock);
+
+ drm_edid_free(ctx->raw_edid);
+ ctx->raw_edid = NULL;
+
+ mutex_unlock(&ctx->lock);
+
+ component_del(&pdev->dev, &vidi_component_ops);
+}
+
+struct platform_driver vidi_driver = {
+ .probe = vidi_probe,
+ .remove = vidi_remove,
+ .driver = {
+ .name = "exynos-drm-vidi",
+ .dev_groups = vidi_groups,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.h b/drivers/gpu/drm/exynos/exynos_drm_vidi.h
new file mode 100644
index 000000000..38a103be3
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* exynos_drm_vidi.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Author: Inki Dae <inki.dae@samsung.com>
+ */
+
+#ifndef _EXYNOS_DRM_VIDI_H_
+#define _EXYNOS_DRM_VIDI_H_
+
+#ifdef CONFIG_DRM_EXYNOS_VIDI
+int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file_priv);
+#else
+#define vidi_connection_ioctl NULL
+#endif
+
+#endif
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
new file mode 100644
index 000000000..d4fce39c2
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -0,0 +1,2149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Authors:
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ * Based on drivers/media/video/s5p-tv/hdmi_drv.c
+ */
+
+#include <drm/exynos_drm.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hdmi.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/wait.h>
+
+#include <sound/hdmi-codec.h>
+#include <media/cec-notifier.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "exynos_drm_crtc.h"
+#include "regs-hdmi.h"
+
+#define HOTPLUG_DEBOUNCE_MS 1100
+
+enum hdmi_type {
+ HDMI_TYPE13,
+ HDMI_TYPE14,
+ HDMI_TYPE_COUNT
+};
+
+#define HDMI_MAPPED_BASE 0xffff0000
+
+enum hdmi_mapped_regs {
+ HDMI_PHY_STATUS = HDMI_MAPPED_BASE,
+ HDMI_PHY_RSTOUT,
+ HDMI_ACR_CON,
+ HDMI_ACR_MCTS0,
+ HDMI_ACR_CTS0,
+ HDMI_ACR_N0
+};
+
+static const u32 hdmi_reg_map[][HDMI_TYPE_COUNT] = {
+ { HDMI_V13_PHY_STATUS, HDMI_PHY_STATUS_0 },
+ { HDMI_V13_PHY_RSTOUT, HDMI_V14_PHY_RSTOUT },
+ { HDMI_V13_ACR_CON, HDMI_V14_ACR_CON },
+ { HDMI_V13_ACR_MCTS0, HDMI_V14_ACR_MCTS0 },
+ { HDMI_V13_ACR_CTS0, HDMI_V14_ACR_CTS0 },
+ { HDMI_V13_ACR_N0, HDMI_V14_ACR_N0 },
+};
+
+static const char * const supply[] = {
+ "vdd",
+ "vdd_osc",
+ "vdd_pll",
+};
+
+struct hdmiphy_config {
+ int pixel_clock;
+ u8 conf[32];
+};
+
+struct hdmiphy_configs {
+ int count;
+ const struct hdmiphy_config *data;
+};
+
+struct string_array_spec {
+ int count;
+ const char * const *data;
+};
+
+#define INIT_ARRAY_SPEC(a) { .count = ARRAY_SIZE(a), .data = a }
+
+struct hdmi_driver_data {
+ unsigned int type;
+ unsigned int is_apb_phy:1;
+ unsigned int has_sysreg:1;
+ struct hdmiphy_configs phy_confs;
+ struct string_array_spec clk_gates;
+ /*
+ * Array of triplets (p_off, p_on, clock), where p_off and p_on are
+ * required parents of clock when HDMI-PHY is respectively off or on.
+ */
+ struct string_array_spec clk_muxes;
+};
+
+struct hdmi_audio {
+ struct platform_device *pdev;
+ struct hdmi_audio_infoframe infoframe;
+ struct hdmi_codec_params params;
+ bool mute;
+};
+
+struct hdmi_context {
+ struct drm_encoder encoder;
+ struct device *dev;
+ struct drm_device *drm_dev;
+ struct drm_connector connector;
+ bool dvi_mode;
+ struct delayed_work hotplug_work;
+ struct cec_notifier *notifier;
+ const struct hdmi_driver_data *drv_data;
+
+ void __iomem *regs;
+ void __iomem *regs_hdmiphy;
+ struct i2c_client *hdmiphy_port;
+ struct i2c_adapter *ddc_adpt;
+ struct gpio_desc *hpd_gpio;
+ int irq;
+ struct regmap *pmureg;
+ struct regmap *sysreg;
+ struct clk **clk_gates;
+ struct clk **clk_muxes;
+ struct regulator_bulk_data regul_bulk[ARRAY_SIZE(supply)];
+ struct regulator *reg_hdmi_en;
+ struct exynos_drm_clk phy_clk;
+ struct drm_bridge *bridge;
+
+ /* mutex protecting subsequent fields below */
+ struct mutex mutex;
+ struct hdmi_audio audio;
+ bool powered;
+};
+
+static inline struct hdmi_context *encoder_to_hdmi(struct drm_encoder *e)
+{
+ return container_of(e, struct hdmi_context, encoder);
+}
+
+static inline struct hdmi_context *connector_to_hdmi(struct drm_connector *c)
+{
+ return container_of(c, struct hdmi_context, connector);
+}
+
+static const struct hdmiphy_config hdmiphy_v13_configs[] = {
+ {
+ .pixel_clock = 27000000,
+ .conf = {
+ 0x01, 0x05, 0x00, 0xD8, 0x10, 0x1C, 0x30, 0x40,
+ 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87,
+ 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0,
+ 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 27027000,
+ .conf = {
+ 0x01, 0x05, 0x00, 0xD4, 0x10, 0x9C, 0x09, 0x64,
+ 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87,
+ 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0,
+ 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74176000,
+ .conf = {
+ 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xef, 0x5B,
+ 0x6D, 0x10, 0x01, 0x51, 0xef, 0xF3, 0x54, 0xb9,
+ 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0,
+ 0x22, 0x40, 0xa5, 0x26, 0x01, 0x00, 0x00, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74250000,
+ .conf = {
+ 0x01, 0x05, 0x00, 0xd8, 0x10, 0x9c, 0xf8, 0x40,
+ 0x6a, 0x10, 0x01, 0x51, 0xff, 0xf1, 0x54, 0xba,
+ 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xe0,
+ 0x22, 0x40, 0xa4, 0x26, 0x01, 0x00, 0x00, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 148500000,
+ .conf = {
+ 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xf8, 0x40,
+ 0x6A, 0x18, 0x00, 0x51, 0xff, 0xF1, 0x54, 0xba,
+ 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xE0,
+ 0x22, 0x40, 0xa4, 0x26, 0x02, 0x00, 0x00, 0x80,
+ },
+ },
+};
+
+static const struct hdmiphy_config hdmiphy_v14_configs[] = {
+ {
+ .pixel_clock = 25200000,
+ .conf = {
+ 0x01, 0x51, 0x2A, 0x75, 0x40, 0x01, 0x00, 0x08,
+ 0x82, 0x80, 0xfc, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 27000000,
+ .conf = {
+ 0x01, 0xd1, 0x22, 0x51, 0x40, 0x08, 0xfc, 0x20,
+ 0x98, 0xa0, 0xcb, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xe4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 27027000,
+ .conf = {
+ 0x01, 0xd1, 0x2d, 0x72, 0x40, 0x64, 0x12, 0x08,
+ 0x43, 0xa0, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 36000000,
+ .conf = {
+ 0x01, 0x51, 0x2d, 0x55, 0x40, 0x01, 0x00, 0x08,
+ 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xab, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 40000000,
+ .conf = {
+ 0x01, 0x51, 0x32, 0x55, 0x40, 0x01, 0x00, 0x08,
+ 0x82, 0x80, 0x2c, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x9a, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 65000000,
+ .conf = {
+ 0x01, 0xd1, 0x36, 0x34, 0x40, 0x1e, 0x0a, 0x08,
+ 0x82, 0xa0, 0x45, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 71000000,
+ .conf = {
+ 0x01, 0xd1, 0x3b, 0x35, 0x40, 0x0c, 0x04, 0x08,
+ 0x85, 0xa0, 0x63, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xad, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 73250000,
+ .conf = {
+ 0x01, 0xd1, 0x3d, 0x35, 0x40, 0x18, 0x02, 0x08,
+ 0x83, 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xa8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74176000,
+ .conf = {
+ 0x01, 0xd1, 0x3e, 0x35, 0x40, 0x5b, 0xde, 0x08,
+ 0x82, 0xa0, 0x73, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x56, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74250000,
+ .conf = {
+ 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08,
+ 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 83500000,
+ .conf = {
+ 0x01, 0xd1, 0x23, 0x11, 0x40, 0x0c, 0xfb, 0x08,
+ 0x85, 0xa0, 0xd1, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 85500000,
+ .conf = {
+ 0x01, 0xd1, 0x24, 0x11, 0x40, 0x40, 0xd0, 0x08,
+ 0x84, 0xa0, 0xd6, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x90, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 106500000,
+ .conf = {
+ 0x01, 0xd1, 0x2c, 0x12, 0x40, 0x0c, 0x09, 0x08,
+ 0x84, 0xa0, 0x0a, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 108000000,
+ .conf = {
+ 0x01, 0x51, 0x2d, 0x15, 0x40, 0x01, 0x00, 0x08,
+ 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 115500000,
+ .conf = {
+ 0x01, 0xd1, 0x30, 0x12, 0x40, 0x40, 0x10, 0x08,
+ 0x80, 0x80, 0x21, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0xaa, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 119000000,
+ .conf = {
+ 0x01, 0xd1, 0x32, 0x1a, 0x40, 0x30, 0xd8, 0x08,
+ 0x04, 0xa0, 0x2a, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x9d, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 146250000,
+ .conf = {
+ 0x01, 0xd1, 0x3d, 0x15, 0x40, 0x18, 0xfd, 0x08,
+ 0x83, 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80,
+ 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x50, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 148500000,
+ .conf = {
+ 0x01, 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0x08,
+ 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80,
+ 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
+ 0x54, 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+};
+
+static const struct hdmiphy_config hdmiphy_5420_configs[] = {
+ {
+ .pixel_clock = 25200000,
+ .conf = {
+ 0x01, 0x52, 0x3F, 0x55, 0x40, 0x01, 0x00, 0xC8,
+ 0x82, 0xC8, 0xBD, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x06, 0x80, 0x01, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xF4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 27000000,
+ .conf = {
+ 0x01, 0xD1, 0x22, 0x51, 0x40, 0x08, 0xFC, 0xE0,
+ 0x98, 0xE8, 0xCB, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x06, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xE4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 27027000,
+ .conf = {
+ 0x01, 0xD1, 0x2D, 0x72, 0x40, 0x64, 0x12, 0xC8,
+ 0x43, 0xE8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x26, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xE3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 36000000,
+ .conf = {
+ 0x01, 0x51, 0x2D, 0x55, 0x40, 0x40, 0x00, 0xC8,
+ 0x02, 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xAB, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 40000000,
+ .conf = {
+ 0x01, 0xD1, 0x21, 0x31, 0x40, 0x3C, 0x28, 0xC8,
+ 0x87, 0xE8, 0xC8, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x9A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 65000000,
+ .conf = {
+ 0x01, 0xD1, 0x36, 0x34, 0x40, 0x0C, 0x04, 0xC8,
+ 0x82, 0xE8, 0x45, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xBD, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 71000000,
+ .conf = {
+ 0x01, 0xD1, 0x3B, 0x35, 0x40, 0x0C, 0x04, 0xC8,
+ 0x85, 0xE8, 0x63, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x57, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 73250000,
+ .conf = {
+ 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x78, 0x8D, 0xC8,
+ 0x81, 0xE8, 0xB7, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x56, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xA8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74176000,
+ .conf = {
+ 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x5B, 0xEF, 0xC8,
+ 0x81, 0xE8, 0xB9, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x56, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xA6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 74250000,
+ .conf = {
+ 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x40, 0xF8, 0x08,
+ 0x81, 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x26, 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x66,
+ 0x54, 0xA5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 83500000,
+ .conf = {
+ 0x01, 0xD1, 0x23, 0x11, 0x40, 0x0C, 0xFB, 0xC8,
+ 0x85, 0xE8, 0xD1, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x4A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 88750000,
+ .conf = {
+ 0x01, 0xD1, 0x25, 0x11, 0x40, 0x18, 0xFF, 0xC8,
+ 0x83, 0xE8, 0xDE, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x45, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 106500000,
+ .conf = {
+ 0x01, 0xD1, 0x2C, 0x12, 0x40, 0x0C, 0x09, 0xC8,
+ 0x84, 0xE8, 0x0A, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 108000000,
+ .conf = {
+ 0x01, 0x51, 0x2D, 0x15, 0x40, 0x01, 0x00, 0xC8,
+ 0x82, 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0xC7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 115500000,
+ .conf = {
+ 0x01, 0xD1, 0x30, 0x14, 0x40, 0x0C, 0x03, 0xC8,
+ 0x88, 0xE8, 0x21, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x6A, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 146250000,
+ .conf = {
+ 0x01, 0xD1, 0x3D, 0x15, 0x40, 0x18, 0xFD, 0xC8,
+ 0x83, 0xE8, 0x6E, 0xD9, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66,
+ 0x54, 0x54, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 148500000,
+ .conf = {
+ 0x01, 0xD1, 0x1F, 0x00, 0x40, 0x40, 0xF8, 0x08,
+ 0x81, 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x26, 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x66,
+ 0x54, 0x4B, 0x25, 0x03, 0x00, 0x80, 0x01, 0x80,
+ },
+ },
+ {
+ .pixel_clock = 154000000,
+ .conf = {
+ 0x01, 0xD1, 0x20, 0x01, 0x40, 0x30, 0x08, 0xCC,
+ 0x8C, 0xE8, 0xC1, 0xD8, 0x45, 0xA0, 0xAC, 0x80,
+ 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x86,
+ 0x54, 0x3F, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
+ },
+ },
+};
+
+static const struct hdmiphy_config hdmiphy_5433_configs[] = {
+ {
+ .pixel_clock = 27000000,
+ .conf = {
+ 0x01, 0x51, 0x2d, 0x75, 0x01, 0x00, 0x88, 0x02,
+ 0x72, 0x50, 0x44, 0x8c, 0x27, 0x00, 0x7c, 0xac,
+ 0xd6, 0x2b, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 27027000,
+ .conf = {
+ 0x01, 0x51, 0x2d, 0x72, 0x64, 0x09, 0x88, 0xc3,
+ 0x71, 0x50, 0x44, 0x8c, 0x27, 0x00, 0x7c, 0xac,
+ 0xd6, 0x2b, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 40000000,
+ .conf = {
+ 0x01, 0x51, 0x32, 0x55, 0x01, 0x00, 0x88, 0x02,
+ 0x4d, 0x50, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 50000000,
+ .conf = {
+ 0x01, 0x51, 0x34, 0x40, 0x64, 0x09, 0x88, 0xc3,
+ 0x3d, 0x50, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 65000000,
+ .conf = {
+ 0x01, 0x51, 0x36, 0x31, 0x40, 0x10, 0x04, 0xc6,
+ 0x2e, 0xe8, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 74176000,
+ .conf = {
+ 0x01, 0x51, 0x3E, 0x35, 0x5B, 0xDE, 0x88, 0x42,
+ 0x53, 0x51, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 74250000,
+ .conf = {
+ 0x01, 0x51, 0x3E, 0x35, 0x40, 0xF0, 0x88, 0xC2,
+ 0x52, 0x51, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 108000000,
+ .conf = {
+ 0x01, 0x51, 0x2d, 0x15, 0x01, 0x00, 0x88, 0x02,
+ 0x72, 0x52, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 148500000,
+ .conf = {
+ 0x01, 0x51, 0x1f, 0x00, 0x40, 0xf8, 0x88, 0xc1,
+ 0x52, 0x52, 0x24, 0x0c, 0x24, 0x0f, 0x7c, 0xa5,
+ 0xd4, 0x2b, 0x87, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x4a, 0x00, 0x40,
+ },
+ },
+ {
+ .pixel_clock = 297000000,
+ .conf = {
+ 0x01, 0x51, 0x3E, 0x05, 0x40, 0xF0, 0x88, 0xC2,
+ 0x52, 0x53, 0x44, 0x8C, 0x27, 0x00, 0x7C, 0xAC,
+ 0xD6, 0x2B, 0x67, 0x00, 0x00, 0x04, 0x00, 0x30,
+ 0x08, 0x10, 0x01, 0x01, 0x48, 0x40, 0x00, 0x40,
+ },
+ },
+};
+
+static const char * const hdmi_clk_gates4[] = {
+ "hdmi", "sclk_hdmi"
+};
+
+static const char * const hdmi_clk_muxes4[] = {
+ "sclk_pixel", "sclk_hdmiphy", "mout_hdmi"
+};
+
+static const char * const hdmi_clk_gates5433[] = {
+ "hdmi_pclk", "hdmi_i_pclk", "i_tmds_clk", "i_pixel_clk", "i_spdif_clk"
+};
+
+static const char * const hdmi_clk_muxes5433[] = {
+ "oscclk", "tmds_clko", "tmds_clko_user",
+ "oscclk", "pixel_clko", "pixel_clko_user"
+};
+
+static const struct hdmi_driver_data exynos4210_hdmi_driver_data = {
+ .type = HDMI_TYPE13,
+ .phy_confs = INIT_ARRAY_SPEC(hdmiphy_v13_configs),
+ .clk_gates = INIT_ARRAY_SPEC(hdmi_clk_gates4),
+ .clk_muxes = INIT_ARRAY_SPEC(hdmi_clk_muxes4),
+};
+
+static const struct hdmi_driver_data exynos4212_hdmi_driver_data = {
+ .type = HDMI_TYPE14,
+ .phy_confs = INIT_ARRAY_SPEC(hdmiphy_v14_configs),
+ .clk_gates = INIT_ARRAY_SPEC(hdmi_clk_gates4),
+ .clk_muxes = INIT_ARRAY_SPEC(hdmi_clk_muxes4),
+};
+
+static const struct hdmi_driver_data exynos5420_hdmi_driver_data = {
+ .type = HDMI_TYPE14,
+ .is_apb_phy = 1,
+ .phy_confs = INIT_ARRAY_SPEC(hdmiphy_5420_configs),
+ .clk_gates = INIT_ARRAY_SPEC(hdmi_clk_gates4),
+ .clk_muxes = INIT_ARRAY_SPEC(hdmi_clk_muxes4),
+};
+
+static const struct hdmi_driver_data exynos5433_hdmi_driver_data = {
+ .type = HDMI_TYPE14,
+ .is_apb_phy = 1,
+ .has_sysreg = 1,
+ .phy_confs = INIT_ARRAY_SPEC(hdmiphy_5433_configs),
+ .clk_gates = INIT_ARRAY_SPEC(hdmi_clk_gates5433),
+ .clk_muxes = INIT_ARRAY_SPEC(hdmi_clk_muxes5433),
+};
+
+static inline u32 hdmi_map_reg(struct hdmi_context *hdata, u32 reg_id)
+{
+ if ((reg_id & 0xffff0000) == HDMI_MAPPED_BASE)
+ return hdmi_reg_map[reg_id & 0xffff][hdata->drv_data->type];
+ return reg_id;
+}
+
+static inline u32 hdmi_reg_read(struct hdmi_context *hdata, u32 reg_id)
+{
+ return readl(hdata->regs + hdmi_map_reg(hdata, reg_id));
+}
+
+static inline void hdmi_reg_writeb(struct hdmi_context *hdata,
+ u32 reg_id, u8 value)
+{
+ writel(value, hdata->regs + hdmi_map_reg(hdata, reg_id));
+}
+
+static inline void hdmi_reg_writev(struct hdmi_context *hdata, u32 reg_id,
+ int bytes, u32 val)
+{
+ reg_id = hdmi_map_reg(hdata, reg_id);
+
+ while (--bytes >= 0) {
+ writel(val & 0xff, hdata->regs + reg_id);
+ val >>= 8;
+ reg_id += 4;
+ }
+}
+
+static inline void hdmi_reg_write_buf(struct hdmi_context *hdata, u32 reg_id,
+ u8 *buf, int size)
+{
+ for (reg_id = hdmi_map_reg(hdata, reg_id); size; --size, reg_id += 4)
+ writel(*buf++, hdata->regs + reg_id);
+}
+
+static inline void hdmi_reg_writemask(struct hdmi_context *hdata,
+ u32 reg_id, u32 value, u32 mask)
+{
+ u32 old;
+
+ reg_id = hdmi_map_reg(hdata, reg_id);
+ old = readl(hdata->regs + reg_id);
+ value = (value & mask) | (old & ~mask);
+ writel(value, hdata->regs + reg_id);
+}
+
+static int hdmiphy_reg_write_buf(struct hdmi_context *hdata,
+ u32 reg_offset, const u8 *buf, u32 len)
+{
+ if ((reg_offset + len) > 32)
+ return -EINVAL;
+
+ if (hdata->hdmiphy_port) {
+ int ret;
+
+ ret = i2c_master_send(hdata->hdmiphy_port, buf, len);
+ if (ret == len)
+ return 0;
+ return ret;
+ } else {
+ int i;
+ for (i = 0; i < len; i++)
+ writel(buf[i], hdata->regs_hdmiphy +
+ ((reg_offset + i)<<2));
+ return 0;
+ }
+}
+
+static int hdmi_clk_enable_gates(struct hdmi_context *hdata)
+{
+ int i, ret;
+
+ for (i = 0; i < hdata->drv_data->clk_gates.count; ++i) {
+ ret = clk_prepare_enable(hdata->clk_gates[i]);
+ if (!ret)
+ continue;
+
+ dev_err(hdata->dev, "Cannot enable clock '%s', %d\n",
+ hdata->drv_data->clk_gates.data[i], ret);
+ while (i--)
+ clk_disable_unprepare(hdata->clk_gates[i]);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void hdmi_clk_disable_gates(struct hdmi_context *hdata)
+{
+ int i = hdata->drv_data->clk_gates.count;
+
+ while (i--)
+ clk_disable_unprepare(hdata->clk_gates[i]);
+}
+
+static int hdmi_clk_set_parents(struct hdmi_context *hdata, bool to_phy)
+{
+ struct device *dev = hdata->dev;
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < hdata->drv_data->clk_muxes.count; i += 3) {
+ struct clk **c = &hdata->clk_muxes[i];
+
+ ret = clk_set_parent(c[2], c[to_phy]);
+ if (!ret)
+ continue;
+
+ dev_err(dev, "Cannot set clock parent of '%s' to '%s', %d\n",
+ hdata->drv_data->clk_muxes.data[i + 2],
+ hdata->drv_data->clk_muxes.data[i + to_phy], ret);
+ }
+
+ return ret;
+}
+
+static int hdmi_audio_infoframe_apply(struct hdmi_context *hdata)
+{
+ struct hdmi_audio_infoframe *infoframe = &hdata->audio.infoframe;
+ u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)];
+ int len;
+
+ len = hdmi_audio_infoframe_pack(infoframe, buf, sizeof(buf));
+ if (len < 0)
+ return len;
+
+ hdmi_reg_writeb(hdata, HDMI_AUI_CON, HDMI_AUI_CON_EVERY_VSYNC);
+ hdmi_reg_write_buf(hdata, HDMI_AUI_HEADER0, buf, len);
+
+ return 0;
+}
+
+static void hdmi_reg_infoframes(struct hdmi_context *hdata)
+{
+ struct drm_display_mode *m = &hdata->encoder.crtc->state->mode;
+ union hdmi_infoframe frm;
+ u8 buf[25];
+ int ret;
+
+ if (hdata->dvi_mode) {
+ hdmi_reg_writeb(hdata, HDMI_AVI_CON,
+ HDMI_AVI_CON_DO_NOT_TRANSMIT);
+ hdmi_reg_writeb(hdata, HDMI_VSI_CON,
+ HDMI_VSI_CON_DO_NOT_TRANSMIT);
+ hdmi_reg_writeb(hdata, HDMI_AUI_CON, HDMI_AUI_CON_NO_TRAN);
+ return;
+ }
+
+ ret = drm_hdmi_avi_infoframe_from_display_mode(&frm.avi,
+ &hdata->connector, m);
+ if (!ret)
+ ret = hdmi_avi_infoframe_pack(&frm.avi, buf, sizeof(buf));
+ if (ret > 0) {
+ hdmi_reg_writeb(hdata, HDMI_AVI_CON, HDMI_AVI_CON_EVERY_VSYNC);
+ hdmi_reg_write_buf(hdata, HDMI_AVI_HEADER0, buf, ret);
+ } else {
+ DRM_INFO("%s: invalid AVI infoframe (%d)\n", __func__, ret);
+ }
+
+ ret = drm_hdmi_vendor_infoframe_from_display_mode(&frm.vendor.hdmi,
+ &hdata->connector, m);
+ if (!ret)
+ ret = hdmi_vendor_infoframe_pack(&frm.vendor.hdmi, buf,
+ sizeof(buf));
+ if (ret > 0) {
+ hdmi_reg_writeb(hdata, HDMI_VSI_CON, HDMI_VSI_CON_EVERY_VSYNC);
+ hdmi_reg_write_buf(hdata, HDMI_VSI_HEADER0, buf, 3);
+ hdmi_reg_write_buf(hdata, HDMI_VSI_DATA(0), buf + 3, ret - 3);
+ }
+
+ hdmi_audio_infoframe_apply(hdata);
+}
+
+static enum drm_connector_status hdmi_detect(struct drm_connector *connector,
+ bool force)
+{
+ struct hdmi_context *hdata = connector_to_hdmi(connector);
+
+ if (gpiod_get_value(hdata->hpd_gpio))
+ return connector_status_connected;
+
+ cec_notifier_set_phys_addr(hdata->notifier, CEC_PHYS_ADDR_INVALID);
+ return connector_status_disconnected;
+}
+
+static void hdmi_connector_destroy(struct drm_connector *connector)
+{
+ struct hdmi_context *hdata = connector_to_hdmi(connector);
+
+ cec_notifier_conn_unregister(hdata->notifier);
+
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs hdmi_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = hdmi_detect,
+ .destroy = 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 hdmi_get_modes(struct drm_connector *connector)
+{
+ struct hdmi_context *hdata = connector_to_hdmi(connector);
+ const struct drm_display_info *info = &connector->display_info;
+ const struct drm_edid *drm_edid;
+ int ret;
+
+ if (!hdata->ddc_adpt)
+ goto no_edid;
+
+ drm_edid = drm_edid_read_ddc(connector, hdata->ddc_adpt);
+
+ ret = drm_edid_connector_update(connector, drm_edid);
+ if (ret)
+ return 0;
+
+ cec_notifier_set_phys_addr(hdata->notifier, info->source_physical_address);
+
+ if (!drm_edid)
+ goto no_edid;
+
+ hdata->dvi_mode = !info->is_hdmi;
+ DRM_DEV_DEBUG_KMS(hdata->dev, "%s : width[%d] x height[%d]\n",
+ (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
+ info->width_mm / 10, info->height_mm / 10);
+
+ ret = drm_edid_connector_add_modes(connector);
+
+ drm_edid_free(drm_edid);
+
+ return ret;
+
+no_edid:
+ return drm_add_modes_noedid(connector, 640, 480);
+}
+
+static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock)
+{
+ const struct hdmiphy_configs *confs = &hdata->drv_data->phy_confs;
+ int i;
+
+ for (i = 0; i < confs->count; i++)
+ if (confs->data[i].pixel_clock == pixel_clock)
+ return i;
+
+ DRM_DEV_DEBUG_KMS(hdata->dev, "Could not find phy config for %d\n",
+ pixel_clock);
+ return -EINVAL;
+}
+
+static enum drm_mode_status hdmi_mode_valid(struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ struct hdmi_context *hdata = connector_to_hdmi(connector);
+ int ret;
+
+ DRM_DEV_DEBUG_KMS(hdata->dev,
+ "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
+ mode->hdisplay, mode->vdisplay,
+ drm_mode_vrefresh(mode),
+ (mode->flags & DRM_MODE_FLAG_INTERLACE) ? true :
+ false, mode->clock * 1000);
+
+ ret = hdmi_find_phy_conf(hdata, mode->clock * 1000);
+ if (ret < 0)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs hdmi_connector_helper_funcs = {
+ .get_modes = hdmi_get_modes,
+ .mode_valid = hdmi_mode_valid,
+};
+
+static int hdmi_create_connector(struct drm_encoder *encoder)
+{
+ struct hdmi_context *hdata = encoder_to_hdmi(encoder);
+ struct drm_connector *connector = &hdata->connector;
+ struct cec_connector_info conn_info;
+ int ret;
+
+ connector->interlace_allowed = true;
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init_with_ddc(hdata->drm_dev, connector,
+ &hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdata->ddc_adpt);
+ if (ret) {
+ DRM_DEV_ERROR(hdata->dev,
+ "Failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &hdmi_connector_helper_funcs);
+ drm_connector_attach_encoder(connector, encoder);
+
+ if (hdata->bridge)
+ ret = drm_bridge_attach(encoder, hdata->bridge, NULL, 0);
+
+ cec_fill_conn_info_from_drm(&conn_info, connector);
+
+ hdata->notifier = cec_notifier_conn_register(hdata->dev, NULL,
+ &conn_info);
+ if (!hdata->notifier) {
+ ret = -ENOMEM;
+ DRM_DEV_ERROR(hdata->dev, "Failed to allocate CEC notifier\n");
+ }
+
+ return ret;
+}
+
+static bool hdmi_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct drm_connector *connector;
+ struct drm_display_mode *m;
+ struct drm_connector_list_iter conn_iter;
+ int mode_ok;
+
+ drm_mode_set_crtcinfo(adjusted_mode, 0);
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (connector->encoder == encoder)
+ break;
+ }
+ if (connector)
+ drm_connector_get(connector);
+ drm_connector_list_iter_end(&conn_iter);
+
+ if (!connector)
+ return true;
+
+ mode_ok = hdmi_mode_valid(connector, adjusted_mode);
+
+ if (mode_ok == MODE_OK)
+ goto cleanup;
+
+ /*
+ * Find the most suitable mode and copy it to adjusted_mode.
+ */
+ list_for_each_entry(m, &connector->modes, head) {
+ mode_ok = hdmi_mode_valid(connector, m);
+
+ if (mode_ok == MODE_OK) {
+ DRM_INFO("desired mode doesn't exist so\n");
+ DRM_INFO("use the most suitable mode among modes.\n");
+
+ DRM_DEV_DEBUG_KMS(dev->dev,
+ "Adjusted Mode: [%d]x[%d] [%d]Hz\n",
+ m->hdisplay, m->vdisplay,
+ drm_mode_vrefresh(m));
+
+ drm_mode_copy(adjusted_mode, m);
+ break;
+ }
+ }
+
+cleanup:
+ drm_connector_put(connector);
+
+ return true;
+}
+
+static void hdmi_reg_acr(struct hdmi_context *hdata, u32 freq)
+{
+ u32 n, cts;
+
+ cts = (freq % 9) ? 27000 : 30000;
+ n = 128 * freq / (27000000 / cts);
+
+ hdmi_reg_writev(hdata, HDMI_ACR_N0, 3, n);
+ hdmi_reg_writev(hdata, HDMI_ACR_MCTS0, 3, cts);
+ hdmi_reg_writev(hdata, HDMI_ACR_CTS0, 3, cts);
+ hdmi_reg_writeb(hdata, HDMI_ACR_CON, 4);
+}
+
+static void hdmi_audio_config(struct hdmi_context *hdata)
+{
+ u32 bit_ch = 1;
+ u32 data_num, val;
+ int i;
+
+ switch (hdata->audio.params.sample_width) {
+ case 20:
+ data_num = 2;
+ break;
+ case 24:
+ data_num = 3;
+ break;
+ default:
+ data_num = 1;
+ bit_ch = 0;
+ break;
+ }
+
+ hdmi_reg_acr(hdata, hdata->audio.params.sample_rate);
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_MUX_CON, HDMI_I2S_IN_DISABLE
+ | HDMI_I2S_AUD_I2S | HDMI_I2S_CUV_I2S_ENABLE
+ | HDMI_I2S_MUX_ENABLE);
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_MUX_CH, HDMI_I2S_CH0_EN
+ | HDMI_I2S_CH1_EN | HDMI_I2S_CH2_EN);
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_MUX_CUV, HDMI_I2S_CUV_RL_EN);
+ hdmi_reg_writeb(hdata, HDMI_I2S_CLK_CON, HDMI_I2S_CLK_DIS);
+ hdmi_reg_writeb(hdata, HDMI_I2S_CLK_CON, HDMI_I2S_CLK_EN);
+
+ val = hdmi_reg_read(hdata, HDMI_I2S_DSD_CON) | 0x01;
+ hdmi_reg_writeb(hdata, HDMI_I2S_DSD_CON, val);
+
+ /* Configuration I2S input ports. Configure I2S_PIN_SEL_0~4 */
+ hdmi_reg_writeb(hdata, HDMI_I2S_PIN_SEL_0, HDMI_I2S_SEL_SCLK(5)
+ | HDMI_I2S_SEL_LRCK(6));
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_PIN_SEL_1, HDMI_I2S_SEL_SDATA1(3)
+ | HDMI_I2S_SEL_SDATA0(4));
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_PIN_SEL_2, HDMI_I2S_SEL_SDATA3(1)
+ | HDMI_I2S_SEL_SDATA2(2));
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_PIN_SEL_3, HDMI_I2S_SEL_DSD(0));
+
+ /* I2S_CON_1 & 2 */
+ hdmi_reg_writeb(hdata, HDMI_I2S_CON_1, HDMI_I2S_SCLK_FALLING_EDGE
+ | HDMI_I2S_L_CH_LOW_POL);
+ hdmi_reg_writeb(hdata, HDMI_I2S_CON_2, HDMI_I2S_MSB_FIRST_MODE
+ | HDMI_I2S_SET_BIT_CH(bit_ch)
+ | HDMI_I2S_SET_SDATA_BIT(data_num)
+ | HDMI_I2S_BASIC_FORMAT);
+
+ /* Configuration of the audio channel status registers */
+ for (i = 0; i < HDMI_I2S_CH_ST_MAXNUM; i++)
+ hdmi_reg_writeb(hdata, HDMI_I2S_CH_ST(i),
+ hdata->audio.params.iec.status[i]);
+
+ hdmi_reg_writeb(hdata, HDMI_I2S_CH_ST_CON, HDMI_I2S_CH_STATUS_RELOAD);
+}
+
+static void hdmi_audio_control(struct hdmi_context *hdata)
+{
+ bool enable = !hdata->audio.mute;
+
+ if (hdata->dvi_mode)
+ return;
+
+ hdmi_reg_writeb(hdata, HDMI_AUI_CON, enable ?
+ HDMI_AVI_CON_EVERY_VSYNC : HDMI_AUI_CON_NO_TRAN);
+ hdmi_reg_writemask(hdata, HDMI_CON_0, enable ?
+ HDMI_ASP_EN : HDMI_ASP_DIS, HDMI_ASP_MASK);
+}
+
+static void hdmi_start(struct hdmi_context *hdata, bool start)
+{
+ struct drm_display_mode *m = &hdata->encoder.crtc->state->mode;
+ u32 val = start ? HDMI_TG_EN : 0;
+
+ if (m->flags & DRM_MODE_FLAG_INTERLACE)
+ val |= HDMI_FIELD_EN;
+
+ hdmi_reg_writemask(hdata, HDMI_CON_0, val, HDMI_EN);
+ hdmi_reg_writemask(hdata, HDMI_TG_CMD, val, HDMI_TG_EN | HDMI_FIELD_EN);
+}
+
+static void hdmi_conf_init(struct hdmi_context *hdata)
+{
+ /* disable HPD interrupts from HDMI IP block, use GPIO instead */
+ hdmi_reg_writemask(hdata, HDMI_INTC_CON, 0, HDMI_INTC_EN_GLOBAL |
+ HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG);
+
+ /* choose HDMI mode */
+ hdmi_reg_writemask(hdata, HDMI_MODE_SEL,
+ HDMI_MODE_HDMI_EN, HDMI_MODE_MASK);
+ /* apply video pre-amble and guard band in HDMI mode only */
+ hdmi_reg_writeb(hdata, HDMI_CON_2, 0);
+ /* disable bluescreen */
+ hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_BLUE_SCR_EN);
+
+ if (hdata->dvi_mode) {
+ hdmi_reg_writemask(hdata, HDMI_MODE_SEL,
+ HDMI_MODE_DVI_EN, HDMI_MODE_MASK);
+ hdmi_reg_writeb(hdata, HDMI_CON_2,
+ HDMI_VID_PREAMBLE_DIS | HDMI_GUARD_BAND_DIS);
+ }
+
+ if (hdata->drv_data->type == HDMI_TYPE13) {
+ /* choose bluescreen (fecal) color */
+ hdmi_reg_writeb(hdata, HDMI_V13_BLUE_SCREEN_0, 0x12);
+ hdmi_reg_writeb(hdata, HDMI_V13_BLUE_SCREEN_1, 0x34);
+ hdmi_reg_writeb(hdata, HDMI_V13_BLUE_SCREEN_2, 0x56);
+
+ /* enable AVI packet every vsync, fixes purple line problem */
+ hdmi_reg_writeb(hdata, HDMI_V13_AVI_CON, 0x02);
+ /* force RGB, look to CEA-861-D, table 7 for more detail */
+ hdmi_reg_writeb(hdata, HDMI_V13_AVI_BYTE(0), 0 << 5);
+ hdmi_reg_writemask(hdata, HDMI_CON_1, 0x10 << 5, 0x11 << 5);
+
+ hdmi_reg_writeb(hdata, HDMI_V13_SPD_CON, 0x02);
+ hdmi_reg_writeb(hdata, HDMI_V13_AUI_CON, 0x02);
+ hdmi_reg_writeb(hdata, HDMI_V13_ACR_CON, 0x04);
+ } else {
+ hdmi_reg_infoframes(hdata);
+
+ /* enable AVI packet every vsync, fixes purple line problem */
+ hdmi_reg_writemask(hdata, HDMI_CON_1, 2, 3 << 5);
+ }
+}
+
+static void hdmiphy_wait_for_pll(struct hdmi_context *hdata)
+{
+ int tries;
+
+ for (tries = 0; tries < 10; ++tries) {
+ u32 val = hdmi_reg_read(hdata, HDMI_PHY_STATUS);
+
+ if (val & HDMI_PHY_STATUS_READY) {
+ DRM_DEV_DEBUG_KMS(hdata->dev,
+ "PLL stabilized after %d tries\n",
+ tries);
+ return;
+ }
+ usleep_range(10, 20);
+ }
+
+ DRM_DEV_ERROR(hdata->dev, "PLL could not reach steady state\n");
+}
+
+static void hdmi_v13_mode_apply(struct hdmi_context *hdata)
+{
+ struct drm_display_mode *m = &hdata->encoder.crtc->state->mode;
+ unsigned int val;
+
+ hdmi_reg_writev(hdata, HDMI_H_BLANK_0, 2, m->htotal - m->hdisplay);
+ hdmi_reg_writev(hdata, HDMI_V13_H_V_LINE_0, 3,
+ (m->htotal << 12) | m->vtotal);
+
+ val = (m->flags & DRM_MODE_FLAG_NVSYNC) ? 1 : 0;
+ hdmi_reg_writev(hdata, HDMI_VSYNC_POL, 1, val);
+
+ val = (m->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0;
+ hdmi_reg_writev(hdata, HDMI_INT_PRO_MODE, 1, val);
+
+ val = (m->hsync_start - m->hdisplay - 2);
+ val |= ((m->hsync_end - m->hdisplay - 2) << 10);
+ val |= ((m->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0)<<20;
+ hdmi_reg_writev(hdata, HDMI_V13_H_SYNC_GEN_0, 3, val);
+
+ /*
+ * Quirk requirement for exynos HDMI IP design,
+ * 2 pixels less than the actual calculation for hsync_start
+ * and end.
+ */
+
+ /* Following values & calculations differ for different type of modes */
+ if (m->flags & DRM_MODE_FLAG_INTERLACE) {
+ val = ((m->vsync_end - m->vdisplay) / 2);
+ val |= ((m->vsync_start - m->vdisplay) / 2) << 12;
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_1_0, 3, val);
+
+ val = m->vtotal / 2;
+ val |= ((m->vtotal - m->vdisplay) / 2) << 11;
+ hdmi_reg_writev(hdata, HDMI_V13_V_BLANK_0, 3, val);
+
+ val = (m->vtotal +
+ ((m->vsync_end - m->vsync_start) * 4) + 5) / 2;
+ val |= m->vtotal << 11;
+ hdmi_reg_writev(hdata, HDMI_V13_V_BLANK_F_0, 3, val);
+
+ val = ((m->vtotal / 2) + 7);
+ val |= ((m->vtotal / 2) + 2) << 12;
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_2_0, 3, val);
+
+ val = ((m->htotal / 2) + (m->hsync_start - m->hdisplay));
+ val |= ((m->htotal / 2) +
+ (m->hsync_start - m->hdisplay)) << 12;
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_3_0, 3, val);
+
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST_L, 2,
+ (m->vtotal - m->vdisplay) / 2);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_SZ_L, 2, m->vdisplay / 2);
+
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST2_L, 2, 0x249);
+ } else {
+ val = m->vtotal;
+ val |= (m->vtotal - m->vdisplay) << 11;
+ hdmi_reg_writev(hdata, HDMI_V13_V_BLANK_0, 3, val);
+
+ hdmi_reg_writev(hdata, HDMI_V13_V_BLANK_F_0, 3, 0);
+
+ val = (m->vsync_end - m->vdisplay);
+ val |= ((m->vsync_start - m->vdisplay) << 12);
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_1_0, 3, val);
+
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_2_0, 3, 0x1001);
+ hdmi_reg_writev(hdata, HDMI_V13_V_SYNC_GEN_3_0, 3, 0x1001);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST_L, 2,
+ m->vtotal - m->vdisplay);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_SZ_L, 2, m->vdisplay);
+ }
+
+ hdmi_reg_writev(hdata, HDMI_TG_H_FSZ_L, 2, m->htotal);
+ hdmi_reg_writev(hdata, HDMI_TG_HACT_ST_L, 2, m->htotal - m->hdisplay);
+ hdmi_reg_writev(hdata, HDMI_TG_HACT_SZ_L, 2, m->hdisplay);
+ hdmi_reg_writev(hdata, HDMI_TG_V_FSZ_L, 2, m->vtotal);
+}
+
+static void hdmi_v14_mode_apply(struct hdmi_context *hdata)
+{
+ struct drm_display_mode *m = &hdata->encoder.crtc->state->mode;
+ struct drm_display_mode *am =
+ &hdata->encoder.crtc->state->adjusted_mode;
+ int hquirk = 0;
+
+ /*
+ * In case video mode coming from CRTC differs from requested one HDMI
+ * sometimes is able to almost properly perform conversion - only
+ * first line is distorted.
+ */
+ if ((m->vdisplay != am->vdisplay) &&
+ (m->hdisplay == 1280 || m->hdisplay == 1024 || m->hdisplay == 1366))
+ hquirk = 258;
+
+ hdmi_reg_writev(hdata, HDMI_H_BLANK_0, 2, m->htotal - m->hdisplay);
+ hdmi_reg_writev(hdata, HDMI_V_LINE_0, 2, m->vtotal);
+ hdmi_reg_writev(hdata, HDMI_H_LINE_0, 2, m->htotal);
+ hdmi_reg_writev(hdata, HDMI_HSYNC_POL, 1,
+ (m->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0);
+ hdmi_reg_writev(hdata, HDMI_VSYNC_POL, 1,
+ (m->flags & DRM_MODE_FLAG_NVSYNC) ? 1 : 0);
+ hdmi_reg_writev(hdata, HDMI_INT_PRO_MODE, 1,
+ (m->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0);
+
+ /*
+ * Quirk requirement for exynos 5 HDMI IP design,
+ * 2 pixels less than the actual calculation for hsync_start
+ * and end.
+ */
+
+ /* Following values & calculations differ for different type of modes */
+ if (m->flags & DRM_MODE_FLAG_INTERLACE) {
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_BEF_2_0, 2,
+ (m->vsync_end - m->vdisplay) / 2);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_BEF_1_0, 2,
+ (m->vsync_start - m->vdisplay) / 2);
+ hdmi_reg_writev(hdata, HDMI_V2_BLANK_0, 2, m->vtotal / 2);
+ hdmi_reg_writev(hdata, HDMI_V1_BLANK_0, 2,
+ (m->vtotal - m->vdisplay) / 2);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F0_0, 2,
+ m->vtotal - m->vdisplay / 2);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F1_0, 2, m->vtotal);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_2_0, 2,
+ (m->vtotal / 2) + 7);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_1_0, 2,
+ (m->vtotal / 2) + 2);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_2_0, 2,
+ (m->htotal / 2) + (m->hsync_start - m->hdisplay));
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_1_0, 2,
+ (m->htotal / 2) + (m->hsync_start - m->hdisplay));
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST_L, 2,
+ (m->vtotal - m->vdisplay) / 2);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_SZ_L, 2, m->vdisplay / 2);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST2_L, 2,
+ m->vtotal - m->vdisplay / 2);
+ hdmi_reg_writev(hdata, HDMI_TG_VSYNC2_L, 2,
+ (m->vtotal / 2) + 1);
+ hdmi_reg_writev(hdata, HDMI_TG_VSYNC_BOT_HDMI_L, 2,
+ (m->vtotal / 2) + 1);
+ hdmi_reg_writev(hdata, HDMI_TG_FIELD_BOT_HDMI_L, 2,
+ (m->vtotal / 2) + 1);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST3_L, 2, 0x0);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST4_L, 2, 0x0);
+ } else {
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_BEF_2_0, 2,
+ m->vsync_end - m->vdisplay);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_BEF_1_0, 2,
+ m->vsync_start - m->vdisplay);
+ hdmi_reg_writev(hdata, HDMI_V2_BLANK_0, 2, m->vtotal);
+ hdmi_reg_writev(hdata, HDMI_V1_BLANK_0, 2,
+ m->vtotal - m->vdisplay);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F0_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F1_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_2_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_1_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_2_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_1_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_ST_L, 2,
+ m->vtotal - m->vdisplay);
+ hdmi_reg_writev(hdata, HDMI_TG_VACT_SZ_L, 2, m->vdisplay);
+ }
+
+ hdmi_reg_writev(hdata, HDMI_H_SYNC_START_0, 2,
+ m->hsync_start - m->hdisplay - 2);
+ hdmi_reg_writev(hdata, HDMI_H_SYNC_END_0, 2,
+ m->hsync_end - m->hdisplay - 2);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_1_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_2_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_3_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_4_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_5_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_VACT_SPACE_6_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F2_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F3_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F4_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_BLANK_F5_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_3_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_4_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_5_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_6_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_3_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_4_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_5_0, 2, 0xffff);
+ hdmi_reg_writev(hdata, HDMI_V_SYNC_LINE_AFT_PXL_6_0, 2, 0xffff);
+
+ hdmi_reg_writev(hdata, HDMI_TG_H_FSZ_L, 2, m->htotal);
+ hdmi_reg_writev(hdata, HDMI_TG_HACT_ST_L, 2,
+ m->htotal - m->hdisplay - hquirk);
+ hdmi_reg_writev(hdata, HDMI_TG_HACT_SZ_L, 2, m->hdisplay + hquirk);
+ hdmi_reg_writev(hdata, HDMI_TG_V_FSZ_L, 2, m->vtotal);
+ if (hdata->drv_data == &exynos5433_hdmi_driver_data)
+ hdmi_reg_writeb(hdata, HDMI_TG_DECON_EN, 1);
+}
+
+static void hdmi_mode_apply(struct hdmi_context *hdata)
+{
+ if (hdata->drv_data->type == HDMI_TYPE13)
+ hdmi_v13_mode_apply(hdata);
+ else
+ hdmi_v14_mode_apply(hdata);
+
+ hdmi_start(hdata, true);
+}
+
+static void hdmiphy_conf_reset(struct hdmi_context *hdata)
+{
+ hdmi_reg_writemask(hdata, HDMI_CORE_RSTOUT, 0, 1);
+ usleep_range(10000, 12000);
+ hdmi_reg_writemask(hdata, HDMI_CORE_RSTOUT, ~0, 1);
+ usleep_range(10000, 12000);
+ hdmi_reg_writemask(hdata, HDMI_PHY_RSTOUT, ~0, HDMI_PHY_SW_RSTOUT);
+ usleep_range(10000, 12000);
+ hdmi_reg_writemask(hdata, HDMI_PHY_RSTOUT, 0, HDMI_PHY_SW_RSTOUT);
+ usleep_range(10000, 12000);
+}
+
+static void hdmiphy_enable_mode_set(struct hdmi_context *hdata, bool enable)
+{
+ u8 v = enable ? HDMI_PHY_ENABLE_MODE_SET : HDMI_PHY_DISABLE_MODE_SET;
+
+ if (hdata->drv_data == &exynos5433_hdmi_driver_data)
+ writel(v, hdata->regs_hdmiphy + HDMIPHY5433_MODE_SET_DONE);
+}
+
+static void hdmiphy_conf_apply(struct hdmi_context *hdata)
+{
+ struct drm_display_mode *m = &hdata->encoder.crtc->state->mode;
+ int ret;
+ const u8 *phy_conf;
+
+ ret = hdmi_find_phy_conf(hdata, m->clock * 1000);
+ if (ret < 0) {
+ DRM_DEV_ERROR(hdata->dev, "failed to find hdmiphy conf\n");
+ return;
+ }
+ phy_conf = hdata->drv_data->phy_confs.data[ret].conf;
+
+ hdmi_clk_set_parents(hdata, false);
+
+ hdmiphy_conf_reset(hdata);
+
+ hdmiphy_enable_mode_set(hdata, true);
+ ret = hdmiphy_reg_write_buf(hdata, 0, phy_conf, 32);
+ if (ret) {
+ DRM_DEV_ERROR(hdata->dev, "failed to configure hdmiphy\n");
+ return;
+ }
+ hdmiphy_enable_mode_set(hdata, false);
+ hdmi_clk_set_parents(hdata, true);
+ usleep_range(10000, 12000);
+ hdmiphy_wait_for_pll(hdata);
+}
+
+/* Should be called with hdata->mutex mutex held */
+static void hdmi_conf_apply(struct hdmi_context *hdata)
+{
+ hdmi_start(hdata, false);
+ hdmi_conf_init(hdata);
+ hdmi_audio_config(hdata);
+ hdmi_mode_apply(hdata);
+ hdmi_audio_control(hdata);
+}
+
+static void hdmi_set_refclk(struct hdmi_context *hdata, bool on)
+{
+ if (!hdata->sysreg)
+ return;
+
+ regmap_update_bits(hdata->sysreg, EXYNOS5433_SYSREG_DISP_HDMI_PHY,
+ SYSREG_HDMI_REFCLK_INT_CLK, on ? ~0 : 0);
+}
+
+/* Should be called with hdata->mutex mutex held. */
+static void hdmiphy_enable(struct hdmi_context *hdata)
+{
+ int ret;
+
+ if (hdata->powered)
+ return;
+
+ ret = pm_runtime_resume_and_get(hdata->dev);
+ if (ret < 0) {
+ dev_err(hdata->dev, "failed to enable HDMIPHY device.\n");
+ return;
+ }
+
+ if (regulator_bulk_enable(ARRAY_SIZE(supply), hdata->regul_bulk))
+ DRM_DEV_DEBUG_KMS(hdata->dev,
+ "failed to enable regulator bulk\n");
+
+ regmap_update_bits(hdata->pmureg, PMU_HDMI_PHY_CONTROL,
+ PMU_HDMI_PHY_ENABLE_BIT, 1);
+
+ hdmi_set_refclk(hdata, true);
+
+ hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, 0, HDMI_PHY_POWER_OFF_EN);
+
+ hdmiphy_conf_apply(hdata);
+
+ hdata->powered = true;
+}
+
+/* Should be called with hdata->mutex mutex held. */
+static void hdmiphy_disable(struct hdmi_context *hdata)
+{
+ if (!hdata->powered)
+ return;
+
+ hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_EN);
+
+ hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, ~0, HDMI_PHY_POWER_OFF_EN);
+
+ hdmi_set_refclk(hdata, false);
+
+ regmap_update_bits(hdata->pmureg, PMU_HDMI_PHY_CONTROL,
+ PMU_HDMI_PHY_ENABLE_BIT, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(supply), hdata->regul_bulk);
+
+ pm_runtime_put_sync(hdata->dev);
+
+ hdata->powered = false;
+}
+
+static void hdmi_enable(struct drm_encoder *encoder)
+{
+ struct hdmi_context *hdata = encoder_to_hdmi(encoder);
+
+ mutex_lock(&hdata->mutex);
+
+ hdmiphy_enable(hdata);
+ hdmi_conf_apply(hdata);
+
+ mutex_unlock(&hdata->mutex);
+}
+
+static void hdmi_disable(struct drm_encoder *encoder)
+{
+ struct hdmi_context *hdata = encoder_to_hdmi(encoder);
+
+ mutex_lock(&hdata->mutex);
+
+ if (hdata->powered) {
+ /*
+ * The SFRs of VP and Mixer are updated by Vertical Sync of
+ * Timing generator which is a part of HDMI so the sequence
+ * to disable TV Subsystem should be as following,
+ * VP -> Mixer -> HDMI
+ *
+ * To achieve such sequence HDMI is disabled together with
+ * HDMI PHY, via pipe clock callback.
+ */
+ mutex_unlock(&hdata->mutex);
+ cancel_delayed_work(&hdata->hotplug_work);
+ if (hdata->notifier)
+ cec_notifier_phys_addr_invalidate(hdata->notifier);
+ return;
+ }
+
+ mutex_unlock(&hdata->mutex);
+}
+
+static const struct drm_encoder_funcs exynos_hdmi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_encoder_helper_funcs exynos_hdmi_encoder_helper_funcs = {
+ .mode_fixup = hdmi_mode_fixup,
+ .enable = hdmi_enable,
+ .disable = hdmi_disable,
+};
+
+static void hdmi_audio_shutdown(struct device *dev, void *data)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+
+ mutex_lock(&hdata->mutex);
+
+ hdata->audio.mute = true;
+
+ if (hdata->powered)
+ hdmi_audio_control(hdata);
+
+ mutex_unlock(&hdata->mutex);
+}
+
+static int hdmi_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+
+ if (daifmt->fmt != HDMI_I2S || daifmt->bit_clk_inv ||
+ daifmt->frame_clk_inv || daifmt->bit_clk_provider ||
+ daifmt->frame_clk_provider) {
+ dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__,
+ daifmt->bit_clk_inv, daifmt->frame_clk_inv,
+ daifmt->bit_clk_provider,
+ daifmt->frame_clk_provider);
+ return -EINVAL;
+ }
+
+ mutex_lock(&hdata->mutex);
+
+ hdata->audio.params = *params;
+
+ if (hdata->powered) {
+ hdmi_audio_config(hdata);
+ hdmi_audio_infoframe_apply(hdata);
+ }
+
+ mutex_unlock(&hdata->mutex);
+
+ return 0;
+}
+
+static int hdmi_audio_mute(struct device *dev, void *data,
+ bool mute, int direction)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+
+ mutex_lock(&hdata->mutex);
+
+ hdata->audio.mute = mute;
+
+ if (hdata->powered)
+ hdmi_audio_control(hdata);
+
+ mutex_unlock(&hdata->mutex);
+
+ return 0;
+}
+
+static int hdmi_audio_get_eld(struct device *dev, void *data, uint8_t *buf,
+ size_t len)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+ struct drm_connector *connector = &hdata->connector;
+
+ mutex_lock(&connector->eld_mutex);
+ memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
+ mutex_unlock(&connector->eld_mutex);
+
+ return 0;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = hdmi_audio_hw_params,
+ .audio_shutdown = hdmi_audio_shutdown,
+ .mute_stream = hdmi_audio_mute,
+ .get_eld = hdmi_audio_get_eld,
+};
+
+static int hdmi_register_audio_device(struct hdmi_context *hdata)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 6,
+ .i2s = 1,
+ .no_capture_mute = 1,
+ };
+
+ hdata->audio.pdev = platform_device_register_data(
+ hdata->dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+ &codec_data, sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(hdata->audio.pdev);
+}
+
+static void hdmi_hotplug_work_func(struct work_struct *work)
+{
+ struct hdmi_context *hdata;
+
+ hdata = container_of(work, struct hdmi_context, hotplug_work.work);
+
+ if (hdata->drm_dev)
+ drm_helper_hpd_irq_event(hdata->drm_dev);
+}
+
+static irqreturn_t hdmi_irq_thread(int irq, void *arg)
+{
+ struct hdmi_context *hdata = arg;
+
+ mod_delayed_work(system_percpu_wq, &hdata->hotplug_work,
+ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+
+ return IRQ_HANDLED;
+}
+
+static int hdmi_clks_get(struct hdmi_context *hdata,
+ const struct string_array_spec *names,
+ struct clk **clks)
+{
+ struct device *dev = hdata->dev;
+ int i;
+
+ for (i = 0; i < names->count; ++i) {
+ struct clk *clk = devm_clk_get(dev, names->data[i]);
+
+ if (IS_ERR(clk)) {
+ int ret = PTR_ERR(clk);
+
+ dev_err(dev, "Cannot get clock %s, %d\n",
+ names->data[i], ret);
+
+ return ret;
+ }
+
+ clks[i] = clk;
+ }
+
+ return 0;
+}
+
+static int hdmi_clk_init(struct hdmi_context *hdata)
+{
+ const struct hdmi_driver_data *drv_data = hdata->drv_data;
+ int count = drv_data->clk_gates.count + drv_data->clk_muxes.count;
+ struct device *dev = hdata->dev;
+ struct clk **clks;
+ int ret;
+
+ if (!count)
+ return 0;
+
+ clks = devm_kcalloc(dev, count, sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ return -ENOMEM;
+
+ hdata->clk_gates = clks;
+ hdata->clk_muxes = clks + drv_data->clk_gates.count;
+
+ ret = hdmi_clks_get(hdata, &drv_data->clk_gates, hdata->clk_gates);
+ if (ret)
+ return ret;
+
+ return hdmi_clks_get(hdata, &drv_data->clk_muxes, hdata->clk_muxes);
+}
+
+
+static void hdmiphy_clk_enable(struct exynos_drm_clk *clk, bool enable)
+{
+ struct hdmi_context *hdata = container_of(clk, struct hdmi_context,
+ phy_clk);
+ mutex_lock(&hdata->mutex);
+
+ if (enable)
+ hdmiphy_enable(hdata);
+ else
+ hdmiphy_disable(hdata);
+
+ mutex_unlock(&hdata->mutex);
+}
+
+static int hdmi_bridge_init(struct hdmi_context *hdata)
+{
+ struct device *dev = hdata->dev;
+ struct device_node *ep, *np;
+
+ ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
+ if (!ep)
+ return 0;
+
+ np = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!np) {
+ DRM_DEV_ERROR(dev, "failed to get remote port parent");
+ return -EINVAL;
+ }
+
+ hdata->bridge = of_drm_find_and_get_bridge(np);
+ of_node_put(np);
+
+ if (!hdata->bridge)
+ return -EPROBE_DEFER;
+
+ return 0;
+}
+
+static int hdmi_resources_init(struct hdmi_context *hdata)
+{
+ struct device *dev = hdata->dev;
+ int i, ret;
+
+ DRM_DEV_DEBUG_KMS(dev, "HDMI resource init\n");
+
+ hdata->hpd_gpio = devm_gpiod_get(dev, "hpd", GPIOD_IN);
+ if (IS_ERR(hdata->hpd_gpio)) {
+ DRM_DEV_ERROR(dev, "cannot get hpd gpio property\n");
+ return PTR_ERR(hdata->hpd_gpio);
+ }
+
+ hdata->irq = gpiod_to_irq(hdata->hpd_gpio);
+ if (hdata->irq < 0) {
+ DRM_DEV_ERROR(dev, "failed to get GPIO irq\n");
+ return hdata->irq;
+ }
+
+ ret = hdmi_clk_init(hdata);
+ if (ret)
+ return ret;
+
+ ret = hdmi_clk_set_parents(hdata, false);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(supply); ++i)
+ hdata->regul_bulk[i].supply = supply[i];
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(supply), hdata->regul_bulk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+ hdata->reg_hdmi_en = devm_regulator_get_optional(dev, "hdmi-en");
+
+ if (PTR_ERR(hdata->reg_hdmi_en) != -ENODEV)
+ if (IS_ERR(hdata->reg_hdmi_en))
+ return PTR_ERR(hdata->reg_hdmi_en);
+
+ return hdmi_bridge_init(hdata);
+}
+
+static const struct of_device_id hdmi_match_types[] = {
+ {
+ .compatible = "samsung,exynos4210-hdmi",
+ .data = &exynos4210_hdmi_driver_data,
+ }, {
+ .compatible = "samsung,exynos4212-hdmi",
+ .data = &exynos4212_hdmi_driver_data,
+ }, {
+ .compatible = "samsung,exynos5420-hdmi",
+ .data = &exynos5420_hdmi_driver_data,
+ }, {
+ .compatible = "samsung,exynos5433-hdmi",
+ .data = &exynos5433_hdmi_driver_data,
+ }, {
+ /* end node */
+ }
+};
+MODULE_DEVICE_TABLE (of, hdmi_match_types);
+
+static int hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm_dev = data;
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &hdata->encoder;
+ struct exynos_drm_crtc *crtc;
+ int ret;
+
+ hdata->drm_dev = drm_dev;
+
+ hdata->phy_clk.enable = hdmiphy_clk_enable;
+
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_hdmi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to initialize encoder ret = %d\n",
+ ret);
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &exynos_hdmi_encoder_helper_funcs);
+
+ ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_HDMI);
+ if (ret < 0)
+ return ret;
+
+ crtc = exynos_drm_crtc_get_by_type(drm_dev, EXYNOS_DISPLAY_TYPE_HDMI);
+ if (IS_ERR(crtc))
+ return PTR_ERR(crtc);
+ crtc->pipe_clk = &hdata->phy_clk;
+
+ ret = hdmi_create_connector(encoder);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to create connector ret = %d\n",
+ ret);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void hdmi_unbind(struct device *dev, struct device *master, void *data)
+{
+}
+
+static const struct component_ops hdmi_component_ops = {
+ .bind = hdmi_bind,
+ .unbind = hdmi_unbind,
+};
+
+static int hdmi_get_ddc_adapter(struct hdmi_context *hdata)
+{
+ const char *compatible_str = "samsung,exynos4210-hdmiddc";
+ struct device_node *np;
+ struct i2c_adapter *adpt;
+
+ np = of_find_compatible_node(NULL, NULL, compatible_str);
+ if (np)
+ np = of_get_next_parent(np);
+ else
+ np = of_parse_phandle(hdata->dev->of_node, "ddc", 0);
+
+ if (!np) {
+ DRM_DEV_ERROR(hdata->dev,
+ "Failed to find ddc node in device tree\n");
+ return -ENODEV;
+ }
+
+ adpt = of_get_i2c_adapter_by_node(np);
+ of_node_put(np);
+
+ if (!adpt) {
+ DRM_INFO("Failed to get ddc i2c adapter by node\n");
+ return -EPROBE_DEFER;
+ }
+
+ hdata->ddc_adpt = adpt;
+
+ return 0;
+}
+
+static int hdmi_get_phy_io(struct hdmi_context *hdata)
+{
+ const char *compatible_str = "samsung,exynos4212-hdmiphy";
+ struct device_node *np __free(device_node) =
+ of_find_compatible_node(NULL, NULL, compatible_str);
+
+ if (!np) {
+ np = of_parse_phandle(hdata->dev->of_node, "phy", 0);
+ if (!np) {
+ DRM_DEV_ERROR(hdata->dev,
+ "Failed to find hdmiphy node in device tree\n");
+ return -ENODEV;
+ }
+ }
+
+ if (hdata->drv_data->is_apb_phy) {
+ hdata->regs_hdmiphy = of_iomap(np, 0);
+ if (!hdata->regs_hdmiphy) {
+ DRM_DEV_ERROR(hdata->dev,
+ "failed to ioremap hdmi phy\n");
+ return -ENOMEM;
+ }
+ } else {
+ hdata->hdmiphy_port = of_find_i2c_device_by_node(np);
+ if (!hdata->hdmiphy_port) {
+ DRM_INFO("Failed to get hdmi phy i2c client\n");
+ return -EPROBE_DEFER;
+ }
+ }
+
+ return 0;
+}
+
+static int hdmi_probe(struct platform_device *pdev)
+{
+ struct hdmi_audio_infoframe *audio_infoframe;
+ struct device *dev = &pdev->dev;
+ struct hdmi_context *hdata;
+ int ret;
+
+ hdata = devm_kzalloc(dev, sizeof(struct hdmi_context), GFP_KERNEL);
+ if (!hdata)
+ return -ENOMEM;
+
+ hdata->drv_data = of_device_get_match_data(dev);
+
+ platform_set_drvdata(pdev, hdata);
+
+ hdata->dev = dev;
+
+ mutex_init(&hdata->mutex);
+
+ ret = hdmi_resources_init(hdata);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev, "hdmi_resources_init failed\n");
+ return ret;
+ }
+
+ hdata->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hdata->regs)) {
+ ret = PTR_ERR(hdata->regs);
+ return ret;
+ }
+
+ ret = hdmi_get_ddc_adapter(hdata);
+ if (ret)
+ return ret;
+
+ ret = hdmi_get_phy_io(hdata);
+ if (ret)
+ goto err_ddc;
+
+ INIT_DELAYED_WORK(&hdata->hotplug_work, hdmi_hotplug_work_func);
+
+ ret = devm_request_threaded_irq(dev, hdata->irq, NULL,
+ hdmi_irq_thread, IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "hdmi", hdata);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to register hdmi interrupt\n");
+ goto err_hdmiphy;
+ }
+
+ hdata->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,syscon-phandle");
+ if (IS_ERR(hdata->pmureg)) {
+ DRM_DEV_ERROR(dev, "syscon regmap lookup failed.\n");
+ ret = -EPROBE_DEFER;
+ goto err_hdmiphy;
+ }
+
+ if (hdata->drv_data->has_sysreg) {
+ hdata->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,sysreg-phandle");
+ if (IS_ERR(hdata->sysreg)) {
+ DRM_DEV_ERROR(dev, "sysreg regmap lookup failed.\n");
+ ret = -EPROBE_DEFER;
+ goto err_hdmiphy;
+ }
+ }
+
+ if (!IS_ERR(hdata->reg_hdmi_en)) {
+ ret = regulator_enable(hdata->reg_hdmi_en);
+ if (ret) {
+ DRM_DEV_ERROR(dev,
+ "failed to enable hdmi-en regulator\n");
+ goto err_hdmiphy;
+ }
+ }
+
+ pm_runtime_enable(dev);
+
+ audio_infoframe = &hdata->audio.infoframe;
+ hdmi_audio_infoframe_init(audio_infoframe);
+ audio_infoframe->coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+ audio_infoframe->sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+ audio_infoframe->sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+ audio_infoframe->channels = 2;
+
+ ret = hdmi_register_audio_device(hdata);
+ if (ret)
+ goto err_rpm_disable;
+
+ ret = component_add(&pdev->dev, &hdmi_component_ops);
+ if (ret)
+ goto err_unregister_audio;
+
+ return ret;
+
+err_unregister_audio:
+ platform_device_unregister(hdata->audio.pdev);
+
+err_rpm_disable:
+ pm_runtime_disable(dev);
+ if (!IS_ERR(hdata->reg_hdmi_en))
+ regulator_disable(hdata->reg_hdmi_en);
+err_hdmiphy:
+ if (hdata->hdmiphy_port)
+ put_device(&hdata->hdmiphy_port->dev);
+ if (hdata->regs_hdmiphy)
+ iounmap(hdata->regs_hdmiphy);
+err_ddc:
+ i2c_put_adapter(hdata->ddc_adpt);
+
+ return ret;
+}
+
+static void hdmi_remove(struct platform_device *pdev)
+{
+ struct hdmi_context *hdata = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&hdata->hotplug_work);
+
+ component_del(&pdev->dev, &hdmi_component_ops);
+ platform_device_unregister(hdata->audio.pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ if (!IS_ERR(hdata->reg_hdmi_en))
+ regulator_disable(hdata->reg_hdmi_en);
+
+ if (hdata->hdmiphy_port)
+ put_device(&hdata->hdmiphy_port->dev);
+
+ if (hdata->regs_hdmiphy)
+ iounmap(hdata->regs_hdmiphy);
+
+ i2c_put_adapter(hdata->ddc_adpt);
+
+ drm_bridge_put(hdata->bridge);
+
+ mutex_destroy(&hdata->mutex);
+}
+
+static int __maybe_unused exynos_hdmi_suspend(struct device *dev)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+
+ hdmi_clk_disable_gates(hdata);
+
+ return 0;
+}
+
+static int __maybe_unused exynos_hdmi_resume(struct device *dev)
+{
+ struct hdmi_context *hdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = hdmi_clk_enable_gates(hdata);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos_hdmi_pm_ops = {
+ SET_RUNTIME_PM_OPS(exynos_hdmi_suspend, exynos_hdmi_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+struct platform_driver hdmi_driver = {
+ .probe = hdmi_probe,
+ .remove = hdmi_remove,
+ .driver = {
+ .name = "exynos-hdmi",
+ .pm = &exynos_hdmi_pm_ops,
+ .of_match_table = hdmi_match_types,
+ },
+};
diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c
new file mode 100644
index 000000000..69dea5049
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_mixer.c
@@ -0,0 +1,1340 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * Authors:
+ * Seung-Woo Kim <sw0312.kim@samsung.com>
+ * Inki Dae <inki.dae@samsung.com>
+ * Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ * Based on drivers/media/video/s5p-tv/mixer_reg.c
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+
+#include <drm/drm_blend.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <drm/exynos_drm.h>
+
+#include "exynos_drm_crtc.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_plane.h"
+#include "regs-mixer.h"
+#include "regs-vp.h"
+
+#define MIXER_WIN_NR 3
+#define VP_DEFAULT_WIN 2
+
+/*
+ * Mixer color space conversion coefficient triplet.
+ * Used for CSC from RGB to YCbCr.
+ * Each coefficient is a 10-bit fixed point number with
+ * sign and no integer part, i.e.
+ * [0:8] = fractional part (representing a value y = x / 2^9)
+ * [9] = sign
+ * Negative values are encoded with two's complement.
+ */
+#define MXR_CSC_C(x) ((int)((x) * 512.0) & 0x3ff)
+#define MXR_CSC_CT(a0, a1, a2) \
+ ((MXR_CSC_C(a0) << 20) | (MXR_CSC_C(a1) << 10) | (MXR_CSC_C(a2) << 0))
+
+/* YCbCr value, used for mixer background color configuration. */
+#define MXR_YCBCR_VAL(y, cb, cr) (((y) << 16) | ((cb) << 8) | ((cr) << 0))
+
+/* The pixelformats that are natively supported by the mixer. */
+#define MXR_FORMAT_RGB565 4
+#define MXR_FORMAT_ARGB1555 5
+#define MXR_FORMAT_ARGB4444 6
+#define MXR_FORMAT_ARGB8888 7
+
+enum mixer_version_id {
+ MXR_VER_0_0_0_16,
+ MXR_VER_16_0_33_0,
+ MXR_VER_128_0_0_184,
+};
+
+enum mixer_flag_bits {
+ MXR_BIT_POWERED,
+ MXR_BIT_VSYNC,
+ MXR_BIT_INTERLACE,
+ MXR_BIT_VP_ENABLED,
+ MXR_BIT_HAS_SCLK,
+};
+
+static const uint32_t mixer_formats[] = {
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+};
+
+static const uint32_t vp_formats[] = {
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+};
+
+struct mixer_context {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct drm_device *drm_dev;
+ void *dma_priv;
+ struct exynos_drm_crtc *crtc;
+ struct exynos_drm_plane planes[MIXER_WIN_NR];
+ unsigned long flags;
+
+ int irq;
+ void __iomem *mixer_regs;
+ void __iomem *vp_regs;
+ spinlock_t reg_slock;
+ struct clk *mixer;
+ struct clk *vp;
+ struct clk *hdmi;
+ struct clk *sclk_mixer;
+ struct clk *sclk_hdmi;
+ struct clk *mout_mixer;
+ enum mixer_version_id mxr_ver;
+ int scan_value;
+};
+
+struct mixer_drv_data {
+ enum mixer_version_id version;
+ bool is_vp_enabled;
+ bool has_sclk;
+};
+
+static const struct exynos_drm_plane_config plane_configs[MIXER_WIN_NR] = {
+ {
+ .zpos = 0,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .pixel_formats = mixer_formats,
+ .num_pixel_formats = ARRAY_SIZE(mixer_formats),
+ .capabilities = EXYNOS_DRM_PLANE_CAP_DOUBLE |
+ EXYNOS_DRM_PLANE_CAP_ZPOS |
+ EXYNOS_DRM_PLANE_CAP_PIX_BLEND |
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND,
+ }, {
+ .zpos = 1,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .pixel_formats = mixer_formats,
+ .num_pixel_formats = ARRAY_SIZE(mixer_formats),
+ .capabilities = EXYNOS_DRM_PLANE_CAP_DOUBLE |
+ EXYNOS_DRM_PLANE_CAP_ZPOS |
+ EXYNOS_DRM_PLANE_CAP_PIX_BLEND |
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND,
+ }, {
+ .zpos = 2,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .pixel_formats = vp_formats,
+ .num_pixel_formats = ARRAY_SIZE(vp_formats),
+ .capabilities = EXYNOS_DRM_PLANE_CAP_SCALE |
+ EXYNOS_DRM_PLANE_CAP_ZPOS |
+ EXYNOS_DRM_PLANE_CAP_TILE |
+ EXYNOS_DRM_PLANE_CAP_WIN_BLEND,
+ },
+};
+
+static const u8 filter_y_horiz_tap8[] = {
+ 0, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 0, 0, 0,
+ 0, 2, 4, 5, 6, 6, 6, 6,
+ 6, 5, 5, 4, 3, 2, 1, 1,
+ 0, -6, -12, -16, -18, -20, -21, -20,
+ -20, -18, -16, -13, -10, -8, -5, -2,
+ 127, 126, 125, 121, 114, 107, 99, 89,
+ 79, 68, 57, 46, 35, 25, 16, 8,
+};
+
+static const u8 filter_y_vert_tap4[] = {
+ 0, -3, -6, -8, -8, -8, -8, -7,
+ -6, -5, -4, -3, -2, -1, -1, 0,
+ 127, 126, 124, 118, 111, 102, 92, 81,
+ 70, 59, 48, 37, 27, 19, 11, 5,
+ 0, 5, 11, 19, 27, 37, 48, 59,
+ 70, 81, 92, 102, 111, 118, 124, 126,
+ 0, 0, -1, -1, -2, -3, -4, -5,
+ -6, -7, -8, -8, -8, -8, -6, -3,
+};
+
+static const u8 filter_cr_horiz_tap4[] = {
+ 0, -3, -6, -8, -8, -8, -8, -7,
+ -6, -5, -4, -3, -2, -1, -1, 0,
+ 127, 126, 124, 118, 111, 102, 92, 81,
+ 70, 59, 48, 37, 27, 19, 11, 5,
+};
+
+static inline u32 vp_reg_read(struct mixer_context *ctx, u32 reg_id)
+{
+ return readl(ctx->vp_regs + reg_id);
+}
+
+static inline void vp_reg_write(struct mixer_context *ctx, u32 reg_id,
+ u32 val)
+{
+ writel(val, ctx->vp_regs + reg_id);
+}
+
+static inline void vp_reg_writemask(struct mixer_context *ctx, u32 reg_id,
+ u32 val, u32 mask)
+{
+ u32 old = vp_reg_read(ctx, reg_id);
+
+ val = (val & mask) | (old & ~mask);
+ writel(val, ctx->vp_regs + reg_id);
+}
+
+static inline u32 mixer_reg_read(struct mixer_context *ctx, u32 reg_id)
+{
+ return readl(ctx->mixer_regs + reg_id);
+}
+
+static inline void mixer_reg_write(struct mixer_context *ctx, u32 reg_id,
+ u32 val)
+{
+ writel(val, ctx->mixer_regs + reg_id);
+}
+
+static inline void mixer_reg_writemask(struct mixer_context *ctx,
+ u32 reg_id, u32 val, u32 mask)
+{
+ u32 old = mixer_reg_read(ctx, reg_id);
+
+ val = (val & mask) | (old & ~mask);
+ writel(val, ctx->mixer_regs + reg_id);
+}
+
+static void mixer_regs_dump(struct mixer_context *ctx)
+{
+#define DUMPREG(reg_id) \
+do { \
+ DRM_DEV_DEBUG_KMS(ctx->dev, #reg_id " = %08x\n", \
+ (u32)readl(ctx->mixer_regs + reg_id)); \
+} while (0)
+
+ DUMPREG(MXR_STATUS);
+ DUMPREG(MXR_CFG);
+ DUMPREG(MXR_INT_EN);
+ DUMPREG(MXR_INT_STATUS);
+
+ DUMPREG(MXR_LAYER_CFG);
+ DUMPREG(MXR_VIDEO_CFG);
+
+ DUMPREG(MXR_GRAPHIC0_CFG);
+ DUMPREG(MXR_GRAPHIC0_BASE);
+ DUMPREG(MXR_GRAPHIC0_SPAN);
+ DUMPREG(MXR_GRAPHIC0_WH);
+ DUMPREG(MXR_GRAPHIC0_SXY);
+ DUMPREG(MXR_GRAPHIC0_DXY);
+
+ DUMPREG(MXR_GRAPHIC1_CFG);
+ DUMPREG(MXR_GRAPHIC1_BASE);
+ DUMPREG(MXR_GRAPHIC1_SPAN);
+ DUMPREG(MXR_GRAPHIC1_WH);
+ DUMPREG(MXR_GRAPHIC1_SXY);
+ DUMPREG(MXR_GRAPHIC1_DXY);
+#undef DUMPREG
+}
+
+static void vp_regs_dump(struct mixer_context *ctx)
+{
+#define DUMPREG(reg_id) \
+do { \
+ DRM_DEV_DEBUG_KMS(ctx->dev, #reg_id " = %08x\n", \
+ (u32) readl(ctx->vp_regs + reg_id)); \
+} while (0)
+
+ DUMPREG(VP_ENABLE);
+ DUMPREG(VP_SRESET);
+ DUMPREG(VP_SHADOW_UPDATE);
+ DUMPREG(VP_FIELD_ID);
+ DUMPREG(VP_MODE);
+ DUMPREG(VP_IMG_SIZE_Y);
+ DUMPREG(VP_IMG_SIZE_C);
+ DUMPREG(VP_PER_RATE_CTRL);
+ DUMPREG(VP_TOP_Y_PTR);
+ DUMPREG(VP_BOT_Y_PTR);
+ DUMPREG(VP_TOP_C_PTR);
+ DUMPREG(VP_BOT_C_PTR);
+ DUMPREG(VP_ENDIAN_MODE);
+ DUMPREG(VP_SRC_H_POSITION);
+ DUMPREG(VP_SRC_V_POSITION);
+ DUMPREG(VP_SRC_WIDTH);
+ DUMPREG(VP_SRC_HEIGHT);
+ DUMPREG(VP_DST_H_POSITION);
+ DUMPREG(VP_DST_V_POSITION);
+ DUMPREG(VP_DST_WIDTH);
+ DUMPREG(VP_DST_HEIGHT);
+ DUMPREG(VP_H_RATIO);
+ DUMPREG(VP_V_RATIO);
+
+#undef DUMPREG
+}
+
+static inline void vp_filter_set(struct mixer_context *ctx,
+ int reg_id, const u8 *data, unsigned int size)
+{
+ /* assure 4-byte align */
+ BUG_ON(size & 3);
+ for (; size; size -= 4, reg_id += 4, data += 4) {
+ u32 val = (data[0] << 24) | (data[1] << 16) |
+ (data[2] << 8) | data[3];
+ vp_reg_write(ctx, reg_id, val);
+ }
+}
+
+static void vp_default_filter(struct mixer_context *ctx)
+{
+ vp_filter_set(ctx, VP_POLY8_Y0_LL,
+ filter_y_horiz_tap8, sizeof(filter_y_horiz_tap8));
+ vp_filter_set(ctx, VP_POLY4_Y0_LL,
+ filter_y_vert_tap4, sizeof(filter_y_vert_tap4));
+ vp_filter_set(ctx, VP_POLY4_C0_LL,
+ filter_cr_horiz_tap4, sizeof(filter_cr_horiz_tap4));
+}
+
+static void mixer_cfg_gfx_blend(struct mixer_context *ctx, unsigned int win,
+ unsigned int pixel_alpha, unsigned int alpha)
+{
+ u32 win_alpha = alpha >> 8;
+ u32 val;
+
+ val = MXR_GRP_CFG_COLOR_KEY_DISABLE; /* no blank key */
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ break;
+ case DRM_MODE_BLEND_COVERAGE:
+ val |= MXR_GRP_CFG_PIXEL_BLEND_EN;
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ default:
+ val |= MXR_GRP_CFG_BLEND_PRE_MUL;
+ val |= MXR_GRP_CFG_PIXEL_BLEND_EN;
+ break;
+ }
+
+ if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
+ val |= MXR_GRP_CFG_WIN_BLEND_EN;
+ val |= win_alpha;
+ }
+ mixer_reg_writemask(ctx, MXR_GRAPHIC_CFG(win),
+ val, MXR_GRP_CFG_MISC_MASK);
+}
+
+static void mixer_cfg_vp_blend(struct mixer_context *ctx, unsigned int alpha)
+{
+ u32 win_alpha = alpha >> 8;
+ u32 val = 0;
+
+ if (alpha != DRM_BLEND_ALPHA_OPAQUE) {
+ val |= MXR_VID_CFG_BLEND_EN;
+ val |= win_alpha;
+ }
+ mixer_reg_write(ctx, MXR_VIDEO_CFG, val);
+}
+
+static bool mixer_is_synced(struct mixer_context *ctx)
+{
+ u32 base, shadow;
+
+ if (ctx->mxr_ver == MXR_VER_16_0_33_0 ||
+ ctx->mxr_ver == MXR_VER_128_0_0_184)
+ return !(mixer_reg_read(ctx, MXR_CFG) &
+ MXR_CFG_LAYER_UPDATE_COUNT_MASK);
+
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags) &&
+ vp_reg_read(ctx, VP_SHADOW_UPDATE))
+ return false;
+
+ base = mixer_reg_read(ctx, MXR_CFG);
+ shadow = mixer_reg_read(ctx, MXR_CFG_S);
+ if (base != shadow)
+ return false;
+
+ base = mixer_reg_read(ctx, MXR_GRAPHIC_BASE(0));
+ shadow = mixer_reg_read(ctx, MXR_GRAPHIC_BASE_S(0));
+ if (base != shadow)
+ return false;
+
+ base = mixer_reg_read(ctx, MXR_GRAPHIC_BASE(1));
+ shadow = mixer_reg_read(ctx, MXR_GRAPHIC_BASE_S(1));
+ if (base != shadow)
+ return false;
+
+ return true;
+}
+
+static int mixer_wait_for_sync(struct mixer_context *ctx)
+{
+ ktime_t timeout = ktime_add_us(ktime_get(), 100000);
+
+ while (!mixer_is_synced(ctx)) {
+ usleep_range(1000, 2000);
+ if (ktime_compare(ktime_get(), timeout) > 0)
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static void mixer_disable_sync(struct mixer_context *ctx)
+{
+ mixer_reg_writemask(ctx, MXR_STATUS, 0, MXR_STATUS_SYNC_ENABLE);
+}
+
+static void mixer_enable_sync(struct mixer_context *ctx)
+{
+ if (ctx->mxr_ver == MXR_VER_16_0_33_0 ||
+ ctx->mxr_ver == MXR_VER_128_0_0_184)
+ mixer_reg_writemask(ctx, MXR_CFG, ~0, MXR_CFG_LAYER_UPDATE);
+ mixer_reg_writemask(ctx, MXR_STATUS, ~0, MXR_STATUS_SYNC_ENABLE);
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags))
+ vp_reg_write(ctx, VP_SHADOW_UPDATE, VP_SHADOW_UPDATE_ENABLE);
+}
+
+static void mixer_cfg_scan(struct mixer_context *ctx, int width, int height)
+{
+ u32 val;
+
+ /* choosing between interlace and progressive mode */
+ val = test_bit(MXR_BIT_INTERLACE, &ctx->flags) ?
+ MXR_CFG_SCAN_INTERLACE : MXR_CFG_SCAN_PROGRESSIVE;
+
+ if (ctx->mxr_ver == MXR_VER_128_0_0_184)
+ mixer_reg_write(ctx, MXR_RESOLUTION,
+ MXR_MXR_RES_HEIGHT(height) | MXR_MXR_RES_WIDTH(width));
+ else
+ val |= ctx->scan_value;
+
+ mixer_reg_writemask(ctx, MXR_CFG, val, MXR_CFG_SCAN_MASK);
+}
+
+static void mixer_cfg_rgb_fmt(struct mixer_context *ctx, struct drm_display_mode *mode)
+{
+ enum hdmi_quantization_range range = drm_default_rgb_quant_range(mode);
+ u32 val;
+
+ if (mode->vdisplay < 720) {
+ val = MXR_CFG_RGB601;
+ } else {
+ val = MXR_CFG_RGB709;
+
+ /* Configure the BT.709 CSC matrix for full range RGB. */
+ mixer_reg_write(ctx, MXR_CM_COEFF_Y,
+ MXR_CSC_CT( 0.184, 0.614, 0.063) |
+ MXR_CM_COEFF_RGB_FULL);
+ mixer_reg_write(ctx, MXR_CM_COEFF_CB,
+ MXR_CSC_CT(-0.102, -0.338, 0.440));
+ mixer_reg_write(ctx, MXR_CM_COEFF_CR,
+ MXR_CSC_CT( 0.440, -0.399, -0.040));
+ }
+
+ if (range == HDMI_QUANTIZATION_RANGE_FULL)
+ val |= MXR_CFG_QUANT_RANGE_FULL;
+ else
+ val |= MXR_CFG_QUANT_RANGE_LIMITED;
+
+ mixer_reg_writemask(ctx, MXR_CFG, val, MXR_CFG_RGB_FMT_MASK);
+}
+
+static void mixer_cfg_layer(struct mixer_context *ctx, unsigned int win,
+ unsigned int priority, bool enable)
+{
+ u32 val = enable ? ~0 : 0;
+
+ switch (win) {
+ case 0:
+ mixer_reg_writemask(ctx, MXR_CFG, val, MXR_CFG_GRP0_ENABLE);
+ mixer_reg_writemask(ctx, MXR_LAYER_CFG,
+ MXR_LAYER_CFG_GRP0_VAL(priority),
+ MXR_LAYER_CFG_GRP0_MASK);
+ break;
+ case 1:
+ mixer_reg_writemask(ctx, MXR_CFG, val, MXR_CFG_GRP1_ENABLE);
+ mixer_reg_writemask(ctx, MXR_LAYER_CFG,
+ MXR_LAYER_CFG_GRP1_VAL(priority),
+ MXR_LAYER_CFG_GRP1_MASK);
+
+ break;
+ case VP_DEFAULT_WIN:
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags)) {
+ vp_reg_writemask(ctx, VP_ENABLE, val, VP_ENABLE_ON);
+ mixer_reg_writemask(ctx, MXR_CFG, val,
+ MXR_CFG_VP_ENABLE);
+ mixer_reg_writemask(ctx, MXR_LAYER_CFG,
+ MXR_LAYER_CFG_VP_VAL(priority),
+ MXR_LAYER_CFG_VP_MASK);
+ }
+ break;
+ }
+}
+
+static void mixer_run(struct mixer_context *ctx)
+{
+ mixer_reg_writemask(ctx, MXR_STATUS, ~0, MXR_STATUS_REG_RUN);
+}
+
+static void mixer_stop(struct mixer_context *ctx)
+{
+ int timeout = 20;
+
+ mixer_reg_writemask(ctx, MXR_STATUS, 0, MXR_STATUS_REG_RUN);
+
+ while (!(mixer_reg_read(ctx, MXR_STATUS) & MXR_STATUS_REG_IDLE) &&
+ --timeout)
+ usleep_range(10000, 12000);
+}
+
+static void mixer_commit(struct mixer_context *ctx)
+{
+ struct drm_display_mode *mode = &ctx->crtc->base.state->adjusted_mode;
+
+ mixer_cfg_scan(ctx, mode->hdisplay, mode->vdisplay);
+ mixer_cfg_rgb_fmt(ctx, mode);
+ mixer_run(ctx);
+}
+
+static void vp_video_buffer(struct mixer_context *ctx,
+ struct exynos_drm_plane *plane)
+{
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ struct drm_framebuffer *fb = state->base.fb;
+ unsigned int priority = state->base.normalized_zpos + 1;
+ unsigned long flags;
+ dma_addr_t luma_addr[2], chroma_addr[2];
+ bool is_tiled, is_nv21;
+ u32 val;
+
+ is_nv21 = (fb->format->format == DRM_FORMAT_NV21);
+ is_tiled = (fb->modifier == DRM_FORMAT_MOD_SAMSUNG_64_32_TILE);
+
+ luma_addr[0] = exynos_drm_fb_dma_addr(fb, 0);
+ chroma_addr[0] = exynos_drm_fb_dma_addr(fb, 1);
+
+ if (test_bit(MXR_BIT_INTERLACE, &ctx->flags)) {
+ if (is_tiled) {
+ luma_addr[1] = luma_addr[0] + 0x40;
+ chroma_addr[1] = chroma_addr[0] + 0x40;
+ } else {
+ luma_addr[1] = luma_addr[0] + fb->pitches[0];
+ chroma_addr[1] = chroma_addr[0] + fb->pitches[1];
+ }
+ } else {
+ luma_addr[1] = 0;
+ chroma_addr[1] = 0;
+ }
+
+ spin_lock_irqsave(&ctx->reg_slock, flags);
+
+ /* interlace or progressive scan mode */
+ val = (test_bit(MXR_BIT_INTERLACE, &ctx->flags) ? ~0 : 0);
+ vp_reg_writemask(ctx, VP_MODE, val, VP_MODE_LINE_SKIP);
+
+ /* setup format */
+ val = (is_nv21 ? VP_MODE_NV21 : VP_MODE_NV12);
+ val |= (is_tiled ? VP_MODE_MEM_TILED : VP_MODE_MEM_LINEAR);
+ vp_reg_writemask(ctx, VP_MODE, val, VP_MODE_FMT_MASK);
+
+ /* setting size of input image */
+ vp_reg_write(ctx, VP_IMG_SIZE_Y, VP_IMG_HSIZE(fb->pitches[0]) |
+ VP_IMG_VSIZE(fb->height));
+ /* chroma plane for NV12/NV21 is half the height of the luma plane */
+ vp_reg_write(ctx, VP_IMG_SIZE_C, VP_IMG_HSIZE(fb->pitches[1]) |
+ VP_IMG_VSIZE(fb->height / 2));
+
+ vp_reg_write(ctx, VP_SRC_WIDTH, state->src.w);
+ vp_reg_write(ctx, VP_SRC_H_POSITION,
+ VP_SRC_H_POSITION_VAL(state->src.x));
+ vp_reg_write(ctx, VP_DST_WIDTH, state->crtc.w);
+ vp_reg_write(ctx, VP_DST_H_POSITION, state->crtc.x);
+
+ if (test_bit(MXR_BIT_INTERLACE, &ctx->flags)) {
+ vp_reg_write(ctx, VP_SRC_HEIGHT, state->src.h / 2);
+ vp_reg_write(ctx, VP_SRC_V_POSITION, state->src.y / 2);
+ vp_reg_write(ctx, VP_DST_HEIGHT, state->crtc.h / 2);
+ vp_reg_write(ctx, VP_DST_V_POSITION, state->crtc.y / 2);
+ } else {
+ vp_reg_write(ctx, VP_SRC_HEIGHT, state->src.h);
+ vp_reg_write(ctx, VP_SRC_V_POSITION, state->src.y);
+ vp_reg_write(ctx, VP_DST_HEIGHT, state->crtc.h);
+ vp_reg_write(ctx, VP_DST_V_POSITION, state->crtc.y);
+ }
+
+ vp_reg_write(ctx, VP_H_RATIO, state->h_ratio);
+ vp_reg_write(ctx, VP_V_RATIO, state->v_ratio);
+
+ vp_reg_write(ctx, VP_ENDIAN_MODE, VP_ENDIAN_MODE_LITTLE);
+
+ /* set buffer address to vp */
+ vp_reg_write(ctx, VP_TOP_Y_PTR, luma_addr[0]);
+ vp_reg_write(ctx, VP_BOT_Y_PTR, luma_addr[1]);
+ vp_reg_write(ctx, VP_TOP_C_PTR, chroma_addr[0]);
+ vp_reg_write(ctx, VP_BOT_C_PTR, chroma_addr[1]);
+
+ mixer_cfg_layer(ctx, plane->index, priority, true);
+ mixer_cfg_vp_blend(ctx, state->base.alpha);
+
+ spin_unlock_irqrestore(&ctx->reg_slock, flags);
+
+ mixer_regs_dump(ctx);
+ vp_regs_dump(ctx);
+}
+
+static void mixer_graph_buffer(struct mixer_context *ctx,
+ struct exynos_drm_plane *plane)
+{
+ struct exynos_drm_plane_state *state =
+ to_exynos_plane_state(plane->base.state);
+ struct drm_framebuffer *fb = state->base.fb;
+ unsigned int priority = state->base.normalized_zpos + 1;
+ unsigned long flags;
+ unsigned int win = plane->index;
+ unsigned int x_ratio = 0, y_ratio = 0;
+ unsigned int dst_x_offset, dst_y_offset;
+ unsigned int pixel_alpha;
+ dma_addr_t dma_addr;
+ unsigned int fmt;
+ u32 val;
+
+ if (fb->format->has_alpha)
+ pixel_alpha = state->base.pixel_blend_mode;
+ else
+ pixel_alpha = DRM_MODE_BLEND_PIXEL_NONE;
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_ARGB4444:
+ fmt = MXR_FORMAT_ARGB4444;
+ break;
+
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ fmt = MXR_FORMAT_ARGB1555;
+ break;
+
+ case DRM_FORMAT_RGB565:
+ fmt = MXR_FORMAT_RGB565;
+ break;
+
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ default:
+ fmt = MXR_FORMAT_ARGB8888;
+ break;
+ }
+
+ /* ratio is already checked by common plane code */
+ x_ratio = state->h_ratio == (1 << 15);
+ y_ratio = state->v_ratio == (1 << 15);
+
+ dst_x_offset = state->crtc.x;
+ dst_y_offset = state->crtc.y;
+
+ /* translate dma address base s.t. the source image offset is zero */
+ dma_addr = exynos_drm_fb_dma_addr(fb, 0)
+ + (state->src.x * fb->format->cpp[0])
+ + (state->src.y * fb->pitches[0]);
+
+ spin_lock_irqsave(&ctx->reg_slock, flags);
+
+ /* setup format */
+ mixer_reg_writemask(ctx, MXR_GRAPHIC_CFG(win),
+ MXR_GRP_CFG_FORMAT_VAL(fmt), MXR_GRP_CFG_FORMAT_MASK);
+
+ /* setup geometry */
+ mixer_reg_write(ctx, MXR_GRAPHIC_SPAN(win),
+ fb->pitches[0] / fb->format->cpp[0]);
+
+ val = MXR_GRP_WH_WIDTH(state->src.w);
+ val |= MXR_GRP_WH_HEIGHT(state->src.h);
+ val |= MXR_GRP_WH_H_SCALE(x_ratio);
+ val |= MXR_GRP_WH_V_SCALE(y_ratio);
+ mixer_reg_write(ctx, MXR_GRAPHIC_WH(win), val);
+
+ /* setup offsets in display image */
+ val = MXR_GRP_DXY_DX(dst_x_offset);
+ val |= MXR_GRP_DXY_DY(dst_y_offset);
+ mixer_reg_write(ctx, MXR_GRAPHIC_DXY(win), val);
+
+ /* set buffer address to mixer */
+ mixer_reg_write(ctx, MXR_GRAPHIC_BASE(win), dma_addr);
+
+ mixer_cfg_layer(ctx, win, priority, true);
+ mixer_cfg_gfx_blend(ctx, win, pixel_alpha, state->base.alpha);
+
+ spin_unlock_irqrestore(&ctx->reg_slock, flags);
+
+ mixer_regs_dump(ctx);
+}
+
+static void vp_win_reset(struct mixer_context *ctx)
+{
+ unsigned int tries = 100;
+
+ vp_reg_write(ctx, VP_SRESET, VP_SRESET_PROCESSING);
+ while (--tries) {
+ /* waiting until VP_SRESET_PROCESSING is 0 */
+ if (~vp_reg_read(ctx, VP_SRESET) & VP_SRESET_PROCESSING)
+ break;
+ mdelay(10);
+ }
+ WARN(tries == 0, "failed to reset Video Processor\n");
+}
+
+static void mixer_win_reset(struct mixer_context *ctx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->reg_slock, flags);
+
+ mixer_reg_writemask(ctx, MXR_CFG, MXR_CFG_DST_HDMI, MXR_CFG_DST_MASK);
+
+ /* set output in RGB888 mode */
+ mixer_reg_writemask(ctx, MXR_CFG, MXR_CFG_OUT_RGB888, MXR_CFG_OUT_MASK);
+
+ /* 16 beat burst in DMA */
+ mixer_reg_writemask(ctx, MXR_STATUS, MXR_STATUS_16_BURST,
+ MXR_STATUS_BURST_MASK);
+
+ /* reset default layer priority */
+ mixer_reg_write(ctx, MXR_LAYER_CFG, 0);
+
+ /* set all background colors to RGB (0,0,0) */
+ mixer_reg_write(ctx, MXR_BG_COLOR0, MXR_YCBCR_VAL(0, 128, 128));
+ mixer_reg_write(ctx, MXR_BG_COLOR1, MXR_YCBCR_VAL(0, 128, 128));
+ mixer_reg_write(ctx, MXR_BG_COLOR2, MXR_YCBCR_VAL(0, 128, 128));
+
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags)) {
+ /* configuration of Video Processor Registers */
+ vp_win_reset(ctx);
+ vp_default_filter(ctx);
+ }
+
+ /* disable all layers */
+ mixer_reg_writemask(ctx, MXR_CFG, 0, MXR_CFG_GRP0_ENABLE);
+ mixer_reg_writemask(ctx, MXR_CFG, 0, MXR_CFG_GRP1_ENABLE);
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags))
+ mixer_reg_writemask(ctx, MXR_CFG, 0, MXR_CFG_VP_ENABLE);
+
+ /* set all source image offsets to zero */
+ mixer_reg_write(ctx, MXR_GRAPHIC_SXY(0), 0);
+ mixer_reg_write(ctx, MXR_GRAPHIC_SXY(1), 0);
+
+ spin_unlock_irqrestore(&ctx->reg_slock, flags);
+}
+
+static irqreturn_t mixer_irq_handler(int irq, void *arg)
+{
+ struct mixer_context *ctx = arg;
+ u32 val;
+
+ spin_lock(&ctx->reg_slock);
+
+ /* read interrupt status for handling and clearing flags for VSYNC */
+ val = mixer_reg_read(ctx, MXR_INT_STATUS);
+
+ /* handling VSYNC */
+ if (val & MXR_INT_STATUS_VSYNC) {
+ /* vsync interrupt use different bit for read and clear */
+ val |= MXR_INT_CLEAR_VSYNC;
+ val &= ~MXR_INT_STATUS_VSYNC;
+
+ /* interlace scan need to check shadow register */
+ if (test_bit(MXR_BIT_INTERLACE, &ctx->flags)
+ && !mixer_is_synced(ctx))
+ goto out;
+
+ drm_crtc_handle_vblank(&ctx->crtc->base);
+ }
+
+out:
+ /* clear interrupts */
+ mixer_reg_write(ctx, MXR_INT_STATUS, val);
+
+ spin_unlock(&ctx->reg_slock);
+
+ return IRQ_HANDLED;
+}
+
+static int mixer_resources_init(struct mixer_context *mixer_ctx)
+{
+ struct device *dev = &mixer_ctx->pdev->dev;
+ struct resource *res;
+ int ret;
+
+ spin_lock_init(&mixer_ctx->reg_slock);
+
+ mixer_ctx->mixer = devm_clk_get(dev, "mixer");
+ if (IS_ERR(mixer_ctx->mixer)) {
+ dev_err(dev, "failed to get clock 'mixer'\n");
+ return -ENODEV;
+ }
+
+ mixer_ctx->hdmi = devm_clk_get(dev, "hdmi");
+ if (IS_ERR(mixer_ctx->hdmi)) {
+ dev_err(dev, "failed to get clock 'hdmi'\n");
+ return PTR_ERR(mixer_ctx->hdmi);
+ }
+
+ mixer_ctx->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi");
+ if (IS_ERR(mixer_ctx->sclk_hdmi)) {
+ dev_err(dev, "failed to get clock 'sclk_hdmi'\n");
+ return -ENODEV;
+ }
+ res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(dev, "get memory resource failed.\n");
+ return -ENXIO;
+ }
+
+ mixer_ctx->mixer_regs = devm_ioremap(dev, res->start,
+ resource_size(res));
+ if (mixer_ctx->mixer_regs == NULL) {
+ dev_err(dev, "register mapping failed.\n");
+ return -ENXIO;
+ }
+
+ ret = platform_get_irq(mixer_ctx->pdev, 0);
+ if (ret < 0)
+ return ret;
+ mixer_ctx->irq = ret;
+
+ ret = devm_request_irq(dev, mixer_ctx->irq, mixer_irq_handler,
+ 0, "drm_mixer", mixer_ctx);
+ if (ret) {
+ dev_err(dev, "request interrupt failed.\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vp_resources_init(struct mixer_context *mixer_ctx)
+{
+ struct device *dev = &mixer_ctx->pdev->dev;
+ struct resource *res;
+
+ mixer_ctx->vp = devm_clk_get(dev, "vp");
+ if (IS_ERR(mixer_ctx->vp)) {
+ dev_err(dev, "failed to get clock 'vp'\n");
+ return -ENODEV;
+ }
+
+ if (test_bit(MXR_BIT_HAS_SCLK, &mixer_ctx->flags)) {
+ mixer_ctx->sclk_mixer = devm_clk_get(dev, "sclk_mixer");
+ if (IS_ERR(mixer_ctx->sclk_mixer)) {
+ dev_err(dev, "failed to get clock 'sclk_mixer'\n");
+ return -ENODEV;
+ }
+ mixer_ctx->mout_mixer = devm_clk_get(dev, "mout_mixer");
+ if (IS_ERR(mixer_ctx->mout_mixer)) {
+ dev_err(dev, "failed to get clock 'mout_mixer'\n");
+ return -ENODEV;
+ }
+
+ if (mixer_ctx->sclk_hdmi && mixer_ctx->mout_mixer)
+ clk_set_parent(mixer_ctx->mout_mixer,
+ mixer_ctx->sclk_hdmi);
+ }
+
+ res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 1);
+ if (res == NULL) {
+ dev_err(dev, "get memory resource failed.\n");
+ return -ENXIO;
+ }
+
+ mixer_ctx->vp_regs = devm_ioremap(dev, res->start,
+ resource_size(res));
+ if (mixer_ctx->vp_regs == NULL) {
+ dev_err(dev, "register mapping failed.\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int mixer_initialize(struct mixer_context *mixer_ctx,
+ struct drm_device *drm_dev)
+{
+ int ret;
+
+ mixer_ctx->drm_dev = drm_dev;
+
+ /* acquire resources: regs, irqs, clocks */
+ ret = mixer_resources_init(mixer_ctx);
+ if (ret) {
+ DRM_DEV_ERROR(mixer_ctx->dev,
+ "mixer_resources_init failed ret=%d\n", ret);
+ return ret;
+ }
+
+ if (test_bit(MXR_BIT_VP_ENABLED, &mixer_ctx->flags)) {
+ /* acquire vp resources: regs, irqs, clocks */
+ ret = vp_resources_init(mixer_ctx);
+ if (ret) {
+ DRM_DEV_ERROR(mixer_ctx->dev,
+ "vp_resources_init failed ret=%d\n", ret);
+ return ret;
+ }
+ }
+
+ return exynos_drm_register_dma(drm_dev, mixer_ctx->dev,
+ &mixer_ctx->dma_priv);
+}
+
+static void mixer_ctx_remove(struct mixer_context *mixer_ctx)
+{
+ exynos_drm_unregister_dma(mixer_ctx->drm_dev, mixer_ctx->dev,
+ &mixer_ctx->dma_priv);
+}
+
+static int mixer_enable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *mixer_ctx = crtc->ctx;
+
+ __set_bit(MXR_BIT_VSYNC, &mixer_ctx->flags);
+ if (!test_bit(MXR_BIT_POWERED, &mixer_ctx->flags))
+ return 0;
+
+ /* enable vsync interrupt */
+ mixer_reg_writemask(mixer_ctx, MXR_INT_STATUS, ~0, MXR_INT_CLEAR_VSYNC);
+ mixer_reg_writemask(mixer_ctx, MXR_INT_EN, ~0, MXR_INT_EN_VSYNC);
+
+ return 0;
+}
+
+static void mixer_disable_vblank(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *mixer_ctx = crtc->ctx;
+
+ __clear_bit(MXR_BIT_VSYNC, &mixer_ctx->flags);
+
+ if (!test_bit(MXR_BIT_POWERED, &mixer_ctx->flags))
+ return;
+
+ /* disable vsync interrupt */
+ mixer_reg_writemask(mixer_ctx, MXR_INT_STATUS, ~0, MXR_INT_CLEAR_VSYNC);
+ mixer_reg_writemask(mixer_ctx, MXR_INT_EN, 0, MXR_INT_EN_VSYNC);
+}
+
+static void mixer_atomic_begin(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *ctx = crtc->ctx;
+
+ if (!test_bit(MXR_BIT_POWERED, &ctx->flags))
+ return;
+
+ if (mixer_wait_for_sync(ctx))
+ dev_err(ctx->dev, "timeout waiting for VSYNC\n");
+ mixer_disable_sync(ctx);
+}
+
+static void mixer_update_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct mixer_context *mixer_ctx = crtc->ctx;
+
+ DRM_DEV_DEBUG_KMS(mixer_ctx->dev, "win: %d\n", plane->index);
+
+ if (!test_bit(MXR_BIT_POWERED, &mixer_ctx->flags))
+ return;
+
+ if (plane->index == VP_DEFAULT_WIN)
+ vp_video_buffer(mixer_ctx, plane);
+ else
+ mixer_graph_buffer(mixer_ctx, plane);
+}
+
+static void mixer_disable_plane(struct exynos_drm_crtc *crtc,
+ struct exynos_drm_plane *plane)
+{
+ struct mixer_context *mixer_ctx = crtc->ctx;
+ unsigned long flags;
+
+ DRM_DEV_DEBUG_KMS(mixer_ctx->dev, "win: %d\n", plane->index);
+
+ if (!test_bit(MXR_BIT_POWERED, &mixer_ctx->flags))
+ return;
+
+ spin_lock_irqsave(&mixer_ctx->reg_slock, flags);
+ mixer_cfg_layer(mixer_ctx, plane->index, 0, false);
+ spin_unlock_irqrestore(&mixer_ctx->reg_slock, flags);
+}
+
+static void mixer_atomic_flush(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *mixer_ctx = crtc->ctx;
+
+ if (!test_bit(MXR_BIT_POWERED, &mixer_ctx->flags))
+ return;
+
+ mixer_enable_sync(mixer_ctx);
+ exynos_crtc_handle_event(crtc);
+}
+
+static void mixer_atomic_enable(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *ctx = crtc->ctx;
+ int ret;
+
+ if (test_bit(MXR_BIT_POWERED, &ctx->flags))
+ return;
+
+ ret = pm_runtime_resume_and_get(ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable MIXER device.\n");
+ return;
+ }
+
+ exynos_drm_pipe_clk_enable(crtc, true);
+
+ mixer_disable_sync(ctx);
+
+ mixer_reg_writemask(ctx, MXR_STATUS, ~0, MXR_STATUS_SOFT_RESET);
+
+ if (test_bit(MXR_BIT_VSYNC, &ctx->flags)) {
+ mixer_reg_writemask(ctx, MXR_INT_STATUS, ~0,
+ MXR_INT_CLEAR_VSYNC);
+ mixer_reg_writemask(ctx, MXR_INT_EN, ~0, MXR_INT_EN_VSYNC);
+ }
+ mixer_win_reset(ctx);
+
+ mixer_commit(ctx);
+
+ mixer_enable_sync(ctx);
+
+ set_bit(MXR_BIT_POWERED, &ctx->flags);
+}
+
+static void mixer_atomic_disable(struct exynos_drm_crtc *crtc)
+{
+ struct mixer_context *ctx = crtc->ctx;
+ int i;
+
+ if (!test_bit(MXR_BIT_POWERED, &ctx->flags))
+ return;
+
+ mixer_stop(ctx);
+ mixer_regs_dump(ctx);
+
+ for (i = 0; i < MIXER_WIN_NR; i++)
+ mixer_disable_plane(crtc, &ctx->planes[i]);
+
+ exynos_drm_pipe_clk_enable(crtc, false);
+
+ pm_runtime_put(ctx->dev);
+
+ clear_bit(MXR_BIT_POWERED, &ctx->flags);
+}
+
+static enum drm_mode_status mixer_mode_valid(struct exynos_drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct mixer_context *ctx = crtc->ctx;
+ u32 w = mode->hdisplay, h = mode->vdisplay;
+
+ DRM_DEV_DEBUG_KMS(ctx->dev, "xres=%d, yres=%d, refresh=%d, intl=%d\n",
+ w, h, drm_mode_vrefresh(mode),
+ !!(mode->flags & DRM_MODE_FLAG_INTERLACE));
+
+ if (ctx->mxr_ver == MXR_VER_128_0_0_184)
+ return MODE_OK;
+
+ if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) ||
+ (w >= 1024 && w <= 1280 && h >= 576 && h <= 720) ||
+ (w >= 1664 && w <= 1920 && h >= 936 && h <= 1080))
+ return MODE_OK;
+
+ if ((w == 1024 && h == 768) ||
+ (w == 1366 && h == 768) ||
+ (w == 1280 && h == 1024))
+ return MODE_OK;
+
+ return MODE_BAD;
+}
+
+static bool mixer_mode_fixup(struct exynos_drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct mixer_context *ctx = crtc->ctx;
+ int width = mode->hdisplay, height = mode->vdisplay, i;
+
+ static const struct {
+ int hdisplay, vdisplay, htotal, vtotal, scan_val;
+ } modes[] = {
+ { 720, 480, 858, 525, MXR_CFG_SCAN_NTSC | MXR_CFG_SCAN_SD },
+ { 720, 576, 864, 625, MXR_CFG_SCAN_PAL | MXR_CFG_SCAN_SD },
+ { 1280, 720, 1650, 750, MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD },
+ { 1920, 1080, 2200, 1125, MXR_CFG_SCAN_HD_1080 |
+ MXR_CFG_SCAN_HD }
+ };
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ __set_bit(MXR_BIT_INTERLACE, &ctx->flags);
+ else
+ __clear_bit(MXR_BIT_INTERLACE, &ctx->flags);
+
+ if (ctx->mxr_ver == MXR_VER_128_0_0_184)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(modes); ++i)
+ if (width <= modes[i].hdisplay && height <= modes[i].vdisplay) {
+ ctx->scan_value = modes[i].scan_val;
+ if (width < modes[i].hdisplay ||
+ height < modes[i].vdisplay) {
+ adjusted_mode->hdisplay = modes[i].hdisplay;
+ adjusted_mode->hsync_start = modes[i].hdisplay;
+ adjusted_mode->hsync_end = modes[i].htotal;
+ adjusted_mode->htotal = modes[i].htotal;
+ adjusted_mode->vdisplay = modes[i].vdisplay;
+ adjusted_mode->vsync_start = modes[i].vdisplay;
+ adjusted_mode->vsync_end = modes[i].vtotal;
+ adjusted_mode->vtotal = modes[i].vtotal;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static const struct exynos_drm_crtc_ops mixer_crtc_ops = {
+ .atomic_enable = mixer_atomic_enable,
+ .atomic_disable = mixer_atomic_disable,
+ .enable_vblank = mixer_enable_vblank,
+ .disable_vblank = mixer_disable_vblank,
+ .atomic_begin = mixer_atomic_begin,
+ .update_plane = mixer_update_plane,
+ .disable_plane = mixer_disable_plane,
+ .atomic_flush = mixer_atomic_flush,
+ .mode_valid = mixer_mode_valid,
+ .mode_fixup = mixer_mode_fixup,
+};
+
+static const struct mixer_drv_data exynos5420_mxr_drv_data = {
+ .version = MXR_VER_128_0_0_184,
+ .is_vp_enabled = 0,
+};
+
+static const struct mixer_drv_data exynos5250_mxr_drv_data = {
+ .version = MXR_VER_16_0_33_0,
+ .is_vp_enabled = 0,
+};
+
+static const struct mixer_drv_data exynos4212_mxr_drv_data = {
+ .version = MXR_VER_0_0_0_16,
+ .is_vp_enabled = 1,
+};
+
+static const struct mixer_drv_data exynos4210_mxr_drv_data = {
+ .version = MXR_VER_0_0_0_16,
+ .is_vp_enabled = 1,
+ .has_sclk = 1,
+};
+
+static const struct of_device_id mixer_match_types[] = {
+ {
+ .compatible = "samsung,exynos4210-mixer",
+ .data = &exynos4210_mxr_drv_data,
+ }, {
+ .compatible = "samsung,exynos4212-mixer",
+ .data = &exynos4212_mxr_drv_data,
+ }, {
+ .compatible = "samsung,exynos5-mixer",
+ .data = &exynos5250_mxr_drv_data,
+ }, {
+ .compatible = "samsung,exynos5250-mixer",
+ .data = &exynos5250_mxr_drv_data,
+ }, {
+ .compatible = "samsung,exynos5420-mixer",
+ .data = &exynos5420_mxr_drv_data,
+ }, {
+ /* end node */
+ }
+};
+MODULE_DEVICE_TABLE(of, mixer_match_types);
+
+static int mixer_bind(struct device *dev, struct device *manager, void *data)
+{
+ struct mixer_context *ctx = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct exynos_drm_plane *exynos_plane;
+ unsigned int i;
+ int ret;
+
+ ret = mixer_initialize(ctx, drm_dev);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < MIXER_WIN_NR; i++) {
+ if (i == VP_DEFAULT_WIN && !test_bit(MXR_BIT_VP_ENABLED,
+ &ctx->flags))
+ continue;
+
+ ret = exynos_plane_init(drm_dev, &ctx->planes[i], i,
+ &plane_configs[i]);
+ if (ret)
+ return ret;
+ }
+
+ exynos_plane = &ctx->planes[DEFAULT_WIN];
+ ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
+ EXYNOS_DISPLAY_TYPE_HDMI, &mixer_crtc_ops, ctx);
+ if (IS_ERR(ctx->crtc)) {
+ mixer_ctx_remove(ctx);
+ ret = PTR_ERR(ctx->crtc);
+ goto free_ctx;
+ }
+
+ return 0;
+
+free_ctx:
+ devm_kfree(dev, ctx);
+ return ret;
+}
+
+static void mixer_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct mixer_context *ctx = dev_get_drvdata(dev);
+
+ mixer_ctx_remove(ctx);
+}
+
+static const struct component_ops mixer_component_ops = {
+ .bind = mixer_bind,
+ .unbind = mixer_unbind,
+};
+
+static int mixer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct mixer_drv_data *drv;
+ struct mixer_context *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ DRM_DEV_ERROR(dev, "failed to alloc mixer context.\n");
+ return -ENOMEM;
+ }
+
+ drv = of_device_get_match_data(dev);
+
+ ctx->pdev = pdev;
+ ctx->dev = dev;
+ ctx->mxr_ver = drv->version;
+
+ if (drv->is_vp_enabled)
+ __set_bit(MXR_BIT_VP_ENABLED, &ctx->flags);
+ if (drv->has_sclk)
+ __set_bit(MXR_BIT_HAS_SCLK, &ctx->flags);
+
+ platform_set_drvdata(pdev, ctx);
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(&pdev->dev, &mixer_component_ops);
+ if (ret)
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void mixer_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ component_del(&pdev->dev, &mixer_component_ops);
+}
+
+static int __maybe_unused exynos_mixer_suspend(struct device *dev)
+{
+ struct mixer_context *ctx = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(ctx->hdmi);
+ clk_disable_unprepare(ctx->mixer);
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags)) {
+ clk_disable_unprepare(ctx->vp);
+ if (test_bit(MXR_BIT_HAS_SCLK, &ctx->flags))
+ clk_disable_unprepare(ctx->sclk_mixer);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused exynos_mixer_resume(struct device *dev)
+{
+ struct mixer_context *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(ctx->mixer);
+ if (ret < 0) {
+ DRM_DEV_ERROR(ctx->dev,
+ "Failed to prepare_enable the mixer clk [%d]\n",
+ ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(ctx->hdmi);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Failed to prepare_enable the hdmi clk [%d]\n",
+ ret);
+ return ret;
+ }
+ if (test_bit(MXR_BIT_VP_ENABLED, &ctx->flags)) {
+ ret = clk_prepare_enable(ctx->vp);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Failed to prepare_enable the vp clk [%d]\n",
+ ret);
+ return ret;
+ }
+ if (test_bit(MXR_BIT_HAS_SCLK, &ctx->flags)) {
+ ret = clk_prepare_enable(ctx->sclk_mixer);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Failed to prepare_enable the " \
+ "sclk_mixer clk [%d]\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos_mixer_pm_ops = {
+ SET_RUNTIME_PM_OPS(exynos_mixer_suspend, exynos_mixer_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+struct platform_driver mixer_driver = {
+ .driver = {
+ .name = "exynos-mixer",
+ .pm = &exynos_mixer_pm_ops,
+ .of_match_table = mixer_match_types,
+ },
+ .probe = mixer_probe,
+ .remove = mixer_remove,
+};
diff --git a/drivers/gpu/drm/exynos/regs-decon5433.h b/drivers/gpu/drm/exynos/regs-decon5433.h
new file mode 100644
index 000000000..c500844da
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-decon5433.h
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2014 Samsung Electronics Co.Ltd
+ */
+
+#ifndef EXYNOS_REGS_DECON5433_H
+#define EXYNOS_REGS_DECON5433_H
+
+/* Exynos543X DECON */
+#define DECON_VIDCON0 0x0000
+#define DECON_VIDOUTCON0 0x0010
+#define DECON_WINCONx(n) (0x0020 + ((n) * 4))
+#define DECON_VIDOSDxH(n) (0x0080 + ((n) * 4))
+#define DECON_SHADOWCON 0x00A0
+#define DECON_VIDOSDxA(n) (0x00B0 + ((n) * 0x20))
+#define DECON_VIDOSDxB(n) (0x00B4 + ((n) * 0x20))
+#define DECON_VIDOSDxC(n) (0x00B8 + ((n) * 0x20))
+#define DECON_VIDOSDxD(n) (0x00BC + ((n) * 0x20))
+#define DECON_VIDOSDxE(n) (0x00C0 + ((n) * 0x20))
+#define DECON_VIDW0xADD0B0(n) (0x0150 + ((n) * 0x10))
+#define DECON_VIDW0xADD0B1(n) (0x0154 + ((n) * 0x10))
+#define DECON_VIDW0xADD0B2(n) (0x0158 + ((n) * 0x10))
+#define DECON_VIDW0xADD1B0(n) (0x01A0 + ((n) * 0x10))
+#define DECON_VIDW0xADD1B1(n) (0x01A4 + ((n) * 0x10))
+#define DECON_VIDW0xADD1B2(n) (0x01A8 + ((n) * 0x10))
+#define DECON_VIDW0xADD2(n) (0x0200 + ((n) * 4))
+#define DECON_LOCALxSIZE(n) (0x0214 + ((n) * 4))
+#define DECON_VIDINTCON0 0x0220
+#define DECON_VIDINTCON1 0x0224
+#define DECON_WxKEYCON0(n) (0x0230 + ((n - 1) * 8))
+#define DECON_WxKEYCON1(n) (0x0234 + ((n - 1) * 8))
+#define DECON_WxKEYALPHA(n) (0x0250 + ((n - 1) * 4))
+#define DECON_WINxMAP(n) (0x0270 + ((n) * 4))
+#define DECON_QOSLUT07_00 0x02C0
+#define DECON_QOSLUT15_08 0x02C4
+#define DECON_QOSCTRL 0x02C8
+#define DECON_BLENDERQx(n) (0x0300 + ((n - 1) * 4))
+#define DECON_BLENDCON 0x0310
+#define DECON_OPE_VIDW0xADD0(n) (0x0400 + ((n) * 4))
+#define DECON_OPE_VIDW0xADD1(n) (0x0414 + ((n) * 4))
+#define DECON_FRAMEFIFO_REG7 0x051C
+#define DECON_FRAMEFIFO_REG8 0x0520
+#define DECON_FRAMEFIFO_STATUS 0x0524
+#define DECON_CMU 0x1404
+#define DECON_UPDATE 0x1410
+#define DECON_CRFMID 0x1414
+#define DECON_UPDATE_SCHEME 0x1438
+#define DECON_VIDCON1 0x2000
+#define DECON_VIDCON2 0x2004
+#define DECON_VIDCON3 0x2008
+#define DECON_VIDCON4 0x200C
+#define DECON_VIDTCON2 0x2028
+#define DECON_FRAME_SIZE 0x2038
+#define DECON_LINECNT_OP_THRESHOLD 0x203C
+#define DECON_TRIGCON 0x2040
+#define DECON_TRIGSKIP 0x2050
+#define DECON_CRCRDATA 0x20B0
+#define DECON_CRCCTRL 0x20B4
+
+/* Exynos5430 DECON */
+#define DECON_VIDTCON0 0x2020
+#define DECON_VIDTCON1 0x2024
+
+/* Exynos5433 DECON */
+#define DECON_VIDTCON00 0x2010
+#define DECON_VIDTCON01 0x2014
+#define DECON_VIDTCON10 0x2018
+#define DECON_VIDTCON11 0x201C
+
+/* Exynos543X DECON Internal */
+#define DECON_W013DSTREOCON 0x0320
+#define DECON_W233DSTREOCON 0x0324
+#define DECON_FRAMEFIFO_REG0 0x0500
+#define DECON_ENHANCER_CTRL 0x2100
+
+/* Exynos543X DECON TV */
+#define DECON_VCLKCON0 0x0014
+#define DECON_VIDINTCON2 0x0228
+#define DECON_VIDINTCON3 0x022C
+
+/* VIDCON0 */
+#define VIDCON0_SWRESET (1 << 28)
+#define VIDCON0_CLKVALUP (1 << 14)
+#define VIDCON0_VLCKFREE (1 << 5)
+#define VIDCON0_STOP_STATUS (1 << 2)
+#define VIDCON0_ENVID (1 << 1)
+#define VIDCON0_ENVID_F (1 << 0)
+
+/* VIDOUTCON0 */
+#define VIDOUT_INTERLACE_FIELD_F (1 << 29)
+#define VIDOUT_INTERLACE_EN_F (1 << 28)
+#define VIDOUT_LCD_ON (1 << 24)
+#define VIDOUT_IF_F_MASK (0x3 << 20)
+#define VIDOUT_RGB_IF (0x0 << 20)
+#define VIDOUT_COMMAND_IF (0x2 << 20)
+
+/* WINCONx */
+#define WINCONx_HAWSWP_F (1 << 16)
+#define WINCONx_WSWP_F (1 << 15)
+#define WINCONx_BURSTLEN_MASK (0x3 << 10)
+#define WINCONx_BURSTLEN_16WORD (0x0 << 10)
+#define WINCONx_BURSTLEN_8WORD (0x1 << 10)
+#define WINCONx_BURSTLEN_4WORD (0x2 << 10)
+#define WINCONx_ALPHA_MUL_F (1 << 7)
+#define WINCONx_BLD_PIX_F (1 << 6)
+#define WINCONx_BPPMODE_MASK (0xf << 2)
+#define WINCONx_BPPMODE_16BPP_565 (0x5 << 2)
+#define WINCONx_BPPMODE_16BPP_A1555 (0x6 << 2)
+#define WINCONx_BPPMODE_16BPP_I1555 (0x7 << 2)
+#define WINCONx_BPPMODE_24BPP_888 (0xb << 2)
+#define WINCONx_BPPMODE_24BPP_A1887 (0xc << 2)
+#define WINCONx_BPPMODE_25BPP_A1888 (0xd << 2)
+#define WINCONx_BPPMODE_32BPP_A8888 (0xd << 2)
+#define WINCONx_BPPMODE_16BPP_A4444 (0xe << 2)
+#define WINCONx_ALPHA_SEL_F (1 << 1)
+#define WINCONx_ENWIN_F (1 << 0)
+#define WINCONx_BLEND_MODE_MASK (0xc2)
+
+/* SHADOWCON */
+#define SHADOWCON_PROTECT_MASK GENMASK(14, 10)
+#define SHADOWCON_Wx_PROTECT(n) (1 << (10 + (n)))
+
+/* VIDOSDxC */
+#define VIDOSDxC_ALPHA0_RGB_MASK (0xffffff)
+
+/* VIDOSDxD */
+#define VIDOSD_Wx_ALPHA_R_F(n) (((n) & 0xff) << 16)
+#define VIDOSD_Wx_ALPHA_G_F(n) (((n) & 0xff) << 8)
+#define VIDOSD_Wx_ALPHA_B_F(n) (((n) & 0xff) << 0)
+
+/* VIDINTCON0 */
+#define VIDINTCON0_FRAMEDONE (1 << 17)
+#define VIDINTCON0_FRAMESEL_BP (0 << 15)
+#define VIDINTCON0_FRAMESEL_VS (1 << 15)
+#define VIDINTCON0_FRAMESEL_AC (2 << 15)
+#define VIDINTCON0_FRAMESEL_FP (3 << 15)
+#define VIDINTCON0_INTFRMEN (1 << 12)
+#define VIDINTCON0_INTEN (1 << 0)
+
+/* VIDINTCON1 */
+#define VIDINTCON1_INTFRMDONEPEND (1 << 2)
+#define VIDINTCON1_INTFRMPEND (1 << 1)
+#define VIDINTCON1_INTFIFOPEND (1 << 0)
+
+/* DECON_CMU */
+#define CMU_CLKGAGE_MODE_SFR_F (1 << 1)
+#define CMU_CLKGAGE_MODE_MEM_F (1 << 0)
+
+/* DECON_UPDATE */
+#define STANDALONE_UPDATE_F (1 << 0)
+
+/* DECON_VIDCON1 */
+#define VIDCON1_LINECNT_MASK (0x0fff << 16)
+#define VIDCON1_I80_ACTIVE (1 << 15)
+#define VIDCON1_VSTATUS_MASK (0x3 << 13)
+#define VIDCON1_VSTATUS_VS (0 << 13)
+#define VIDCON1_VSTATUS_BP (1 << 13)
+#define VIDCON1_VSTATUS_AC (2 << 13)
+#define VIDCON1_VSTATUS_FP (3 << 13)
+#define VIDCON1_VCLK_MASK (0x3 << 9)
+#define VIDCON1_VCLK_RUN_VDEN_DISABLE (0x3 << 9)
+#define VIDCON1_VCLK_HOLD (0x0 << 9)
+#define VIDCON1_VCLK_RUN (0x1 << 9)
+
+
+/* DECON_VIDTCON00 */
+#define VIDTCON00_VBPD_F(x) (((x) & 0xfff) << 16)
+#define VIDTCON00_VFPD_F(x) ((x) & 0xfff)
+
+/* DECON_VIDTCON01 */
+#define VIDTCON01_VSPW_F(x) (((x) & 0xfff) << 16)
+
+/* DECON_VIDTCON10 */
+#define VIDTCON10_HBPD_F(x) (((x) & 0xfff) << 16)
+#define VIDTCON10_HFPD_F(x) ((x) & 0xfff)
+
+/* DECON_VIDTCON11 */
+#define VIDTCON11_HSPW_F(x) (((x) & 0xfff) << 16)
+
+/* DECON_VIDTCON2 */
+#define VIDTCON2_LINEVAL(x) (((x) & 0xfff) << 16)
+#define VIDTCON2_HOZVAL(x) ((x) & 0xfff)
+
+/* TRIGCON */
+#define TRIGCON_TRIGEN_PER_F (1 << 31)
+#define TRIGCON_TRIGEN_F (1 << 30)
+#define TRIGCON_TE_AUTO_MASK (1 << 29)
+#define TRIGCON_WB_SWTRIGCMD (1 << 28)
+#define TRIGCON_SWTRIGCMD_W4BUF (1 << 26)
+#define TRIGCON_TRIGMODE_W4BUF (1 << 25)
+#define TRIGCON_SWTRIGCMD_W3BUF (1 << 21)
+#define TRIGCON_TRIGMODE_W3BUF (1 << 20)
+#define TRIGCON_SWTRIGCMD_W2BUF (1 << 16)
+#define TRIGCON_TRIGMODE_W2BUF (1 << 15)
+#define TRIGCON_SWTRIGCMD_W1BUF (1 << 11)
+#define TRIGCON_TRIGMODE_W1BUF (1 << 10)
+#define TRIGCON_SWTRIGCMD_W0BUF (1 << 6)
+#define TRIGCON_TRIGMODE_W0BUF (1 << 5)
+#define TRIGCON_HWTRIGMASK (1 << 4)
+#define TRIGCON_HWTRIGEN (1 << 3)
+#define TRIGCON_HWTRIG_INV (1 << 2)
+#define TRIGCON_SWTRIGCMD (1 << 1)
+#define TRIGCON_SWTRIGEN (1 << 0)
+
+/* DECON_CRCCTRL */
+#define CRCCTRL_CRCCLKEN (0x1 << 2)
+#define CRCCTRL_CRCSTART_F (0x1 << 1)
+#define CRCCTRL_CRCEN (0x1 << 0)
+#define CRCCTRL_MASK (0x7)
+
+/* BLENDCON */
+#define BLEND_NEW (1 << 0)
+
+/* BLENDERQx */
+#define BLENDERQ_ZERO 0x0
+#define BLENDERQ_ONE 0x1
+#define BLENDERQ_ALPHA_A 0x2
+#define BLENDERQ_ONE_MINUS_ALPHA_A 0x3
+#define BLENDERQ_ALPHA0 0x6
+#define BLENDERQ_Q_FUNC_F(n) (n << 18)
+#define BLENDERQ_P_FUNC_F(n) (n << 12)
+#define BLENDERQ_B_FUNC_F(n) (n << 6)
+#define BLENDERQ_A_FUNC_F(n) (n << 0)
+
+/* BLENDCON */
+#define BLEND_NEW (1 << 0)
+
+#endif /* EXYNOS_REGS_DECON5433_H */
diff --git a/drivers/gpu/drm/exynos/regs-decon7.h b/drivers/gpu/drm/exynos/regs-decon7.h
new file mode 100644
index 000000000..216c106da
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-decon7.h
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Ajay Kumar <ajaykumar.rs@samsung.com>
+ */
+
+#ifndef EXYNOS_REGS_DECON7_H
+#define EXYNOS_REGS_DECON7_H
+
+/* VIDCON0 */
+#define VIDCON0 0x00
+
+#define VIDCON0_SWRESET (1 << 28)
+#define VIDCON0_DECON_STOP_STATUS (1 << 2)
+#define VIDCON0_ENVID (1 << 1)
+#define VIDCON0_ENVID_F (1 << 0)
+
+/* VIDOUTCON0 */
+#define VIDOUTCON0 0x4
+
+#define VIDOUTCON0_DUAL_MASK (0x3 << 24)
+#define VIDOUTCON0_DUAL_ON (0x3 << 24)
+#define VIDOUTCON0_DISP_IF_1_ON (0x2 << 24)
+#define VIDOUTCON0_DISP_IF_0_ON (0x1 << 24)
+#define VIDOUTCON0_DUAL_OFF (0x0 << 24)
+#define VIDOUTCON0_IF_SHIFT 23
+#define VIDOUTCON0_IF_MASK (0x1 << 23)
+#define VIDOUTCON0_RGBIF (0x0 << 23)
+#define VIDOUTCON0_I80IF (0x1 << 23)
+
+/* VIDCON3 */
+#define VIDCON3 0x8
+
+/* VIDCON4 */
+#define VIDCON4 0xC
+#define VIDCON4_FIFOCNT_START_EN (1 << 0)
+
+/* VCLKCON0 */
+#define VCLKCON0 0x10
+#define VCLKCON0_CLKVALUP (1 << 8)
+#define VCLKCON0_VCLKFREE (1 << 0)
+
+/* VCLKCON */
+#define VCLKCON1 0x14
+#define VCLKCON1_CLKVAL_NUM_VCLK(val) (((val) & 0xff) << 0)
+#define VCLKCON2 0x18
+
+/* SHADOWCON */
+#define SHADOWCON 0x30
+
+#define SHADOWCON_WINx_PROTECT(_shf, _win) (1 << ((_shf) + (_win)))
+
+/* WINCONx */
+#define WINCON(_win) (0x50 + ((_win) * 4))
+
+#define WINCONx_BUFSTATUS (0x3 << 30)
+#define WINCONx_BUFSEL_MASK (0x3 << 28)
+#define WINCONx_BUFSEL_SHIFT 28
+#define WINCONx_TRIPLE_BUF_MODE (0x1 << 18)
+#define WINCONx_DOUBLE_BUF_MODE (0x0 << 18)
+#define WINCONx_BURSTLEN_16WORD(_shf) (0x0 << (_shf))
+#define WINCONx_BURSTLEN_8WORD(_shf) (0x1 << (_shf))
+#define WINCONx_BURSTLEN_MASK(_shf) (0x1 << (_shf))
+#define WINCONx_BLD_PLANE (0 << 8)
+#define WINCONx_BLD_PIX (1 << 8)
+#define WINCONx_ALPHA_MUL (1 << 7)
+
+#define WINCONx_BPPMODE_MASK (0xf << 2)
+#define WINCONx_BPPMODE_SHIFT 2
+#define WINCONx_BPPMODE_16BPP_565 (0x8 << 2)
+#define WINCONx_BPPMODE_24BPP_BGRx (0x7 << 2)
+#define WINCONx_BPPMODE_24BPP_RGBx (0x6 << 2)
+#define WINCONx_BPPMODE_24BPP_xBGR (0x5 << 2)
+#define WINCONx_BPPMODE_24BPP_xRGB (0x4 << 2)
+#define WINCONx_BPPMODE_32BPP_BGRA (0x3 << 2)
+#define WINCONx_BPPMODE_32BPP_RGBA (0x2 << 2)
+#define WINCONx_BPPMODE_32BPP_ABGR (0x1 << 2)
+#define WINCONx_BPPMODE_32BPP_ARGB (0x0 << 2)
+#define WINCONx_ALPHA_SEL (1 << 1)
+#define WINCONx_ENWIN (1 << 0)
+
+#define WINCON1_ALPHA_MUL_F (1 << 7)
+#define WINCON2_ALPHA_MUL_F (1 << 7)
+#define WINCON3_ALPHA_MUL_F (1 << 7)
+#define WINCON4_ALPHA_MUL_F (1 << 7)
+
+/* VIDOSDxH: The height for the OSD image(READ ONLY)*/
+#define VIDOSD_H(_x) (0x80 + ((_x) * 4))
+
+/* Frame buffer start addresses: VIDWxxADD0n */
+#define VIDW_BUF_START(_base, _win) ((_base) + ((_win) * 0x10))
+#define VIDW_BUF_START1(_base, _win) ((_base) + ((_win) * 0x10))
+#define VIDW_BUF_START2(_base, _win) ((_base) + ((_win) * 0x10))
+
+#define VIDW_WHOLE_X(_win) (0x0130 + ((_win) * 8))
+#define VIDW_WHOLE_Y(_win) (0x0134 + ((_win) * 8))
+#define VIDW_OFFSET_X(_win) (0x0170 + ((_win) * 8))
+#define VIDW_OFFSET_Y(_win) (0x0174 + ((_win) * 8))
+#define VIDW_BLKOFFSET(_win) (0x01B0 + ((_win) * 4))
+#define VIDW_BLKSIZE(win) (0x0200 + ((_win) * 4))
+
+/* Interrupt controls register */
+#define VIDINTCON2 0x228
+
+#define VIDINTCON1_INTEXTRA1_EN (1 << 1)
+#define VIDINTCON1_INTEXTRA0_EN (1 << 0)
+
+/* Interrupt controls and status register */
+#define VIDINTCON3 0x22C
+
+#define VIDINTCON1_INTEXTRA1_PEND (1 << 1)
+#define VIDINTCON1_INTEXTRA0_PEND (1 << 0)
+
+/* VIDOSDxA ~ VIDOSDxE */
+#define VIDOSD_BASE 0x230
+
+#define OSD_STRIDE 0x20
+
+#define VIDOSD_A(_win) (VIDOSD_BASE + \
+ ((_win) * OSD_STRIDE) + 0x00)
+#define VIDOSD_B(_win) (VIDOSD_BASE + \
+ ((_win) * OSD_STRIDE) + 0x04)
+#define VIDOSD_C(_win) (VIDOSD_BASE + \
+ ((_win) * OSD_STRIDE) + 0x08)
+#define VIDOSD_D(_win) (VIDOSD_BASE + \
+ ((_win) * OSD_STRIDE) + 0x0C)
+#define VIDOSD_E(_win) (VIDOSD_BASE + \
+ ((_win) * OSD_STRIDE) + 0x10)
+
+#define VIDOSDxA_TOPLEFT_X_MASK (0x1fff << 13)
+#define VIDOSDxA_TOPLEFT_X_SHIFT 13
+#define VIDOSDxA_TOPLEFT_X_LIMIT 0x1fff
+#define VIDOSDxA_TOPLEFT_X(_x) (((_x) & 0x1fff) << 13)
+
+#define VIDOSDxA_TOPLEFT_Y_MASK (0x1fff << 0)
+#define VIDOSDxA_TOPLEFT_Y_SHIFT 0
+#define VIDOSDxA_TOPLEFT_Y_LIMIT 0x1fff
+#define VIDOSDxA_TOPLEFT_Y(_x) (((_x) & 0x1fff) << 0)
+
+#define VIDOSDxB_BOTRIGHT_X_MASK (0x1fff << 13)
+#define VIDOSDxB_BOTRIGHT_X_SHIFT 13
+#define VIDOSDxB_BOTRIGHT_X_LIMIT 0x1fff
+#define VIDOSDxB_BOTRIGHT_X(_x) (((_x) & 0x1fff) << 13)
+
+#define VIDOSDxB_BOTRIGHT_Y_MASK (0x1fff << 0)
+#define VIDOSDxB_BOTRIGHT_Y_SHIFT 0
+#define VIDOSDxB_BOTRIGHT_Y_LIMIT 0x1fff
+#define VIDOSDxB_BOTRIGHT_Y(_x) (((_x) & 0x1fff) << 0)
+
+#define VIDOSDxC_ALPHA0_R_F(_x) (((_x) & 0xFF) << 16)
+#define VIDOSDxC_ALPHA0_G_F(_x) (((_x) & 0xFF) << 8)
+#define VIDOSDxC_ALPHA0_B_F(_x) (((_x) & 0xFF) << 0)
+
+#define VIDOSDxD_ALPHA1_R_F(_x) (((_x) & 0xFF) << 16)
+#define VIDOSDxD_ALPHA1_G_F(_x) (((_x) & 0xFF) << 8)
+#define VIDOSDxD_ALPHA1_B_F(_x) (((_x) & 0xFF) >> 0)
+
+/* Window MAP (Color map) */
+#define WINxMAP(_win) (0x340 + ((_win) * 4))
+
+#define WINxMAP_MAP (1 << 24)
+#define WINxMAP_MAP_COLOUR_MASK (0xffffff << 0)
+#define WINxMAP_MAP_COLOUR_SHIFT 0
+#define WINxMAP_MAP_COLOUR_LIMIT 0xffffff
+#define WINxMAP_MAP_COLOUR(_x) ((_x) << 0)
+
+/* Window colour-key control registers */
+#define WKEYCON 0x370
+
+#define WKEYCON0 0x00
+#define WKEYCON1 0x04
+#define WxKEYCON0_KEYBL_EN (1 << 26)
+#define WxKEYCON0_KEYEN_F (1 << 25)
+#define WxKEYCON0_DIRCON (1 << 24)
+#define WxKEYCON0_COMPKEY_MASK (0xffffff << 0)
+#define WxKEYCON0_COMPKEY_SHIFT 0
+#define WxKEYCON0_COMPKEY_LIMIT 0xffffff
+#define WxKEYCON0_COMPKEY(_x) ((_x) << 0)
+#define WxKEYCON1_COLVAL_MASK (0xffffff << 0)
+#define WxKEYCON1_COLVAL_SHIFT 0
+#define WxKEYCON1_COLVAL_LIMIT 0xffffff
+#define WxKEYCON1_COLVAL(_x) ((_x) << 0)
+
+/* color key control register for hardware window 1 ~ 4. */
+#define WKEYCON0_BASE(x) ((WKEYCON + WKEYCON0) + ((x - 1) * 8))
+/* color key value register for hardware window 1 ~ 4. */
+#define WKEYCON1_BASE(x) ((WKEYCON + WKEYCON1) + ((x - 1) * 8))
+
+/* Window KEY Alpha value */
+#define WxKEYALPHA(_win) (0x3A0 + (((_win) - 1) * 0x4))
+
+#define Wx_KEYALPHA_R_F_SHIFT 16
+#define Wx_KEYALPHA_G_F_SHIFT 8
+#define Wx_KEYALPHA_B_F_SHIFT 0
+
+/* Blending equation */
+#define BLENDE(_win) (0x03C0 + ((_win) * 4))
+#define BLENDE_COEF_ZERO 0x0
+#define BLENDE_COEF_ONE 0x1
+#define BLENDE_COEF_ALPHA_A 0x2
+#define BLENDE_COEF_ONE_MINUS_ALPHA_A 0x3
+#define BLENDE_COEF_ALPHA_B 0x4
+#define BLENDE_COEF_ONE_MINUS_ALPHA_B 0x5
+#define BLENDE_COEF_ALPHA0 0x6
+#define BLENDE_COEF_A 0xA
+#define BLENDE_COEF_ONE_MINUS_A 0xB
+#define BLENDE_COEF_B 0xC
+#define BLENDE_COEF_ONE_MINUS_B 0xD
+#define BLENDE_Q_FUNC(_v) ((_v) << 18)
+#define BLENDE_P_FUNC(_v) ((_v) << 12)
+#define BLENDE_B_FUNC(_v) ((_v) << 6)
+#define BLENDE_A_FUNC(_v) ((_v) << 0)
+
+/* Blending equation control */
+#define BLENDCON 0x3D8
+#define BLENDCON_NEW_MASK (1 << 0)
+#define BLENDCON_NEW_8BIT_ALPHA_VALUE (1 << 0)
+#define BLENDCON_NEW_4BIT_ALPHA_VALUE (0 << 0)
+
+/* Interrupt control register */
+#define VIDINTCON0 0x500
+
+#define VIDINTCON0_WAKEUP_MASK (0x3f << 26)
+#define VIDINTCON0_INTEXTRAEN (1 << 21)
+
+#define VIDINTCON0_FRAMESEL0_SHIFT 15
+#define VIDINTCON0_FRAMESEL0_MASK (0x3 << 15)
+#define VIDINTCON0_FRAMESEL0_BACKPORCH (0x0 << 15)
+#define VIDINTCON0_FRAMESEL0_VSYNC (0x1 << 15)
+#define VIDINTCON0_FRAMESEL0_ACTIVE (0x2 << 15)
+#define VIDINTCON0_FRAMESEL0_FRONTPORCH (0x3 << 15)
+
+#define VIDINTCON0_INT_FRAME (1 << 11)
+
+#define VIDINTCON0_FIFOLEVEL_MASK (0x7 << 3)
+#define VIDINTCON0_FIFOLEVEL_SHIFT 3
+#define VIDINTCON0_FIFOLEVEL_EMPTY (0x0 << 3)
+#define VIDINTCON0_FIFOLEVEL_TO25PC (0x1 << 3)
+#define VIDINTCON0_FIFOLEVEL_TO50PC (0x2 << 3)
+#define VIDINTCON0_FIFOLEVEL_FULL (0x4 << 3)
+
+#define VIDINTCON0_FIFOSEL_MAIN_EN (1 << 1)
+#define VIDINTCON0_INT_FIFO (1 << 1)
+
+#define VIDINTCON0_INT_ENABLE (1 << 0)
+
+/* Interrupt controls and status register */
+#define VIDINTCON1 0x504
+
+#define VIDINTCON1_INT_EXTRA (1 << 3)
+#define VIDINTCON1_INT_I80 (1 << 2)
+#define VIDINTCON1_INT_FRAME (1 << 1)
+#define VIDINTCON1_INT_FIFO (1 << 0)
+
+/* VIDCON1 */
+#define VIDCON1(_x) (0x0600 + ((_x) * 0x50))
+#define VIDCON1_LINECNT_GET(_v) (((_v) >> 17) & 0x1fff)
+#define VIDCON1_VCLK_MASK (0x3 << 9)
+#define VIDCON1_VCLK_HOLD (0x0 << 9)
+#define VIDCON1_VCLK_RUN (0x1 << 9)
+#define VIDCON1_VCLK_RUN_VDEN_DISABLE (0x3 << 9)
+#define VIDCON1_RGB_ORDER_O_MASK (0x7 << 4)
+#define VIDCON1_RGB_ORDER_O_RGB (0x0 << 4)
+#define VIDCON1_RGB_ORDER_O_GBR (0x1 << 4)
+#define VIDCON1_RGB_ORDER_O_BRG (0x2 << 4)
+#define VIDCON1_RGB_ORDER_O_BGR (0x4 << 4)
+#define VIDCON1_RGB_ORDER_O_RBG (0x5 << 4)
+#define VIDCON1_RGB_ORDER_O_GRB (0x6 << 4)
+
+/* VIDTCON0 */
+#define VIDTCON0 0x610
+
+#define VIDTCON0_VBPD_MASK (0xffff << 16)
+#define VIDTCON0_VBPD_SHIFT 16
+#define VIDTCON0_VBPD_LIMIT 0xffff
+#define VIDTCON0_VBPD(_x) ((_x) << 16)
+
+#define VIDTCON0_VFPD_MASK (0xffff << 0)
+#define VIDTCON0_VFPD_SHIFT 0
+#define VIDTCON0_VFPD_LIMIT 0xffff
+#define VIDTCON0_VFPD(_x) ((_x) << 0)
+
+/* VIDTCON1 */
+#define VIDTCON1 0x614
+
+#define VIDTCON1_VSPW_MASK (0xffff << 16)
+#define VIDTCON1_VSPW_SHIFT 16
+#define VIDTCON1_VSPW_LIMIT 0xffff
+#define VIDTCON1_VSPW(_x) ((_x) << 16)
+
+/* VIDTCON2 */
+#define VIDTCON2 0x618
+
+#define VIDTCON2_HBPD_MASK (0xffff << 16)
+#define VIDTCON2_HBPD_SHIFT 16
+#define VIDTCON2_HBPD_LIMIT 0xffff
+#define VIDTCON2_HBPD(_x) ((_x) << 16)
+
+#define VIDTCON2_HFPD_MASK (0xffff << 0)
+#define VIDTCON2_HFPD_SHIFT 0
+#define VIDTCON2_HFPD_LIMIT 0xffff
+#define VIDTCON2_HFPD(_x) ((_x) << 0)
+
+/* VIDTCON3 */
+#define VIDTCON3 0x61C
+
+#define VIDTCON3_HSPW_MASK (0xffff << 16)
+#define VIDTCON3_HSPW_SHIFT 16
+#define VIDTCON3_HSPW_LIMIT 0xffff
+#define VIDTCON3_HSPW(_x) ((_x) << 16)
+
+/* VIDTCON4 */
+#define VIDTCON4 0x620
+
+#define VIDTCON4_LINEVAL_MASK (0xfff << 16)
+#define VIDTCON4_LINEVAL_SHIFT 16
+#define VIDTCON4_LINEVAL_LIMIT 0xfff
+#define VIDTCON4_LINEVAL(_x) (((_x) & 0xfff) << 16)
+
+#define VIDTCON4_HOZVAL_MASK (0xfff << 0)
+#define VIDTCON4_HOZVAL_SHIFT 0
+#define VIDTCON4_HOZVAL_LIMIT 0xfff
+#define VIDTCON4_HOZVAL(_x) (((_x) & 0xfff) << 0)
+
+/* LINECNT OP THRSHOLD*/
+#define LINECNT_OP_THRESHOLD 0x630
+
+/* CRCCTRL */
+#define CRCCTRL 0x6C8
+#define CRCCTRL_CRCCLKEN (0x1 << 2)
+#define CRCCTRL_CRCSTART_F (0x1 << 1)
+#define CRCCTRL_CRCEN (0x1 << 0)
+
+/* DECON_CMU */
+#define DECON_CMU 0x704
+
+#define DECON_CMU_ALL_CLKGATE_ENABLE 0x3
+#define DECON_CMU_SE_CLKGATE_ENABLE (0x1 << 2)
+#define DECON_CMU_SFR_CLKGATE_ENABLE (0x1 << 1)
+#define DECON_CMU_MEM_CLKGATE_ENABLE (0x1 << 0)
+
+/* DECON_UPDATE */
+#define DECON_UPDATE 0x710
+
+#define DECON_UPDATE_SLAVE_SYNC (1 << 4)
+#define DECON_UPDATE_STANDALONE_F (1 << 0)
+
+#endif /* EXYNOS_REGS_DECON7_H */
diff --git a/drivers/gpu/drm/exynos/regs-fimc.h b/drivers/gpu/drm/exynos/regs-fimc.h
new file mode 100644
index 000000000..98e4bc18f
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-fimc.h
@@ -0,0 +1,665 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* drivers/gpu/drm/exynos/regs-fimc.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Register definition file for Samsung Camera Interface (FIMC) driver
+*/
+
+#ifndef EXYNOS_REGS_FIMC_H
+#define EXYNOS_REGS_FIMC_H
+
+/*
+ * Register part
+*/
+/* Input source format */
+#define EXYNOS_CISRCFMT (0x00)
+/* Window offset */
+#define EXYNOS_CIWDOFST (0x04)
+/* Global control */
+#define EXYNOS_CIGCTRL (0x08)
+/* Window offset 2 */
+#define EXYNOS_CIWDOFST2 (0x14)
+/* Y 1st frame start address for output DMA */
+#define EXYNOS_CIOYSA1 (0x18)
+/* Y 2nd frame start address for output DMA */
+#define EXYNOS_CIOYSA2 (0x1c)
+/* Y 3rd frame start address for output DMA */
+#define EXYNOS_CIOYSA3 (0x20)
+/* Y 4th frame start address for output DMA */
+#define EXYNOS_CIOYSA4 (0x24)
+/* Cb 1st frame start address for output DMA */
+#define EXYNOS_CIOCBSA1 (0x28)
+/* Cb 2nd frame start address for output DMA */
+#define EXYNOS_CIOCBSA2 (0x2c)
+/* Cb 3rd frame start address for output DMA */
+#define EXYNOS_CIOCBSA3 (0x30)
+/* Cb 4th frame start address for output DMA */
+#define EXYNOS_CIOCBSA4 (0x34)
+/* Cr 1st frame start address for output DMA */
+#define EXYNOS_CIOCRSA1 (0x38)
+/* Cr 2nd frame start address for output DMA */
+#define EXYNOS_CIOCRSA2 (0x3c)
+/* Cr 3rd frame start address for output DMA */
+#define EXYNOS_CIOCRSA3 (0x40)
+/* Cr 4th frame start address for output DMA */
+#define EXYNOS_CIOCRSA4 (0x44)
+/* Target image format */
+#define EXYNOS_CITRGFMT (0x48)
+/* Output DMA control */
+#define EXYNOS_CIOCTRL (0x4c)
+/* Pre-scaler control 1 */
+#define EXYNOS_CISCPRERATIO (0x50)
+/* Pre-scaler control 2 */
+#define EXYNOS_CISCPREDST (0x54)
+/* Main scaler control */
+#define EXYNOS_CISCCTRL (0x58)
+/* Target area */
+#define EXYNOS_CITAREA (0x5c)
+/* Status */
+#define EXYNOS_CISTATUS (0x64)
+/* Status2 */
+#define EXYNOS_CISTATUS2 (0x68)
+/* Image capture enable command */
+#define EXYNOS_CIIMGCPT (0xc0)
+/* Capture sequence */
+#define EXYNOS_CICPTSEQ (0xc4)
+/* Image effects */
+#define EXYNOS_CIIMGEFF (0xd0)
+/* Y frame start address for input DMA */
+#define EXYNOS_CIIYSA0 (0xd4)
+/* Cb frame start address for input DMA */
+#define EXYNOS_CIICBSA0 (0xd8)
+/* Cr frame start address for input DMA */
+#define EXYNOS_CIICRSA0 (0xdc)
+/* Input DMA Y Line Skip */
+#define EXYNOS_CIILINESKIP_Y (0xec)
+/* Input DMA Cb Line Skip */
+#define EXYNOS_CIILINESKIP_CB (0xf0)
+/* Input DMA Cr Line Skip */
+#define EXYNOS_CIILINESKIP_CR (0xf4)
+/* Real input DMA image size */
+#define EXYNOS_CIREAL_ISIZE (0xf8)
+/* Input DMA control */
+#define EXYNOS_MSCTRL (0xfc)
+/* Y frame start address for input DMA */
+#define EXYNOS_CIIYSA1 (0x144)
+/* Cb frame start address for input DMA */
+#define EXYNOS_CIICBSA1 (0x148)
+/* Cr frame start address for input DMA */
+#define EXYNOS_CIICRSA1 (0x14c)
+/* Output DMA Y offset */
+#define EXYNOS_CIOYOFF (0x168)
+/* Output DMA CB offset */
+#define EXYNOS_CIOCBOFF (0x16c)
+/* Output DMA CR offset */
+#define EXYNOS_CIOCROFF (0x170)
+/* Input DMA Y offset */
+#define EXYNOS_CIIYOFF (0x174)
+/* Input DMA CB offset */
+#define EXYNOS_CIICBOFF (0x178)
+/* Input DMA CR offset */
+#define EXYNOS_CIICROFF (0x17c)
+/* Input DMA original image size */
+#define EXYNOS_ORGISIZE (0x180)
+/* Output DMA original image size */
+#define EXYNOS_ORGOSIZE (0x184)
+/* Real output DMA image size */
+#define EXYNOS_CIEXTEN (0x188)
+/* DMA parameter */
+#define EXYNOS_CIDMAPARAM (0x18c)
+/* MIPI CSI image format */
+#define EXYNOS_CSIIMGFMT (0x194)
+/* FIMC Clock Source Select */
+#define EXYNOS_MISC_FIMC (0x198)
+
+/* Add for FIMC v5.1 */
+/* Output Frame Buffer Sequence */
+#define EXYNOS_CIFCNTSEQ (0x1fc)
+/* Y 5th frame start address for output DMA */
+#define EXYNOS_CIOYSA5 (0x200)
+/* Y 6th frame start address for output DMA */
+#define EXYNOS_CIOYSA6 (0x204)
+/* Y 7th frame start address for output DMA */
+#define EXYNOS_CIOYSA7 (0x208)
+/* Y 8th frame start address for output DMA */
+#define EXYNOS_CIOYSA8 (0x20c)
+/* Y 9th frame start address for output DMA */
+#define EXYNOS_CIOYSA9 (0x210)
+/* Y 10th frame start address for output DMA */
+#define EXYNOS_CIOYSA10 (0x214)
+/* Y 11th frame start address for output DMA */
+#define EXYNOS_CIOYSA11 (0x218)
+/* Y 12th frame start address for output DMA */
+#define EXYNOS_CIOYSA12 (0x21c)
+/* Y 13th frame start address for output DMA */
+#define EXYNOS_CIOYSA13 (0x220)
+/* Y 14th frame start address for output DMA */
+#define EXYNOS_CIOYSA14 (0x224)
+/* Y 15th frame start address for output DMA */
+#define EXYNOS_CIOYSA15 (0x228)
+/* Y 16th frame start address for output DMA */
+#define EXYNOS_CIOYSA16 (0x22c)
+/* Y 17th frame start address for output DMA */
+#define EXYNOS_CIOYSA17 (0x230)
+/* Y 18th frame start address for output DMA */
+#define EXYNOS_CIOYSA18 (0x234)
+/* Y 19th frame start address for output DMA */
+#define EXYNOS_CIOYSA19 (0x238)
+/* Y 20th frame start address for output DMA */
+#define EXYNOS_CIOYSA20 (0x23c)
+/* Y 21th frame start address for output DMA */
+#define EXYNOS_CIOYSA21 (0x240)
+/* Y 22th frame start address for output DMA */
+#define EXYNOS_CIOYSA22 (0x244)
+/* Y 23th frame start address for output DMA */
+#define EXYNOS_CIOYSA23 (0x248)
+/* Y 24th frame start address for output DMA */
+#define EXYNOS_CIOYSA24 (0x24c)
+/* Y 25th frame start address for output DMA */
+#define EXYNOS_CIOYSA25 (0x250)
+/* Y 26th frame start address for output DMA */
+#define EXYNOS_CIOYSA26 (0x254)
+/* Y 27th frame start address for output DMA */
+#define EXYNOS_CIOYSA27 (0x258)
+/* Y 28th frame start address for output DMA */
+#define EXYNOS_CIOYSA28 (0x25c)
+/* Y 29th frame start address for output DMA */
+#define EXYNOS_CIOYSA29 (0x260)
+/* Y 30th frame start address for output DMA */
+#define EXYNOS_CIOYSA30 (0x264)
+/* Y 31th frame start address for output DMA */
+#define EXYNOS_CIOYSA31 (0x268)
+/* Y 32th frame start address for output DMA */
+#define EXYNOS_CIOYSA32 (0x26c)
+
+/* CB 5th frame start address for output DMA */
+#define EXYNOS_CIOCBSA5 (0x270)
+/* CB 6th frame start address for output DMA */
+#define EXYNOS_CIOCBSA6 (0x274)
+/* CB 7th frame start address for output DMA */
+#define EXYNOS_CIOCBSA7 (0x278)
+/* CB 8th frame start address for output DMA */
+#define EXYNOS_CIOCBSA8 (0x27c)
+/* CB 9th frame start address for output DMA */
+#define EXYNOS_CIOCBSA9 (0x280)
+/* CB 10th frame start address for output DMA */
+#define EXYNOS_CIOCBSA10 (0x284)
+/* CB 11th frame start address for output DMA */
+#define EXYNOS_CIOCBSA11 (0x288)
+/* CB 12th frame start address for output DMA */
+#define EXYNOS_CIOCBSA12 (0x28c)
+/* CB 13th frame start address for output DMA */
+#define EXYNOS_CIOCBSA13 (0x290)
+/* CB 14th frame start address for output DMA */
+#define EXYNOS_CIOCBSA14 (0x294)
+/* CB 15th frame start address for output DMA */
+#define EXYNOS_CIOCBSA15 (0x298)
+/* CB 16th frame start address for output DMA */
+#define EXYNOS_CIOCBSA16 (0x29c)
+/* CB 17th frame start address for output DMA */
+#define EXYNOS_CIOCBSA17 (0x2a0)
+/* CB 18th frame start address for output DMA */
+#define EXYNOS_CIOCBSA18 (0x2a4)
+/* CB 19th frame start address for output DMA */
+#define EXYNOS_CIOCBSA19 (0x2a8)
+/* CB 20th frame start address for output DMA */
+#define EXYNOS_CIOCBSA20 (0x2ac)
+/* CB 21th frame start address for output DMA */
+#define EXYNOS_CIOCBSA21 (0x2b0)
+/* CB 22th frame start address for output DMA */
+#define EXYNOS_CIOCBSA22 (0x2b4)
+/* CB 23th frame start address for output DMA */
+#define EXYNOS_CIOCBSA23 (0x2b8)
+/* CB 24th frame start address for output DMA */
+#define EXYNOS_CIOCBSA24 (0x2bc)
+/* CB 25th frame start address for output DMA */
+#define EXYNOS_CIOCBSA25 (0x2c0)
+/* CB 26th frame start address for output DMA */
+#define EXYNOS_CIOCBSA26 (0x2c4)
+/* CB 27th frame start address for output DMA */
+#define EXYNOS_CIOCBSA27 (0x2c8)
+/* CB 28th frame start address for output DMA */
+#define EXYNOS_CIOCBSA28 (0x2cc)
+/* CB 29th frame start address for output DMA */
+#define EXYNOS_CIOCBSA29 (0x2d0)
+/* CB 30th frame start address for output DMA */
+#define EXYNOS_CIOCBSA30 (0x2d4)
+/* CB 31th frame start address for output DMA */
+#define EXYNOS_CIOCBSA31 (0x2d8)
+/* CB 32th frame start address for output DMA */
+#define EXYNOS_CIOCBSA32 (0x2dc)
+
+/* CR 5th frame start address for output DMA */
+#define EXYNOS_CIOCRSA5 (0x2e0)
+/* CR 6th frame start address for output DMA */
+#define EXYNOS_CIOCRSA6 (0x2e4)
+/* CR 7th frame start address for output DMA */
+#define EXYNOS_CIOCRSA7 (0x2e8)
+/* CR 8th frame start address for output DMA */
+#define EXYNOS_CIOCRSA8 (0x2ec)
+/* CR 9th frame start address for output DMA */
+#define EXYNOS_CIOCRSA9 (0x2f0)
+/* CR 10th frame start address for output DMA */
+#define EXYNOS_CIOCRSA10 (0x2f4)
+/* CR 11th frame start address for output DMA */
+#define EXYNOS_CIOCRSA11 (0x2f8)
+/* CR 12th frame start address for output DMA */
+#define EXYNOS_CIOCRSA12 (0x2fc)
+/* CR 13th frame start address for output DMA */
+#define EXYNOS_CIOCRSA13 (0x300)
+/* CR 14th frame start address for output DMA */
+#define EXYNOS_CIOCRSA14 (0x304)
+/* CR 15th frame start address for output DMA */
+#define EXYNOS_CIOCRSA15 (0x308)
+/* CR 16th frame start address for output DMA */
+#define EXYNOS_CIOCRSA16 (0x30c)
+/* CR 17th frame start address for output DMA */
+#define EXYNOS_CIOCRSA17 (0x310)
+/* CR 18th frame start address for output DMA */
+#define EXYNOS_CIOCRSA18 (0x314)
+/* CR 19th frame start address for output DMA */
+#define EXYNOS_CIOCRSA19 (0x318)
+/* CR 20th frame start address for output DMA */
+#define EXYNOS_CIOCRSA20 (0x31c)
+/* CR 21th frame start address for output DMA */
+#define EXYNOS_CIOCRSA21 (0x320)
+/* CR 22th frame start address for output DMA */
+#define EXYNOS_CIOCRSA22 (0x324)
+/* CR 23th frame start address for output DMA */
+#define EXYNOS_CIOCRSA23 (0x328)
+/* CR 24th frame start address for output DMA */
+#define EXYNOS_CIOCRSA24 (0x32c)
+/* CR 25th frame start address for output DMA */
+#define EXYNOS_CIOCRSA25 (0x330)
+/* CR 26th frame start address for output DMA */
+#define EXYNOS_CIOCRSA26 (0x334)
+/* CR 27th frame start address for output DMA */
+#define EXYNOS_CIOCRSA27 (0x338)
+/* CR 28th frame start address for output DMA */
+#define EXYNOS_CIOCRSA28 (0x33c)
+/* CR 29th frame start address for output DMA */
+#define EXYNOS_CIOCRSA29 (0x340)
+/* CR 30th frame start address for output DMA */
+#define EXYNOS_CIOCRSA30 (0x344)
+/* CR 31th frame start address for output DMA */
+#define EXYNOS_CIOCRSA31 (0x348)
+/* CR 32th frame start address for output DMA */
+#define EXYNOS_CIOCRSA32 (0x34c)
+
+/*
+ * Macro part
+*/
+/* frame start address 1 ~ 4, 5 ~ 32 */
+/* Number of Default PingPong Memory */
+#define DEF_PP 4
+#define EXYNOS_CIOYSA(__x) \
+ (((__x) < DEF_PP) ? \
+ (EXYNOS_CIOYSA1 + (__x) * 4) : \
+ (EXYNOS_CIOYSA5 + ((__x) - DEF_PP) * 4))
+#define EXYNOS_CIOCBSA(__x) \
+ (((__x) < DEF_PP) ? \
+ (EXYNOS_CIOCBSA1 + (__x) * 4) : \
+ (EXYNOS_CIOCBSA5 + ((__x) - DEF_PP) * 4))
+#define EXYNOS_CIOCRSA(__x) \
+ (((__x) < DEF_PP) ? \
+ (EXYNOS_CIOCRSA1 + (__x) * 4) : \
+ (EXYNOS_CIOCRSA5 + ((__x) - DEF_PP) * 4))
+/* Number of Default PingPong Memory */
+#define DEF_IPP 1
+#define EXYNOS_CIIYSA(__x) \
+ (((__x) < DEF_IPP) ? \
+ (EXYNOS_CIIYSA0) : (EXYNOS_CIIYSA1))
+#define EXYNOS_CIICBSA(__x) \
+ (((__x) < DEF_IPP) ? \
+ (EXYNOS_CIICBSA0) : (EXYNOS_CIICBSA1))
+#define EXYNOS_CIICRSA(__x) \
+ (((__x) < DEF_IPP) ? \
+ (EXYNOS_CIICRSA0) : (EXYNOS_CIICRSA1))
+
+#define EXYNOS_CISRCFMT_SOURCEHSIZE(x) ((x) << 16)
+#define EXYNOS_CISRCFMT_SOURCEVSIZE(x) ((x) << 0)
+
+#define EXYNOS_CIWDOFST_WINHOROFST(x) ((x) << 16)
+#define EXYNOS_CIWDOFST_WINVEROFST(x) ((x) << 0)
+
+#define EXYNOS_CIWDOFST2_WINHOROFST2(x) ((x) << 16)
+#define EXYNOS_CIWDOFST2_WINVEROFST2(x) ((x) << 0)
+
+#define EXYNOS_CITRGFMT_TARGETHSIZE(x) (((x) & 0x1fff) << 16)
+#define EXYNOS_CITRGFMT_TARGETVSIZE(x) (((x) & 0x1fff) << 0)
+
+#define EXYNOS_CISCPRERATIO_SHFACTOR(x) ((x) << 28)
+#define EXYNOS_CISCPRERATIO_PREHORRATIO(x) ((x) << 16)
+#define EXYNOS_CISCPRERATIO_PREVERRATIO(x) ((x) << 0)
+
+#define EXYNOS_CISCPREDST_PREDSTWIDTH(x) ((x) << 16)
+#define EXYNOS_CISCPREDST_PREDSTHEIGHT(x) ((x) << 0)
+
+#define EXYNOS_CISCCTRL_MAINHORRATIO(x) ((x) << 16)
+#define EXYNOS_CISCCTRL_MAINVERRATIO(x) ((x) << 0)
+
+#define EXYNOS_CITAREA_TARGET_AREA(x) ((x) << 0)
+
+#define EXYNOS_CISTATUS_GET_FRAME_COUNT(x) (((x) >> 26) & 0x3)
+#define EXYNOS_CISTATUS_GET_FRAME_END(x) (((x) >> 17) & 0x1)
+#define EXYNOS_CISTATUS_GET_LAST_CAPTURE_END(x) (((x) >> 16) & 0x1)
+#define EXYNOS_CISTATUS_GET_LCD_STATUS(x) (((x) >> 9) & 0x1)
+#define EXYNOS_CISTATUS_GET_ENVID_STATUS(x) (((x) >> 8) & 0x1)
+
+#define EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(x) (((x) >> 7) & 0x3f)
+#define EXYNOS_CISTATUS2_GET_FRAMECOUNT_PRESENT(x) ((x) & 0x3f)
+
+#define EXYNOS_CIIMGEFF_FIN(x) ((x & 0x7) << 26)
+#define EXYNOS_CIIMGEFF_PAT_CB(x) ((x) << 13)
+#define EXYNOS_CIIMGEFF_PAT_CR(x) ((x) << 0)
+
+#define EXYNOS_CIILINESKIP(x) (((x) & 0xf) << 24)
+
+#define EXYNOS_CIREAL_ISIZE_HEIGHT(x) ((x) << 16)
+#define EXYNOS_CIREAL_ISIZE_WIDTH(x) ((x) << 0)
+
+#define EXYNOS_MSCTRL_SUCCESSIVE_COUNT(x) ((x) << 24)
+#define EXYNOS_MSCTRL_GET_INDMA_STATUS(x) ((x) & 0x1)
+
+#define EXYNOS_CIOYOFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIOYOFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIOCBOFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIOCBOFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIOCROFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIOCROFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIIYOFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIIYOFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIICBOFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIICBOFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIICROFF_VERTICAL(x) ((x) << 16)
+#define EXYNOS_CIICROFF_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_ORGISIZE_VERTICAL(x) ((x) << 16)
+#define EXYNOS_ORGISIZE_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_ORGOSIZE_VERTICAL(x) ((x) << 16)
+#define EXYNOS_ORGOSIZE_HORIZONTAL(x) ((x) << 0)
+
+#define EXYNOS_CIEXTEN_TARGETH_EXT(x) ((((x) & 0x2000) >> 13) << 26)
+#define EXYNOS_CIEXTEN_TARGETV_EXT(x) ((((x) & 0x2000) >> 13) << 24)
+#define EXYNOS_CIEXTEN_MAINHORRATIO_EXT(x) (((x) & 0x3F) << 10)
+#define EXYNOS_CIEXTEN_MAINVERRATIO_EXT(x) ((x) & 0x3F)
+
+/*
+ * Bit definition part
+*/
+/* Source format register */
+#define EXYNOS_CISRCFMT_ITU601_8BIT (1 << 31)
+#define EXYNOS_CISRCFMT_ITU656_8BIT (0 << 31)
+#define EXYNOS_CISRCFMT_ITU601_16BIT (1 << 29)
+#define EXYNOS_CISRCFMT_ORDER422_YCBYCR (0 << 14)
+#define EXYNOS_CISRCFMT_ORDER422_YCRYCB (1 << 14)
+#define EXYNOS_CISRCFMT_ORDER422_CBYCRY (2 << 14)
+#define EXYNOS_CISRCFMT_ORDER422_CRYCBY (3 << 14)
+/* ITU601 16bit only */
+#define EXYNOS_CISRCFMT_ORDER422_Y4CBCRCBCR (0 << 14)
+/* ITU601 16bit only */
+#define EXYNOS_CISRCFMT_ORDER422_Y4CRCBCRCB (1 << 14)
+
+/* Window offset register */
+#define EXYNOS_CIWDOFST_WINOFSEN (1 << 31)
+#define EXYNOS_CIWDOFST_CLROVFIY (1 << 30)
+#define EXYNOS_CIWDOFST_CLROVRLB (1 << 29)
+#define EXYNOS_CIWDOFST_WINHOROFST_MASK (0x7ff << 16)
+#define EXYNOS_CIWDOFST_CLROVFICB (1 << 15)
+#define EXYNOS_CIWDOFST_CLROVFICR (1 << 14)
+#define EXYNOS_CIWDOFST_WINVEROFST_MASK (0xfff << 0)
+
+/* Global control register */
+#define EXYNOS_CIGCTRL_SWRST (1 << 31)
+#define EXYNOS_CIGCTRL_CAMRST_A (1 << 30)
+#define EXYNOS_CIGCTRL_SELCAM_ITU_B (0 << 29)
+#define EXYNOS_CIGCTRL_SELCAM_ITU_A (1 << 29)
+#define EXYNOS_CIGCTRL_SELCAM_ITU_MASK (1 << 29)
+#define EXYNOS_CIGCTRL_TESTPATTERN_NORMAL (0 << 27)
+#define EXYNOS_CIGCTRL_TESTPATTERN_COLOR_BAR (1 << 27)
+#define EXYNOS_CIGCTRL_TESTPATTERN_HOR_INC (2 << 27)
+#define EXYNOS_CIGCTRL_TESTPATTERN_VER_INC (3 << 27)
+#define EXYNOS_CIGCTRL_TESTPATTERN_MASK (3 << 27)
+#define EXYNOS_CIGCTRL_TESTPATTERN_SHIFT (27)
+#define EXYNOS_CIGCTRL_INVPOLPCLK (1 << 26)
+#define EXYNOS_CIGCTRL_INVPOLVSYNC (1 << 25)
+#define EXYNOS_CIGCTRL_INVPOLHREF (1 << 24)
+#define EXYNOS_CIGCTRL_IRQ_OVFEN (1 << 22)
+#define EXYNOS_CIGCTRL_HREF_MASK (1 << 21)
+#define EXYNOS_CIGCTRL_IRQ_EDGE (0 << 20)
+#define EXYNOS_CIGCTRL_IRQ_LEVEL (1 << 20)
+#define EXYNOS_CIGCTRL_IRQ_CLR (1 << 19)
+#define EXYNOS_CIGCTRL_IRQ_END_DISABLE (1 << 18)
+#define EXYNOS_CIGCTRL_IRQ_DISABLE (0 << 16)
+#define EXYNOS_CIGCTRL_IRQ_ENABLE (1 << 16)
+#define EXYNOS_CIGCTRL_SHADOW_DISABLE (1 << 12)
+#define EXYNOS_CIGCTRL_CAM_JPEG (1 << 8)
+#define EXYNOS_CIGCTRL_SELCAM_MIPI_B (0 << 7)
+#define EXYNOS_CIGCTRL_SELCAM_MIPI_A (1 << 7)
+#define EXYNOS_CIGCTRL_SELCAM_MIPI_MASK (1 << 7)
+#define EXYNOS_CIGCTRL_SELWB_CAMIF_CAMERA (0 << 6)
+#define EXYNOS_CIGCTRL_SELWB_CAMIF_WRITEBACK (1 << 6)
+#define EXYNOS_CIGCTRL_SELWRITEBACK_MASK (1 << 10)
+#define EXYNOS_CIGCTRL_SELWRITEBACK_A (1 << 10)
+#define EXYNOS_CIGCTRL_SELWRITEBACK_B (0 << 10)
+#define EXYNOS_CIGCTRL_SELWB_CAMIF_MASK (1 << 6)
+#define EXYNOS_CIGCTRL_CSC_ITU601 (0 << 5)
+#define EXYNOS_CIGCTRL_CSC_ITU709 (1 << 5)
+#define EXYNOS_CIGCTRL_CSC_MASK (1 << 5)
+#define EXYNOS_CIGCTRL_INVPOLHSYNC (1 << 4)
+#define EXYNOS_CIGCTRL_SELCAM_FIMC_ITU (0 << 3)
+#define EXYNOS_CIGCTRL_SELCAM_FIMC_MIPI (1 << 3)
+#define EXYNOS_CIGCTRL_SELCAM_FIMC_MASK (1 << 3)
+#define EXYNOS_CIGCTRL_PROGRESSIVE (0 << 0)
+#define EXYNOS_CIGCTRL_INTERLACE (1 << 0)
+
+/* Window offset2 register */
+#define EXYNOS_CIWDOFST_WINHOROFST2_MASK (0xfff << 16)
+#define EXYNOS_CIWDOFST_WINVEROFST2_MASK (0xfff << 16)
+
+/* Target format register */
+#define EXYNOS_CITRGFMT_INROT90_CLOCKWISE (1 << 31)
+#define EXYNOS_CITRGFMT_OUTFORMAT_YCBCR420 (0 << 29)
+#define EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422 (1 << 29)
+#define EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422_1PLANE (2 << 29)
+#define EXYNOS_CITRGFMT_OUTFORMAT_RGB (3 << 29)
+#define EXYNOS_CITRGFMT_OUTFORMAT_MASK (3 << 29)
+#define EXYNOS_CITRGFMT_FLIP_SHIFT (14)
+#define EXYNOS_CITRGFMT_FLIP_NORMAL (0 << 14)
+#define EXYNOS_CITRGFMT_FLIP_X_MIRROR (1 << 14)
+#define EXYNOS_CITRGFMT_FLIP_Y_MIRROR (2 << 14)
+#define EXYNOS_CITRGFMT_FLIP_180 (3 << 14)
+#define EXYNOS_CITRGFMT_FLIP_MASK (3 << 14)
+#define EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE (1 << 13)
+#define EXYNOS_CITRGFMT_TARGETV_MASK (0x1fff << 0)
+#define EXYNOS_CITRGFMT_TARGETH_MASK (0x1fff << 16)
+
+/* Output DMA control register */
+#define EXYNOS_CIOCTRL_WEAVE_OUT (1 << 31)
+#define EXYNOS_CIOCTRL_WEAVE_MASK (1 << 31)
+#define EXYNOS_CIOCTRL_LASTENDEN (1 << 30)
+#define EXYNOS_CIOCTRL_ORDER2P_LSB_CBCR (0 << 24)
+#define EXYNOS_CIOCTRL_ORDER2P_LSB_CRCB (1 << 24)
+#define EXYNOS_CIOCTRL_ORDER2P_MSB_CRCB (2 << 24)
+#define EXYNOS_CIOCTRL_ORDER2P_MSB_CBCR (3 << 24)
+#define EXYNOS_CIOCTRL_ORDER2P_SHIFT (24)
+#define EXYNOS_CIOCTRL_ORDER2P_MASK (3 << 24)
+#define EXYNOS_CIOCTRL_YCBCR_3PLANE (0 << 3)
+#define EXYNOS_CIOCTRL_YCBCR_2PLANE (1 << 3)
+#define EXYNOS_CIOCTRL_YCBCR_PLANE_MASK (1 << 3)
+#define EXYNOS_CIOCTRL_LASTIRQ_ENABLE (1 << 2)
+#define EXYNOS_CIOCTRL_ALPHA_OUT (0xff << 4)
+#define EXYNOS_CIOCTRL_ORDER422_YCBYCR (0 << 0)
+#define EXYNOS_CIOCTRL_ORDER422_YCRYCB (1 << 0)
+#define EXYNOS_CIOCTRL_ORDER422_CBYCRY (2 << 0)
+#define EXYNOS_CIOCTRL_ORDER422_CRYCBY (3 << 0)
+#define EXYNOS_CIOCTRL_ORDER422_MASK (3 << 0)
+
+/* Main scaler control register */
+#define EXYNOS_CISCCTRL_SCALERBYPASS (1 << 31)
+#define EXYNOS_CISCCTRL_SCALEUP_H (1 << 30)
+#define EXYNOS_CISCCTRL_SCALEUP_V (1 << 29)
+#define EXYNOS_CISCCTRL_CSCR2Y_NARROW (0 << 28)
+#define EXYNOS_CISCCTRL_CSCR2Y_WIDE (1 << 28)
+#define EXYNOS_CISCCTRL_CSCY2R_NARROW (0 << 27)
+#define EXYNOS_CISCCTRL_CSCY2R_WIDE (1 << 27)
+#define EXYNOS_CISCCTRL_LCDPATHEN_FIFO (1 << 26)
+#define EXYNOS_CISCCTRL_PROGRESSIVE (0 << 25)
+#define EXYNOS_CISCCTRL_INTERLACE (1 << 25)
+#define EXYNOS_CISCCTRL_SCAN_MASK (1 << 25)
+#define EXYNOS_CISCCTRL_SCALERSTART (1 << 15)
+#define EXYNOS_CISCCTRL_INRGB_FMT_RGB565 (0 << 13)
+#define EXYNOS_CISCCTRL_INRGB_FMT_RGB666 (1 << 13)
+#define EXYNOS_CISCCTRL_INRGB_FMT_RGB888 (2 << 13)
+#define EXYNOS_CISCCTRL_INRGB_FMT_RGB_MASK (3 << 13)
+#define EXYNOS_CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11)
+#define EXYNOS_CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11)
+#define EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11)
+#define EXYNOS_CISCCTRL_OUTRGB_FMT_RGB_MASK (3 << 11)
+#define EXYNOS_CISCCTRL_EXTRGB_NORMAL (0 << 10)
+#define EXYNOS_CISCCTRL_EXTRGB_EXTENSION (1 << 10)
+#define EXYNOS_CISCCTRL_ONE2ONE (1 << 9)
+#define EXYNOS_CISCCTRL_MAIN_V_RATIO_MASK (0x1ff << 0)
+#define EXYNOS_CISCCTRL_MAIN_H_RATIO_MASK (0x1ff << 16)
+
+/* Status register */
+#define EXYNOS_CISTATUS_OVFIY (1 << 31)
+#define EXYNOS_CISTATUS_OVFICB (1 << 30)
+#define EXYNOS_CISTATUS_OVFICR (1 << 29)
+#define EXYNOS_CISTATUS_VSYNC (1 << 28)
+#define EXYNOS_CISTATUS_SCALERSTART (1 << 26)
+#define EXYNOS_CISTATUS_WINOFSTEN (1 << 25)
+#define EXYNOS_CISTATUS_IMGCPTEN (1 << 22)
+#define EXYNOS_CISTATUS_IMGCPTENSC (1 << 21)
+#define EXYNOS_CISTATUS_VSYNC_A (1 << 20)
+#define EXYNOS_CISTATUS_VSYNC_B (1 << 19)
+#define EXYNOS_CISTATUS_OVRLB (1 << 18)
+#define EXYNOS_CISTATUS_FRAMEEND (1 << 17)
+#define EXYNOS_CISTATUS_LASTCAPTUREEND (1 << 16)
+#define EXYNOS_CISTATUS_VVALID_A (1 << 15)
+#define EXYNOS_CISTATUS_VVALID_B (1 << 14)
+
+/* Image capture enable register */
+#define EXYNOS_CIIMGCPT_IMGCPTEN (1 << 31)
+#define EXYNOS_CIIMGCPT_IMGCPTEN_SC (1 << 30)
+#define EXYNOS_CIIMGCPT_CPT_FREN_ENABLE (1 << 25)
+#define EXYNOS_CIIMGCPT_CPT_FRMOD_EN (0 << 18)
+#define EXYNOS_CIIMGCPT_CPT_FRMOD_CNT (1 << 18)
+
+/* Image effects register */
+#define EXYNOS_CIIMGEFF_IE_DISABLE (0 << 30)
+#define EXYNOS_CIIMGEFF_IE_ENABLE (1 << 30)
+#define EXYNOS_CIIMGEFF_IE_SC_BEFORE (0 << 29)
+#define EXYNOS_CIIMGEFF_IE_SC_AFTER (1 << 29)
+#define EXYNOS_CIIMGEFF_FIN_BYPASS (0 << 26)
+#define EXYNOS_CIIMGEFF_FIN_ARBITRARY (1 << 26)
+#define EXYNOS_CIIMGEFF_FIN_NEGATIVE (2 << 26)
+#define EXYNOS_CIIMGEFF_FIN_ARTFREEZE (3 << 26)
+#define EXYNOS_CIIMGEFF_FIN_EMBOSSING (4 << 26)
+#define EXYNOS_CIIMGEFF_FIN_SILHOUETTE (5 << 26)
+#define EXYNOS_CIIMGEFF_FIN_MASK (7 << 26)
+#define EXYNOS_CIIMGEFF_PAT_CBCR_MASK ((0xff << 13) | (0xff << 0))
+
+/* Real input DMA size register */
+#define EXYNOS_CIREAL_ISIZE_AUTOLOAD_ENABLE (1 << 31)
+#define EXYNOS_CIREAL_ISIZE_ADDR_CH_DISABLE (1 << 30)
+#define EXYNOS_CIREAL_ISIZE_HEIGHT_MASK (0x3FFF << 16)
+#define EXYNOS_CIREAL_ISIZE_WIDTH_MASK (0x3FFF << 0)
+
+/* Input DMA control register */
+#define EXYNOS_MSCTRL_FIELD_MASK (1 << 31)
+#define EXYNOS_MSCTRL_FIELD_WEAVE (1 << 31)
+#define EXYNOS_MSCTRL_FIELD_NORMAL (0 << 31)
+#define EXYNOS_MSCTRL_BURST_CNT (24)
+#define EXYNOS_MSCTRL_BURST_CNT_MASK (0xf << 24)
+#define EXYNOS_MSCTRL_ORDER2P_LSB_CBCR (0 << 16)
+#define EXYNOS_MSCTRL_ORDER2P_LSB_CRCB (1 << 16)
+#define EXYNOS_MSCTRL_ORDER2P_MSB_CRCB (2 << 16)
+#define EXYNOS_MSCTRL_ORDER2P_MSB_CBCR (3 << 16)
+#define EXYNOS_MSCTRL_ORDER2P_SHIFT (16)
+#define EXYNOS_MSCTRL_ORDER2P_SHIFT_MASK (0x3 << 16)
+#define EXYNOS_MSCTRL_C_INT_IN_3PLANE (0 << 15)
+#define EXYNOS_MSCTRL_C_INT_IN_2PLANE (1 << 15)
+#define EXYNOS_MSCTRL_FLIP_SHIFT (13)
+#define EXYNOS_MSCTRL_FLIP_NORMAL (0 << 13)
+#define EXYNOS_MSCTRL_FLIP_X_MIRROR (1 << 13)
+#define EXYNOS_MSCTRL_FLIP_Y_MIRROR (2 << 13)
+#define EXYNOS_MSCTRL_FLIP_180 (3 << 13)
+#define EXYNOS_MSCTRL_FLIP_MASK (3 << 13)
+#define EXYNOS_MSCTRL_ORDER422_CRYCBY (0 << 4)
+#define EXYNOS_MSCTRL_ORDER422_YCRYCB (1 << 4)
+#define EXYNOS_MSCTRL_ORDER422_CBYCRY (2 << 4)
+#define EXYNOS_MSCTRL_ORDER422_YCBYCR (3 << 4)
+#define EXYNOS_MSCTRL_INPUT_EXTCAM (0 << 3)
+#define EXYNOS_MSCTRL_INPUT_MEMORY (1 << 3)
+#define EXYNOS_MSCTRL_INPUT_MASK (1 << 3)
+#define EXYNOS_MSCTRL_INFORMAT_YCBCR420 (0 << 1)
+#define EXYNOS_MSCTRL_INFORMAT_YCBCR422 (1 << 1)
+#define EXYNOS_MSCTRL_INFORMAT_YCBCR422_1PLANE (2 << 1)
+#define EXYNOS_MSCTRL_INFORMAT_RGB (3 << 1)
+#define EXYNOS_MSCTRL_ENVID (1 << 0)
+
+/* DMA parameter register */
+#define EXYNOS_CIDMAPARAM_R_MODE_LINEAR (0 << 29)
+#define EXYNOS_CIDMAPARAM_R_MODE_CONFTILE (1 << 29)
+#define EXYNOS_CIDMAPARAM_R_MODE_16X16 (2 << 29)
+#define EXYNOS_CIDMAPARAM_R_MODE_64X32 (3 << 29)
+#define EXYNOS_CIDMAPARAM_R_MODE_MASK (3 << 29)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_64 (0 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_128 (1 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_256 (2 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_512 (3 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_1024 (4 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_2048 (5 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_HSIZE_4096 (6 << 24)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_1 (0 << 20)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_2 (1 << 20)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_4 (2 << 20)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_8 (3 << 20)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_16 (4 << 20)
+#define EXYNOS_CIDMAPARAM_R_TILE_VSIZE_32 (5 << 20)
+#define EXYNOS_CIDMAPARAM_W_MODE_LINEAR (0 << 13)
+#define EXYNOS_CIDMAPARAM_W_MODE_CONFTILE (1 << 13)
+#define EXYNOS_CIDMAPARAM_W_MODE_16X16 (2 << 13)
+#define EXYNOS_CIDMAPARAM_W_MODE_64X32 (3 << 13)
+#define EXYNOS_CIDMAPARAM_W_MODE_MASK (3 << 13)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_64 (0 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_128 (1 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_256 (2 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_512 (3 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_1024 (4 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_2048 (5 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_HSIZE_4096 (6 << 8)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_1 (0 << 4)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_2 (1 << 4)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_4 (2 << 4)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_8 (3 << 4)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_16 (4 << 4)
+#define EXYNOS_CIDMAPARAM_W_TILE_VSIZE_32 (5 << 4)
+
+/* Gathering Extension register */
+#define EXYNOS_CIEXTEN_TARGETH_EXT_MASK (1 << 26)
+#define EXYNOS_CIEXTEN_TARGETV_EXT_MASK (1 << 24)
+#define EXYNOS_CIEXTEN_MAINHORRATIO_EXT_MASK (0x3F << 10)
+#define EXYNOS_CIEXTEN_MAINVERRATIO_EXT_MASK (0x3F)
+#define EXYNOS_CIEXTEN_YUV444_OUT (1 << 22)
+
+/* FIMC Clock Source Select register */
+#define EXYNOS_CLKSRC_HCLK (0 << 1)
+#define EXYNOS_CLKSRC_HCLK_MASK (1 << 1)
+#define EXYNOS_CLKSRC_SCLK (1 << 1)
+
+/* SYSREG for FIMC writeback */
+#define SYSREG_CAMERA_BLK (0x0218)
+#define SYSREG_FIMD0WB_DEST_MASK (0x3 << 23)
+#define SYSREG_FIMD0WB_DEST_SHIFT 23
+
+#endif /* EXYNOS_REGS_FIMC_H */
diff --git a/drivers/gpu/drm/exynos/regs-gsc.h b/drivers/gpu/drm/exynos/regs-gsc.h
new file mode 100644
index 000000000..9e203cab9
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-gsc.h
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* linux/drivers/gpu/drm/exynos/regs-gsc.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Register definition file for Samsung G-Scaler driver
+ */
+
+#ifndef EXYNOS_REGS_GSC_H_
+#define EXYNOS_REGS_GSC_H_
+
+/* G-Scaler enable */
+#define GSC_ENABLE 0x00
+#define GSC_ENABLE_PP_UPDATE_TIME_MASK (1 << 9)
+#define GSC_ENABLE_PP_UPDATE_TIME_CURR (0 << 9)
+#define GSC_ENABLE_PP_UPDATE_TIME_EOPAS (1 << 9)
+#define GSC_ENABLE_CLK_GATE_MODE_MASK (1 << 8)
+#define GSC_ENABLE_CLK_GATE_MODE_FREE (1 << 8)
+#define GSC_ENABLE_IPC_MODE_MASK (1 << 7)
+#define GSC_ENABLE_NORM_MODE (0 << 7)
+#define GSC_ENABLE_IPC_MODE (1 << 7)
+#define GSC_ENABLE_PP_UPDATE_MODE_MASK (1 << 6)
+#define GSC_ENABLE_PP_UPDATE_FIRE_MODE (1 << 6)
+#define GSC_ENABLE_IN_PP_UPDATE (1 << 5)
+#define GSC_ENABLE_ON_CLEAR_MASK (1 << 4)
+#define GSC_ENABLE_ON_CLEAR_ONESHOT (1 << 4)
+#define GSC_ENABLE_QOS_ENABLE (1 << 3)
+#define GSC_ENABLE_OP_STATUS (1 << 2)
+#define GSC_ENABLE_SFR_UPDATE (1 << 1)
+#define GSC_ENABLE_ON (1 << 0)
+
+/* G-Scaler S/W reset */
+#define GSC_SW_RESET 0x04
+#define GSC_SW_RESET_SRESET (1 << 0)
+
+/* G-Scaler IRQ */
+#define GSC_IRQ 0x08
+#define GSC_IRQ_STATUS_OR_IRQ (1 << 17)
+#define GSC_IRQ_STATUS_OR_FRM_DONE (1 << 16)
+#define GSC_IRQ_OR_MASK (1 << 2)
+#define GSC_IRQ_FRMDONE_MASK (1 << 1)
+#define GSC_IRQ_ENABLE (1 << 0)
+
+/* G-Scaler input control */
+#define GSC_IN_CON 0x10
+#define GSC_IN_CHROM_STRIDE_SEL_MASK (1 << 20)
+#define GSC_IN_CHROM_STRIDE_SEPAR (1 << 20)
+#define GSC_IN_RB_SWAP_MASK (1 << 19)
+#define GSC_IN_RB_SWAP (1 << 19)
+#define GSC_IN_ROT_MASK (7 << 16)
+#define GSC_IN_ROT_270 (7 << 16)
+#define GSC_IN_ROT_90_YFLIP (6 << 16)
+#define GSC_IN_ROT_90_XFLIP (5 << 16)
+#define GSC_IN_ROT_90 (4 << 16)
+#define GSC_IN_ROT_180 (3 << 16)
+#define GSC_IN_ROT_YFLIP (2 << 16)
+#define GSC_IN_ROT_XFLIP (1 << 16)
+#define GSC_IN_RGB_TYPE_MASK (3 << 14)
+#define GSC_IN_RGB_HD_WIDE (3 << 14)
+#define GSC_IN_RGB_HD_NARROW (2 << 14)
+#define GSC_IN_RGB_SD_WIDE (1 << 14)
+#define GSC_IN_RGB_SD_NARROW (0 << 14)
+#define GSC_IN_YUV422_1P_ORDER_MASK (1 << 13)
+#define GSC_IN_YUV422_1P_ORDER_LSB_Y (0 << 13)
+#define GSC_IN_YUV422_1P_OEDER_LSB_C (1 << 13)
+#define GSC_IN_CHROMA_ORDER_MASK (1 << 12)
+#define GSC_IN_CHROMA_ORDER_CBCR (0 << 12)
+#define GSC_IN_CHROMA_ORDER_CRCB (1 << 12)
+#define GSC_IN_FORMAT_MASK (7 << 8)
+#define GSC_IN_XRGB8888 (0 << 8)
+#define GSC_IN_RGB565 (1 << 8)
+#define GSC_IN_YUV420_2P (2 << 8)
+#define GSC_IN_YUV420_3P (3 << 8)
+#define GSC_IN_YUV422_1P (4 << 8)
+#define GSC_IN_YUV422_2P (5 << 8)
+#define GSC_IN_YUV422_3P (6 << 8)
+#define GSC_IN_TILE_TYPE_MASK (1 << 4)
+#define GSC_IN_TILE_C_16x8 (0 << 4)
+#define GSC_IN_TILE_C_16x16 (1 << 4)
+#define GSC_IN_TILE_MODE (1 << 3)
+#define GSC_IN_LOCAL_SEL_MASK (3 << 1)
+#define GSC_IN_LOCAL_CAM3 (3 << 1)
+#define GSC_IN_LOCAL_FIMD_WB (2 << 1)
+#define GSC_IN_LOCAL_CAM1 (1 << 1)
+#define GSC_IN_LOCAL_CAM0 (0 << 1)
+#define GSC_IN_PATH_MASK (1 << 0)
+#define GSC_IN_PATH_LOCAL (1 << 0)
+#define GSC_IN_PATH_MEMORY (0 << 0)
+
+/* G-Scaler source image size */
+#define GSC_SRCIMG_SIZE 0x14
+#define GSC_SRCIMG_HEIGHT_MASK (0x1fff << 16)
+#define GSC_SRCIMG_HEIGHT(x) ((x) << 16)
+#define GSC_SRCIMG_WIDTH_MASK (0x3fff << 0)
+#define GSC_SRCIMG_WIDTH(x) ((x) << 0)
+
+/* G-Scaler source image offset */
+#define GSC_SRCIMG_OFFSET 0x18
+#define GSC_SRCIMG_OFFSET_Y_MASK (0x1fff << 16)
+#define GSC_SRCIMG_OFFSET_Y(x) ((x) << 16)
+#define GSC_SRCIMG_OFFSET_X_MASK (0x1fff << 0)
+#define GSC_SRCIMG_OFFSET_X(x) ((x) << 0)
+
+/* G-Scaler cropped source image size */
+#define GSC_CROPPED_SIZE 0x1C
+#define GSC_CROPPED_HEIGHT_MASK (0x1fff << 16)
+#define GSC_CROPPED_HEIGHT(x) ((x) << 16)
+#define GSC_CROPPED_WIDTH_MASK (0x1fff << 0)
+#define GSC_CROPPED_WIDTH(x) ((x) << 0)
+
+/* G-Scaler output control */
+#define GSC_OUT_CON 0x20
+#define GSC_OUT_GLOBAL_ALPHA_MASK (0xff << 24)
+#define GSC_OUT_GLOBAL_ALPHA(x) ((x) << 24)
+#define GSC_OUT_CHROM_STRIDE_SEL_MASK (1 << 13)
+#define GSC_OUT_CHROM_STRIDE_SEPAR (1 << 13)
+#define GSC_OUT_RB_SWAP_MASK (1 << 12)
+#define GSC_OUT_RB_SWAP (1 << 12)
+#define GSC_OUT_RGB_TYPE_MASK (3 << 10)
+#define GSC_OUT_RGB_HD_NARROW (3 << 10)
+#define GSC_OUT_RGB_HD_WIDE (2 << 10)
+#define GSC_OUT_RGB_SD_NARROW (1 << 10)
+#define GSC_OUT_RGB_SD_WIDE (0 << 10)
+#define GSC_OUT_YUV422_1P_ORDER_MASK (1 << 9)
+#define GSC_OUT_YUV422_1P_ORDER_LSB_Y (0 << 9)
+#define GSC_OUT_YUV422_1P_OEDER_LSB_C (1 << 9)
+#define GSC_OUT_CHROMA_ORDER_MASK (1 << 8)
+#define GSC_OUT_CHROMA_ORDER_CBCR (0 << 8)
+#define GSC_OUT_CHROMA_ORDER_CRCB (1 << 8)
+#define GSC_OUT_FORMAT_MASK (7 << 4)
+#define GSC_OUT_XRGB8888 (0 << 4)
+#define GSC_OUT_RGB565 (1 << 4)
+#define GSC_OUT_YUV420_2P (2 << 4)
+#define GSC_OUT_YUV420_3P (3 << 4)
+#define GSC_OUT_YUV422_1P (4 << 4)
+#define GSC_OUT_YUV422_2P (5 << 4)
+#define GSC_OUT_YUV422_3P (6 << 4)
+#define GSC_OUT_YUV444 (7 << 4)
+#define GSC_OUT_TILE_TYPE_MASK (1 << 2)
+#define GSC_OUT_TILE_C_16x8 (0 << 2)
+#define GSC_OUT_TILE_C_16x16 (1 << 2)
+#define GSC_OUT_TILE_MODE (1 << 1)
+#define GSC_OUT_PATH_MASK (1 << 0)
+#define GSC_OUT_PATH_LOCAL (1 << 0)
+#define GSC_OUT_PATH_MEMORY (0 << 0)
+
+/* G-Scaler scaled destination image size */
+#define GSC_SCALED_SIZE 0x24
+#define GSC_SCALED_HEIGHT_MASK (0x1fff << 16)
+#define GSC_SCALED_HEIGHT(x) ((x) << 16)
+#define GSC_SCALED_WIDTH_MASK (0x1fff << 0)
+#define GSC_SCALED_WIDTH(x) ((x) << 0)
+
+/* G-Scaler pre scale ratio */
+#define GSC_PRE_SCALE_RATIO 0x28
+#define GSC_PRESC_SHFACTOR_MASK (7 << 28)
+#define GSC_PRESC_SHFACTOR(x) ((x) << 28)
+#define GSC_PRESC_V_RATIO_MASK (7 << 16)
+#define GSC_PRESC_V_RATIO(x) ((x) << 16)
+#define GSC_PRESC_H_RATIO_MASK (7 << 0)
+#define GSC_PRESC_H_RATIO(x) ((x) << 0)
+
+/* G-Scaler main scale horizontal ratio */
+#define GSC_MAIN_H_RATIO 0x2C
+#define GSC_MAIN_H_RATIO_MASK (0xfffff << 0)
+#define GSC_MAIN_H_RATIO_VALUE(x) ((x) << 0)
+
+/* G-Scaler main scale vertical ratio */
+#define GSC_MAIN_V_RATIO 0x30
+#define GSC_MAIN_V_RATIO_MASK (0xfffff << 0)
+#define GSC_MAIN_V_RATIO_VALUE(x) ((x) << 0)
+
+/* G-Scaler input chrominance stride */
+#define GSC_IN_CHROM_STRIDE 0x3C
+#define GSC_IN_CHROM_STRIDE_MASK (0x3fff << 0)
+#define GSC_IN_CHROM_STRIDE_VALUE(x) ((x) << 0)
+
+/* G-Scaler destination image size */
+#define GSC_DSTIMG_SIZE 0x40
+#define GSC_DSTIMG_HEIGHT_MASK (0x1fff << 16)
+#define GSC_DSTIMG_HEIGHT(x) ((x) << 16)
+#define GSC_DSTIMG_WIDTH_MASK (0x1fff << 0)
+#define GSC_DSTIMG_WIDTH(x) ((x) << 0)
+
+/* G-Scaler destination image offset */
+#define GSC_DSTIMG_OFFSET 0x44
+#define GSC_DSTIMG_OFFSET_Y_MASK (0x1fff << 16)
+#define GSC_DSTIMG_OFFSET_Y(x) ((x) << 16)
+#define GSC_DSTIMG_OFFSET_X_MASK (0x1fff << 0)
+#define GSC_DSTIMG_OFFSET_X(x) ((x) << 0)
+
+/* G-Scaler output chrominance stride */
+#define GSC_OUT_CHROM_STRIDE 0x48
+#define GSC_OUT_CHROM_STRIDE_MASK (0x3fff << 0)
+#define GSC_OUT_CHROM_STRIDE_VALUE(x) ((x) << 0)
+
+/* G-Scaler input y address mask */
+#define GSC_IN_BASE_ADDR_Y_MASK 0x4C
+/* G-Scaler input y base address */
+#define GSC_IN_BASE_ADDR_Y(n) (0x50 + (n) * 0x4)
+/* G-Scaler input y base current address */
+#define GSC_IN_BASE_ADDR_Y_CUR(n) (0x60 + (n) * 0x4)
+
+/* G-Scaler input cb address mask */
+#define GSC_IN_BASE_ADDR_CB_MASK 0x7C
+/* G-Scaler input cb base address */
+#define GSC_IN_BASE_ADDR_CB(n) (0x80 + (n) * 0x4)
+/* G-Scaler input cb base current address */
+#define GSC_IN_BASE_ADDR_CB_CUR(n) (0x90 + (n) * 0x4)
+
+/* G-Scaler input cr address mask */
+#define GSC_IN_BASE_ADDR_CR_MASK 0xAC
+/* G-Scaler input cr base address */
+#define GSC_IN_BASE_ADDR_CR(n) (0xB0 + (n) * 0x4)
+/* G-Scaler input cr base current address */
+#define GSC_IN_BASE_ADDR_CR_CUR(n) (0xC0 + (n) * 0x4)
+
+/* G-Scaler input address mask */
+#define GSC_IN_CURR_ADDR_INDEX (0xf << 24)
+#define GSC_IN_CURR_GET_INDEX(x) ((x) >> 24)
+#define GSC_IN_BASE_ADDR_PINGPONG(x) ((x) << 16)
+#define GSC_IN_BASE_ADDR_MASK (0xff << 0)
+
+/* G-Scaler output y address mask */
+#define GSC_OUT_BASE_ADDR_Y_MASK 0x10C
+/* G-Scaler output y base address */
+#define GSC_OUT_BASE_ADDR_Y(n) (0x110 + (n) * 0x4)
+
+/* G-Scaler output cb address mask */
+#define GSC_OUT_BASE_ADDR_CB_MASK 0x15C
+/* G-Scaler output cb base address */
+#define GSC_OUT_BASE_ADDR_CB(n) (0x160 + (n) * 0x4)
+
+/* G-Scaler output cr address mask */
+#define GSC_OUT_BASE_ADDR_CR_MASK 0x1AC
+/* G-Scaler output cr base address */
+#define GSC_OUT_BASE_ADDR_CR(n) (0x1B0 + (n) * 0x4)
+
+/* G-Scaler output address mask */
+#define GSC_OUT_CURR_ADDR_INDEX (0xf << 24)
+#define GSC_OUT_CURR_GET_INDEX(x) ((x) >> 24)
+#define GSC_OUT_BASE_ADDR_PINGPONG(x) ((x) << 16)
+#define GSC_OUT_BASE_ADDR_MASK (0xffff << 0)
+
+/* G-Scaler horizontal scaling filter */
+#define GSC_HCOEF(n, s, x) (0x300 + (n) * 0x4 + (s) * 0x30 + (x) * 0x300)
+
+/* G-Scaler vertical scaling filter */
+#define GSC_VCOEF(n, s, x) (0x200 + (n) * 0x4 + (s) * 0x30 + (x) * 0x300)
+
+/* G-Scaler BUS control */
+#define GSC_BUSCON 0xA78
+#define GSC_BUSCON_INT_TIME_MASK (1 << 8)
+#define GSC_BUSCON_INT_DATA_TRANS (0 << 8)
+#define GSC_BUSCON_INT_AXI_RESPONSE (1 << 8)
+#define GSC_BUSCON_AWCACHE(x) ((x) << 4)
+#define GSC_BUSCON_ARCACHE(x) ((x) << 0)
+
+/* G-Scaler V position */
+#define GSC_VPOSITION 0xA7C
+#define GSC_VPOS_F(x) ((x) << 0)
+
+
+/* G-Scaler clock initial count */
+#define GSC_CLK_INIT_COUNT 0xC00
+#define GSC_CLK_GATE_MODE_INIT_CNT(x) ((x) << 0)
+
+/* G-Scaler clock snoop count */
+#define GSC_CLK_SNOOP_COUNT 0xC04
+#define GSC_CLK_GATE_MODE_SNOOP_CNT(x) ((x) << 0)
+
+/* SYSCON. GSCBLK_CFG */
+#define SYSREG_GSCBLK_CFG1 0x0224
+#define GSC_BLK_DISP1WB_DEST(x) (x << 10)
+#define GSC_BLK_SW_RESET_WB_DEST(x) (1 << (18 + x))
+#define GSC_BLK_PXLASYNC_LO_MASK_WB(x) (0 << (14 + x))
+#define GSC_BLK_GSCL_WB_IN_SRC_SEL(x) (1 << (2 * x))
+#define SYSREG_GSCBLK_CFG2 0x2000
+#define PXLASYNC_LO_MASK_CAMIF_GSCL(x) (1 << (x))
+
+#endif /* EXYNOS_REGS_GSC_H_ */
diff --git a/drivers/gpu/drm/exynos/regs-hdmi.h b/drivers/gpu/drm/exynos/regs-hdmi.h
new file mode 100644
index 000000000..8496f230c
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-hdmi.h
@@ -0,0 +1,608 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *
+ * Cloned from drivers/media/video/s5p-tv/regs-hdmi.h
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * HDMI register header file for Samsung TVOUT driver
+*/
+
+#ifndef SAMSUNG_REGS_HDMI_H
+#define SAMSUNG_REGS_HDMI_H
+
+/*
+ * Register part
+*/
+
+/* HDMI Version 1.3 & Common */
+#define HDMI_CTRL_BASE(x) ((x) + 0x00000000)
+#define HDMI_CORE_BASE(x) ((x) + 0x00010000)
+#define HDMI_I2S_BASE(x) ((x) + 0x00040000)
+#define HDMI_TG_BASE(x) ((x) + 0x00050000)
+
+/* Control registers */
+#define HDMI_INTC_CON HDMI_CTRL_BASE(0x0000)
+#define HDMI_INTC_FLAG HDMI_CTRL_BASE(0x0004)
+#define HDMI_HPD_STATUS HDMI_CTRL_BASE(0x000C)
+#define HDMI_V13_PHY_RSTOUT HDMI_CTRL_BASE(0x0014)
+#define HDMI_V13_PHY_VPLL HDMI_CTRL_BASE(0x0018)
+#define HDMI_V13_PHY_CMU HDMI_CTRL_BASE(0x001C)
+#define HDMI_V13_CORE_RSTOUT HDMI_CTRL_BASE(0x0020)
+
+/* Core registers */
+#define HDMI_CON_0 HDMI_CORE_BASE(0x0000)
+#define HDMI_CON_1 HDMI_CORE_BASE(0x0004)
+#define HDMI_CON_2 HDMI_CORE_BASE(0x0008)
+#define HDMI_SYS_STATUS HDMI_CORE_BASE(0x0010)
+#define HDMI_V13_PHY_STATUS HDMI_CORE_BASE(0x0014)
+#define HDMI_STATUS_EN HDMI_CORE_BASE(0x0020)
+#define HDMI_HPD HDMI_CORE_BASE(0x0030)
+#define HDMI_MODE_SEL HDMI_CORE_BASE(0x0040)
+#define HDMI_ENC_EN HDMI_CORE_BASE(0x0044)
+#define HDMI_V13_BLUE_SCREEN_0 HDMI_CORE_BASE(0x0050)
+#define HDMI_V13_BLUE_SCREEN_1 HDMI_CORE_BASE(0x0054)
+#define HDMI_V13_BLUE_SCREEN_2 HDMI_CORE_BASE(0x0058)
+#define HDMI_H_BLANK_0 HDMI_CORE_BASE(0x00A0)
+#define HDMI_H_BLANK_1 HDMI_CORE_BASE(0x00A4)
+#define HDMI_V13_V_BLANK_0 HDMI_CORE_BASE(0x00B0)
+#define HDMI_V13_V_BLANK_1 HDMI_CORE_BASE(0x00B4)
+#define HDMI_V13_V_BLANK_2 HDMI_CORE_BASE(0x00B8)
+#define HDMI_V13_H_V_LINE_0 HDMI_CORE_BASE(0x00C0)
+#define HDMI_V13_H_V_LINE_1 HDMI_CORE_BASE(0x00C4)
+#define HDMI_V13_H_V_LINE_2 HDMI_CORE_BASE(0x00C8)
+#define HDMI_VSYNC_POL HDMI_CORE_BASE(0x00E4)
+#define HDMI_INT_PRO_MODE HDMI_CORE_BASE(0x00E8)
+#define HDMI_V13_V_BLANK_F_0 HDMI_CORE_BASE(0x0110)
+#define HDMI_V13_V_BLANK_F_1 HDMI_CORE_BASE(0x0114)
+#define HDMI_V13_V_BLANK_F_2 HDMI_CORE_BASE(0x0118)
+#define HDMI_V13_H_SYNC_GEN_0 HDMI_CORE_BASE(0x0120)
+#define HDMI_V13_H_SYNC_GEN_1 HDMI_CORE_BASE(0x0124)
+#define HDMI_V13_H_SYNC_GEN_2 HDMI_CORE_BASE(0x0128)
+#define HDMI_V13_V_SYNC_GEN_1_0 HDMI_CORE_BASE(0x0130)
+#define HDMI_V13_V_SYNC_GEN_1_1 HDMI_CORE_BASE(0x0134)
+#define HDMI_V13_V_SYNC_GEN_1_2 HDMI_CORE_BASE(0x0138)
+#define HDMI_V13_V_SYNC_GEN_2_0 HDMI_CORE_BASE(0x0140)
+#define HDMI_V13_V_SYNC_GEN_2_1 HDMI_CORE_BASE(0x0144)
+#define HDMI_V13_V_SYNC_GEN_2_2 HDMI_CORE_BASE(0x0148)
+#define HDMI_V13_V_SYNC_GEN_3_0 HDMI_CORE_BASE(0x0150)
+#define HDMI_V13_V_SYNC_GEN_3_1 HDMI_CORE_BASE(0x0154)
+#define HDMI_V13_V_SYNC_GEN_3_2 HDMI_CORE_BASE(0x0158)
+#define HDMI_V13_AVI_CON HDMI_CORE_BASE(0x0300)
+#define HDMI_V13_AVI_BYTE(n) HDMI_CORE_BASE(0x0320 + 4 * (n))
+#define HDMI_V13_DC_CONTROL HDMI_CORE_BASE(0x05C0)
+#define HDMI_V13_VIDEO_PATTERN_GEN HDMI_CORE_BASE(0x05C4)
+#define HDMI_V13_HPD_GEN HDMI_CORE_BASE(0x05C8)
+#define HDMI_V13_AUI_CON HDMI_CORE_BASE(0x0360)
+#define HDMI_V13_SPD_CON HDMI_CORE_BASE(0x0400)
+
+/* Timing generator registers */
+#define HDMI_TG_CMD HDMI_TG_BASE(0x0000)
+#define HDMI_TG_H_FSZ_L HDMI_TG_BASE(0x0018)
+#define HDMI_TG_H_FSZ_H HDMI_TG_BASE(0x001C)
+#define HDMI_TG_HACT_ST_L HDMI_TG_BASE(0x0020)
+#define HDMI_TG_HACT_ST_H HDMI_TG_BASE(0x0024)
+#define HDMI_TG_HACT_SZ_L HDMI_TG_BASE(0x0028)
+#define HDMI_TG_HACT_SZ_H HDMI_TG_BASE(0x002C)
+#define HDMI_TG_V_FSZ_L HDMI_TG_BASE(0x0030)
+#define HDMI_TG_V_FSZ_H HDMI_TG_BASE(0x0034)
+#define HDMI_TG_VSYNC_L HDMI_TG_BASE(0x0038)
+#define HDMI_TG_VSYNC_H HDMI_TG_BASE(0x003C)
+#define HDMI_TG_VSYNC2_L HDMI_TG_BASE(0x0040)
+#define HDMI_TG_VSYNC2_H HDMI_TG_BASE(0x0044)
+#define HDMI_TG_VACT_ST_L HDMI_TG_BASE(0x0048)
+#define HDMI_TG_VACT_ST_H HDMI_TG_BASE(0x004C)
+#define HDMI_TG_VACT_SZ_L HDMI_TG_BASE(0x0050)
+#define HDMI_TG_VACT_SZ_H HDMI_TG_BASE(0x0054)
+#define HDMI_TG_FIELD_CHG_L HDMI_TG_BASE(0x0058)
+#define HDMI_TG_FIELD_CHG_H HDMI_TG_BASE(0x005C)
+#define HDMI_TG_VACT_ST2_L HDMI_TG_BASE(0x0060)
+#define HDMI_TG_VACT_ST2_H HDMI_TG_BASE(0x0064)
+#define HDMI_TG_VSYNC_TOP_HDMI_L HDMI_TG_BASE(0x0078)
+#define HDMI_TG_VSYNC_TOP_HDMI_H HDMI_TG_BASE(0x007C)
+#define HDMI_TG_VSYNC_BOT_HDMI_L HDMI_TG_BASE(0x0080)
+#define HDMI_TG_VSYNC_BOT_HDMI_H HDMI_TG_BASE(0x0084)
+#define HDMI_TG_FIELD_TOP_HDMI_L HDMI_TG_BASE(0x0088)
+#define HDMI_TG_FIELD_TOP_HDMI_H HDMI_TG_BASE(0x008C)
+#define HDMI_TG_FIELD_BOT_HDMI_L HDMI_TG_BASE(0x0090)
+#define HDMI_TG_FIELD_BOT_HDMI_H HDMI_TG_BASE(0x0094)
+
+/*
+ * Bit definition part
+ */
+
+/* HDMI_INTC_CON */
+#define HDMI_INTC_EN_GLOBAL (1 << 6)
+#define HDMI_INTC_EN_HPD_PLUG (1 << 3)
+#define HDMI_INTC_EN_HPD_UNPLUG (1 << 2)
+
+/* HDMI_INTC_FLAG */
+#define HDMI_INTC_FLAG_HPD_PLUG (1 << 3)
+#define HDMI_INTC_FLAG_HPD_UNPLUG (1 << 2)
+
+/* HDMI_PHY_RSTOUT */
+#define HDMI_PHY_SW_RSTOUT (1 << 0)
+
+/* HDMI_CORE_RSTOUT */
+#define HDMI_CORE_SW_RSTOUT (1 << 0)
+
+/* HDMI_CON_0 */
+#define HDMI_BLUE_SCR_EN (1 << 5)
+#define HDMI_ASP_EN (1 << 2)
+#define HDMI_ASP_DIS (0 << 2)
+#define HDMI_ASP_MASK (1 << 2)
+#define HDMI_EN (1 << 0)
+
+/* HDMI_CON_2 */
+#define HDMI_VID_PREAMBLE_DIS (1 << 5)
+#define HDMI_GUARD_BAND_DIS (1 << 1)
+
+/* HDMI_PHY_STATUS */
+#define HDMI_PHY_STATUS_READY (1 << 0)
+
+/* HDMI_MODE_SEL */
+#define HDMI_MODE_HDMI_EN (1 << 1)
+#define HDMI_MODE_DVI_EN (1 << 0)
+#define HDMI_MODE_MASK (3 << 0)
+
+/* HDMI_TG_CMD */
+#define HDMI_TG_EN (1 << 0)
+#define HDMI_FIELD_EN (1 << 1)
+
+
+/* HDMI Version 1.4 */
+/* Control registers */
+/* #define HDMI_INTC_CON HDMI_CTRL_BASE(0x0000) */
+/* #define HDMI_INTC_FLAG HDMI_CTRL_BASE(0x0004) */
+#define HDMI_HDCP_KEY_LOAD HDMI_CTRL_BASE(0x0008)
+/* #define HDMI_HPD_STATUS HDMI_CTRL_BASE(0x000C) */
+#define HDMI_INTC_CON_1 HDMI_CTRL_BASE(0x0010)
+#define HDMI_INTC_FLAG_1 HDMI_CTRL_BASE(0x0014)
+#define HDMI_PHY_STATUS_0 HDMI_CTRL_BASE(0x0020)
+#define HDMI_PHY_STATUS_CMU HDMI_CTRL_BASE(0x0024)
+#define HDMI_PHY_STATUS_PLL HDMI_CTRL_BASE(0x0028)
+#define HDMI_PHY_CON_0 HDMI_CTRL_BASE(0x0030)
+#define HDMI_HPD_CTRL HDMI_CTRL_BASE(0x0040)
+#define HDMI_HPD_ST HDMI_CTRL_BASE(0x0044)
+#define HDMI_HPD_TH_X HDMI_CTRL_BASE(0x0050)
+#define HDMI_AUDIO_CLKSEL HDMI_CTRL_BASE(0x0070)
+#define HDMI_V14_PHY_RSTOUT HDMI_CTRL_BASE(0x0074)
+#define HDMI_PHY_VPLL HDMI_CTRL_BASE(0x0078)
+#define HDMI_PHY_CMU HDMI_CTRL_BASE(0x007C)
+#define HDMI_CORE_RSTOUT HDMI_CTRL_BASE(0x0080)
+
+/* PHY Control bit definition */
+
+/* HDMI_PHY_CON_0 */
+#define HDMI_PHY_POWER_OFF_EN (1 << 0)
+
+/* Video related registers */
+#define HDMI_YMAX HDMI_CORE_BASE(0x0060)
+#define HDMI_YMIN HDMI_CORE_BASE(0x0064)
+#define HDMI_CMAX HDMI_CORE_BASE(0x0068)
+#define HDMI_CMIN HDMI_CORE_BASE(0x006C)
+
+#define HDMI_V2_BLANK_0 HDMI_CORE_BASE(0x00B0)
+#define HDMI_V2_BLANK_1 HDMI_CORE_BASE(0x00B4)
+#define HDMI_V1_BLANK_0 HDMI_CORE_BASE(0x00B8)
+#define HDMI_V1_BLANK_1 HDMI_CORE_BASE(0x00BC)
+
+#define HDMI_V_LINE_0 HDMI_CORE_BASE(0x00C0)
+#define HDMI_V_LINE_1 HDMI_CORE_BASE(0x00C4)
+#define HDMI_H_LINE_0 HDMI_CORE_BASE(0x00C8)
+#define HDMI_H_LINE_1 HDMI_CORE_BASE(0x00CC)
+
+#define HDMI_HSYNC_POL HDMI_CORE_BASE(0x00E0)
+
+#define HDMI_V_BLANK_F0_0 HDMI_CORE_BASE(0x0110)
+#define HDMI_V_BLANK_F0_1 HDMI_CORE_BASE(0x0114)
+#define HDMI_V_BLANK_F1_0 HDMI_CORE_BASE(0x0118)
+#define HDMI_V_BLANK_F1_1 HDMI_CORE_BASE(0x011C)
+
+#define HDMI_H_SYNC_START_0 HDMI_CORE_BASE(0x0120)
+#define HDMI_H_SYNC_START_1 HDMI_CORE_BASE(0x0124)
+#define HDMI_H_SYNC_END_0 HDMI_CORE_BASE(0x0128)
+#define HDMI_H_SYNC_END_1 HDMI_CORE_BASE(0x012C)
+
+#define HDMI_V_SYNC_LINE_BEF_2_0 HDMI_CORE_BASE(0x0130)
+#define HDMI_V_SYNC_LINE_BEF_2_1 HDMI_CORE_BASE(0x0134)
+#define HDMI_V_SYNC_LINE_BEF_1_0 HDMI_CORE_BASE(0x0138)
+#define HDMI_V_SYNC_LINE_BEF_1_1 HDMI_CORE_BASE(0x013C)
+
+#define HDMI_V_SYNC_LINE_AFT_2_0 HDMI_CORE_BASE(0x0140)
+#define HDMI_V_SYNC_LINE_AFT_2_1 HDMI_CORE_BASE(0x0144)
+#define HDMI_V_SYNC_LINE_AFT_1_0 HDMI_CORE_BASE(0x0148)
+#define HDMI_V_SYNC_LINE_AFT_1_1 HDMI_CORE_BASE(0x014C)
+
+#define HDMI_V_SYNC_LINE_AFT_PXL_2_0 HDMI_CORE_BASE(0x0150)
+#define HDMI_V_SYNC_LINE_AFT_PXL_2_1 HDMI_CORE_BASE(0x0154)
+#define HDMI_V_SYNC_LINE_AFT_PXL_1_0 HDMI_CORE_BASE(0x0158)
+#define HDMI_V_SYNC_LINE_AFT_PXL_1_1 HDMI_CORE_BASE(0x015C)
+
+#define HDMI_V_BLANK_F2_0 HDMI_CORE_BASE(0x0160)
+#define HDMI_V_BLANK_F2_1 HDMI_CORE_BASE(0x0164)
+#define HDMI_V_BLANK_F3_0 HDMI_CORE_BASE(0x0168)
+#define HDMI_V_BLANK_F3_1 HDMI_CORE_BASE(0x016C)
+#define HDMI_V_BLANK_F4_0 HDMI_CORE_BASE(0x0170)
+#define HDMI_V_BLANK_F4_1 HDMI_CORE_BASE(0x0174)
+#define HDMI_V_BLANK_F5_0 HDMI_CORE_BASE(0x0178)
+#define HDMI_V_BLANK_F5_1 HDMI_CORE_BASE(0x017C)
+
+#define HDMI_V_SYNC_LINE_AFT_3_0 HDMI_CORE_BASE(0x0180)
+#define HDMI_V_SYNC_LINE_AFT_3_1 HDMI_CORE_BASE(0x0184)
+#define HDMI_V_SYNC_LINE_AFT_4_0 HDMI_CORE_BASE(0x0188)
+#define HDMI_V_SYNC_LINE_AFT_4_1 HDMI_CORE_BASE(0x018C)
+#define HDMI_V_SYNC_LINE_AFT_5_0 HDMI_CORE_BASE(0x0190)
+#define HDMI_V_SYNC_LINE_AFT_5_1 HDMI_CORE_BASE(0x0194)
+#define HDMI_V_SYNC_LINE_AFT_6_0 HDMI_CORE_BASE(0x0198)
+#define HDMI_V_SYNC_LINE_AFT_6_1 HDMI_CORE_BASE(0x019C)
+
+#define HDMI_V_SYNC_LINE_AFT_PXL_3_0 HDMI_CORE_BASE(0x01A0)
+#define HDMI_V_SYNC_LINE_AFT_PXL_3_1 HDMI_CORE_BASE(0x01A4)
+#define HDMI_V_SYNC_LINE_AFT_PXL_4_0 HDMI_CORE_BASE(0x01A8)
+#define HDMI_V_SYNC_LINE_AFT_PXL_4_1 HDMI_CORE_BASE(0x01AC)
+#define HDMI_V_SYNC_LINE_AFT_PXL_5_0 HDMI_CORE_BASE(0x01B0)
+#define HDMI_V_SYNC_LINE_AFT_PXL_5_1 HDMI_CORE_BASE(0x01B4)
+#define HDMI_V_SYNC_LINE_AFT_PXL_6_0 HDMI_CORE_BASE(0x01B8)
+#define HDMI_V_SYNC_LINE_AFT_PXL_6_1 HDMI_CORE_BASE(0x01BC)
+
+#define HDMI_VACT_SPACE_1_0 HDMI_CORE_BASE(0x01C0)
+#define HDMI_VACT_SPACE_1_1 HDMI_CORE_BASE(0x01C4)
+#define HDMI_VACT_SPACE_2_0 HDMI_CORE_BASE(0x01C8)
+#define HDMI_VACT_SPACE_2_1 HDMI_CORE_BASE(0x01CC)
+#define HDMI_VACT_SPACE_3_0 HDMI_CORE_BASE(0x01D0)
+#define HDMI_VACT_SPACE_3_1 HDMI_CORE_BASE(0x01D4)
+#define HDMI_VACT_SPACE_4_0 HDMI_CORE_BASE(0x01D8)
+#define HDMI_VACT_SPACE_4_1 HDMI_CORE_BASE(0x01DC)
+#define HDMI_VACT_SPACE_5_0 HDMI_CORE_BASE(0x01E0)
+#define HDMI_VACT_SPACE_5_1 HDMI_CORE_BASE(0x01E4)
+#define HDMI_VACT_SPACE_6_0 HDMI_CORE_BASE(0x01E8)
+#define HDMI_VACT_SPACE_6_1 HDMI_CORE_BASE(0x01EC)
+
+#define HDMI_GCP_CON HDMI_CORE_BASE(0x0200)
+#define HDMI_GCP_BYTE1 HDMI_CORE_BASE(0x0210)
+#define HDMI_GCP_BYTE2 HDMI_CORE_BASE(0x0214)
+#define HDMI_GCP_BYTE3 HDMI_CORE_BASE(0x0218)
+
+/* Audio related registers */
+#define HDMI_ASP_CON HDMI_CORE_BASE(0x0300)
+#define HDMI_ASP_SP_FLAT HDMI_CORE_BASE(0x0304)
+#define HDMI_ASP_CHCFG0 HDMI_CORE_BASE(0x0310)
+#define HDMI_ASP_CHCFG1 HDMI_CORE_BASE(0x0314)
+#define HDMI_ASP_CHCFG2 HDMI_CORE_BASE(0x0318)
+#define HDMI_ASP_CHCFG3 HDMI_CORE_BASE(0x031C)
+
+#define HDMI_V13_ACR_CON HDMI_CORE_BASE(0x0180)
+#define HDMI_V13_ACR_MCTS0 HDMI_CORE_BASE(0x0184)
+#define HDMI_V13_ACR_MCTS1 HDMI_CORE_BASE(0x0188)
+#define HDMI_V13_ACR_MCTS2 HDMI_CORE_BASE(0x018C)
+#define HDMI_V13_ACR_CTS0 HDMI_CORE_BASE(0x0190)
+#define HDMI_V13_ACR_CTS1 HDMI_CORE_BASE(0x0194)
+#define HDMI_V13_ACR_CTS2 HDMI_CORE_BASE(0x0198)
+#define HDMI_V13_ACR_N0 HDMI_CORE_BASE(0x01A0)
+#define HDMI_V13_ACR_N1 HDMI_CORE_BASE(0x01A4)
+#define HDMI_V13_ACR_N2 HDMI_CORE_BASE(0x01A8)
+#define HDMI_V14_ACR_CON HDMI_CORE_BASE(0x0400)
+#define HDMI_V14_ACR_MCTS0 HDMI_CORE_BASE(0x0410)
+#define HDMI_V14_ACR_MCTS1 HDMI_CORE_BASE(0x0414)
+#define HDMI_V14_ACR_MCTS2 HDMI_CORE_BASE(0x0418)
+#define HDMI_V14_ACR_CTS0 HDMI_CORE_BASE(0x0420)
+#define HDMI_V14_ACR_CTS1 HDMI_CORE_BASE(0x0424)
+#define HDMI_V14_ACR_CTS2 HDMI_CORE_BASE(0x0428)
+#define HDMI_V14_ACR_N0 HDMI_CORE_BASE(0x0430)
+#define HDMI_V14_ACR_N1 HDMI_CORE_BASE(0x0434)
+#define HDMI_V14_ACR_N2 HDMI_CORE_BASE(0x0438)
+
+/* Packet related registers */
+#define HDMI_ACP_CON HDMI_CORE_BASE(0x0500)
+#define HDMI_ACP_TYPE HDMI_CORE_BASE(0x0514)
+#define HDMI_ACP_DATA(n) HDMI_CORE_BASE(0x0520 + 4 * (n))
+
+#define HDMI_ISRC_CON HDMI_CORE_BASE(0x0600)
+#define HDMI_ISRC1_HEADER1 HDMI_CORE_BASE(0x0614)
+#define HDMI_ISRC1_DATA(n) HDMI_CORE_BASE(0x0620 + 4 * (n))
+#define HDMI_ISRC2_DATA(n) HDMI_CORE_BASE(0x06A0 + 4 * (n))
+
+#define HDMI_AVI_CON HDMI_CORE_BASE(0x0700)
+#define HDMI_AVI_HEADER0 HDMI_CORE_BASE(0x0710)
+#define HDMI_AVI_HEADER1 HDMI_CORE_BASE(0x0714)
+#define HDMI_AVI_HEADER2 HDMI_CORE_BASE(0x0718)
+#define HDMI_AVI_CHECK_SUM HDMI_CORE_BASE(0x071C)
+#define HDMI_AVI_BYTE(n) HDMI_CORE_BASE(0x0720 + 4 * (n-1))
+
+#define HDMI_AUI_CON HDMI_CORE_BASE(0x0800)
+#define HDMI_AUI_HEADER0 HDMI_CORE_BASE(0x0810)
+#define HDMI_AUI_HEADER1 HDMI_CORE_BASE(0x0814)
+#define HDMI_AUI_HEADER2 HDMI_CORE_BASE(0x0818)
+#define HDMI_AUI_CHECK_SUM HDMI_CORE_BASE(0x081C)
+#define HDMI_AUI_BYTE(n) HDMI_CORE_BASE(0x0820 + 4 * (n-1))
+
+#define HDMI_MPG_CON HDMI_CORE_BASE(0x0900)
+#define HDMI_MPG_CHECK_SUM HDMI_CORE_BASE(0x091C)
+#define HDMI_MPG_DATA(n) HDMI_CORE_BASE(0x0920 + 4 * (n))
+
+#define HDMI_SPD_CON HDMI_CORE_BASE(0x0A00)
+#define HDMI_SPD_HEADER0 HDMI_CORE_BASE(0x0A10)
+#define HDMI_SPD_HEADER1 HDMI_CORE_BASE(0x0A14)
+#define HDMI_SPD_HEADER2 HDMI_CORE_BASE(0x0A18)
+#define HDMI_SPD_DATA(n) HDMI_CORE_BASE(0x0A20 + 4 * (n))
+
+#define HDMI_GAMUT_CON HDMI_CORE_BASE(0x0B00)
+#define HDMI_GAMUT_HEADER0 HDMI_CORE_BASE(0x0B10)
+#define HDMI_GAMUT_HEADER1 HDMI_CORE_BASE(0x0B14)
+#define HDMI_GAMUT_HEADER2 HDMI_CORE_BASE(0x0B18)
+#define HDMI_GAMUT_METADATA(n) HDMI_CORE_BASE(0x0B20 + 4 * (n))
+
+#define HDMI_VSI_CON HDMI_CORE_BASE(0x0C00)
+#define HDMI_VSI_HEADER0 HDMI_CORE_BASE(0x0C10)
+#define HDMI_VSI_HEADER1 HDMI_CORE_BASE(0x0C14)
+#define HDMI_VSI_HEADER2 HDMI_CORE_BASE(0x0C18)
+#define HDMI_VSI_DATA(n) HDMI_CORE_BASE(0x0C20 + 4 * (n))
+
+#define HDMI_DC_CONTROL HDMI_CORE_BASE(0x0D00)
+#define HDMI_VIDEO_PATTERN_GEN HDMI_CORE_BASE(0x0D04)
+
+#define HDMI_AN_SEED_SEL HDMI_CORE_BASE(0x0E48)
+#define HDMI_AN_SEED_0 HDMI_CORE_BASE(0x0E58)
+#define HDMI_AN_SEED_1 HDMI_CORE_BASE(0x0E5C)
+#define HDMI_AN_SEED_2 HDMI_CORE_BASE(0x0E60)
+#define HDMI_AN_SEED_3 HDMI_CORE_BASE(0x0E64)
+
+/* AVI bit definition */
+#define HDMI_AVI_CON_DO_NOT_TRANSMIT (0 << 1)
+#define HDMI_AVI_CON_EVERY_VSYNC (1 << 1)
+
+#define AVI_ACTIVE_FORMAT_VALID (1 << 4)
+#define AVI_UNDERSCANNED_DISPLAY_VALID (1 << 1)
+
+/* AUI bit definition */
+#define HDMI_AUI_CON_NO_TRAN (0 << 0)
+#define HDMI_AUI_CON_EVERY_VSYNC (1 << 1)
+
+/* VSI bit definition */
+#define HDMI_VSI_CON_DO_NOT_TRANSMIT (0 << 0)
+#define HDMI_VSI_CON_EVERY_VSYNC (1 << 1)
+
+/* HDCP related registers */
+#define HDMI_HDCP_SHA1(n) HDMI_CORE_BASE(0x7000 + 4 * (n))
+#define HDMI_HDCP_KSV_LIST(n) HDMI_CORE_BASE(0x7050 + 4 * (n))
+
+#define HDMI_HDCP_KSV_LIST_CON HDMI_CORE_BASE(0x7064)
+#define HDMI_HDCP_SHA_RESULT HDMI_CORE_BASE(0x7070)
+#define HDMI_HDCP_CTRL1 HDMI_CORE_BASE(0x7080)
+#define HDMI_HDCP_CTRL2 HDMI_CORE_BASE(0x7084)
+#define HDMI_HDCP_CHECK_RESULT HDMI_CORE_BASE(0x7090)
+#define HDMI_HDCP_BKSV(n) HDMI_CORE_BASE(0x70A0 + 4 * (n))
+#define HDMI_HDCP_AKSV(n) HDMI_CORE_BASE(0x70C0 + 4 * (n))
+#define HDMI_HDCP_AN(n) HDMI_CORE_BASE(0x70E0 + 4 * (n))
+
+#define HDMI_HDCP_BCAPS HDMI_CORE_BASE(0x7100)
+#define HDMI_HDCP_BSTATUS_0 HDMI_CORE_BASE(0x7110)
+#define HDMI_HDCP_BSTATUS_1 HDMI_CORE_BASE(0x7114)
+#define HDMI_HDCP_RI_0 HDMI_CORE_BASE(0x7140)
+#define HDMI_HDCP_RI_1 HDMI_CORE_BASE(0x7144)
+#define HDMI_HDCP_I2C_INT HDMI_CORE_BASE(0x7180)
+#define HDMI_HDCP_AN_INT HDMI_CORE_BASE(0x7190)
+#define HDMI_HDCP_WDT_INT HDMI_CORE_BASE(0x71A0)
+#define HDMI_HDCP_RI_INT HDMI_CORE_BASE(0x71B0)
+#define HDMI_HDCP_RI_COMPARE_0 HDMI_CORE_BASE(0x71D0)
+#define HDMI_HDCP_RI_COMPARE_1 HDMI_CORE_BASE(0x71D4)
+#define HDMI_HDCP_FRAME_COUNT HDMI_CORE_BASE(0x71E0)
+
+#define HDMI_RGB_ROUND_EN HDMI_CORE_BASE(0xD500)
+#define HDMI_VACT_SPACE_R_0 HDMI_CORE_BASE(0xD504)
+#define HDMI_VACT_SPACE_R_1 HDMI_CORE_BASE(0xD508)
+#define HDMI_VACT_SPACE_G_0 HDMI_CORE_BASE(0xD50C)
+#define HDMI_VACT_SPACE_G_1 HDMI_CORE_BASE(0xD510)
+#define HDMI_VACT_SPACE_B_0 HDMI_CORE_BASE(0xD514)
+#define HDMI_VACT_SPACE_B_1 HDMI_CORE_BASE(0xD518)
+
+#define HDMI_BLUE_SCREEN_B_0 HDMI_CORE_BASE(0xD520)
+#define HDMI_BLUE_SCREEN_B_1 HDMI_CORE_BASE(0xD524)
+#define HDMI_BLUE_SCREEN_G_0 HDMI_CORE_BASE(0xD528)
+#define HDMI_BLUE_SCREEN_G_1 HDMI_CORE_BASE(0xD52C)
+#define HDMI_BLUE_SCREEN_R_0 HDMI_CORE_BASE(0xD530)
+#define HDMI_BLUE_SCREEN_R_1 HDMI_CORE_BASE(0xD534)
+
+/* HDMI I2S register */
+#define HDMI_I2S_CLK_CON HDMI_I2S_BASE(0x000)
+#define HDMI_I2S_CON_1 HDMI_I2S_BASE(0x004)
+#define HDMI_I2S_CON_2 HDMI_I2S_BASE(0x008)
+#define HDMI_I2S_PIN_SEL_0 HDMI_I2S_BASE(0x00c)
+#define HDMI_I2S_PIN_SEL_1 HDMI_I2S_BASE(0x010)
+#define HDMI_I2S_PIN_SEL_2 HDMI_I2S_BASE(0x014)
+#define HDMI_I2S_PIN_SEL_3 HDMI_I2S_BASE(0x018)
+#define HDMI_I2S_DSD_CON HDMI_I2S_BASE(0x01c)
+#define HDMI_I2S_MUX_CON HDMI_I2S_BASE(0x020)
+#define HDMI_I2S_CH_ST_CON HDMI_I2S_BASE(0x024)
+/* n must be within range 0...(HDMI_I2S_CH_ST_MAXNUM - 1) */
+#define HDMI_I2S_CH_ST_MAXNUM 5
+#define HDMI_I2S_CH_ST(n) HDMI_I2S_BASE(0x028 + 4 * (n))
+#define HDMI_I2S_CH_ST_SH_0 HDMI_I2S_BASE(0x03c)
+#define HDMI_I2S_CH_ST_SH_1 HDMI_I2S_BASE(0x040)
+#define HDMI_I2S_CH_ST_SH_2 HDMI_I2S_BASE(0x044)
+#define HDMI_I2S_CH_ST_SH_3 HDMI_I2S_BASE(0x048)
+#define HDMI_I2S_CH_ST_SH_4 HDMI_I2S_BASE(0x04c)
+#define HDMI_I2S_MUX_CH HDMI_I2S_BASE(0x054)
+#define HDMI_I2S_MUX_CUV HDMI_I2S_BASE(0x058)
+
+/* I2S bit definition */
+
+/* I2S_CLK_CON */
+#define HDMI_I2S_CLK_DIS (0)
+#define HDMI_I2S_CLK_EN (1)
+
+/* I2S_CON_1 */
+#define HDMI_I2S_SCLK_FALLING_EDGE (0 << 1)
+#define HDMI_I2S_SCLK_RISING_EDGE (1 << 1)
+#define HDMI_I2S_L_CH_LOW_POL (0)
+#define HDMI_I2S_L_CH_HIGH_POL (1)
+
+/* I2S_CON_2 */
+#define HDMI_I2S_MSB_FIRST_MODE (0 << 6)
+#define HDMI_I2S_LSB_FIRST_MODE (1 << 6)
+#define HDMI_I2S_BIT_CH_32FS (0 << 4)
+#define HDMI_I2S_BIT_CH_48FS (1 << 4)
+#define HDMI_I2S_BIT_CH_RESERVED (2 << 4)
+#define HDMI_I2S_SDATA_16BIT (1 << 2)
+#define HDMI_I2S_SDATA_20BIT (2 << 2)
+#define HDMI_I2S_SDATA_24BIT (3 << 2)
+#define HDMI_I2S_BASIC_FORMAT (0)
+#define HDMI_I2S_L_JUST_FORMAT (2)
+#define HDMI_I2S_R_JUST_FORMAT (3)
+#define HDMI_I2S_CON_2_CLR (~(0xFF))
+#define HDMI_I2S_SET_BIT_CH(x) (((x) & 0x7) << 4)
+#define HDMI_I2S_SET_SDATA_BIT(x) (((x) & 0x7) << 2)
+
+/* I2S_PIN_SEL_0 */
+#define HDMI_I2S_SEL_SCLK(x) (((x) & 0x7) << 4)
+#define HDMI_I2S_SEL_LRCK(x) ((x) & 0x7)
+
+/* I2S_PIN_SEL_1 */
+#define HDMI_I2S_SEL_SDATA1(x) (((x) & 0x7) << 4)
+#define HDMI_I2S_SEL_SDATA0(x) ((x) & 0x7)
+
+/* I2S_PIN_SEL_2 */
+#define HDMI_I2S_SEL_SDATA3(x) (((x) & 0x7) << 4)
+#define HDMI_I2S_SEL_SDATA2(x) ((x) & 0x7)
+
+/* I2S_PIN_SEL_3 */
+#define HDMI_I2S_SEL_DSD(x) ((x) & 0x7)
+
+/* I2S_DSD_CON */
+#define HDMI_I2S_DSD_CLK_RI_EDGE (1 << 1)
+#define HDMI_I2S_DSD_CLK_FA_EDGE (0 << 1)
+#define HDMI_I2S_DSD_ENABLE (1)
+#define HDMI_I2S_DSD_DISABLE (0)
+
+/* I2S_MUX_CON */
+#define HDMI_I2S_NOISE_FILTER_ZERO (0 << 5)
+#define HDMI_I2S_NOISE_FILTER_2_STAGE (1 << 5)
+#define HDMI_I2S_NOISE_FILTER_3_STAGE (2 << 5)
+#define HDMI_I2S_NOISE_FILTER_4_STAGE (3 << 5)
+#define HDMI_I2S_NOISE_FILTER_5_STAGE (4 << 5)
+#define HDMI_I2S_IN_DISABLE (1 << 4)
+#define HDMI_I2S_IN_ENABLE (0 << 4)
+#define HDMI_I2S_AUD_SPDIF (0 << 2)
+#define HDMI_I2S_AUD_I2S (1 << 2)
+#define HDMI_I2S_AUD_DSD (2 << 2)
+#define HDMI_I2S_CUV_SPDIF_ENABLE (0 << 1)
+#define HDMI_I2S_CUV_I2S_ENABLE (1 << 1)
+#define HDMI_I2S_MUX_DISABLE (0)
+#define HDMI_I2S_MUX_ENABLE (1)
+#define HDMI_I2S_MUX_CON_CLR (~(0xFF))
+
+/* I2S_CH_ST_CON */
+#define HDMI_I2S_CH_STATUS_RELOAD (1)
+#define HDMI_I2S_CH_ST_CON_CLR (~(1))
+
+/* I2S_CH_ST_0 / I2S_CH_ST_SH_0 */
+#define HDMI_I2S_CH_STATUS_MODE_0 (0 << 6)
+#define HDMI_I2S_2AUD_CH_WITHOUT_PREEMPH (0 << 3)
+#define HDMI_I2S_2AUD_CH_WITH_PREEMPH (1 << 3)
+#define HDMI_I2S_DEFAULT_EMPHASIS (0 << 3)
+#define HDMI_I2S_COPYRIGHT (0 << 2)
+#define HDMI_I2S_NO_COPYRIGHT (1 << 2)
+#define HDMI_I2S_LINEAR_PCM (0 << 1)
+#define HDMI_I2S_NO_LINEAR_PCM (1 << 1)
+#define HDMI_I2S_CONSUMER_FORMAT (0)
+#define HDMI_I2S_PROF_FORMAT (1)
+#define HDMI_I2S_CH_ST_0_CLR (~(0xFF))
+
+/* I2S_CH_ST_1 / I2S_CH_ST_SH_1 */
+#define HDMI_I2S_CD_PLAYER (0x00)
+#define HDMI_I2S_DAT_PLAYER (0x03)
+#define HDMI_I2S_DCC_PLAYER (0x43)
+#define HDMI_I2S_MINI_DISC_PLAYER (0x49)
+
+/* I2S_CH_ST_2 / I2S_CH_ST_SH_2 */
+#define HDMI_I2S_CHANNEL_NUM_MASK (0xF << 4)
+#define HDMI_I2S_SOURCE_NUM_MASK (0xF)
+#define HDMI_I2S_SET_CHANNEL_NUM(x) (((x) & (0xF)) << 4)
+#define HDMI_I2S_SET_SOURCE_NUM(x) ((x) & (0xF))
+
+/* I2S_CH_ST_3 / I2S_CH_ST_SH_3 */
+#define HDMI_I2S_CLK_ACCUR_LEVEL_1 (1 << 4)
+#define HDMI_I2S_CLK_ACCUR_LEVEL_2 (0 << 4)
+#define HDMI_I2S_CLK_ACCUR_LEVEL_3 (2 << 4)
+#define HDMI_I2S_SMP_FREQ_44_1 (0x0)
+#define HDMI_I2S_SMP_FREQ_48 (0x2)
+#define HDMI_I2S_SMP_FREQ_32 (0x3)
+#define HDMI_I2S_SMP_FREQ_96 (0xA)
+#define HDMI_I2S_SET_SMP_FREQ(x) ((x) & (0xF))
+
+/* I2S_CH_ST_4 / I2S_CH_ST_SH_4 */
+#define HDMI_I2S_ORG_SMP_FREQ_44_1 (0xF << 4)
+#define HDMI_I2S_ORG_SMP_FREQ_88_2 (0x7 << 4)
+#define HDMI_I2S_ORG_SMP_FREQ_22_05 (0xB << 4)
+#define HDMI_I2S_ORG_SMP_FREQ_176_4 (0x3 << 4)
+#define HDMI_I2S_WORD_LEN_NOT_DEFINE (0x0 << 1)
+#define HDMI_I2S_WORD_LEN_MAX24_20BITS (0x1 << 1)
+#define HDMI_I2S_WORD_LEN_MAX24_22BITS (0x2 << 1)
+#define HDMI_I2S_WORD_LEN_MAX24_23BITS (0x4 << 1)
+#define HDMI_I2S_WORD_LEN_MAX24_24BITS (0x5 << 1)
+#define HDMI_I2S_WORD_LEN_MAX24_21BITS (0x6 << 1)
+#define HDMI_I2S_WORD_LEN_MAX20_16BITS (0x1 << 1)
+#define HDMI_I2S_WORD_LEN_MAX20_18BITS (0x2 << 1)
+#define HDMI_I2S_WORD_LEN_MAX20_19BITS (0x4 << 1)
+#define HDMI_I2S_WORD_LEN_MAX20_20BITS (0x5 << 1)
+#define HDMI_I2S_WORD_LEN_MAX20_17BITS (0x6 << 1)
+#define HDMI_I2S_WORD_LEN_MAX_24BITS (1)
+#define HDMI_I2S_WORD_LEN_MAX_20BITS (0)
+
+/* I2S_MUX_CH */
+#define HDMI_I2S_CH3_R_EN (1 << 7)
+#define HDMI_I2S_CH3_L_EN (1 << 6)
+#define HDMI_I2S_CH3_EN (3 << 6)
+#define HDMI_I2S_CH2_R_EN (1 << 5)
+#define HDMI_I2S_CH2_L_EN (1 << 4)
+#define HDMI_I2S_CH2_EN (3 << 4)
+#define HDMI_I2S_CH1_R_EN (1 << 3)
+#define HDMI_I2S_CH1_L_EN (1 << 2)
+#define HDMI_I2S_CH1_EN (3 << 2)
+#define HDMI_I2S_CH0_R_EN (1 << 1)
+#define HDMI_I2S_CH0_L_EN (1)
+#define HDMI_I2S_CH0_EN (3)
+#define HDMI_I2S_CH_ALL_EN (0xFF)
+#define HDMI_I2S_MUX_CH_CLR (~HDMI_I2S_CH_ALL_EN)
+
+/* I2S_MUX_CUV */
+#define HDMI_I2S_CUV_R_EN (1 << 1)
+#define HDMI_I2S_CUV_L_EN (1)
+#define HDMI_I2S_CUV_RL_EN (0x03)
+
+/* I2S_CUV_L_R */
+#define HDMI_I2S_CUV_R_DATA_MASK (0x7 << 4)
+#define HDMI_I2S_CUV_L_DATA_MASK (0x7)
+
+/* Timing generator registers */
+/* TG configure/status registers */
+#define HDMI_TG_VACT_ST3_L HDMI_TG_BASE(0x0068)
+#define HDMI_TG_VACT_ST3_H HDMI_TG_BASE(0x006c)
+#define HDMI_TG_VACT_ST4_L HDMI_TG_BASE(0x0070)
+#define HDMI_TG_VACT_ST4_H HDMI_TG_BASE(0x0074)
+#define HDMI_TG_3D HDMI_TG_BASE(0x00F0)
+#define HDMI_TG_DECON_EN HDMI_TG_BASE(0x01e0)
+
+/* HDMI PHY Registers Offsets*/
+#define HDMIPHY_POWER 0x74
+#define HDMIPHY_MODE_SET_DONE 0x7c
+#define HDMIPHY5433_MODE_SET_DONE 0x84
+
+/* HDMI PHY Values */
+#define HDMI_PHY_POWER_ON 0x80
+#define HDMI_PHY_POWER_OFF 0xff
+
+/* HDMI PHY Values */
+#define HDMI_PHY_DISABLE_MODE_SET 0x80
+#define HDMI_PHY_ENABLE_MODE_SET 0x00
+
+/* PMU Registers for PHY */
+#define PMU_HDMI_PHY_CONTROL 0x700
+#define PMU_HDMI_PHY_ENABLE_BIT BIT(0)
+
+#define EXYNOS5433_SYSREG_DISP_HDMI_PHY 0x1008
+#define SYSREG_HDMI_REFCLK_INT_CLK 1
+
+#endif /* SAMSUNG_REGS_HDMI_H */
diff --git a/drivers/gpu/drm/exynos/regs-mixer.h b/drivers/gpu/drm/exynos/regs-mixer.h
new file mode 100644
index 000000000..85ca66b89
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-mixer.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *
+ * Cloned from drivers/media/video/s5p-tv/regs-mixer.h
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Mixer register header file for Samsung Mixer driver
+*/
+#ifndef SAMSUNG_REGS_MIXER_H
+#define SAMSUNG_REGS_MIXER_H
+
+/*
+ * Register part
+ */
+#define MXR_STATUS 0x0000
+#define MXR_CFG 0x0004
+#define MXR_INT_EN 0x0008
+#define MXR_INT_STATUS 0x000C
+#define MXR_LAYER_CFG 0x0010
+#define MXR_VIDEO_CFG 0x0014
+#define MXR_GRAPHIC0_CFG 0x0020
+#define MXR_GRAPHIC0_BASE 0x0024
+#define MXR_GRAPHIC0_SPAN 0x0028
+#define MXR_GRAPHIC0_SXY 0x002C
+#define MXR_GRAPHIC0_WH 0x0030
+#define MXR_GRAPHIC0_DXY 0x0034
+#define MXR_GRAPHIC0_BLANK 0x0038
+#define MXR_GRAPHIC1_CFG 0x0040
+#define MXR_GRAPHIC1_BASE 0x0044
+#define MXR_GRAPHIC1_SPAN 0x0048
+#define MXR_GRAPHIC1_SXY 0x004C
+#define MXR_GRAPHIC1_WH 0x0050
+#define MXR_GRAPHIC1_DXY 0x0054
+#define MXR_GRAPHIC1_BLANK 0x0058
+#define MXR_BG_CFG 0x0060
+#define MXR_BG_COLOR0 0x0064
+#define MXR_BG_COLOR1 0x0068
+#define MXR_BG_COLOR2 0x006C
+#define MXR_CM_COEFF_Y 0x0080
+#define MXR_CM_COEFF_CB 0x0084
+#define MXR_CM_COEFF_CR 0x0088
+#define MXR_MO 0x0304
+#define MXR_RESOLUTION 0x0310
+
+#define MXR_CFG_S 0x2004
+#define MXR_GRAPHIC0_BASE_S 0x2024
+#define MXR_GRAPHIC1_BASE_S 0x2044
+
+/* for parametrized access to layer registers */
+#define MXR_GRAPHIC_CFG(i) (0x0020 + (i) * 0x20)
+#define MXR_GRAPHIC_BASE(i) (0x0024 + (i) * 0x20)
+#define MXR_GRAPHIC_SPAN(i) (0x0028 + (i) * 0x20)
+#define MXR_GRAPHIC_SXY(i) (0x002C + (i) * 0x20)
+#define MXR_GRAPHIC_WH(i) (0x0030 + (i) * 0x20)
+#define MXR_GRAPHIC_DXY(i) (0x0034 + (i) * 0x20)
+#define MXR_GRAPHIC_BLANK(i) (0x0038 + (i) * 0x20)
+#define MXR_GRAPHIC_BASE_S(i) (0x2024 + (i) * 0x20)
+
+/*
+ * Bit definition part
+ */
+
+/* generates mask for range of bits */
+#define MXR_MASK(high_bit, low_bit) \
+ (((2 << ((high_bit) - (low_bit))) - 1) << (low_bit))
+
+#define MXR_MASK_VAL(val, high_bit, low_bit) \
+ (((val) << (low_bit)) & MXR_MASK(high_bit, low_bit))
+
+/* bits for MXR_STATUS */
+#define MXR_STATUS_SOFT_RESET (1 << 8)
+#define MXR_STATUS_16_BURST (1 << 7)
+#define MXR_STATUS_BURST_MASK (1 << 7)
+#define MXR_STATUS_BIG_ENDIAN (1 << 3)
+#define MXR_STATUS_ENDIAN_MASK (1 << 3)
+#define MXR_STATUS_SYNC_ENABLE (1 << 2)
+#define MXR_STATUS_REG_IDLE (1 << 1)
+#define MXR_STATUS_REG_RUN (1 << 0)
+
+/* bits for MXR_CFG */
+#define MXR_CFG_LAYER_UPDATE (1 << 31)
+#define MXR_CFG_LAYER_UPDATE_COUNT_MASK (3 << 29)
+#define MXR_CFG_QUANT_RANGE_FULL (0 << 9)
+#define MXR_CFG_QUANT_RANGE_LIMITED (1 << 9)
+#define MXR_CFG_RGB601 (0 << 10)
+#define MXR_CFG_RGB709 (1 << 10)
+
+#define MXR_CFG_RGB_FMT_MASK 0x600
+#define MXR_CFG_OUT_YUV444 (0 << 8)
+#define MXR_CFG_OUT_RGB888 (1 << 8)
+#define MXR_CFG_OUT_MASK (1 << 8)
+#define MXR_CFG_DST_SDO (0 << 7)
+#define MXR_CFG_DST_HDMI (1 << 7)
+#define MXR_CFG_DST_MASK (1 << 7)
+#define MXR_CFG_SCAN_HD_720 (0 << 6)
+#define MXR_CFG_SCAN_HD_1080 (1 << 6)
+#define MXR_CFG_GRP1_ENABLE (1 << 5)
+#define MXR_CFG_GRP0_ENABLE (1 << 4)
+#define MXR_CFG_VP_ENABLE (1 << 3)
+#define MXR_CFG_SCAN_INTERLACE (0 << 2)
+#define MXR_CFG_SCAN_PROGRESSIVE (1 << 2)
+#define MXR_CFG_SCAN_NTSC (0 << 1)
+#define MXR_CFG_SCAN_PAL (1 << 1)
+#define MXR_CFG_SCAN_SD (0 << 0)
+#define MXR_CFG_SCAN_HD (1 << 0)
+#define MXR_CFG_SCAN_MASK 0x47
+
+/* bits for MXR_VIDEO_CFG */
+#define MXR_VID_CFG_BLEND_EN (1 << 16)
+
+/* bits for MXR_GRAPHICn_CFG */
+#define MXR_GRP_CFG_COLOR_KEY_DISABLE (1 << 21)
+#define MXR_GRP_CFG_BLEND_PRE_MUL (1 << 20)
+#define MXR_GRP_CFG_WIN_BLEND_EN (1 << 17)
+#define MXR_GRP_CFG_PIXEL_BLEND_EN (1 << 16)
+#define MXR_GRP_CFG_MISC_MASK ((3 << 16) | (3 << 20) | 0xff)
+#define MXR_GRP_CFG_FORMAT_VAL(x) MXR_MASK_VAL(x, 11, 8)
+#define MXR_GRP_CFG_FORMAT_MASK MXR_GRP_CFG_FORMAT_VAL(~0)
+#define MXR_GRP_CFG_ALPHA_VAL(x) MXR_MASK_VAL(x, 7, 0)
+
+/* bits for MXR_GRAPHICn_WH */
+#define MXR_GRP_WH_H_SCALE(x) MXR_MASK_VAL(x, 28, 28)
+#define MXR_GRP_WH_V_SCALE(x) MXR_MASK_VAL(x, 12, 12)
+#define MXR_GRP_WH_WIDTH(x) MXR_MASK_VAL(x, 26, 16)
+#define MXR_GRP_WH_HEIGHT(x) MXR_MASK_VAL(x, 10, 0)
+
+/* bits for MXR_RESOLUTION */
+#define MXR_MXR_RES_HEIGHT(x) MXR_MASK_VAL(x, 26, 16)
+#define MXR_MXR_RES_WIDTH(x) MXR_MASK_VAL(x, 10, 0)
+
+/* bits for MXR_GRAPHICn_SXY */
+#define MXR_GRP_SXY_SX(x) MXR_MASK_VAL(x, 26, 16)
+#define MXR_GRP_SXY_SY(x) MXR_MASK_VAL(x, 10, 0)
+
+/* bits for MXR_GRAPHICn_DXY */
+#define MXR_GRP_DXY_DX(x) MXR_MASK_VAL(x, 26, 16)
+#define MXR_GRP_DXY_DY(x) MXR_MASK_VAL(x, 10, 0)
+
+/* bits for MXR_INT_EN */
+#define MXR_INT_EN_VSYNC (1 << 11)
+#define MXR_INT_EN_ALL (0x0f << 8)
+
+/* bits for MXR_INT_STATUS */
+#define MXR_INT_CLEAR_VSYNC (1 << 11)
+#define MXR_INT_STATUS_VSYNC (1 << 0)
+
+/* bits for MXR_LAYER_CFG */
+#define MXR_LAYER_CFG_GRP1_VAL(x) MXR_MASK_VAL(x, 11, 8)
+#define MXR_LAYER_CFG_GRP1_MASK MXR_LAYER_CFG_GRP1_VAL(~0)
+#define MXR_LAYER_CFG_GRP0_VAL(x) MXR_MASK_VAL(x, 7, 4)
+#define MXR_LAYER_CFG_GRP0_MASK MXR_LAYER_CFG_GRP0_VAL(~0)
+#define MXR_LAYER_CFG_VP_VAL(x) MXR_MASK_VAL(x, 3, 0)
+#define MXR_LAYER_CFG_VP_MASK MXR_LAYER_CFG_VP_VAL(~0)
+
+/* bits for MXR_CM_COEFF_Y */
+#define MXR_CM_COEFF_RGB_FULL (1 << 30)
+
+#endif /* SAMSUNG_REGS_MIXER_H */
+
diff --git a/drivers/gpu/drm/exynos/regs-rotator.h b/drivers/gpu/drm/exynos/regs-rotator.h
new file mode 100644
index 000000000..e6559f565
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-rotator.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* drivers/gpu/drm/exynos/regs-rotator.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Register definition file for Samsung Rotator Interface (Rotator) driver
+*/
+
+#ifndef EXYNOS_REGS_ROTATOR_H
+#define EXYNOS_REGS_ROTATOR_H
+
+/* Configuration */
+#define ROT_CONFIG 0x00
+#define ROT_CONFIG_IRQ (3 << 8)
+
+/* Image Control */
+#define ROT_CONTROL 0x10
+#define ROT_CONTROL_PATTERN_WRITE (1 << 16)
+#define ROT_CONTROL_FMT_YCBCR420_2P (1 << 8)
+#define ROT_CONTROL_FMT_RGB888 (6 << 8)
+#define ROT_CONTROL_FMT_MASK (7 << 8)
+#define ROT_CONTROL_FLIP_VERTICAL (2 << 6)
+#define ROT_CONTROL_FLIP_HORIZONTAL (3 << 6)
+#define ROT_CONTROL_FLIP_MASK (3 << 6)
+#define ROT_CONTROL_ROT_90 (1 << 4)
+#define ROT_CONTROL_ROT_180 (2 << 4)
+#define ROT_CONTROL_ROT_270 (3 << 4)
+#define ROT_CONTROL_ROT_MASK (3 << 4)
+#define ROT_CONTROL_START (1 << 0)
+
+/* Status */
+#define ROT_STATUS 0x20
+#define ROT_STATUS_IRQ_PENDING(x) (1 << (x))
+#define ROT_STATUS_IRQ(x) (((x) >> 8) & 0x3)
+#define ROT_STATUS_IRQ_VAL_COMPLETE 1
+#define ROT_STATUS_IRQ_VAL_ILLEGAL 2
+
+/* Buffer Address */
+#define ROT_SRC_BUF_ADDR(n) (0x30 + ((n) << 2))
+#define ROT_DST_BUF_ADDR(n) (0x50 + ((n) << 2))
+
+/* Buffer Size */
+#define ROT_SRC_BUF_SIZE 0x3c
+#define ROT_DST_BUF_SIZE 0x5c
+#define ROT_SET_BUF_SIZE_H(x) ((x) << 16)
+#define ROT_SET_BUF_SIZE_W(x) ((x) << 0)
+#define ROT_GET_BUF_SIZE_H(x) ((x) >> 16)
+#define ROT_GET_BUF_SIZE_W(x) ((x) & 0xffff)
+
+/* Crop Position */
+#define ROT_SRC_CROP_POS 0x40
+#define ROT_DST_CROP_POS 0x60
+#define ROT_CROP_POS_Y(x) ((x) << 16)
+#define ROT_CROP_POS_X(x) ((x) << 0)
+
+/* Source Crop Size */
+#define ROT_SRC_CROP_SIZE 0x44
+#define ROT_SRC_CROP_SIZE_H(x) ((x) << 16)
+#define ROT_SRC_CROP_SIZE_W(x) ((x) << 0)
+
+/* Round to nearest aligned value */
+#define ROT_ALIGN(x, align, mask) (((x) + (1 << ((align) - 1))) & (mask))
+/* Minimum limit value */
+#define ROT_MIN(min, mask) (((min) + ~(mask)) & (mask))
+/* Maximum limit value */
+#define ROT_MAX(max, mask) ((max) & (mask))
+
+#endif /* EXYNOS_REGS_ROTATOR_H */
+
diff --git a/drivers/gpu/drm/exynos/regs-scaler.h b/drivers/gpu/drm/exynos/regs-scaler.h
new file mode 100644
index 000000000..654c5f85f
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-scaler.h
@@ -0,0 +1,423 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* drivers/gpu/drm/exynos/regs-scaler.h
+ *
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ *
+ * Register definition file for Samsung scaler driver
+ */
+
+#ifndef EXYNOS_REGS_SCALER_H
+#define EXYNOS_REGS_SCALER_H
+
+/* Register part */
+
+/* Global setting */
+#define SCALER_STATUS 0x0 /* no shadow */
+#define SCALER_CFG 0x4
+
+/* Interrupt */
+#define SCALER_INT_EN 0x8 /* no shadow */
+#define SCALER_INT_STATUS 0xc /* no shadow */
+
+/* SRC */
+#define SCALER_SRC_CFG 0x10
+#define SCALER_SRC_Y_BASE 0x14
+#define SCALER_SRC_CB_BASE 0x18
+#define SCALER_SRC_CR_BASE 0x294
+#define SCALER_SRC_SPAN 0x1c
+#define SCALER_SRC_Y_POS 0x20
+#define SCALER_SRC_WH 0x24
+#define SCALER_SRC_C_POS 0x28
+
+/* DST */
+#define SCALER_DST_CFG 0x30
+#define SCALER_DST_Y_BASE 0x34
+#define SCALER_DST_CB_BASE 0x38
+#define SCALER_DST_CR_BASE 0x298
+#define SCALER_DST_SPAN 0x3c
+#define SCALER_DST_WH 0x40
+#define SCALER_DST_POS 0x44
+
+/* Ratio */
+#define SCALER_H_RATIO 0x50
+#define SCALER_V_RATIO 0x54
+
+/* Rotation */
+#define SCALER_ROT_CFG 0x58
+
+/* Coefficient */
+/*
+ * YHCOEF_{x}{A|B|C|D} CHCOEF_{x}{A|B|C|D}
+ *
+ * A B C D A B C D
+ * 0 60 64 68 6c 140 144 148 14c
+ * 1 70 74 78 7c 150 154 158 15c
+ * 2 80 84 88 8c 160 164 168 16c
+ * 3 90 94 98 9c 170 174 178 17c
+ * 4 a0 a4 a8 ac 180 184 188 18c
+ * 5 b0 b4 b8 bc 190 194 198 19c
+ * 6 c0 c4 c8 cc 1a0 1a4 1a8 1ac
+ * 7 d0 d4 d8 dc 1b0 1b4 1b8 1bc
+ * 8 e0 e4 e8 ec 1c0 1c4 1c8 1cc
+ *
+ *
+ * YVCOEF_{x}{A|B} CVCOEF_{x}{A|B}
+ *
+ * A B A B
+ * 0 f0 f4 1d0 1d4
+ * 1 f8 fc 1d8 1dc
+ * 2 100 104 1e0 1e4
+ * 3 108 10c 1e8 1ec
+ * 4 110 114 1f0 1f4
+ * 5 118 11c 1f8 1fc
+ * 6 120 124 200 204
+ * 7 128 12c 208 20c
+ * 8 130 134 210 214
+ */
+#define _SCALER_HCOEF_DELTA(r, c) ((r) * 0x10 + (c) * 0x4)
+#define _SCALER_VCOEF_DELTA(r, c) ((r) * 0x8 + (c) * 0x4)
+
+#define SCALER_YHCOEF(r, c) (0x60 + _SCALER_HCOEF_DELTA((r), (c)))
+#define SCALER_YVCOEF(r, c) (0xf0 + _SCALER_VCOEF_DELTA((r), (c)))
+#define SCALER_CHCOEF(r, c) (0x140 + _SCALER_HCOEF_DELTA((r), (c)))
+#define SCALER_CVCOEF(r, c) (0x1d0 + _SCALER_VCOEF_DELTA((r), (c)))
+
+
+/* Color Space Conversion */
+#define SCALER_CSC_COEF(x, y) (0x220 + (y) * 0xc + (x) * 0x4)
+
+/* Dithering */
+#define SCALER_DITH_CFG 0x250
+
+/* Version Number */
+#define SCALER_VER 0x260 /* no shadow */
+
+/* Cycle count and Timeout */
+#define SCALER_CYCLE_COUNT 0x278 /* no shadow */
+#define SCALER_TIMEOUT_CTRL 0x2c0 /* no shadow */
+#define SCALER_TIMEOUT_CNT 0x2c4 /* no shadow */
+
+/* Blending */
+#define SCALER_SRC_BLEND_COLOR 0x280
+#define SCALER_SRC_BLEND_ALPHA 0x284
+#define SCALER_DST_BLEND_COLOR 0x288
+#define SCALER_DST_BLEND_ALPHA 0x28c
+
+/* Color Fill */
+#define SCALER_FILL_COLOR 0x290
+
+/* Multiple Command Queue */
+#define SCALER_ADDR_Q_CONFIG 0x2a0 /* no shadow */
+#define SCALER_SRC_ADDR_Q_STATUS 0x2a4 /* no shadow */
+#define SCALER_SRC_ADDR_Q 0x2a8 /* no shadow */
+
+/* CRC */
+#define SCALER_CRC_COLOR00_10 0x2b0 /* no shadow */
+#define SCALER_CRC_COLOR20_30 0x2b4 /* no shadow */
+#define SCALER_CRC_COLOR01_11 0x2b8 /* no shadow */
+#define SCALER_CRC_COLOR21_31 0x2bc /* no shadow */
+
+/* Shadow Registers */
+#define SCALER_SHADOW_OFFSET 0x1000
+
+
+/* Bit definition part */
+#define SCALER_MASK(hi_b, lo_b) ((1 << ((hi_b) - (lo_b) + 1)) - 1)
+#define SCALER_GET(reg, hi_b, lo_b) \
+ (((reg) >> (lo_b)) & SCALER_MASK(hi_b, lo_b))
+#define SCALER_SET(val, hi_b, lo_b) \
+ (((val) & SCALER_MASK(hi_b, lo_b)) << lo_b)
+
+/* SCALER_STATUS */
+#define SCALER_STATUS_SCALER_RUNNING (1 << 1)
+#define SCALER_STATUS_SCALER_READY_CLK_DOWN (1 << 0)
+
+/* SCALER_CFG */
+#define SCALER_CFG_FILL_EN (1 << 24)
+#define SCALER_CFG_BLEND_COLOR_DIVIDE_ALPHA_EN (1 << 17)
+#define SCALER_CFG_BLEND_EN (1 << 16)
+#define SCALER_CFG_CSC_Y_OFFSET_SRC_EN (1 << 10)
+#define SCALER_CFG_CSC_Y_OFFSET_DST_EN (1 << 9)
+#define SCALER_CFG_16_BURST_MODE (1 << 8)
+#define SCALER_CFG_SOFT_RESET (1 << 1)
+#define SCALER_CFG_START_CMD (1 << 0)
+
+/* SCALER_INT_EN */
+#define SCALER_INT_EN_TIMEOUT (1 << 31)
+#define SCALER_INT_EN_ILLEGAL_BLEND (1 << 24)
+#define SCALER_INT_EN_ILLEGAL_RATIO (1 << 23)
+#define SCALER_INT_EN_ILLEGAL_DST_HEIGHT (1 << 22)
+#define SCALER_INT_EN_ILLEGAL_DST_WIDTH (1 << 21)
+#define SCALER_INT_EN_ILLEGAL_DST_V_POS (1 << 20)
+#define SCALER_INT_EN_ILLEGAL_DST_H_POS (1 << 19)
+#define SCALER_INT_EN_ILLEGAL_DST_C_SPAN (1 << 18)
+#define SCALER_INT_EN_ILLEGAL_DST_Y_SPAN (1 << 17)
+#define SCALER_INT_EN_ILLEGAL_DST_CR_BASE (1 << 16)
+#define SCALER_INT_EN_ILLEGAL_DST_CB_BASE (1 << 15)
+#define SCALER_INT_EN_ILLEGAL_DST_Y_BASE (1 << 14)
+#define SCALER_INT_EN_ILLEGAL_DST_COLOR (1 << 13)
+#define SCALER_INT_EN_ILLEGAL_SRC_HEIGHT (1 << 12)
+#define SCALER_INT_EN_ILLEGAL_SRC_WIDTH (1 << 11)
+#define SCALER_INT_EN_ILLEGAL_SRC_CV_POS (1 << 10)
+#define SCALER_INT_EN_ILLEGAL_SRC_CH_POS (1 << 9)
+#define SCALER_INT_EN_ILLEGAL_SRC_YV_POS (1 << 8)
+#define SCALER_INT_EN_ILLEGAL_SRC_YH_POS (1 << 7)
+#define SCALER_INT_EN_ILLEGAL_DST_SPAN (1 << 6)
+#define SCALER_INT_EN_ILLEGAL_SRC_Y_SPAN (1 << 5)
+#define SCALER_INT_EN_ILLEGAL_SRC_CR_BASE (1 << 4)
+#define SCALER_INT_EN_ILLEGAL_SRC_CB_BASE (1 << 3)
+#define SCALER_INT_EN_ILLEGAL_SRC_Y_BASE (1 << 2)
+#define SCALER_INT_EN_ILLEGAL_SRC_COLOR (1 << 1)
+#define SCALER_INT_EN_FRAME_END (1 << 0)
+
+/* SCALER_INT_STATUS */
+#define SCALER_INT_STATUS_TIMEOUT (1 << 31)
+#define SCALER_INT_STATUS_ILLEGAL_BLEND (1 << 24)
+#define SCALER_INT_STATUS_ILLEGAL_RATIO (1 << 23)
+#define SCALER_INT_STATUS_ILLEGAL_DST_HEIGHT (1 << 22)
+#define SCALER_INT_STATUS_ILLEGAL_DST_WIDTH (1 << 21)
+#define SCALER_INT_STATUS_ILLEGAL_DST_V_POS (1 << 20)
+#define SCALER_INT_STATUS_ILLEGAL_DST_H_POS (1 << 19)
+#define SCALER_INT_STATUS_ILLEGAL_DST_C_SPAN (1 << 18)
+#define SCALER_INT_STATUS_ILLEGAL_DST_Y_SPAN (1 << 17)
+#define SCALER_INT_STATUS_ILLEGAL_DST_CR_BASE (1 << 16)
+#define SCALER_INT_STATUS_ILLEGAL_DST_CB_BASE (1 << 15)
+#define SCALER_INT_STATUS_ILLEGAL_DST_Y_BASE (1 << 14)
+#define SCALER_INT_STATUS_ILLEGAL_DST_COLOR (1 << 13)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_HEIGHT (1 << 12)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_WIDTH (1 << 11)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_CV_POS (1 << 10)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_CH_POS (1 << 9)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_YV_POS (1 << 8)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_YH_POS (1 << 7)
+#define SCALER_INT_STATUS_ILLEGAL_DST_SPAN (1 << 6)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_Y_SPAN (1 << 5)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_CR_BASE (1 << 4)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_CB_BASE (1 << 3)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_Y_BASE (1 << 2)
+#define SCALER_INT_STATUS_ILLEGAL_SRC_COLOR (1 << 1)
+#define SCALER_INT_STATUS_FRAME_END (1 << 0)
+
+/* SCALER_SRC_CFG */
+#define SCALER_SRC_CFG_TILE_EN (1 << 10)
+#define SCALER_SRC_CFG_GET_BYTE_SWAP(r) SCALER_GET(r, 6, 5)
+#define SCALER_SRC_CFG_SET_BYTE_SWAP(v) SCALER_SET(v, 6, 5)
+#define SCALER_SRC_CFG_GET_COLOR_FORMAT(r) SCALER_GET(r, 4, 0)
+#define SCALER_SRC_CFG_SET_COLOR_FORMAT(v) SCALER_SET(v, 4, 0)
+#define SCALER_YUV420_2P_UV 0
+#define SCALER_YUV422_2P_UV 2
+#define SCALER_YUV444_2P_UV 3
+#define SCALER_RGB_565 4
+#define SCALER_ARGB1555 5
+#define SCALER_ARGB8888 6
+#define SCALER_ARGB8888_PRE 7
+#define SCALER_YUV422_1P_YVYU 9
+#define SCALER_YUV422_1P_YUYV 10
+#define SCALER_YUV422_1P_UYVY 11
+#define SCALER_ARGB4444 12
+#define SCALER_L8A8 13
+#define SCALER_RGBA8888 14
+#define SCALER_L8 15
+#define SCALER_YUV420_2P_VU 16
+#define SCALER_YUV422_2P_VU 18
+#define SCALER_YUV444_2P_VU 19
+#define SCALER_YUV420_3P 20
+#define SCALER_YUV422_3P 22
+#define SCALER_YUV444_3P 23
+
+/* SCALER_SRC_SPAN */
+#define SCALER_SRC_SPAN_GET_C_SPAN(r) SCALER_GET(r, 29, 16)
+#define SCALER_SRC_SPAN_SET_C_SPAN(v) SCALER_SET(v, 29, 16)
+#define SCALER_SRC_SPAN_GET_Y_SPAN(r) SCALER_GET(r, 13, 0)
+#define SCALER_SRC_SPAN_SET_Y_SPAN(v) SCALER_SET(v, 13, 0)
+
+/* SCALER_SRC_Y_POS */
+#define SCALER_SRC_Y_POS_GET_YH_POS(r) SCALER_GET(r, 31, 16)
+#define SCALER_SRC_Y_POS_SET_YH_POS(v) SCALER_SET(v, 31, 16)
+#define SCALER_SRC_Y_POS_GET_YV_POS(r) SCALER_GET(r, 15, 0)
+#define SCALER_SRC_Y_POS_SET_YV_POS(v) SCALER_SET(v, 15, 0)
+
+/* SCALER_SRC_WH */
+#define SCALER_SRC_WH_GET_WIDTH(r) SCALER_GET(r, 29, 16)
+#define SCALER_SRC_WH_SET_WIDTH(v) SCALER_SET(v, 29, 16)
+#define SCALER_SRC_WH_GET_HEIGHT(r) SCALER_GET(r, 13, 0)
+#define SCALER_SRC_WH_SET_HEIGHT(v) SCALER_SET(v, 13, 0)
+
+/* SCALER_SRC_C_POS */
+#define SCALER_SRC_C_POS_GET_CH_POS(r) SCALER_GET(r, 31, 16)
+#define SCALER_SRC_C_POS_SET_CH_POS(v) SCALER_SET(v, 31, 16)
+#define SCALER_SRC_C_POS_GET_CV_POS(r) SCALER_GET(r, 15, 0)
+#define SCALER_SRC_C_POS_SET_CV_POS(v) SCALER_SET(v, 15, 0)
+
+/* SCALER_DST_CFG */
+#define SCALER_DST_CFG_GET_BYTE_SWAP(r) SCALER_GET(r, 6, 5)
+#define SCALER_DST_CFG_SET_BYTE_SWAP(v) SCALER_SET(v, 6, 5)
+#define SCALER_DST_CFG_GET_COLOR_FORMAT(r) SCALER_GET(r, 4, 0)
+#define SCALER_DST_CFG_SET_COLOR_FORMAT(v) SCALER_SET(v, 4, 0)
+
+/* SCALER_DST_SPAN */
+#define SCALER_DST_SPAN_GET_C_SPAN(r) SCALER_GET(r, 29, 16)
+#define SCALER_DST_SPAN_SET_C_SPAN(v) SCALER_SET(v, 29, 16)
+#define SCALER_DST_SPAN_GET_Y_SPAN(r) SCALER_GET(r, 13, 0)
+#define SCALER_DST_SPAN_SET_Y_SPAN(v) SCALER_SET(v, 13, 0)
+
+/* SCALER_DST_WH */
+#define SCALER_DST_WH_GET_WIDTH(r) SCALER_GET(r, 29, 16)
+#define SCALER_DST_WH_SET_WIDTH(v) SCALER_SET(v, 29, 16)
+#define SCALER_DST_WH_GET_HEIGHT(r) SCALER_GET(r, 13, 0)
+#define SCALER_DST_WH_SET_HEIGHT(v) SCALER_SET(v, 13, 0)
+
+/* SCALER_DST_POS */
+#define SCALER_DST_POS_GET_H_POS(r) SCALER_GET(r, 29, 16)
+#define SCALER_DST_POS_SET_H_POS(v) SCALER_SET(v, 29, 16)
+#define SCALER_DST_POS_GET_V_POS(r) SCALER_GET(r, 13, 0)
+#define SCALER_DST_POS_SET_V_POS(v) SCALER_SET(v, 13, 0)
+
+/* SCALER_H_RATIO */
+#define SCALER_H_RATIO_GET(r) SCALER_GET(r, 18, 0)
+#define SCALER_H_RATIO_SET(v) SCALER_SET(v, 18, 0)
+
+/* SCALER_V_RATIO */
+#define SCALER_V_RATIO_GET(r) SCALER_GET(r, 18, 0)
+#define SCALER_V_RATIO_SET(v) SCALER_SET(v, 18, 0)
+
+/* SCALER_ROT_CFG */
+#define SCALER_ROT_CFG_FLIP_X_EN (1 << 3)
+#define SCALER_ROT_CFG_FLIP_Y_EN (1 << 2)
+#define SCALER_ROT_CFG_GET_ROTMODE(r) SCALER_GET(r, 1, 0)
+#define SCALER_ROT_CFG_SET_ROTMODE(v) SCALER_SET(v, 1, 0)
+#define SCALER_ROT_MODE_90 1
+#define SCALER_ROT_MODE_180 2
+#define SCALER_ROT_MODE_270 3
+
+/* SCALER_HCOEF, SCALER_VCOEF */
+#define SCALER_COEF_SHIFT(i) (16 * (1 - (i) % 2))
+#define SCALER_COEF_GET(r, i) \
+ (((r) >> SCALER_COEF_SHIFT(i)) & 0x1ff)
+#define SCALER_COEF_SET(v, i) \
+ (((v) & 0x1ff) << SCALER_COEF_SHIFT(i))
+
+/* SCALER_CSC_COEFxy */
+#define SCALER_CSC_COEF_GET(r) SCALER_GET(r, 11, 0)
+#define SCALER_CSC_COEF_SET(v) SCALER_SET(v, 11, 0)
+
+/* SCALER_DITH_CFG */
+#define SCALER_DITH_CFG_GET_R_TYPE(r) SCALER_GET(r, 8, 6)
+#define SCALER_DITH_CFG_SET_R_TYPE(v) SCALER_SET(v, 8, 6)
+#define SCALER_DITH_CFG_GET_G_TYPE(r) SCALER_GET(r, 5, 3)
+#define SCALER_DITH_CFG_SET_G_TYPE(v) SCALER_SET(v, 5, 3)
+#define SCALER_DITH_CFG_GET_B_TYPE(r) SCALER_GET(r, 2, 0)
+#define SCALER_DITH_CFG_SET_B_TYPE(v) SCALER_SET(v, 2, 0)
+
+/* SCALER_TIMEOUT_CTRL */
+#define SCALER_TIMEOUT_CTRL_GET_TIMER_VALUE(r) SCALER_GET(r, 31, 16)
+#define SCALER_TIMEOUT_CTRL_SET_TIMER_VALUE(v) SCALER_SET(v, 31, 16)
+#define SCALER_TIMEOUT_CTRL_GET_TIMER_DIV(r) SCALER_GET(r, 7, 4)
+#define SCALER_TIMEOUT_CTRL_SET_TIMER_DIV(v) SCALER_SET(v, 7, 4)
+#define SCALER_TIMEOUT_CTRL_TIMER_ENABLE (1 << 0)
+
+/* SCALER_TIMEOUT_CNT */
+#define SCALER_TIMEOUT_CTRL_GET_TIMER_COUNT(r) SCALER_GET(r, 31, 16)
+
+/* SCALER_SRC_BLEND_COLOR */
+#define SCALER_SRC_BLEND_COLOR_SEL_INV (1 << 31)
+#define SCALER_SRC_BLEND_COLOR_GET_SEL(r) SCALER_GET(r, 30, 29)
+#define SCALER_SRC_BLEND_COLOR_SET_SEL(v) SCALER_SET(v, 30, 29)
+#define SCALER_SRC_BLEND_COLOR_OP_SEL_INV (1 << 28)
+#define SCALER_SRC_BLEND_COLOR_GET_OP_SEL(r) SCALER_GET(r, 27, 24)
+#define SCALER_SRC_BLEND_COLOR_SET_OP_SEL(v) SCALER_SET(v, 27, 24)
+#define SCALER_SRC_BLEND_COLOR_GET_COLOR0(r) SCALER_GET(r, 23, 16)
+#define SCALER_SRC_BLEND_COLOR_SET_COLOR0(v) SCALER_SET(v, 23, 16)
+#define SCALER_SRC_BLEND_COLOR_GET_COLOR1(r) SCALER_GET(r, 15, 8)
+#define SCALER_SRC_BLEND_COLOR_SET_COLOR1(v) SCALER_SET(v, 15, 8)
+#define SCALER_SRC_BLEND_COLOR_GET_COLOR2(r) SCALER_GET(r, 7, 0)
+#define SCALER_SRC_BLEND_COLOR_SET_COLOR2(v) SCALER_SET(v, 7, 0)
+
+/* SCALER_SRC_BLEND_ALPHA */
+#define SCALER_SRC_BLEND_ALPHA_SEL_INV (1 << 31)
+#define SCALER_SRC_BLEND_ALPHA_GET_SEL(r) SCALER_GET(r, 30, 29)
+#define SCALER_SRC_BLEND_ALPHA_SET_SEL(v) SCALER_SET(v, 30, 29)
+#define SCALER_SRC_BLEND_ALPHA_OP_SEL_INV (1 << 28)
+#define SCALER_SRC_BLEND_ALPHA_GET_OP_SEL(r) SCALER_GET(r, 27, 24)
+#define SCALER_SRC_BLEND_ALPHA_SET_OP_SEL(v) SCALER_SET(v, 27, 24)
+#define SCALER_SRC_BLEND_ALPHA_GET_ALPHA(r) SCALER_GET(r, 7, 0)
+#define SCALER_SRC_BLEND_ALPHA_SET_ALPHA(v) SCALER_SET(v, 7, 0)
+
+/* SCALER_DST_BLEND_COLOR */
+#define SCALER_DST_BLEND_COLOR_SEL_INV (1 << 31)
+#define SCALER_DST_BLEND_COLOR_GET_SEL(r) SCALER_GET(r, 30, 29)
+#define SCALER_DST_BLEND_COLOR_SET_SEL(v) SCALER_SET(v, 30, 29)
+#define SCALER_DST_BLEND_COLOR_OP_SEL_INV (1 << 28)
+#define SCALER_DST_BLEND_COLOR_GET_OP_SEL(r) SCALER_GET(r, 27, 24)
+#define SCALER_DST_BLEND_COLOR_SET_OP_SEL(v) SCALER_SET(v, 27, 24)
+#define SCALER_DST_BLEND_COLOR_GET_COLOR0(r) SCALER_GET(r, 23, 16)
+#define SCALER_DST_BLEND_COLOR_SET_COLOR0(v) SCALER_SET(v, 23, 16)
+#define SCALER_DST_BLEND_COLOR_GET_COLOR1(r) SCALER_GET(r, 15, 8)
+#define SCALER_DST_BLEND_COLOR_SET_COLOR1(v) SCALER_SET(v, 15, 8)
+#define SCALER_DST_BLEND_COLOR_GET_COLOR2(r) SCALER_GET(r, 7, 0)
+#define SCALER_DST_BLEND_COLOR_SET_COLOR2(v) SCALER_SET(v, 7, 0)
+
+/* SCALER_DST_BLEND_ALPHA */
+#define SCALER_DST_BLEND_ALPHA_SEL_INV (1 << 31)
+#define SCALER_DST_BLEND_ALPHA_GET_SEL(r) SCALER_GET(r, 30, 29)
+#define SCALER_DST_BLEND_ALPHA_SET_SEL(v) SCALER_SET(v, 30, 29)
+#define SCALER_DST_BLEND_ALPHA_OP_SEL_INV (1 << 28)
+#define SCALER_DST_BLEND_ALPHA_GET_OP_SEL(r) SCALER_GET(r, 27, 24)
+#define SCALER_DST_BLEND_ALPHA_SET_OP_SEL(v) SCALER_SET(v, 27, 24)
+#define SCALER_DST_BLEND_ALPHA_GET_ALPHA(r) SCALER_GET(r, 7, 0)
+#define SCALER_DST_BLEND_ALPHA_SET_ALPHA(v) SCALER_SET(v, 7, 0)
+
+/* SCALER_FILL_COLOR */
+#define SCALER_FILL_COLOR_GET_ALPHA(r) SCALER_GET(r, 31, 24)
+#define SCALER_FILL_COLOR_SET_ALPHA(v) SCALER_SET(v, 31, 24)
+#define SCALER_FILL_COLOR_GET_FILL_COLOR0(r) SCALER_GET(r, 23, 16)
+#define SCALER_FILL_COLOR_SET_FILL_COLOR0(v) SCALER_SET(v, 23, 16)
+#define SCALER_FILL_COLOR_GET_FILL_COLOR1(r) SCALER_GET(r, 15, 8)
+#define SCALER_FILL_COLOR_SET_FILL_COLOR1(v) SCALER_SET(v, 15, 8)
+#define SCALER_FILL_COLOR_GET_FILL_COLOR2(r) SCALER_GET(r, 7, 0)
+#define SCALER_FILL_COLOR_SET_FILL_COLOR2(v) SCALER_SET(v, 7, 0)
+
+/* SCALER_ADDR_Q_CONFIG */
+#define SCALER_ADDR_Q_CONFIG_RST (1 << 0)
+
+/* SCALER_SRC_ADDR_Q_STATUS */
+#define SCALER_SRC_ADDR_Q_STATUS_Y_FULL (1 << 23)
+#define SCALER_SRC_ADDR_Q_STATUS_Y_EMPTY (1 << 22)
+#define SCALER_SRC_ADDR_Q_STATUS_GET_Y_WR_IDX(r) SCALER_GET(r, 21, 16)
+#define SCALER_SRC_ADDR_Q_STATUS_CB_FULL (1 << 15)
+#define SCALER_SRC_ADDR_Q_STATUS_CB_EMPTY (1 << 14)
+#define SCALER_SRC_ADDR_Q_STATUS_GET_CB_WR_IDX(r) SCALER_GET(r, 13, 8)
+#define SCALER_SRC_ADDR_Q_STATUS_CR_FULL (1 << 7)
+#define SCALER_SRC_ADDR_Q_STATUS_CR_EMPTY (1 << 6)
+#define SCALER_SRC_ADDR_Q_STATUS_GET_CR_WR_IDX(r) SCALER_GET(r, 5, 0)
+
+/* SCALER_DST_ADDR_Q_STATUS */
+#define SCALER_DST_ADDR_Q_STATUS_Y_FULL (1 << 23)
+#define SCALER_DST_ADDR_Q_STATUS_Y_EMPTY (1 << 22)
+#define SCALER_DST_ADDR_Q_STATUS_GET_Y_WR_IDX(r) SCALER_GET(r, 21, 16)
+#define SCALER_DST_ADDR_Q_STATUS_CB_FULL (1 << 15)
+#define SCALER_DST_ADDR_Q_STATUS_CB_EMPTY (1 << 14)
+#define SCALER_DST_ADDR_Q_STATUS_GET_CB_WR_IDX(r) SCALER_GET(r, 13, 8)
+#define SCALER_DST_ADDR_Q_STATUS_CR_FULL (1 << 7)
+#define SCALER_DST_ADDR_Q_STATUS_CR_EMPTY (1 << 6)
+#define SCALER_DST_ADDR_Q_STATUS_GET_CR_WR_IDX(r) SCALER_GET(r, 5, 0)
+
+/* SCALER_CRC_COLOR00_10 */
+#define SCALER_CRC_COLOR00_10_GET_00(r) SCALER_GET(r, 31, 16)
+#define SCALER_CRC_COLOR00_10_GET_10(r) SCALER_GET(r, 15, 0)
+
+/* SCALER_CRC_COLOR20_30 */
+#define SCALER_CRC_COLOR20_30_GET_20(r) SCALER_GET(r, 31, 16)
+#define SCALER_CRC_COLOR20_30_GET_30(r) SCALER_GET(r, 15, 0)
+
+/* SCALER_CRC_COLOR01_11 */
+#define SCALER_CRC_COLOR01_11_GET_01(r) SCALER_GET(r, 31, 16)
+#define SCALER_CRC_COLOR01_11_GET_11(r) SCALER_GET(r, 15, 0)
+
+/* SCALER_CRC_COLOR21_31 */
+#define SCALER_CRC_COLOR21_31_GET_21(r) SCALER_GET(r, 31, 16)
+#define SCALER_CRC_COLOR21_31_GET_31(r) SCALER_GET(r, 15, 0)
+
+#endif /* EXYNOS_REGS_SCALER_H */
diff --git a/drivers/gpu/drm/exynos/regs-vp.h b/drivers/gpu/drm/exynos/regs-vp.h
new file mode 100644
index 000000000..43c927e65
--- /dev/null
+++ b/drivers/gpu/drm/exynos/regs-vp.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *
+ * Cloned from drivers/media/video/s5p-tv/regs-vp.h
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Video processor register header file for Samsung Mixer driver
+ */
+
+#ifndef SAMSUNG_REGS_VP_H
+#define SAMSUNG_REGS_VP_H
+
+/*
+ * Register part
+ */
+
+#define VP_ENABLE 0x0000
+#define VP_SRESET 0x0004
+#define VP_SHADOW_UPDATE 0x0008
+#define VP_FIELD_ID 0x000C
+#define VP_MODE 0x0010
+#define VP_IMG_SIZE_Y 0x0014
+#define VP_IMG_SIZE_C 0x0018
+#define VP_PER_RATE_CTRL 0x001C
+#define VP_TOP_Y_PTR 0x0028
+#define VP_BOT_Y_PTR 0x002C
+#define VP_TOP_C_PTR 0x0030
+#define VP_BOT_C_PTR 0x0034
+#define VP_ENDIAN_MODE 0x03CC
+#define VP_SRC_H_POSITION 0x0044
+#define VP_SRC_V_POSITION 0x0048
+#define VP_SRC_WIDTH 0x004C
+#define VP_SRC_HEIGHT 0x0050
+#define VP_DST_H_POSITION 0x0054
+#define VP_DST_V_POSITION 0x0058
+#define VP_DST_WIDTH 0x005C
+#define VP_DST_HEIGHT 0x0060
+#define VP_H_RATIO 0x0064
+#define VP_V_RATIO 0x0068
+#define VP_POLY8_Y0_LL 0x006C
+#define VP_POLY4_Y0_LL 0x00EC
+#define VP_POLY4_C0_LL 0x012C
+
+/*
+ * Bit definition part
+ */
+
+/* generates mask for range of bits */
+
+#define VP_MASK(high_bit, low_bit) \
+ (((2 << ((high_bit) - (low_bit))) - 1) << (low_bit))
+
+#define VP_MASK_VAL(val, high_bit, low_bit) \
+ (((val) << (low_bit)) & VP_MASK(high_bit, low_bit))
+
+ /* VP_ENABLE */
+#define VP_ENABLE_ON (1 << 0)
+
+/* VP_SRESET */
+#define VP_SRESET_PROCESSING (1 << 0)
+
+/* VP_SHADOW_UPDATE */
+#define VP_SHADOW_UPDATE_ENABLE (1 << 0)
+
+/* VP_MODE */
+#define VP_MODE_NV12 (0 << 6)
+#define VP_MODE_NV21 (1 << 6)
+#define VP_MODE_LINE_SKIP (1 << 5)
+#define VP_MODE_MEM_LINEAR (0 << 4)
+#define VP_MODE_MEM_TILED (1 << 4)
+#define VP_MODE_FMT_MASK (5 << 4)
+#define VP_MODE_FIELD_ID_AUTO_TOGGLING (1 << 2)
+#define VP_MODE_2D_IPC (1 << 1)
+
+/* VP_IMG_SIZE_Y */
+/* VP_IMG_SIZE_C */
+#define VP_IMG_HSIZE(x) VP_MASK_VAL(x, 29, 16)
+#define VP_IMG_VSIZE(x) VP_MASK_VAL(x, 13, 0)
+
+/* VP_SRC_H_POSITION */
+#define VP_SRC_H_POSITION_VAL(x) VP_MASK_VAL(x, 14, 4)
+
+/* VP_ENDIAN_MODE */
+#define VP_ENDIAN_MODE_LITTLE (1 << 0)
+
+#endif /* SAMSUNG_REGS_VP_H */