summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/pl111
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/gpu/drm/pl111
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/pl111')
-rw-r--r--drivers/gpu/drm/pl111/Kconfig16
-rw-r--r--drivers/gpu/drm/pl111/Makefile9
-rw-r--r--drivers/gpu/drm/pl111/pl111_debugfs.c59
-rw-r--r--drivers/gpu/drm/pl111/pl111_display.c596
-rw-r--r--drivers/gpu/drm/pl111/pl111_drm.h162
-rw-r--r--drivers/gpu/drm/pl111/pl111_drv.c446
-rw-r--r--drivers/gpu/drm/pl111/pl111_nomadik.c36
-rw-r--r--drivers/gpu/drm/pl111/pl111_nomadik.h19
-rw-r--r--drivers/gpu/drm/pl111/pl111_versatile.c564
-rw-r--r--drivers/gpu/drm/pl111/pl111_versatile.h12
10 files changed, 1919 insertions, 0 deletions
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig
new file mode 100644
index 000000000..ad24cdf1d
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_PL111
+ tristate "DRM Support for PL111 CLCD Controller"
+ depends on DRM
+ depends on ARM || ARM64 || COMPILE_TEST
+ depends on VEXPRESS_CONFIG || VEXPRESS_CONFIG=n
+ depends on COMMON_CLK
+ select DRM_KMS_HELPER
+ select DRM_GEM_DMA_HELPER
+ select DRM_BRIDGE
+ select DRM_PANEL_BRIDGE
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ help
+ Choose this option for DRM support for the PL111 CLCD controller.
+ If M is selected the module will be called pl111_drm.
+
diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile
new file mode 100644
index 000000000..67d430d43
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+pl111_drm-y += pl111_display.o \
+ pl111_versatile.o \
+ pl111_drv.o
+
+pl111_drm-$(CONFIG_ARCH_NOMADIK) += pl111_nomadik.o
+pl111_drm-$(CONFIG_DEBUG_FS) += pl111_debugfs.o
+
+obj-$(CONFIG_DRM_PL111) += pl111_drm.o
diff --git a/drivers/gpu/drm/pl111/pl111_debugfs.c b/drivers/gpu/drm/pl111/pl111_debugfs.c
new file mode 100644
index 000000000..6744fa16f
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_debugfs.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright © 2017 Broadcom
+ */
+
+#include <linux/seq_file.h>
+
+#include <drm/drm_debugfs.h>
+#include <drm/drm_file.h>
+
+#include "pl111_drm.h"
+
+#define REGDEF(reg) { reg, #reg }
+static const struct {
+ u32 reg;
+ const char *name;
+} pl111_reg_defs[] = {
+ REGDEF(CLCD_TIM0),
+ REGDEF(CLCD_TIM1),
+ REGDEF(CLCD_TIM2),
+ REGDEF(CLCD_TIM3),
+ REGDEF(CLCD_UBAS),
+ REGDEF(CLCD_LBAS),
+ REGDEF(CLCD_PL111_CNTL),
+ REGDEF(CLCD_PL111_IENB),
+ REGDEF(CLCD_PL111_RIS),
+ REGDEF(CLCD_PL111_MIS),
+ REGDEF(CLCD_PL111_ICR),
+ REGDEF(CLCD_PL111_UCUR),
+ REGDEF(CLCD_PL111_LCUR),
+};
+
+static int pl111_debugfs_regs(struct seq_file *m, void *unused)
+{
+ struct drm_info_node *node = (struct drm_info_node *)m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pl111_reg_defs); i++) {
+ seq_printf(m, "%s (0x%04x): 0x%08x\n",
+ pl111_reg_defs[i].name, pl111_reg_defs[i].reg,
+ readl(priv->regs + pl111_reg_defs[i].reg));
+ }
+
+ return 0;
+}
+
+static const struct drm_info_list pl111_debugfs_list[] = {
+ {"regs", pl111_debugfs_regs, 0},
+};
+
+void
+pl111_debugfs_init(struct drm_minor *minor)
+{
+ drm_debugfs_create_files(pl111_debugfs_list,
+ ARRAY_SIZE(pl111_debugfs_list),
+ minor->debugfs_root, minor);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c
new file mode 100644
index 000000000..6afdf260a
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_display.c
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-buf.h>
+#include <linux/media-bus-format.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "pl111_drm.h"
+
+irqreturn_t pl111_irq(int irq, void *data)
+{
+ struct pl111_drm_dev_private *priv = data;
+ u32 irq_stat;
+ irqreturn_t status = IRQ_NONE;
+
+ irq_stat = readl(priv->regs + CLCD_PL111_MIS);
+
+ if (!irq_stat)
+ return IRQ_NONE;
+
+ if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) {
+ drm_crtc_handle_vblank(&priv->pipe.crtc);
+
+ status = IRQ_HANDLED;
+ }
+
+ /* Clear the interrupt once done */
+ writel(irq_stat, priv->regs + CLCD_PL111_ICR);
+
+ return status;
+}
+
+static enum drm_mode_status
+pl111_mode_valid(struct drm_simple_display_pipe *pipe,
+ const struct drm_display_mode *mode)
+{
+ struct drm_device *drm = pipe->crtc.dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ u32 cpp = priv->variant->fb_bpp / 8;
+ u64 bw;
+
+ /*
+ * We use the pixelclock to also account for interlaced modes, the
+ * resulting bandwidth is in bytes per second.
+ */
+ bw = mode->clock * 1000ULL; /* In Hz */
+ bw = bw * mode->hdisplay * mode->vdisplay * cpp;
+ bw = div_u64(bw, mode->htotal * mode->vtotal);
+
+ /*
+ * If no bandwidth constraints, anything goes, else
+ * check if we are too fast.
+ */
+ if (priv->memory_bw && (bw > priv->memory_bw)) {
+ DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu too fast\n",
+ mode->hdisplay, mode->vdisplay,
+ mode->clock * 1000, cpp, bw);
+
+ return MODE_BAD;
+ }
+ DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu bytes/s OK\n",
+ mode->hdisplay, mode->vdisplay,
+ mode->clock * 1000, cpp, bw);
+
+ return MODE_OK;
+}
+
+static int pl111_display_check(struct drm_simple_display_pipe *pipe,
+ struct drm_plane_state *pstate,
+ struct drm_crtc_state *cstate)
+{
+ const struct drm_display_mode *mode = &cstate->mode;
+ struct drm_framebuffer *old_fb = pipe->plane.state->fb;
+ struct drm_framebuffer *fb = pstate->fb;
+
+ if (mode->hdisplay % 16)
+ return -EINVAL;
+
+ if (fb) {
+ u32 offset = drm_fb_dma_get_gem_addr(fb, pstate, 0);
+
+ /* FB base address must be dword aligned. */
+ if (offset & 3)
+ return -EINVAL;
+
+ /* There's no pitch register -- the mode's hdisplay
+ * controls it.
+ */
+ if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0])
+ return -EINVAL;
+
+ /* We can't change the FB format in a flicker-free
+ * manner (and only update it during CRTC enable).
+ */
+ if (old_fb && old_fb->format != fb->format)
+ cstate->mode_changed = true;
+ }
+
+ return 0;
+}
+
+static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
+ struct drm_crtc_state *cstate,
+ struct drm_plane_state *plane_state)
+{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_plane *plane = &pipe->plane;
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ const struct drm_display_mode *mode = &cstate->mode;
+ struct drm_framebuffer *fb = plane->state->fb;
+ struct drm_connector *connector = priv->connector;
+ struct drm_bridge *bridge = priv->bridge;
+ bool grayscale = false;
+ u32 cntl;
+ u32 ppl, hsw, hfp, hbp;
+ u32 lpp, vsw, vfp, vbp;
+ u32 cpl, tim2;
+ int ret;
+
+ ret = clk_set_rate(priv->clk, mode->clock * 1000);
+ if (ret) {
+ dev_err(drm->dev,
+ "Failed to set pixel clock rate to %d: %d\n",
+ mode->clock * 1000, ret);
+ }
+
+ clk_prepare_enable(priv->clk);
+
+ ppl = (mode->hdisplay / 16) - 1;
+ hsw = mode->hsync_end - mode->hsync_start - 1;
+ hfp = mode->hsync_start - mode->hdisplay - 1;
+ hbp = mode->htotal - mode->hsync_end - 1;
+
+ lpp = mode->vdisplay - 1;
+ vsw = mode->vsync_end - mode->vsync_start - 1;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ cpl = mode->hdisplay - 1;
+
+ writel((ppl << 2) |
+ (hsw << 8) |
+ (hfp << 16) |
+ (hbp << 24),
+ priv->regs + CLCD_TIM0);
+ writel(lpp |
+ (vsw << 10) |
+ (vfp << 16) |
+ (vbp << 24),
+ priv->regs + CLCD_TIM1);
+
+ spin_lock(&priv->tim2_lock);
+
+ tim2 = readl(priv->regs + CLCD_TIM2);
+ tim2 &= (TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK);
+
+ if (priv->variant->broken_clockdivider)
+ tim2 |= TIM2_BCD;
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ tim2 |= TIM2_IHS;
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ tim2 |= TIM2_IVS;
+
+ if (connector) {
+ if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
+ tim2 |= TIM2_IOE;
+
+ if (connector->display_info.bus_flags &
+ DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+ tim2 |= TIM2_IPC;
+
+ if (connector->display_info.num_bus_formats == 1 &&
+ connector->display_info.bus_formats[0] ==
+ MEDIA_BUS_FMT_Y8_1X8)
+ grayscale = true;
+
+ /*
+ * The AC pin bias frequency is set to max count when using
+ * grayscale so at least once in a while we will reverse
+ * polarity and get rid of any DC built up that could
+ * damage the display.
+ */
+ if (grayscale)
+ tim2 |= TIM2_ACB_MASK;
+ }
+
+ if (bridge) {
+ const struct drm_bridge_timings *btimings = bridge->timings;
+
+ /*
+ * Here is when things get really fun. Sometimes the bridge
+ * timings are such that the signal out from PL11x is not
+ * stable before the receiving bridge (such as a dumb VGA DAC
+ * or similar) samples it. If that happens, we compensate by
+ * the only method we have: output the data on the opposite
+ * edge of the clock so it is for sure stable when it gets
+ * sampled.
+ *
+ * The PL111 manual does not contain proper timining diagrams
+ * or data for these details, but we know from experiments
+ * that the setup time is more than 3000 picoseconds (3 ns).
+ * If we have a bridge that requires the signal to be stable
+ * earlier than 3000 ps before the clock pulse, we have to
+ * output the data on the opposite edge to avoid flicker.
+ */
+ if (btimings && btimings->setup_time_ps >= 3000)
+ tim2 ^= TIM2_IPC;
+ }
+
+ tim2 |= cpl << 16;
+ writel(tim2, priv->regs + CLCD_TIM2);
+ spin_unlock(&priv->tim2_lock);
+
+ writel(0, priv->regs + CLCD_TIM3);
+
+ /*
+ * Detect grayscale bus format. We do not support a grayscale mode
+ * toward userspace, instead we expose an RGB24 buffer and then the
+ * hardware will activate its grayscaler to convert to the grayscale
+ * format.
+ */
+ if (grayscale)
+ cntl = CNTL_LCDEN | CNTL_LCDMONO8;
+ else
+ /* Else we assume TFT display */
+ cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDVCOMP(1);
+
+ /* On the ST Micro variant, assume all 24 bits are connected */
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_CDWID_24;
+
+ /*
+ * Note that the ARM hardware's format reader takes 'r' from
+ * the low bit, while DRM formats list channels from high bit
+ * to low bit as you read left to right. The ST Micro version of
+ * the PL110 (LCDC) however uses the standard DRM format.
+ */
+ switch (fb->format->format) {
+ case DRM_FORMAT_BGR888:
+ /* Only supported on the ST Micro variant */
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_LCDBPP24_PACKED | CNTL_BGR;
+ break;
+ case DRM_FORMAT_RGB888:
+ /* Only supported on the ST Micro variant */
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_LCDBPP24_PACKED;
+ break;
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_LCDBPP24 | CNTL_BGR;
+ else
+ cntl |= CNTL_LCDBPP24;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_LCDBPP24;
+ else
+ cntl |= CNTL_LCDBPP24 | CNTL_BGR;
+ break;
+ case DRM_FORMAT_BGR565:
+ if (priv->variant->is_pl110)
+ cntl |= CNTL_LCDBPP16;
+ else if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_LCDBPP16 | CNTL_ST_1XBPP_565 | CNTL_BGR;
+ else
+ cntl |= CNTL_LCDBPP16_565;
+ break;
+ case DRM_FORMAT_RGB565:
+ if (priv->variant->is_pl110)
+ cntl |= CNTL_LCDBPP16 | CNTL_BGR;
+ else if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_LCDBPP16 | CNTL_ST_1XBPP_565;
+ else
+ cntl |= CNTL_LCDBPP16_565 | CNTL_BGR;
+ break;
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ cntl |= CNTL_LCDBPP16;
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_1XBPP_5551 | CNTL_BGR;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ cntl |= CNTL_LCDBPP16;
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_1XBPP_5551;
+ else
+ cntl |= CNTL_BGR;
+ break;
+ case DRM_FORMAT_ABGR4444:
+ case DRM_FORMAT_XBGR4444:
+ cntl |= CNTL_LCDBPP16_444;
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_1XBPP_444 | CNTL_BGR;
+ break;
+ case DRM_FORMAT_ARGB4444:
+ case DRM_FORMAT_XRGB4444:
+ cntl |= CNTL_LCDBPP16_444;
+ if (priv->variant->st_bitmux_control)
+ cntl |= CNTL_ST_1XBPP_444;
+ else
+ cntl |= CNTL_BGR;
+ break;
+ default:
+ WARN_ONCE(true, "Unknown FB format 0x%08x\n",
+ fb->format->format);
+ break;
+ }
+
+ /* The PL110 in Integrator/Versatile does the BGR routing externally */
+ if (priv->variant->external_bgr)
+ cntl &= ~CNTL_BGR;
+
+ /* Power sequence: first enable and chill */
+ writel(cntl, priv->regs + priv->ctrl);
+
+ /*
+ * We expect this delay to stabilize the contrast
+ * voltage Vee as stipulated by the manual
+ */
+ msleep(20);
+
+ if (priv->variant_display_enable)
+ priv->variant_display_enable(drm, fb->format->format);
+
+ /* Power Up */
+ cntl |= CNTL_LCDPWR;
+ writel(cntl, priv->regs + priv->ctrl);
+
+ if (!priv->variant->broken_vblank)
+ drm_crtc_vblank_on(crtc);
+}
+
+static void pl111_display_disable(struct drm_simple_display_pipe *pipe)
+{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ u32 cntl;
+
+ if (!priv->variant->broken_vblank)
+ drm_crtc_vblank_off(crtc);
+
+ /* Power Down */
+ cntl = readl(priv->regs + priv->ctrl);
+ if (cntl & CNTL_LCDPWR) {
+ cntl &= ~CNTL_LCDPWR;
+ writel(cntl, priv->regs + priv->ctrl);
+ }
+
+ /*
+ * We expect this delay to stabilize the contrast voltage Vee as
+ * stipulated by the manual
+ */
+ msleep(20);
+
+ if (priv->variant_display_disable)
+ priv->variant_display_disable(drm);
+
+ /* Disable */
+ writel(0, priv->regs + priv->ctrl);
+
+ clk_disable_unprepare(priv->clk);
+}
+
+static void pl111_display_update(struct drm_simple_display_pipe *pipe,
+ struct drm_plane_state *old_pstate)
+{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ struct drm_pending_vblank_event *event = crtc->state->event;
+ struct drm_plane *plane = &pipe->plane;
+ struct drm_plane_state *pstate = plane->state;
+ struct drm_framebuffer *fb = pstate->fb;
+
+ if (fb) {
+ u32 addr = drm_fb_dma_get_gem_addr(fb, pstate, 0);
+
+ writel(addr, priv->regs + CLCD_UBAS);
+ }
+
+ if (event) {
+ crtc->state->event = NULL;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->active && 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 int pl111_display_enable_vblank(struct drm_simple_display_pipe *pipe)
+{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+
+ writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + priv->ienb);
+
+ return 0;
+}
+
+static void pl111_display_disable_vblank(struct drm_simple_display_pipe *pipe)
+{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+
+ writel(0, priv->regs + priv->ienb);
+}
+
+static struct drm_simple_display_pipe_funcs pl111_display_funcs = {
+ .mode_valid = pl111_mode_valid,
+ .check = pl111_display_check,
+ .enable = pl111_display_enable,
+ .disable = pl111_display_disable,
+ .update = pl111_display_update,
+};
+
+static int pl111_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate, bool set_parent)
+{
+ int best_div = 1, div;
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+ unsigned long best_prate = 0;
+ unsigned long best_diff = ~0ul;
+ int max_div = (1 << (TIM2_PCD_LO_BITS + TIM2_PCD_HI_BITS)) - 1;
+
+ for (div = 1; div < max_div; div++) {
+ unsigned long this_prate, div_rate, diff;
+
+ if (set_parent)
+ this_prate = clk_hw_round_rate(parent, rate * div);
+ else
+ this_prate = *prate;
+ div_rate = DIV_ROUND_UP_ULL(this_prate, div);
+ diff = abs(rate - div_rate);
+
+ if (diff < best_diff) {
+ best_div = div;
+ best_diff = diff;
+ best_prate = this_prate;
+ }
+ }
+
+ *prate = best_prate;
+ return best_div;
+}
+
+static long pl111_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ int div = pl111_clk_div_choose_div(hw, rate, prate, true);
+
+ return DIV_ROUND_UP_ULL(*prate, div);
+}
+
+static unsigned long pl111_clk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct pl111_drm_dev_private *priv =
+ container_of(hw, struct pl111_drm_dev_private, clk_div);
+ u32 tim2 = readl(priv->regs + CLCD_TIM2);
+ int div;
+
+ if (tim2 & TIM2_BCD)
+ return prate;
+
+ div = tim2 & TIM2_PCD_LO_MASK;
+ div |= (tim2 & TIM2_PCD_HI_MASK) >>
+ (TIM2_PCD_HI_SHIFT - TIM2_PCD_LO_BITS);
+ div += 2;
+
+ return DIV_ROUND_UP_ULL(prate, div);
+}
+
+static int pl111_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct pl111_drm_dev_private *priv =
+ container_of(hw, struct pl111_drm_dev_private, clk_div);
+ int div = pl111_clk_div_choose_div(hw, rate, &prate, false);
+ u32 tim2;
+
+ spin_lock(&priv->tim2_lock);
+ tim2 = readl(priv->regs + CLCD_TIM2);
+ tim2 &= ~(TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK);
+
+ if (div == 1) {
+ tim2 |= TIM2_BCD;
+ } else {
+ div -= 2;
+ tim2 |= div & TIM2_PCD_LO_MASK;
+ tim2 |= (div >> TIM2_PCD_LO_BITS) << TIM2_PCD_HI_SHIFT;
+ }
+
+ writel(tim2, priv->regs + CLCD_TIM2);
+ spin_unlock(&priv->tim2_lock);
+
+ return 0;
+}
+
+static const struct clk_ops pl111_clk_div_ops = {
+ .recalc_rate = pl111_clk_div_recalc_rate,
+ .round_rate = pl111_clk_div_round_rate,
+ .set_rate = pl111_clk_div_set_rate,
+};
+
+static int
+pl111_init_clock_divider(struct drm_device *drm)
+{
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ struct clk *parent = devm_clk_get(drm->dev, "clcdclk");
+ struct clk_hw *div = &priv->clk_div;
+ const char *parent_name;
+ struct clk_init_data init = {
+ .name = "pl111_div",
+ .ops = &pl111_clk_div_ops,
+ .parent_names = &parent_name,
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ };
+ int ret;
+
+ if (IS_ERR(parent)) {
+ dev_err(drm->dev, "CLCD: unable to get clcdclk.\n");
+ return PTR_ERR(parent);
+ }
+
+ spin_lock_init(&priv->tim2_lock);
+
+ /* If the clock divider is broken, use the parent directly */
+ if (priv->variant->broken_clockdivider) {
+ priv->clk = parent;
+ return 0;
+ }
+ parent_name = __clk_get_name(parent);
+ div->init = &init;
+
+ ret = devm_clk_hw_register(drm->dev, div);
+
+ priv->clk = div->clk;
+ return ret;
+}
+
+int pl111_display_init(struct drm_device *drm)
+{
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ int ret;
+
+ ret = pl111_init_clock_divider(drm);
+ if (ret)
+ return ret;
+
+ if (!priv->variant->broken_vblank) {
+ pl111_display_funcs.enable_vblank = pl111_display_enable_vblank;
+ pl111_display_funcs.disable_vblank = pl111_display_disable_vblank;
+ }
+
+ ret = drm_simple_display_pipe_init(drm, &priv->pipe,
+ &pl111_display_funcs,
+ priv->variant->formats,
+ priv->variant->nformats,
+ NULL,
+ priv->connector);
+ if (ret)
+ return ret;
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h
new file mode 100644
index 000000000..2a46b5bd8
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ */
+
+#ifndef _PL111_DRM_H_
+#define _PL111_DRM_H_
+
+#include <linux/clk-provider.h>
+#include <linux/interrupt.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_simple_kms_helper.h>
+
+/*
+ * CLCD Controller Internal Register addresses
+ */
+#define CLCD_TIM0 0x00000000
+#define CLCD_TIM1 0x00000004
+#define CLCD_TIM2 0x00000008
+#define CLCD_TIM3 0x0000000c
+#define CLCD_UBAS 0x00000010
+#define CLCD_LBAS 0x00000014
+
+#define CLCD_PL110_IENB 0x00000018
+#define CLCD_PL110_CNTL 0x0000001c
+#define CLCD_PL110_STAT 0x00000020
+#define CLCD_PL110_INTR 0x00000024
+#define CLCD_PL110_UCUR 0x00000028
+#define CLCD_PL110_LCUR 0x0000002C
+
+#define CLCD_PL111_CNTL 0x00000018
+#define CLCD_PL111_IENB 0x0000001c
+#define CLCD_PL111_RIS 0x00000020
+#define CLCD_PL111_MIS 0x00000024
+#define CLCD_PL111_ICR 0x00000028
+#define CLCD_PL111_UCUR 0x0000002c
+#define CLCD_PL111_LCUR 0x00000030
+
+#define CLCD_PALL 0x00000200
+#define CLCD_PALETTE 0x00000200
+
+#define TIM2_PCD_LO_MASK GENMASK(4, 0)
+#define TIM2_PCD_LO_BITS 5
+#define TIM2_CLKSEL (1 << 5)
+#define TIM2_ACB_MASK GENMASK(10, 6)
+#define TIM2_IVS (1 << 11)
+#define TIM2_IHS (1 << 12)
+#define TIM2_IPC (1 << 13)
+#define TIM2_IOE (1 << 14)
+#define TIM2_BCD (1 << 26)
+#define TIM2_PCD_HI_MASK GENMASK(31, 27)
+#define TIM2_PCD_HI_BITS 5
+#define TIM2_PCD_HI_SHIFT 27
+
+#define CNTL_LCDEN (1 << 0)
+#define CNTL_LCDBPP1 (0 << 1)
+#define CNTL_LCDBPP2 (1 << 1)
+#define CNTL_LCDBPP4 (2 << 1)
+#define CNTL_LCDBPP8 (3 << 1)
+#define CNTL_LCDBPP16 (4 << 1)
+#define CNTL_LCDBPP16_565 (6 << 1)
+#define CNTL_LCDBPP16_444 (7 << 1)
+#define CNTL_LCDBPP24 (5 << 1)
+#define CNTL_LCDBW (1 << 4)
+#define CNTL_LCDTFT (1 << 5)
+#define CNTL_LCDMONO8 (1 << 6)
+#define CNTL_LCDDUAL (1 << 7)
+#define CNTL_BGR (1 << 8)
+#define CNTL_BEBO (1 << 9)
+#define CNTL_BEPO (1 << 10)
+#define CNTL_LCDPWR (1 << 11)
+#define CNTL_LCDVCOMP(x) ((x) << 12)
+#define CNTL_LDMAFIFOTIME (1 << 15)
+#define CNTL_WATERMARK (1 << 16)
+
+/* ST Microelectronics variant bits */
+#define CNTL_ST_1XBPP_444 0x0
+#define CNTL_ST_1XBPP_5551 (1 << 17)
+#define CNTL_ST_1XBPP_565 (1 << 18)
+#define CNTL_ST_CDWID_12 0x0
+#define CNTL_ST_CDWID_16 (1 << 19)
+#define CNTL_ST_CDWID_18 (1 << 20)
+#define CNTL_ST_CDWID_24 ((1 << 19) | (1 << 20))
+#define CNTL_ST_CEAEN (1 << 21)
+#define CNTL_ST_LCDBPP24_PACKED (6 << 1)
+
+#define CLCD_IRQ_NEXTBASE_UPDATE BIT(2)
+
+struct drm_minor;
+
+/**
+ * struct pl111_variant_data - encodes IP differences
+ * @name: the name of this variant
+ * @is_pl110: this is the early PL110 variant
+ * @is_lcdc: this is the ST Microelectronics Nomadik LCDC variant
+ * @external_bgr: this is the Versatile Pl110 variant with external
+ * BGR/RGB routing
+ * @broken_clockdivider: the clock divider is broken and we need to
+ * use the supplied clock directly
+ * @broken_vblank: the vblank IRQ is broken on this variant
+ * @st_bitmux_control: this variant is using the ST Micro bitmux
+ * extensions to the control register
+ * @formats: array of supported pixel formats on this variant
+ * @nformats: the length of the array of supported pixel formats
+ * @fb_bpp: desired bits per pixel on the default framebuffer
+ */
+struct pl111_variant_data {
+ const char *name;
+ bool is_pl110;
+ bool is_lcdc;
+ bool external_bgr;
+ bool broken_clockdivider;
+ bool broken_vblank;
+ bool st_bitmux_control;
+ const u32 *formats;
+ unsigned int nformats;
+ unsigned int fb_bpp;
+};
+
+struct pl111_drm_dev_private {
+ struct drm_device *drm;
+
+ struct drm_connector *connector;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ struct drm_simple_display_pipe pipe;
+
+ void *regs;
+ u32 memory_bw;
+ u32 ienb;
+ u32 ctrl;
+ /* The pixel clock (a reference to our clock divider off of CLCDCLK). */
+ struct clk *clk;
+ /* pl111's internal clock divider. */
+ struct clk_hw clk_div;
+ /* Lock to sync access to CLCD_TIM2 between the common clock
+ * subsystem and pl111_display_enable().
+ */
+ spinlock_t tim2_lock;
+ const struct pl111_variant_data *variant;
+ void (*variant_display_enable) (struct drm_device *drm, u32 format);
+ void (*variant_display_disable) (struct drm_device *drm);
+ bool use_device_memory;
+};
+
+int pl111_display_init(struct drm_device *dev);
+irqreturn_t pl111_irq(int irq, void *data);
+void pl111_debugfs_init(struct drm_minor *minor);
+
+#endif /* _PL111_DRM_H_ */
diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c
new file mode 100644
index 000000000..eb25eedb5
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drv.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ */
+
+/**
+ * DOC: ARM PrimeCell PL110 and PL111 CLCD Driver
+ *
+ * The PL110/PL111 is a simple LCD controller that can support TFT
+ * and STN displays. This driver exposes a standard KMS interface
+ * for them.
+ *
+ * The driver currently doesn't expose the cursor. The DRM API for
+ * cursors requires support for 64x64 ARGB8888 cursor images, while
+ * the hardware can only support 64x64 monochrome with masking
+ * cursors. While one could imagine trying to hack something together
+ * to look at the ARGB8888 and program reasonable in monochrome, we
+ * just don't expose the cursor at all instead, and leave cursor
+ * support to the application software cursor layer.
+ *
+ * TODO:
+ *
+ * - Fix race between setting plane base address and getting IRQ for
+ * vsync firing the pageflip completion.
+ *
+ * - Read back hardware state at boot to skip reprogramming the
+ * hardware when doing a no-op modeset.
+ *
+ * - Use the CLKSEL bit to support switching between the two external
+ * clock parents.
+ */
+
+#include <linux/amba/bus.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/shmem_fs.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "pl111_drm.h"
+#include "pl111_versatile.h"
+#include "pl111_nomadik.h"
+
+#define DRIVER_DESC "DRM module for PL111"
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static int pl111_modeset_init(struct drm_device *dev)
+{
+ struct drm_mode_config *mode_config;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ struct device_node *np = dev->dev->of_node;
+ struct device_node *remote;
+ struct drm_panel *panel = NULL;
+ struct drm_bridge *bridge = NULL;
+ bool defer = false;
+ int ret;
+ int i;
+
+ ret = drmm_mode_config_init(dev);
+ if (ret)
+ return ret;
+
+ mode_config = &dev->mode_config;
+ mode_config->funcs = &mode_config_funcs;
+ mode_config->min_width = 1;
+ mode_config->max_width = 1024;
+ mode_config->min_height = 1;
+ mode_config->max_height = 768;
+
+ i = 0;
+ for_each_endpoint_of_node(np, remote) {
+ struct drm_panel *tmp_panel;
+ struct drm_bridge *tmp_bridge;
+
+ dev_dbg(dev->dev, "checking endpoint %d\n", i);
+
+ ret = drm_of_find_panel_or_bridge(dev->dev->of_node,
+ 0, i,
+ &tmp_panel,
+ &tmp_bridge);
+ if (ret) {
+ if (ret == -EPROBE_DEFER) {
+ /*
+ * Something deferred, but that is often just
+ * another way of saying -ENODEV, but let's
+ * cast a vote for later deferral.
+ */
+ defer = true;
+ } else if (ret != -ENODEV) {
+ /* Continue, maybe something else is working */
+ dev_err(dev->dev,
+ "endpoint %d returns %d\n", i, ret);
+ }
+ }
+
+ if (tmp_panel) {
+ dev_info(dev->dev,
+ "found panel on endpoint %d\n", i);
+ panel = tmp_panel;
+ }
+ if (tmp_bridge) {
+ dev_info(dev->dev,
+ "found bridge on endpoint %d\n", i);
+ bridge = tmp_bridge;
+ }
+
+ i++;
+ }
+
+ /*
+ * If we can't find neither panel nor bridge on any of the
+ * endpoints, and any of them retured -EPROBE_DEFER, then
+ * let's defer this driver too.
+ */
+ if ((!panel && !bridge) && defer)
+ return -EPROBE_DEFER;
+
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_Unknown);
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ goto finish;
+ }
+ } else if (bridge) {
+ dev_info(dev->dev, "Using non-panel bridge\n");
+ } else {
+ dev_err(dev->dev, "No bridge, exiting\n");
+ return -ENODEV;
+ }
+
+ priv->bridge = bridge;
+ if (panel) {
+ priv->panel = panel;
+ priv->connector = drm_panel_bridge_connector(bridge);
+ }
+
+ ret = pl111_display_init(dev);
+ if (ret != 0) {
+ dev_err(dev->dev, "Failed to init display\n");
+ goto out_bridge;
+ }
+
+ ret = drm_simple_display_pipe_attach_bridge(&priv->pipe,
+ bridge);
+ if (ret)
+ return ret;
+
+ if (!priv->variant->broken_vblank) {
+ ret = drm_vblank_init(dev, 1);
+ if (ret != 0) {
+ dev_err(dev->dev, "Failed to init vblank\n");
+ goto out_bridge;
+ }
+ }
+
+ drm_mode_config_reset(dev);
+
+ drm_kms_helper_poll_init(dev);
+
+ goto finish;
+
+out_bridge:
+ if (panel)
+ drm_panel_bridge_remove(bridge);
+finish:
+ return ret;
+}
+
+static struct drm_gem_object *
+pl111_gem_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sgt)
+{
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+
+ /*
+ * When using device-specific reserved memory we can't import
+ * DMA buffers: those are passed by reference in any global
+ * memory and we can only handle a specific range of memory.
+ */
+ if (priv->use_device_memory)
+ return ERR_PTR(-EINVAL);
+
+ return drm_gem_dma_prime_import_sg_table(dev, attach, sgt);
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(drm_fops);
+
+static const struct drm_driver pl111_drm_driver = {
+ .driver_features =
+ DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ .ioctls = NULL,
+ .fops = &drm_fops,
+ .name = "pl111",
+ .desc = DRIVER_DESC,
+ .date = "20170317",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+ .dumb_create = drm_gem_dma_dumb_create,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import_sg_table = pl111_gem_import_sg_table,
+ .gem_prime_mmap = drm_gem_prime_mmap,
+
+#if defined(CONFIG_DEBUG_FS)
+ .debugfs_init = pl111_debugfs_init,
+#endif
+};
+
+static int pl111_amba_probe(struct amba_device *amba_dev,
+ const struct amba_id *id)
+{
+ struct device *dev = &amba_dev->dev;
+ struct pl111_drm_dev_private *priv;
+ const struct pl111_variant_data *variant = id->data;
+ struct drm_device *drm;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ drm = drm_dev_alloc(&pl111_drm_driver, dev);
+ if (IS_ERR(drm))
+ return PTR_ERR(drm);
+ amba_set_drvdata(amba_dev, drm);
+ priv->drm = drm;
+ drm->dev_private = priv;
+ priv->variant = variant;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (!ret) {
+ dev_info(dev, "using device-specific reserved memory\n");
+ priv->use_device_memory = true;
+ }
+
+ if (of_property_read_u32(dev->of_node, "max-memory-bandwidth",
+ &priv->memory_bw)) {
+ dev_info(dev, "no max memory bandwidth specified, assume unlimited\n");
+ priv->memory_bw = 0;
+ }
+
+ /* The two main variants swap this register */
+ if (variant->is_pl110 || variant->is_lcdc) {
+ priv->ienb = CLCD_PL110_IENB;
+ priv->ctrl = CLCD_PL110_CNTL;
+ } else {
+ priv->ienb = CLCD_PL111_IENB;
+ priv->ctrl = CLCD_PL111_CNTL;
+ }
+
+ priv->regs = devm_ioremap_resource(dev, &amba_dev->res);
+ if (IS_ERR(priv->regs)) {
+ dev_err(dev, "%s failed mmio\n", __func__);
+ ret = PTR_ERR(priv->regs);
+ goto dev_put;
+ }
+
+ /* This may override some variant settings */
+ ret = pl111_versatile_init(dev, priv);
+ if (ret)
+ goto dev_put;
+
+ pl111_nomadik_init(dev);
+
+ /* turn off interrupts before requesting the irq */
+ writel(0, priv->regs + priv->ienb);
+
+ ret = devm_request_irq(dev, amba_dev->irq[0], pl111_irq, 0,
+ variant->name, priv);
+ if (ret != 0) {
+ dev_err(dev, "%s failed irq %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = pl111_modeset_init(drm);
+ if (ret != 0)
+ goto dev_put;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret < 0)
+ goto dev_put;
+
+ drm_fbdev_generic_setup(drm, priv->variant->fb_bpp);
+
+ return 0;
+
+dev_put:
+ drm_dev_put(drm);
+ of_reserved_mem_device_release(dev);
+
+ return ret;
+}
+
+static void pl111_amba_remove(struct amba_device *amba_dev)
+{
+ struct device *dev = &amba_dev->dev;
+ struct drm_device *drm = amba_get_drvdata(amba_dev);
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+
+ drm_dev_unregister(drm);
+ if (priv->panel)
+ drm_panel_bridge_remove(priv->bridge);
+ drm_dev_put(drm);
+ of_reserved_mem_device_release(dev);
+}
+
+/*
+ * This early variant lacks the 565 and 444 pixel formats.
+ */
+static const u32 pl110_pixel_formats[] = {
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+};
+
+static const struct pl111_variant_data pl110_variant = {
+ .name = "PL110",
+ .is_pl110 = true,
+ .formats = pl110_pixel_formats,
+ .nformats = ARRAY_SIZE(pl110_pixel_formats),
+ .fb_bpp = 16,
+};
+
+/* RealView, Versatile Express etc use this modern variant */
+static const u32 pl111_pixel_formats[] = {
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB4444,
+};
+
+static const struct pl111_variant_data pl111_variant = {
+ .name = "PL111",
+ .formats = pl111_pixel_formats,
+ .nformats = ARRAY_SIZE(pl111_pixel_formats),
+ .fb_bpp = 32,
+};
+
+static const u32 pl110_nomadik_pixel_formats[] = {
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB4444,
+};
+
+static const struct pl111_variant_data pl110_nomadik_variant = {
+ .name = "LCDC (PL110 Nomadik)",
+ .formats = pl110_nomadik_pixel_formats,
+ .nformats = ARRAY_SIZE(pl110_nomadik_pixel_formats),
+ .is_lcdc = true,
+ .st_bitmux_control = true,
+ .broken_vblank = true,
+ .fb_bpp = 16,
+};
+
+static const struct amba_id pl111_id_table[] = {
+ {
+ .id = 0x00041110,
+ .mask = 0x000fffff,
+ .data = (void *)&pl110_variant,
+ },
+ {
+ .id = 0x00180110,
+ .mask = 0x00fffffe,
+ .data = (void *)&pl110_nomadik_variant,
+ },
+ {
+ .id = 0x00041111,
+ .mask = 0x000fffff,
+ .data = (void *)&pl111_variant,
+ },
+ {0, 0},
+};
+MODULE_DEVICE_TABLE(amba, pl111_id_table);
+
+static struct amba_driver pl111_amba_driver __maybe_unused = {
+ .drv = {
+ .name = "drm-clcd-pl111",
+ },
+ .probe = pl111_amba_probe,
+ .remove = pl111_amba_remove,
+ .id_table = pl111_id_table,
+};
+
+#ifdef CONFIG_ARM_AMBA
+module_amba_driver(pl111_amba_driver);
+#endif
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("ARM Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/pl111/pl111_nomadik.c b/drivers/gpu/drm/pl111/pl111_nomadik.c
new file mode 100644
index 000000000..6f385e59b
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_nomadik.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include "pl111_nomadik.h"
+
+#define PMU_CTRL_OFFSET 0x0000
+#define PMU_CTRL_LCDNDIF BIT(26)
+
+void pl111_nomadik_init(struct device *dev)
+{
+ struct regmap *pmu_regmap;
+
+ /*
+ * Just bail out of this is not found, we could be running
+ * multiplatform on something else than Nomadik.
+ */
+ pmu_regmap =
+ syscon_regmap_lookup_by_compatible("stericsson,nomadik-pmu");
+ if (IS_ERR(pmu_regmap))
+ return;
+
+ /*
+ * This bit in the PMU controller multiplexes the two graphics
+ * blocks found in the Nomadik STn8815. The other one is called
+ * MDIF (Master Display Interface) and gets muxed out here.
+ */
+ regmap_update_bits(pmu_regmap,
+ PMU_CTRL_OFFSET,
+ PMU_CTRL_LCDNDIF,
+ 0);
+ dev_info(dev, "set Nomadik PMU mux to CLCD mode\n");
+}
+EXPORT_SYMBOL_GPL(pl111_nomadik_init);
diff --git a/drivers/gpu/drm/pl111/pl111_nomadik.h b/drivers/gpu/drm/pl111/pl111_nomadik.h
new file mode 100644
index 000000000..47ccf5c83
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_nomadik.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#ifndef PL111_NOMADIK_H
+#define PL111_NOMADIK_H
+#endif
+
+struct device;
+
+#ifdef CONFIG_ARCH_NOMADIK
+
+void pl111_nomadik_init(struct device *dev);
+
+#else
+
+static inline void pl111_nomadik_init(struct device *dev)
+{
+}
+
+#endif
diff --git a/drivers/gpu/drm/pl111/pl111_versatile.c b/drivers/gpu/drm/pl111/pl111_versatile.c
new file mode 100644
index 000000000..1b436b75f
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_versatile.c
@@ -0,0 +1,564 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Versatile family (ARM reference designs) handling for the PL11x.
+ * This is based on code and know-how in the previous frame buffer
+ * driver in drivers/video/fbdev/amba-clcd.c:
+ * Copyright (C) 2001 ARM Limited, by David A Rusling
+ * Updated to 2.5 by Deep Blue Solutions Ltd.
+ * Major contributions and discoveries by Russell King.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/vexpress.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "pl111_versatile.h"
+#include "pl111_drm.h"
+
+static struct regmap *versatile_syscon_map;
+
+/*
+ * We detect the different syscon types from the compatible strings.
+ */
+enum versatile_clcd {
+ INTEGRATOR_IMPD1,
+ INTEGRATOR_CLCD_CM,
+ VERSATILE_CLCD,
+ REALVIEW_CLCD_EB,
+ REALVIEW_CLCD_PB1176,
+ REALVIEW_CLCD_PB11MP,
+ REALVIEW_CLCD_PBA8,
+ REALVIEW_CLCD_PBX,
+ VEXPRESS_CLCD_V2M,
+};
+
+static const struct of_device_id versatile_clcd_of_match[] = {
+ {
+ .compatible = "arm,core-module-integrator",
+ .data = (void *)INTEGRATOR_CLCD_CM,
+ },
+ {
+ .compatible = "arm,versatile-sysreg",
+ .data = (void *)VERSATILE_CLCD,
+ },
+ {
+ .compatible = "arm,realview-eb-syscon",
+ .data = (void *)REALVIEW_CLCD_EB,
+ },
+ {
+ .compatible = "arm,realview-pb1176-syscon",
+ .data = (void *)REALVIEW_CLCD_PB1176,
+ },
+ {
+ .compatible = "arm,realview-pb11mp-syscon",
+ .data = (void *)REALVIEW_CLCD_PB11MP,
+ },
+ {
+ .compatible = "arm,realview-pba8-syscon",
+ .data = (void *)REALVIEW_CLCD_PBA8,
+ },
+ {
+ .compatible = "arm,realview-pbx-syscon",
+ .data = (void *)REALVIEW_CLCD_PBX,
+ },
+ {
+ .compatible = "arm,vexpress-muxfpga",
+ .data = (void *)VEXPRESS_CLCD_V2M,
+ },
+ {},
+};
+
+static const struct of_device_id impd1_clcd_of_match[] = {
+ {
+ .compatible = "arm,im-pd1-syscon",
+ .data = (void *)INTEGRATOR_IMPD1,
+ },
+ {},
+};
+
+/*
+ * Core module CLCD control on the Integrator/CP, bits
+ * 8 thru 19 of the CM_CONTROL register controls a bunch
+ * of CLCD settings.
+ */
+#define INTEGRATOR_HDR_CTRL_OFFSET 0x0C
+#define INTEGRATOR_CLCD_LCDBIASEN BIT(8)
+#define INTEGRATOR_CLCD_LCDBIASUP BIT(9)
+#define INTEGRATOR_CLCD_LCDBIASDN BIT(10)
+/* Bits 11,12,13 controls the LCD or VGA bridge type */
+#define INTEGRATOR_CLCD_LCDMUX_LCD24 BIT(11)
+#define INTEGRATOR_CLCD_LCDMUX_SHARP (BIT(11)|BIT(12))
+#define INTEGRATOR_CLCD_LCDMUX_VGA555 BIT(13)
+#define INTEGRATOR_CLCD_LCDMUX_VGA24 (BIT(11)|BIT(12)|BIT(13))
+#define INTEGRATOR_CLCD_LCD0_EN BIT(14)
+#define INTEGRATOR_CLCD_LCD1_EN BIT(15)
+/* R/L flip on Sharp */
+#define INTEGRATOR_CLCD_LCD_STATIC1 BIT(16)
+/* U/D flip on Sharp */
+#define INTEGRATOR_CLCD_LCD_STATIC2 BIT(17)
+/* No connection on Sharp */
+#define INTEGRATOR_CLCD_LCD_STATIC BIT(18)
+/* 0 = 24bit VGA, 1 = 18bit VGA */
+#define INTEGRATOR_CLCD_LCD_N24BITEN BIT(19)
+
+#define INTEGRATOR_CLCD_MASK GENMASK(19, 8)
+
+static void pl111_integrator_enable(struct drm_device *drm, u32 format)
+{
+ u32 val;
+
+ dev_info(drm->dev, "enable Integrator CLCD connectors\n");
+
+ /* FIXME: really needed? */
+ val = INTEGRATOR_CLCD_LCD_STATIC1 | INTEGRATOR_CLCD_LCD_STATIC2 |
+ INTEGRATOR_CLCD_LCD0_EN | INTEGRATOR_CLCD_LCD1_EN;
+
+ switch (format) {
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_XRGB8888:
+ /* 24bit formats */
+ val |= INTEGRATOR_CLCD_LCDMUX_VGA24;
+ break;
+ case DRM_FORMAT_XBGR1555:
+ case DRM_FORMAT_XRGB1555:
+ /* Pseudocolor, RGB555, BGR555 */
+ val |= INTEGRATOR_CLCD_LCDMUX_VGA555;
+ break;
+ default:
+ dev_err(drm->dev, "unhandled format on Integrator 0x%08x\n",
+ format);
+ break;
+ }
+
+ regmap_update_bits(versatile_syscon_map,
+ INTEGRATOR_HDR_CTRL_OFFSET,
+ INTEGRATOR_CLCD_MASK,
+ val);
+}
+
+#define IMPD1_CTRL_OFFSET 0x18
+#define IMPD1_CTRL_DISP_LCD (0 << 0)
+#define IMPD1_CTRL_DISP_VGA (1 << 0)
+#define IMPD1_CTRL_DISP_LCD1 (2 << 0)
+#define IMPD1_CTRL_DISP_ENABLE (1 << 2)
+#define IMPD1_CTRL_DISP_MASK (7 << 0)
+
+static void pl111_impd1_enable(struct drm_device *drm, u32 format)
+{
+ u32 val;
+
+ dev_info(drm->dev, "enable IM-PD1 CLCD connectors\n");
+ val = IMPD1_CTRL_DISP_VGA | IMPD1_CTRL_DISP_ENABLE;
+
+ regmap_update_bits(versatile_syscon_map,
+ IMPD1_CTRL_OFFSET,
+ IMPD1_CTRL_DISP_MASK,
+ val);
+}
+
+static void pl111_impd1_disable(struct drm_device *drm)
+{
+ dev_info(drm->dev, "disable IM-PD1 CLCD connectors\n");
+
+ regmap_update_bits(versatile_syscon_map,
+ IMPD1_CTRL_OFFSET,
+ IMPD1_CTRL_DISP_MASK,
+ 0);
+}
+
+/*
+ * This configuration register in the Versatile and RealView
+ * family is uniformly present but appears more and more
+ * unutilized starting with the RealView series.
+ */
+#define SYS_CLCD 0x50
+#define SYS_CLCD_MODE_MASK (BIT(0)|BIT(1))
+#define SYS_CLCD_MODE_888 0
+#define SYS_CLCD_MODE_5551 BIT(0)
+#define SYS_CLCD_MODE_565_R_LSB BIT(1)
+#define SYS_CLCD_MODE_565_B_LSB (BIT(0)|BIT(1))
+#define SYS_CLCD_CONNECTOR_MASK (BIT(2)|BIT(3)|BIT(4)|BIT(5))
+#define SYS_CLCD_NLCDIOON BIT(2)
+#define SYS_CLCD_VDDPOSSWITCH BIT(3)
+#define SYS_CLCD_PWR3V5SWITCH BIT(4)
+#define SYS_CLCD_VDDNEGSWITCH BIT(5)
+
+static void pl111_versatile_disable(struct drm_device *drm)
+{
+ dev_info(drm->dev, "disable Versatile CLCD connectors\n");
+ regmap_update_bits(versatile_syscon_map,
+ SYS_CLCD,
+ SYS_CLCD_CONNECTOR_MASK,
+ 0);
+}
+
+static void pl111_versatile_enable(struct drm_device *drm, u32 format)
+{
+ u32 val = 0;
+
+ dev_info(drm->dev, "enable Versatile CLCD connectors\n");
+
+ switch (format) {
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ val |= SYS_CLCD_MODE_888;
+ break;
+ case DRM_FORMAT_BGR565:
+ val |= SYS_CLCD_MODE_565_R_LSB;
+ break;
+ case DRM_FORMAT_RGB565:
+ val |= SYS_CLCD_MODE_565_B_LSB;
+ break;
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ val |= SYS_CLCD_MODE_5551;
+ break;
+ default:
+ dev_err(drm->dev, "unhandled format on Versatile 0x%08x\n",
+ format);
+ break;
+ }
+
+ /* Set up the MUX */
+ regmap_update_bits(versatile_syscon_map,
+ SYS_CLCD,
+ SYS_CLCD_MODE_MASK,
+ val);
+
+ /* Then enable the display */
+ regmap_update_bits(versatile_syscon_map,
+ SYS_CLCD,
+ SYS_CLCD_CONNECTOR_MASK,
+ SYS_CLCD_NLCDIOON | SYS_CLCD_PWR3V5SWITCH);
+}
+
+static void pl111_realview_clcd_disable(struct drm_device *drm)
+{
+ dev_info(drm->dev, "disable RealView CLCD connectors\n");
+ regmap_update_bits(versatile_syscon_map,
+ SYS_CLCD,
+ SYS_CLCD_CONNECTOR_MASK,
+ 0);
+}
+
+static void pl111_realview_clcd_enable(struct drm_device *drm, u32 format)
+{
+ dev_info(drm->dev, "enable RealView CLCD connectors\n");
+ regmap_update_bits(versatile_syscon_map,
+ SYS_CLCD,
+ SYS_CLCD_CONNECTOR_MASK,
+ SYS_CLCD_NLCDIOON | SYS_CLCD_PWR3V5SWITCH);
+}
+
+/* PL110 pixel formats for Integrator, vanilla PL110 */
+static const u32 pl110_integrator_pixel_formats[] = {
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+};
+
+/* Extended PL110 pixel formats for Integrator and Versatile */
+static const u32 pl110_versatile_pixel_formats[] = {
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_BGR565, /* Uses external PLD */
+ DRM_FORMAT_RGB565, /* Uses external PLD */
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+};
+
+static const u32 pl111_realview_pixel_formats[] = {
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB4444,
+};
+
+/*
+ * The Integrator variant is a PL110 with a bunch of broken, or not
+ * yet implemented features
+ */
+static const struct pl111_variant_data pl110_integrator = {
+ .name = "PL110 Integrator",
+ .is_pl110 = true,
+ .broken_clockdivider = true,
+ .broken_vblank = true,
+ .formats = pl110_integrator_pixel_formats,
+ .nformats = ARRAY_SIZE(pl110_integrator_pixel_formats),
+ .fb_bpp = 16,
+};
+
+/*
+ * The IM-PD1 variant is a PL110 with a bunch of broken, or not
+ * yet implemented features
+ */
+static const struct pl111_variant_data pl110_impd1 = {
+ .name = "PL110 IM-PD1",
+ .is_pl110 = true,
+ .broken_clockdivider = true,
+ .broken_vblank = true,
+ .formats = pl110_integrator_pixel_formats,
+ .nformats = ARRAY_SIZE(pl110_integrator_pixel_formats),
+ .fb_bpp = 16,
+};
+
+/*
+ * This is the in-between PL110 variant found in the ARM Versatile,
+ * supporting RGB565/BGR565
+ */
+static const struct pl111_variant_data pl110_versatile = {
+ .name = "PL110 Versatile",
+ .is_pl110 = true,
+ .external_bgr = true,
+ .formats = pl110_versatile_pixel_formats,
+ .nformats = ARRAY_SIZE(pl110_versatile_pixel_formats),
+ .fb_bpp = 16,
+};
+
+/*
+ * RealView PL111 variant, the only real difference from the vanilla
+ * PL111 is that we select 16bpp framebuffer by default to be able
+ * to get 1024x768 without saturating the memory bus.
+ */
+static const struct pl111_variant_data pl111_realview = {
+ .name = "PL111 RealView",
+ .formats = pl111_realview_pixel_formats,
+ .nformats = ARRAY_SIZE(pl111_realview_pixel_formats),
+ .fb_bpp = 16,
+};
+
+/*
+ * Versatile Express PL111 variant, again we just push the maximum
+ * BPP to 16 to be able to get 1024x768 without saturating the memory
+ * bus. The clockdivider also seems broken on the Versatile Express.
+ */
+static const struct pl111_variant_data pl111_vexpress = {
+ .name = "PL111 Versatile Express",
+ .formats = pl111_realview_pixel_formats,
+ .nformats = ARRAY_SIZE(pl111_realview_pixel_formats),
+ .fb_bpp = 16,
+ .broken_clockdivider = true,
+};
+
+#define VEXPRESS_FPGAMUX_MOTHERBOARD 0x00
+#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_1 0x01
+#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_2 0x02
+
+static int pl111_vexpress_clcd_init(struct device *dev, struct device_node *np,
+ struct pl111_drm_dev_private *priv)
+{
+ struct platform_device *pdev;
+ struct device_node *root;
+ struct device_node *child;
+ struct device_node *ct_clcd = NULL;
+ struct regmap *map;
+ bool has_coretile_clcd = false;
+ bool has_coretile_hdlcd = false;
+ bool mux_motherboard = true;
+ u32 val;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_VEXPRESS_CONFIG))
+ return -ENODEV;
+
+ /*
+ * Check if we have a CLCD or HDLCD on the core tile by checking if a
+ * CLCD or HDLCD is available in the root of the device tree.
+ */
+ root = of_find_node_by_path("/");
+ if (!root)
+ return -EINVAL;
+
+ for_each_available_child_of_node(root, child) {
+ if (of_device_is_compatible(child, "arm,pl111")) {
+ has_coretile_clcd = true;
+ ct_clcd = child;
+ of_node_put(child);
+ break;
+ }
+ if (of_device_is_compatible(child, "arm,hdlcd")) {
+ has_coretile_hdlcd = true;
+ of_node_put(child);
+ break;
+ }
+ }
+
+ of_node_put(root);
+
+ /*
+ * If there is a coretile HDLCD and it has a driver,
+ * do not mux the CLCD on the motherboard to the DVI.
+ */
+ if (has_coretile_hdlcd && IS_ENABLED(CONFIG_DRM_HDLCD))
+ mux_motherboard = false;
+
+ /*
+ * On the Vexpress CA9 we let the CLCD on the coretile
+ * take precedence, so also in this case do not mux the
+ * motherboard to the DVI.
+ */
+ if (has_coretile_clcd)
+ mux_motherboard = false;
+
+ if (mux_motherboard) {
+ dev_info(dev, "DVI muxed to motherboard CLCD\n");
+ val = VEXPRESS_FPGAMUX_MOTHERBOARD;
+ } else if (ct_clcd == dev->of_node) {
+ dev_info(dev,
+ "DVI muxed to daughterboard 1 (core tile) CLCD\n");
+ val = VEXPRESS_FPGAMUX_DAUGHTERBOARD_1;
+ } else {
+ dev_info(dev, "core tile graphics present\n");
+ dev_info(dev, "this device will be deactivated\n");
+ return -ENODEV;
+ }
+
+ /* Call into deep Vexpress configuration API */
+ pdev = of_find_device_by_node(np);
+ if (!pdev) {
+ dev_err(dev, "can't find the sysreg device, deferring\n");
+ return -EPROBE_DEFER;
+ }
+
+ map = devm_regmap_init_vexpress_config(&pdev->dev);
+ if (IS_ERR(map)) {
+ platform_device_put(pdev);
+ return PTR_ERR(map);
+ }
+
+ ret = regmap_write(map, 0, val);
+ platform_device_put(pdev);
+ if (ret) {
+ dev_err(dev, "error setting DVI muxmode\n");
+ return -ENODEV;
+ }
+
+ priv->variant = &pl111_vexpress;
+ dev_info(dev, "initializing Versatile Express PL111\n");
+
+ return 0;
+}
+
+int pl111_versatile_init(struct device *dev, struct pl111_drm_dev_private *priv)
+{
+ const struct of_device_id *clcd_id;
+ enum versatile_clcd versatile_clcd_type;
+ struct device_node *np;
+ struct regmap *map;
+
+ np = of_find_matching_node_and_match(NULL, versatile_clcd_of_match,
+ &clcd_id);
+ if (!np) {
+ /* Non-ARM reference designs, just bail out */
+ return 0;
+ }
+
+ versatile_clcd_type = (enum versatile_clcd)clcd_id->data;
+
+ /* Versatile Express special handling */
+ if (versatile_clcd_type == VEXPRESS_CLCD_V2M) {
+ int ret = pl111_vexpress_clcd_init(dev, np, priv);
+ of_node_put(np);
+ if (ret)
+ dev_err(dev, "Versatile Express init failed - %d", ret);
+ return ret;
+ }
+
+ /*
+ * On the Integrator, check if we should use the IM-PD1 instead,
+ * if we find it, it will take precedence. This is on the Integrator/AP
+ * which only has this option for PL110 graphics.
+ */
+ if (versatile_clcd_type == INTEGRATOR_CLCD_CM) {
+ np = of_find_matching_node_and_match(NULL, impd1_clcd_of_match,
+ &clcd_id);
+ if (np)
+ versatile_clcd_type = (enum versatile_clcd)clcd_id->data;
+ }
+
+ map = syscon_node_to_regmap(np);
+ of_node_put(np);
+ if (IS_ERR(map)) {
+ dev_err(dev, "no Versatile syscon regmap\n");
+ return PTR_ERR(map);
+ }
+
+ switch (versatile_clcd_type) {
+ case INTEGRATOR_CLCD_CM:
+ versatile_syscon_map = map;
+ priv->variant = &pl110_integrator;
+ priv->variant_display_enable = pl111_integrator_enable;
+ dev_info(dev, "set up callbacks for Integrator PL110\n");
+ break;
+ case INTEGRATOR_IMPD1:
+ versatile_syscon_map = map;
+ priv->variant = &pl110_impd1;
+ priv->variant_display_enable = pl111_impd1_enable;
+ priv->variant_display_disable = pl111_impd1_disable;
+ dev_info(dev, "set up callbacks for IM-PD1 PL110\n");
+ break;
+ case VERSATILE_CLCD:
+ versatile_syscon_map = map;
+ /* This can do RGB565 with external PLD */
+ priv->variant = &pl110_versatile;
+ priv->variant_display_enable = pl111_versatile_enable;
+ priv->variant_display_disable = pl111_versatile_disable;
+ /*
+ * The Versatile has a variant halfway between PL110
+ * and PL111 where these two registers have already been
+ * swapped.
+ */
+ priv->ienb = CLCD_PL111_IENB;
+ priv->ctrl = CLCD_PL111_CNTL;
+ dev_info(dev, "set up callbacks for Versatile PL110\n");
+ break;
+ case REALVIEW_CLCD_EB:
+ case REALVIEW_CLCD_PB1176:
+ case REALVIEW_CLCD_PB11MP:
+ case REALVIEW_CLCD_PBA8:
+ case REALVIEW_CLCD_PBX:
+ versatile_syscon_map = map;
+ priv->variant = &pl111_realview;
+ priv->variant_display_enable = pl111_realview_clcd_enable;
+ priv->variant_display_disable = pl111_realview_clcd_disable;
+ dev_info(dev, "set up callbacks for RealView PL111\n");
+ break;
+ default:
+ dev_info(dev, "unknown Versatile system controller\n");
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pl111_versatile_init);
diff --git a/drivers/gpu/drm/pl111/pl111_versatile.h b/drivers/gpu/drm/pl111/pl111_versatile.h
new file mode 100644
index 000000000..143877010
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_versatile.h
@@ -0,0 +1,12 @@
+#include <linux/device.h>
+#include "pl111_drm.h"
+
+#ifndef PL111_VERSATILE_H
+#define PL111_VERSATILE_H
+
+struct device;
+struct pl111_drm_dev_private;
+
+int pl111_versatile_init(struct device *dev, struct pl111_drm_dev_private *priv);
+
+#endif