summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/fsl-dcu
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/fsl-dcu
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/fsl-dcu')
-rw-r--r--drivers/gpu/drm/fsl-dcu/Kconfig15
-rw-r--r--drivers/gpu/drm/fsl-dcu/Makefile8
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c192
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h15
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c390
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h201
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c53
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h28
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c231
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h14
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c158
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_tcon.c107
-rw-r--r--drivers/gpu/drm/fsl-dcu/fsl_tcon.h29
13 files changed, 1441 insertions, 0 deletions
diff --git a/drivers/gpu/drm/fsl-dcu/Kconfig b/drivers/gpu/drm/fsl-dcu/Kconfig
new file mode 100644
index 000000000..0e0f910ce
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_FSL_DCU
+ tristate "DRM Support for Freescale DCU"
+ depends on DRM && OF && ARM && COMMON_CLK
+ select BACKLIGHT_CLASS_DEVICE
+ select DRM_CLIENT_SELECTION
+ select DRM_GEM_DMA_HELPER
+ select DRM_KMS_HELPER
+ select DRM_PANEL
+ select REGMAP_MMIO
+ select VIDEOMODE_HELPERS
+ select MFD_SYSCON if SOC_LS1021A
+ help
+ Choose this option if you have an Freescale DCU chipset.
+ If M is selected the module will be called fsl-dcu-drm.
diff --git a/drivers/gpu/drm/fsl-dcu/Makefile b/drivers/gpu/drm/fsl-dcu/Makefile
new file mode 100644
index 000000000..b55c4482d
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+fsl-dcu-drm-y := fsl_dcu_drm_drv.o \
+ fsl_dcu_drm_kms.o \
+ fsl_dcu_drm_rgb.o \
+ fsl_dcu_drm_plane.o \
+ fsl_dcu_drm_crtc.o \
+ fsl_tcon.o
+obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu-drm.o
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c
new file mode 100644
index 000000000..9ef16f5eb
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#include <video/videomode.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "fsl_dcu_drm_crtc.h"
+#include "fsl_dcu_drm_drv.h"
+#include "fsl_dcu_drm_plane.h"
+
+static void fsl_dcu_drm_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ struct drm_pending_vblank_event *event = crtc->state->event;
+
+ regmap_write(fsl_dev->regmap,
+ DCU_UPDATE_MODE, DCU_UPDATE_MODE_READREG);
+
+ if (event) {
+ crtc->state->event = NULL;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (drm_crtc_vblank_get(crtc) == 0)
+ drm_crtc_arm_vblank_event(crtc, event);
+ else
+ drm_crtc_send_vblank_event(crtc, event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ }
+}
+
+static void fsl_dcu_drm_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
+ crtc);
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+
+ /* always disable planes on the CRTC */
+ drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, true);
+
+ drm_crtc_vblank_off(crtc);
+
+ regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE,
+ DCU_MODE_DCU_MODE_MASK,
+ DCU_MODE_DCU_MODE(DCU_MODE_OFF));
+ regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE,
+ DCU_UPDATE_MODE_READREG);
+ clk_disable_unprepare(fsl_dev->pix_clk);
+}
+
+static void fsl_dcu_drm_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+
+ clk_prepare_enable(fsl_dev->pix_clk);
+ regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE,
+ DCU_MODE_DCU_MODE_MASK,
+ DCU_MODE_DCU_MODE(DCU_MODE_NORMAL));
+ regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE,
+ DCU_UPDATE_MODE_READREG);
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ struct drm_connector *con = &fsl_dev->connector.base;
+ struct drm_display_mode *mode = &crtc->state->mode;
+ unsigned int pol = 0;
+ struct videomode vm;
+
+ clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000);
+
+ drm_display_mode_to_videomode(mode, &vm);
+
+ /* INV_PXCK as default (most display sample data on rising edge) */
+ if (!(con->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE))
+ pol |= DCU_SYN_POL_INV_PXCK;
+
+ if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW)
+ pol |= DCU_SYN_POL_INV_HS_LOW;
+
+ if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW)
+ pol |= DCU_SYN_POL_INV_VS_LOW;
+
+ regmap_write(fsl_dev->regmap, DCU_HSYN_PARA,
+ DCU_HSYN_PARA_BP(vm.hback_porch) |
+ DCU_HSYN_PARA_PW(vm.hsync_len) |
+ DCU_HSYN_PARA_FP(vm.hfront_porch));
+ regmap_write(fsl_dev->regmap, DCU_VSYN_PARA,
+ DCU_VSYN_PARA_BP(vm.vback_porch) |
+ DCU_VSYN_PARA_PW(vm.vsync_len) |
+ DCU_VSYN_PARA_FP(vm.vfront_porch));
+ regmap_write(fsl_dev->regmap, DCU_DISP_SIZE,
+ DCU_DISP_SIZE_DELTA_Y(vm.vactive) |
+ DCU_DISP_SIZE_DELTA_X(vm.hactive));
+ regmap_write(fsl_dev->regmap, DCU_SYN_POL, pol);
+ regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) |
+ DCU_BGND_G(0) | DCU_BGND_B(0));
+ regmap_write(fsl_dev->regmap, DCU_DCU_MODE,
+ DCU_MODE_BLEND_ITER(1) | DCU_MODE_RASTER_EN);
+ regmap_write(fsl_dev->regmap, DCU_THRESHOLD,
+ DCU_THRESHOLD_LS_BF_VS(BF_VS_VAL) |
+ DCU_THRESHOLD_OUT_BUF_HIGH(BUF_MAX_VAL) |
+ DCU_THRESHOLD_OUT_BUF_LOW(BUF_MIN_VAL));
+ return;
+}
+
+static const struct drm_crtc_helper_funcs fsl_dcu_drm_crtc_helper_funcs = {
+ .atomic_disable = fsl_dcu_drm_crtc_atomic_disable,
+ .atomic_flush = fsl_dcu_drm_crtc_atomic_flush,
+ .atomic_enable = fsl_dcu_drm_crtc_atomic_enable,
+ .mode_set_nofb = fsl_dcu_drm_crtc_mode_set_nofb,
+};
+
+static int fsl_dcu_drm_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ unsigned int value;
+
+ regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value);
+ value &= ~DCU_INT_MASK_VBLANK;
+ regmap_write(fsl_dev->regmap, DCU_INT_MASK, value);
+
+ return 0;
+}
+
+static void fsl_dcu_drm_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ unsigned int value;
+
+ regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value);
+ value |= DCU_INT_MASK_VBLANK;
+ regmap_write(fsl_dev->regmap, DCU_INT_MASK, value);
+}
+
+static const struct drm_crtc_funcs fsl_dcu_drm_crtc_funcs = {
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .destroy = drm_crtc_cleanup,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .set_config = drm_atomic_helper_set_config,
+ .enable_vblank = fsl_dcu_drm_crtc_enable_vblank,
+ .disable_vblank = fsl_dcu_drm_crtc_disable_vblank,
+};
+
+int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev)
+{
+ struct drm_plane *primary;
+ struct drm_crtc *crtc = &fsl_dev->crtc;
+ int ret;
+
+ fsl_dcu_drm_init_planes(fsl_dev->drm);
+
+ primary = fsl_dcu_drm_primary_create_plane(fsl_dev->drm);
+ if (!primary)
+ return -ENOMEM;
+
+ ret = drm_crtc_init_with_planes(fsl_dev->drm, crtc, primary, NULL,
+ &fsl_dcu_drm_crtc_funcs, NULL);
+ if (ret) {
+ primary->funcs->destroy(primary);
+ return ret;
+ }
+
+ drm_crtc_helper_add(crtc, &fsl_dcu_drm_crtc_helper_funcs);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h
new file mode 100644
index 000000000..df4b28073
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#ifndef __FSL_DCU_DRM_CRTC_H__
+#define __FSL_DCU_DRM_CRTC_H__
+
+struct fsl_dcu_drm_device;
+
+int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev);
+
+#endif /* __FSL_DCU_DRM_CRTC_H__ */
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c
new file mode 100644
index 000000000..3bbfc1b56
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_module.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "fsl_dcu_drm_crtc.h"
+#include "fsl_dcu_drm_drv.h"
+#include "fsl_tcon.h"
+
+static int legacyfb_depth = 24;
+module_param(legacyfb_depth, int, 0444);
+
+static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == DCU_INT_STATUS || reg == DCU_UPDATE_MODE)
+ return true;
+
+ return false;
+}
+
+static const struct regmap_config fsl_dcu_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .volatile_reg = fsl_dcu_drm_is_volatile_reg,
+};
+
+static void fsl_dcu_irq_reset(struct drm_device *dev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+
+ regmap_write(fsl_dev->regmap, DCU_INT_STATUS, ~0);
+ regmap_write(fsl_dev->regmap, DCU_INT_MASK, ~0);
+}
+
+static irqreturn_t fsl_dcu_drm_irq(int irq, void *arg)
+{
+ struct drm_device *dev = arg;
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ unsigned int int_status;
+ int ret;
+
+ ret = regmap_read(fsl_dev->regmap, DCU_INT_STATUS, &int_status);
+ if (ret) {
+ dev_err(dev->dev, "read DCU_INT_STATUS failed\n");
+ return IRQ_NONE;
+ }
+
+ if (int_status & DCU_INT_STATUS_VBLANK)
+ drm_handle_vblank(dev, 0);
+
+ regmap_write(fsl_dev->regmap, DCU_INT_STATUS, int_status);
+
+ return IRQ_HANDLED;
+}
+
+static int fsl_dcu_irq_install(struct drm_device *dev, unsigned int irq)
+{
+ if (irq == IRQ_NOTCONNECTED)
+ return -ENOTCONN;
+
+ fsl_dcu_irq_reset(dev);
+
+ return request_irq(irq, fsl_dcu_drm_irq, 0, dev->driver->name, dev);
+}
+
+static void fsl_dcu_irq_uninstall(struct drm_device *dev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+
+ fsl_dcu_irq_reset(dev);
+ free_irq(fsl_dev->irq, dev);
+}
+
+static int fsl_dcu_load(struct drm_device *dev, unsigned long flags)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ struct regmap *scfg;
+ int ret;
+
+ ret = fsl_dcu_drm_modeset_init(fsl_dev);
+ if (ret < 0)
+ return dev_err_probe(dev->dev, ret, "failed to initialize mode setting\n");
+
+ scfg = syscon_regmap_lookup_by_compatible("fsl,ls1021a-scfg");
+ if (PTR_ERR(scfg) != -ENODEV) {
+ /*
+ * For simplicity, enable the PIXCLK unconditionally,
+ * resulting in increased power consumption. Disabling
+ * the clock in PM or on unload could be implemented as
+ * a future improvement.
+ */
+ ret = regmap_update_bits(scfg, SCFG_PIXCLKCR, SCFG_PIXCLKCR_PXCEN,
+ SCFG_PIXCLKCR_PXCEN);
+ if (ret < 0)
+ return dev_err_probe(dev->dev, ret, "failed to enable pixclk\n");
+ }
+
+ ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to initialize vblank\n");
+ goto done_vblank;
+ }
+
+ ret = fsl_dcu_irq_install(dev, fsl_dev->irq);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to install IRQ handler\n");
+ goto done_irq;
+ }
+
+ if (legacyfb_depth != 16 && legacyfb_depth != 24 &&
+ legacyfb_depth != 32) {
+ dev_warn(dev->dev,
+ "Invalid legacyfb_depth. Defaulting to 24bpp\n");
+ legacyfb_depth = 24;
+ }
+
+ return 0;
+done_irq:
+ drm_kms_helper_poll_fini(dev);
+
+ drm_mode_config_cleanup(dev);
+done_vblank:
+ dev->dev_private = NULL;
+
+ return ret;
+}
+
+static void fsl_dcu_unload(struct drm_device *dev)
+{
+ drm_atomic_helper_shutdown(dev);
+ drm_kms_helper_poll_fini(dev);
+
+ drm_mode_config_cleanup(dev);
+ fsl_dcu_irq_uninstall(dev);
+
+ dev->dev_private = NULL;
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(fsl_dcu_drm_fops);
+
+static const struct drm_driver fsl_dcu_drm_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+ .load = fsl_dcu_load,
+ .unload = fsl_dcu_unload,
+ DRM_GEM_DMA_DRIVER_OPS,
+ DRM_FBDEV_DMA_DRIVER_OPS,
+ .fops = &fsl_dcu_drm_fops,
+ .name = "fsl-dcu-drm",
+ .desc = "Freescale DCU DRM",
+ .major = 1,
+ .minor = 1,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_dcu_drm_pm_suspend(struct device *dev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev);
+ int ret;
+
+ if (!fsl_dev)
+ return 0;
+
+ disable_irq(fsl_dev->irq);
+
+ ret = drm_mode_config_helper_suspend(fsl_dev->drm);
+ if (ret) {
+ enable_irq(fsl_dev->irq);
+ return ret;
+ }
+
+ clk_disable_unprepare(fsl_dev->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_drm_pm_resume(struct device *dev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev);
+ int ret;
+
+ if (!fsl_dev)
+ return 0;
+
+ ret = clk_prepare_enable(fsl_dev->clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable dcu clk\n");
+ return ret;
+ }
+
+ if (fsl_dev->tcon)
+ fsl_tcon_bypass_enable(fsl_dev->tcon);
+ fsl_dcu_drm_init_planes(fsl_dev->drm);
+ enable_irq(fsl_dev->irq);
+
+ drm_mode_config_helper_resume(fsl_dev->drm);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_dcu_drm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_drm_pm_suspend, fsl_dcu_drm_pm_resume)
+};
+
+static const struct fsl_dcu_soc_data fsl_dcu_ls1021a_data = {
+ .name = "ls1021a",
+ .total_layer = 16,
+ .max_layer = 4,
+ .layer_regs = LS1021A_LAYER_REG_NUM,
+};
+
+static const struct fsl_dcu_soc_data fsl_dcu_vf610_data = {
+ .name = "vf610",
+ .total_layer = 64,
+ .max_layer = 6,
+ .layer_regs = VF610_LAYER_REG_NUM,
+};
+
+static const struct of_device_id fsl_dcu_of_match[] = {
+ {
+ .compatible = "fsl,ls1021a-dcu",
+ .data = &fsl_dcu_ls1021a_data,
+ }, {
+ .compatible = "fsl,vf610-dcu",
+ .data = &fsl_dcu_vf610_data,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, fsl_dcu_of_match);
+
+static int fsl_dcu_drm_probe(struct platform_device *pdev)
+{
+ struct fsl_dcu_drm_device *fsl_dev;
+ struct drm_device *drm;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ struct clk *pix_clk_in;
+ char pix_clk_name[32];
+ const char *pix_clk_in_name;
+ const struct of_device_id *id;
+ int ret;
+ u8 div_ratio_shift = 0;
+
+ fsl_dev = devm_kzalloc(dev, sizeof(*fsl_dev), GFP_KERNEL);
+ if (!fsl_dev)
+ return -ENOMEM;
+
+ id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node);
+ if (!id)
+ return -ENODEV;
+ fsl_dev->soc = id->data;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base)) {
+ ret = PTR_ERR(base);
+ return ret;
+ }
+
+ fsl_dev->irq = platform_get_irq(pdev, 0);
+ if (fsl_dev->irq < 0)
+ return fsl_dev->irq;
+
+ fsl_dev->regmap = devm_regmap_init_mmio(dev, base,
+ &fsl_dcu_regmap_config);
+ if (IS_ERR(fsl_dev->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(fsl_dev->regmap);
+ }
+
+ fsl_dev->clk = devm_clk_get(dev, "dcu");
+ if (IS_ERR(fsl_dev->clk)) {
+ dev_err(dev, "failed to get dcu clock\n");
+ return PTR_ERR(fsl_dev->clk);
+ }
+ ret = clk_prepare_enable(fsl_dev->clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable dcu clk\n");
+ return ret;
+ }
+
+ pix_clk_in = devm_clk_get(dev, "pix");
+ if (IS_ERR(pix_clk_in)) {
+ /* legancy binding, use dcu clock as pixel clock input */
+ pix_clk_in = fsl_dev->clk;
+ }
+
+ if (of_property_read_bool(dev->of_node, "big-endian"))
+ div_ratio_shift = 24;
+
+ pix_clk_in_name = __clk_get_name(pix_clk_in);
+ snprintf(pix_clk_name, sizeof(pix_clk_name), "%s_pix", pix_clk_in_name);
+ fsl_dev->pix_clk = clk_register_divider(dev, pix_clk_name,
+ pix_clk_in_name, 0, base + DCU_DIV_RATIO,
+ div_ratio_shift, 8, CLK_DIVIDER_ROUND_CLOSEST, NULL);
+ if (IS_ERR(fsl_dev->pix_clk)) {
+ dev_err(dev, "failed to register pix clk\n");
+ ret = PTR_ERR(fsl_dev->pix_clk);
+ goto disable_clk;
+ }
+
+ fsl_dev->tcon = fsl_tcon_init(dev);
+
+ drm = drm_dev_alloc(&fsl_dcu_drm_driver, dev);
+ if (IS_ERR(drm)) {
+ ret = PTR_ERR(drm);
+ goto unregister_pix_clk;
+ }
+
+ fsl_dev->dev = dev;
+ fsl_dev->drm = drm;
+ fsl_dev->np = dev->of_node;
+ drm->dev_private = fsl_dev;
+ dev_set_drvdata(dev, fsl_dev);
+
+ ret = drm_dev_register(drm, 0);
+ if (ret < 0)
+ goto put;
+
+ drm_client_setup_with_color_mode(drm, legacyfb_depth);
+
+ return 0;
+
+put:
+ drm_dev_put(drm);
+unregister_pix_clk:
+ clk_unregister(fsl_dev->pix_clk);
+disable_clk:
+ clk_disable_unprepare(fsl_dev->clk);
+ return ret;
+}
+
+static void fsl_dcu_drm_remove(struct platform_device *pdev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev);
+
+ drm_dev_unregister(fsl_dev->drm);
+ drm_dev_put(fsl_dev->drm);
+ clk_disable_unprepare(fsl_dev->clk);
+ clk_unregister(fsl_dev->pix_clk);
+}
+
+static void fsl_dcu_drm_shutdown(struct platform_device *pdev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev);
+
+ drm_atomic_helper_shutdown(fsl_dev->drm);
+}
+
+static struct platform_driver fsl_dcu_drm_platform_driver = {
+ .probe = fsl_dcu_drm_probe,
+ .remove = fsl_dcu_drm_remove,
+ .shutdown = fsl_dcu_drm_shutdown,
+ .driver = {
+ .name = "fsl-dcu",
+ .pm = &fsl_dcu_drm_pm_ops,
+ .of_match_table = fsl_dcu_of_match,
+ },
+};
+
+drm_module_platform_driver(fsl_dcu_drm_platform_driver);
+
+MODULE_DESCRIPTION("Freescale DCU DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h
new file mode 100644
index 000000000..566396013
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#ifndef __FSL_DCU_DRM_DRV_H__
+#define __FSL_DCU_DRM_DRV_H__
+
+#include <drm/drm_encoder.h>
+
+#include "fsl_dcu_drm_crtc.h"
+#include "fsl_dcu_drm_output.h"
+#include "fsl_dcu_drm_plane.h"
+
+#define DCU_DCU_MODE 0x0010
+#define DCU_MODE_BLEND_ITER(x) ((x) << 20)
+#define DCU_MODE_RASTER_EN BIT(14)
+#define DCU_MODE_DCU_MODE(x) (x)
+#define DCU_MODE_DCU_MODE_MASK 0x03
+#define DCU_MODE_OFF 0
+#define DCU_MODE_NORMAL 1
+#define DCU_MODE_TEST 2
+#define DCU_MODE_COLORBAR 3
+
+#define DCU_BGND 0x0014
+#define DCU_BGND_R(x) ((x) << 16)
+#define DCU_BGND_G(x) ((x) << 8)
+#define DCU_BGND_B(x) (x)
+
+#define DCU_DISP_SIZE 0x0018
+#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16)
+/*Regisiter value 1/16 of horizontal resolution*/
+#define DCU_DISP_SIZE_DELTA_X(x) ((x) >> 4)
+
+#define DCU_HSYN_PARA 0x001c
+#define DCU_HSYN_PARA_BP(x) ((x) << 22)
+#define DCU_HSYN_PARA_PW(x) ((x) << 11)
+#define DCU_HSYN_PARA_FP(x) (x)
+
+#define DCU_VSYN_PARA 0x0020
+#define DCU_VSYN_PARA_BP(x) ((x) << 22)
+#define DCU_VSYN_PARA_PW(x) ((x) << 11)
+#define DCU_VSYN_PARA_FP(x) (x)
+
+#define DCU_SYN_POL 0x0024
+#define DCU_SYN_POL_INV_PXCK BIT(6)
+#define DCU_SYN_POL_NEG BIT(5)
+#define DCU_SYN_POL_INV_VS_LOW BIT(1)
+#define DCU_SYN_POL_INV_HS_LOW BIT(0)
+
+#define DCU_THRESHOLD 0x0028
+#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16)
+#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8)
+#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x)
+#define BF_VS_VAL 0x03
+#define BUF_MAX_VAL 0x78
+#define BUF_MIN_VAL 0x0a
+
+#define DCU_INT_STATUS 0x002C
+#define DCU_INT_STATUS_VSYNC BIT(0)
+#define DCU_INT_STATUS_UNDRUN BIT(1)
+#define DCU_INT_STATUS_LSBFVS BIT(2)
+#define DCU_INT_STATUS_VBLANK BIT(3)
+#define DCU_INT_STATUS_CRCREADY BIT(4)
+#define DCU_INT_STATUS_CRCOVERFLOW BIT(5)
+#define DCU_INT_STATUS_P1FIFOLO BIT(6)
+#define DCU_INT_STATUS_P1FIFOHI BIT(7)
+#define DCU_INT_STATUS_P2FIFOLO BIT(8)
+#define DCU_INT_STATUS_P2FIFOHI BIT(9)
+#define DCU_INT_STATUS_PROGEND BIT(10)
+#define DCU_INT_STATUS_IPMERROR BIT(11)
+#define DCU_INT_STATUS_LYRTRANS BIT(12)
+#define DCU_INT_STATUS_DMATRANS BIT(14)
+#define DCU_INT_STATUS_P3FIFOLO BIT(16)
+#define DCU_INT_STATUS_P3FIFOHI BIT(17)
+#define DCU_INT_STATUS_P4FIFOLO BIT(18)
+#define DCU_INT_STATUS_P4FIFOHI BIT(19)
+#define DCU_INT_STATUS_P1EMPTY BIT(26)
+#define DCU_INT_STATUS_P2EMPTY BIT(27)
+#define DCU_INT_STATUS_P3EMPTY BIT(28)
+#define DCU_INT_STATUS_P4EMPTY BIT(29)
+
+#define DCU_INT_MASK 0x0030
+#define DCU_INT_MASK_VSYNC BIT(0)
+#define DCU_INT_MASK_UNDRUN BIT(1)
+#define DCU_INT_MASK_LSBFVS BIT(2)
+#define DCU_INT_MASK_VBLANK BIT(3)
+#define DCU_INT_MASK_CRCREADY BIT(4)
+#define DCU_INT_MASK_CRCOVERFLOW BIT(5)
+#define DCU_INT_MASK_P1FIFOLO BIT(6)
+#define DCU_INT_MASK_P1FIFOHI BIT(7)
+#define DCU_INT_MASK_P2FIFOLO BIT(8)
+#define DCU_INT_MASK_P2FIFOHI BIT(9)
+#define DCU_INT_MASK_PROGEND BIT(10)
+#define DCU_INT_MASK_IPMERROR BIT(11)
+#define DCU_INT_MASK_LYRTRANS BIT(12)
+#define DCU_INT_MASK_DMATRANS BIT(14)
+#define DCU_INT_MASK_P3FIFOLO BIT(16)
+#define DCU_INT_MASK_P3FIFOHI BIT(17)
+#define DCU_INT_MASK_P4FIFOLO BIT(18)
+#define DCU_INT_MASK_P4FIFOHI BIT(19)
+#define DCU_INT_MASK_P1EMPTY BIT(26)
+#define DCU_INT_MASK_P2EMPTY BIT(27)
+#define DCU_INT_MASK_P3EMPTY BIT(28)
+#define DCU_INT_MASK_P4EMPTY BIT(29)
+
+#define DCU_DIV_RATIO 0x0054
+
+#define DCU_UPDATE_MODE 0x00cc
+#define DCU_UPDATE_MODE_MODE BIT(31)
+#define DCU_UPDATE_MODE_READREG BIT(30)
+
+#define DCU_DCFB_MAX 0x300
+
+#define DCU_CTRLDESCLN(layer, reg) (0x200 + (reg - 1) * 4 + (layer) * 0x40)
+
+#define DCU_LAYER_HEIGHT(x) ((x) << 16)
+#define DCU_LAYER_WIDTH(x) (x)
+
+#define DCU_LAYER_POSY(x) ((x) << 16)
+#define DCU_LAYER_POSX(x) (x)
+
+#define DCU_LAYER_EN BIT(31)
+#define DCU_LAYER_TILE_EN BIT(30)
+#define DCU_LAYER_DATA_SEL_CLUT BIT(29)
+#define DCU_LAYER_SAFETY_EN BIT(28)
+#define DCU_LAYER_TRANS(x) ((x) << 20)
+#define DCU_LAYER_BPP(x) ((x) << 16)
+#define DCU_LAYER_RLE_EN BIT(15)
+#define DCU_LAYER_LUOFFS(x) ((x) << 4)
+#define DCU_LAYER_BB_ON BIT(2)
+#define DCU_LAYER_AB_NONE 0
+#define DCU_LAYER_AB_CHROMA_KEYING 1
+#define DCU_LAYER_AB_WHOLE_FRAME 2
+
+#define DCU_LAYER_CKMAX_R(x) ((x) << 16)
+#define DCU_LAYER_CKMAX_G(x) ((x) << 8)
+#define DCU_LAYER_CKMAX_B(x) (x)
+
+#define DCU_LAYER_CKMIN_R(x) ((x) << 16)
+#define DCU_LAYER_CKMIN_G(x) ((x) << 8)
+#define DCU_LAYER_CKMIN_B(x) (x)
+
+#define DCU_LAYER_TILE_VER(x) ((x) << 16)
+#define DCU_LAYER_TILE_HOR(x) (x)
+
+#define DCU_LAYER_FG_FCOLOR(x) (x)
+
+#define DCU_LAYER_BG_BCOLOR(x) (x)
+
+#define DCU_LAYER_POST_SKIP(x) ((x) << 16)
+#define DCU_LAYER_PRE_SKIP(x) (x)
+
+#define FSL_DCU_RGB565 4
+#define FSL_DCU_RGB888 5
+#define FSL_DCU_ARGB8888 6
+#define FSL_DCU_ARGB1555 11
+#define FSL_DCU_ARGB4444 12
+#define FSL_DCU_YUV422 14
+
+#define SCFG_PIXCLKCR 0x28
+#define SCFG_PIXCLKCR_PXCEN BIT(31)
+
+#define VF610_LAYER_REG_NUM 9
+#define LS1021A_LAYER_REG_NUM 10
+
+struct clk;
+struct device;
+struct drm_device;
+
+struct fsl_dcu_soc_data {
+ const char *name;
+ /*total layer number*/
+ unsigned int total_layer;
+ /*max layer number DCU supported*/
+ unsigned int max_layer;
+ unsigned int layer_regs;
+};
+
+struct fsl_dcu_drm_device {
+ struct device *dev;
+ struct device_node *np;
+ struct regmap *regmap;
+ int irq;
+ struct clk *clk;
+ struct clk *pix_clk;
+ struct fsl_tcon *tcon;
+ /*protects hardware register*/
+ spinlock_t irq_lock;
+ struct drm_device *drm;
+ struct drm_crtc crtc;
+ struct drm_encoder encoder;
+ struct fsl_dcu_drm_connector connector;
+ const struct fsl_dcu_soc_data *soc;
+};
+
+int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev);
+
+#endif /* __FSL_DCU_DRM_DRV_H__ */
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c
new file mode 100644
index 000000000..5b4700073
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_probe_helper.h>
+
+#include "fsl_dcu_drm_crtc.h"
+#include "fsl_dcu_drm_drv.h"
+
+static const struct drm_mode_config_funcs fsl_dcu_drm_mode_config_funcs = {
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+ .fb_create = drm_gem_fb_create,
+};
+
+int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev)
+{
+ int ret;
+
+ drm_mode_config_init(fsl_dev->drm);
+
+ fsl_dev->drm->mode_config.min_width = 0;
+ fsl_dev->drm->mode_config.min_height = 0;
+ fsl_dev->drm->mode_config.max_width = 2031;
+ fsl_dev->drm->mode_config.max_height = 2047;
+ fsl_dev->drm->mode_config.funcs = &fsl_dcu_drm_mode_config_funcs;
+
+ ret = fsl_dcu_drm_crtc_create(fsl_dev);
+ if (ret)
+ goto err;
+
+ ret = fsl_dcu_drm_encoder_create(fsl_dev, &fsl_dev->crtc);
+ if (ret)
+ goto err;
+
+ ret = fsl_dcu_create_outputs(fsl_dev);
+ if (ret)
+ goto err;
+
+ drm_mode_config_reset(fsl_dev->drm);
+ drm_kms_helper_poll_init(fsl_dev->drm);
+
+ return 0;
+
+err:
+ drm_mode_config_cleanup(fsl_dev->drm);
+ return ret;
+}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h
new file mode 100644
index 000000000..8d7dedb95
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#ifndef __FSL_DCU_DRM_CONNECTOR_H__
+#define __FSL_DCU_DRM_CONNECTOR_H__
+
+struct fsl_dcu_drm_connector {
+ struct drm_connector base;
+ struct drm_encoder *encoder;
+ struct drm_panel *panel;
+};
+
+static inline struct fsl_dcu_drm_connector *
+to_fsl_dcu_connector(struct drm_connector *con)
+{
+ return con ? container_of(con, struct fsl_dcu_drm_connector, base)
+ : NULL;
+}
+
+int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev,
+ struct drm_crtc *crtc);
+int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev);
+
+#endif /* __FSL_DCU_DRM_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c
new file mode 100644
index 000000000..b34f880c1
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "fsl_dcu_drm_drv.h"
+#include "fsl_dcu_drm_plane.h"
+
+static int fsl_dcu_drm_plane_index(struct drm_plane *plane)
+{
+ struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private;
+ unsigned int total_layer = fsl_dev->soc->total_layer;
+ unsigned int index;
+
+ index = drm_plane_index(plane);
+ if (index < total_layer)
+ return total_layer - index - 1;
+
+ dev_err(fsl_dev->dev, "No more layer left\n");
+ return -EINVAL;
+}
+
+static int fsl_dcu_drm_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 drm_framebuffer *fb = new_plane_state->fb;
+
+ if (!new_plane_state->fb || !new_plane_state->crtc)
+ return 0;
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_ARGB4444:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_YUV422:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void fsl_dcu_drm_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private;
+ unsigned int value;
+ int index;
+
+ index = fsl_dcu_drm_plane_index(plane);
+ if (index < 0)
+ return;
+
+ regmap_read(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), &value);
+ value &= ~DCU_LAYER_EN;
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), value);
+}
+
+static void fsl_dcu_drm_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+
+{
+ struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private;
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = plane->state->fb;
+ struct drm_gem_dma_object *gem;
+ unsigned int alpha = DCU_LAYER_AB_NONE, bpp;
+ int index;
+
+ if (!fb)
+ return;
+
+ index = fsl_dcu_drm_plane_index(plane);
+ if (index < 0)
+ return;
+
+ gem = drm_fb_dma_get_gem_obj(fb, 0);
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ bpp = FSL_DCU_RGB565;
+ break;
+ case DRM_FORMAT_RGB888:
+ bpp = FSL_DCU_RGB888;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ alpha = DCU_LAYER_AB_WHOLE_FRAME;
+ fallthrough;
+ case DRM_FORMAT_XRGB8888:
+ bpp = FSL_DCU_ARGB8888;
+ break;
+ case DRM_FORMAT_ARGB4444:
+ alpha = DCU_LAYER_AB_WHOLE_FRAME;
+ fallthrough;
+ case DRM_FORMAT_XRGB4444:
+ bpp = FSL_DCU_ARGB4444;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ alpha = DCU_LAYER_AB_WHOLE_FRAME;
+ fallthrough;
+ case DRM_FORMAT_XRGB1555:
+ bpp = FSL_DCU_ARGB1555;
+ break;
+ case DRM_FORMAT_YUV422:
+ bpp = FSL_DCU_YUV422;
+ break;
+ default:
+ return;
+ }
+
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 1),
+ DCU_LAYER_HEIGHT(new_state->crtc_h) |
+ DCU_LAYER_WIDTH(new_state->crtc_w));
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 2),
+ DCU_LAYER_POSY(new_state->crtc_y) |
+ DCU_LAYER_POSX(new_state->crtc_x));
+ regmap_write(fsl_dev->regmap,
+ DCU_CTRLDESCLN(index, 3), gem->dma_addr);
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4),
+ DCU_LAYER_EN |
+ DCU_LAYER_TRANS(0xff) |
+ DCU_LAYER_BPP(bpp) |
+ alpha);
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 5),
+ DCU_LAYER_CKMAX_R(0xFF) |
+ DCU_LAYER_CKMAX_G(0xFF) |
+ DCU_LAYER_CKMAX_B(0xFF));
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 6),
+ DCU_LAYER_CKMIN_R(0) |
+ DCU_LAYER_CKMIN_G(0) |
+ DCU_LAYER_CKMIN_B(0));
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 7), 0);
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 8),
+ DCU_LAYER_FG_FCOLOR(0));
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 9),
+ DCU_LAYER_BG_BCOLOR(0));
+
+ if (!strcmp(fsl_dev->soc->name, "ls1021a")) {
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 10),
+ DCU_LAYER_POST_SKIP(0) |
+ DCU_LAYER_PRE_SKIP(0));
+ }
+
+ return;
+}
+
+static const struct drm_plane_helper_funcs fsl_dcu_drm_plane_helper_funcs = {
+ .atomic_check = fsl_dcu_drm_plane_atomic_check,
+ .atomic_disable = fsl_dcu_drm_plane_atomic_disable,
+ .atomic_update = fsl_dcu_drm_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs fsl_dcu_drm_plane_funcs = {
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+ .destroy = drm_plane_helper_destroy,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .update_plane = drm_atomic_helper_update_plane,
+};
+
+static const u32 fsl_dcu_drm_plane_formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_YUV422,
+};
+
+void fsl_dcu_drm_init_planes(struct drm_device *dev)
+{
+ struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
+ int i, j;
+
+ for (i = 0; i < fsl_dev->soc->total_layer; i++) {
+ for (j = 1; j <= fsl_dev->soc->layer_regs; j++)
+ regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(i, j), 0);
+ }
+}
+
+struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev)
+{
+ struct drm_plane *primary;
+ int ret;
+
+ primary = kzalloc_obj(*primary);
+ if (!primary) {
+ DRM_DEBUG_KMS("Failed to allocate primary plane\n");
+ return NULL;
+ }
+
+ /* possible_crtc's will be filled in later by crtc_init */
+ ret = drm_universal_plane_init(dev, primary, 0,
+ &fsl_dcu_drm_plane_funcs,
+ fsl_dcu_drm_plane_formats,
+ ARRAY_SIZE(fsl_dcu_drm_plane_formats),
+ NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret) {
+ kfree(primary);
+ primary = NULL;
+ }
+ drm_plane_helper_add(primary, &fsl_dcu_drm_plane_helper_funcs);
+
+ return primary;
+}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h
new file mode 100644
index 000000000..c1d80ae7b
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#ifndef __FSL_DCU_DRM_PLANE_H__
+#define __FSL_DCU_DRM_PLANE_H__
+
+void fsl_dcu_drm_init_planes(struct drm_device *dev);
+struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev);
+
+#endif /* __FSL_DCU_DRM_PLANE_H__ */
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
new file mode 100644
index 000000000..ec71fbbb0
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ */
+
+#include <linux/backlight.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "fsl_dcu_drm_drv.h"
+#include "fsl_tcon.h"
+
+int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev,
+ struct drm_crtc *crtc)
+{
+ struct drm_encoder *encoder = &fsl_dev->encoder;
+ int ret;
+
+ encoder->possible_crtcs = 1;
+
+ /* Use bypass mode for parallel RGB/LVDS encoder */
+ if (fsl_dev->tcon)
+ fsl_tcon_bypass_enable(fsl_dev->tcon);
+
+ ret = drm_simple_encoder_init(fsl_dev->drm, encoder,
+ DRM_MODE_ENCODER_LVDS);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void fsl_dcu_drm_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs fsl_dcu_drm_connector_funcs = {
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .destroy = fsl_dcu_drm_connector_destroy,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .reset = drm_atomic_helper_connector_reset,
+};
+
+static int fsl_dcu_drm_connector_get_modes(struct drm_connector *connector)
+{
+ struct fsl_dcu_drm_connector *fsl_connector;
+
+ fsl_connector = to_fsl_dcu_connector(connector);
+ return drm_panel_get_modes(fsl_connector->panel, connector);
+}
+
+static enum drm_mode_status
+fsl_dcu_drm_connector_mode_valid(struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ if (mode->hdisplay & 0xf)
+ return MODE_ERROR;
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = fsl_dcu_drm_connector_get_modes,
+ .mode_valid = fsl_dcu_drm_connector_mode_valid,
+};
+
+static int fsl_dcu_attach_panel(struct fsl_dcu_drm_device *fsl_dev,
+ struct drm_panel *panel)
+{
+ struct drm_encoder *encoder = &fsl_dev->encoder;
+ struct drm_connector *connector = &fsl_dev->connector.base;
+ int ret;
+
+ fsl_dev->connector.encoder = encoder;
+
+ ret = drm_connector_init(fsl_dev->drm, connector,
+ &fsl_dcu_drm_connector_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+ if (ret < 0)
+ return ret;
+
+ drm_connector_helper_add(connector, &connector_helper_funcs);
+ ret = drm_connector_register(connector);
+ if (ret < 0)
+ goto err_cleanup;
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret < 0)
+ goto err_sysfs;
+
+ return 0;
+
+err_sysfs:
+ drm_connector_unregister(connector);
+err_cleanup:
+ drm_connector_cleanup(connector);
+ return ret;
+}
+
+static void fsl_dcu_panel_put_action(void *data)
+{
+ struct drm_panel *panel = data;
+
+ drm_panel_put(panel);
+}
+
+int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
+{
+ struct device_node *panel_node;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ int ret;
+
+ /* This is for backward compatibility */
+ panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0);
+ if (panel_node) {
+ fsl_dev->connector.panel = of_drm_find_panel(panel_node);
+ of_node_put(panel_node);
+ if (IS_ERR(fsl_dev->connector.panel))
+ return PTR_ERR(fsl_dev->connector.panel);
+
+ ret = devm_add_action_or_reset(fsl_dev->dev,
+ fsl_dcu_panel_put_action,
+ fsl_dev->connector.panel);
+ if (ret)
+ return ret;
+
+ return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel);
+ }
+
+ ret = drm_of_find_panel_or_bridge(fsl_dev->np, 0, 0, &panel, &bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ ret = devm_add_action_or_reset(fsl_dev->dev,
+ fsl_dcu_panel_put_action, panel);
+ if (ret)
+ return ret;
+
+ fsl_dev->connector.panel = panel;
+ return fsl_dcu_attach_panel(fsl_dev, panel);
+ }
+
+ return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, 0);
+}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.c b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c
new file mode 100644
index 000000000..49bbd00c7
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Toradex AG
+ *
+ * Stefan Agner <stefan@agner.ch>
+ *
+ * Freescale TCON device driver
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "fsl_tcon.h"
+
+void fsl_tcon_bypass_disable(struct fsl_tcon *tcon)
+{
+ regmap_update_bits(tcon->regs, FSL_TCON_CTRL1,
+ FSL_TCON_CTRL1_TCON_BYPASS, 0);
+}
+
+void fsl_tcon_bypass_enable(struct fsl_tcon *tcon)
+{
+ regmap_update_bits(tcon->regs, FSL_TCON_CTRL1,
+ FSL_TCON_CTRL1_TCON_BYPASS,
+ FSL_TCON_CTRL1_TCON_BYPASS);
+}
+
+static const struct regmap_config fsl_tcon_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .name = "tcon",
+};
+
+static int fsl_tcon_init_regmap(struct device *dev,
+ struct fsl_tcon *tcon,
+ struct device_node *np)
+{
+ struct resource res;
+ void __iomem *regs;
+
+ if (of_address_to_resource(np, 0, &res))
+ return -EINVAL;
+
+ regs = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ tcon->regs = devm_regmap_init_mmio(dev, regs,
+ &fsl_tcon_regmap_config);
+ return PTR_ERR_OR_ZERO(tcon->regs);
+}
+
+struct fsl_tcon *fsl_tcon_init(struct device *dev)
+{
+ struct fsl_tcon *tcon;
+ struct device_node *np;
+ int ret;
+
+ /* TCON node is not mandatory, some devices do not provide TCON */
+ np = of_parse_phandle(dev->of_node, "fsl,tcon", 0);
+ if (!np)
+ return NULL;
+
+ tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
+ if (!tcon)
+ goto err_node_put;
+
+ ret = fsl_tcon_init_regmap(dev, tcon, np);
+ if (ret) {
+ dev_err(dev, "Couldn't create the TCON regmap\n");
+ goto err_node_put;
+ }
+
+ tcon->ipg_clk = of_clk_get_by_name(np, "ipg");
+ if (IS_ERR(tcon->ipg_clk)) {
+ dev_err(dev, "Couldn't get the TCON bus clock\n");
+ goto err_node_put;
+ }
+
+ ret = clk_prepare_enable(tcon->ipg_clk);
+ if (ret) {
+ dev_err(dev, "Couldn't enable the TCON clock\n");
+ goto err_node_put;
+ }
+
+ of_node_put(np);
+ dev_info(dev, "Using TCON in bypass mode\n");
+
+ return tcon;
+
+err_node_put:
+ of_node_put(np);
+ return NULL;
+}
+
+void fsl_tcon_free(struct fsl_tcon *tcon)
+{
+ clk_disable_unprepare(tcon->ipg_clk);
+ clk_put(tcon->ipg_clk);
+}
+
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.h b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h
new file mode 100644
index 000000000..23aa78f14
--- /dev/null
+++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015 Toradex AG
+ *
+ * Stefan Agner <stefan@agner.ch>
+ *
+ * Freescale TCON device driver
+ */
+
+#ifndef __FSL_TCON_H__
+#define __FSL_TCON_H__
+
+#include <linux/bitops.h>
+
+#define FSL_TCON_CTRL1 0x0
+#define FSL_TCON_CTRL1_TCON_BYPASS BIT(29)
+
+struct fsl_tcon {
+ struct regmap *regs;
+ struct clk *ipg_clk;
+};
+
+struct fsl_tcon *fsl_tcon_init(struct device *dev);
+void fsl_tcon_free(struct fsl_tcon *tcon);
+
+void fsl_tcon_bypass_disable(struct fsl_tcon *tcon);
+void fsl_tcon_bypass_enable(struct fsl_tcon *tcon);
+
+#endif /* __FSL_TCON_H__ */