drm/pl111: Initial drm/kms driver for pl111
authorTom Cooksey <tom.cooksey@arm.com>
Thu, 13 Apr 2017 03:17:46 +0000 (20:17 -0700)
committerEric Anholt <eric@anholt.net>
Mon, 8 May 2017 19:24:06 +0000 (12:24 -0700)
This is a modesetting driver for the pl111 CLCD display controller
found on various ARM platforms such as the Versatile Express. The
driver has only been tested on the bcm911360_entphn platform so far,
with PRIME-based buffer sharing between vc4 and clcd.

It reuses the existing devicetree binding, while not using quite as
many of its properties as the fbdev driver does (those are left for
future work).

v2: Nearly complete rewrite by anholt, cutting 2/3 of the code thanks
    to DRM core's excellent new helpers.
v3: Don't match pl110 any more, don't attach if we don't have a DRM
    panel, use DRM_GEM_CMA_FOPS, update MAINTAINERS, use the simple
    display helper, use drm_gem_cma_dumb_create (same as our wrapper).
v4: Change the driver's .name to not clash with fbdev in sysfs, drop
    platform alias, drop redundant "drm" in DRM driver name, hook up
    .prepare_fb to the CMA helper so that DMA fences should work.
v5: Move register definitions inside the driver directory, fix build
    in COMPILE_TEST and !AMBA mode.
v6: Drop TIM2_CLKSEL for now to be consistent with existing DT
    bindings, switch back to external register definitions.

Signed-off-by: Tom Cooksey <tom.cooksey@arm.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org> (v5)
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/20170413031746.12921-2-eric@anholt.net
Documentation/gpu/index.rst
Documentation/gpu/pl111.rst [new file with mode: 0644]
MAINTAINERS
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/pl111/Kconfig [new file with mode: 0644]
drivers/gpu/drm/pl111/Makefile [new file with mode: 0644]
drivers/gpu/drm/pl111/pl111_connector.c [new file with mode: 0644]
drivers/gpu/drm/pl111/pl111_display.c [new file with mode: 0644]
drivers/gpu/drm/pl111/pl111_drm.h [new file with mode: 0644]
drivers/gpu/drm/pl111/pl111_drv.c [new file with mode: 0644]

index c572f09..037a39a 100644 (file)
@@ -12,6 +12,7 @@ Linux GPU Driver Developer's Guide
    drm-uapi
    i915
    meson
+   pl111
    tinydrm
    vc4
    vga-switcheroo
diff --git a/Documentation/gpu/pl111.rst b/Documentation/gpu/pl111.rst
new file mode 100644 (file)
index 0000000..9b03736
--- /dev/null
@@ -0,0 +1,6 @@
+==========================================
+ drm/pl111 ARM PrimeCell PL111 CLCD Driver
+==========================================
+
+.. kernel-doc:: drivers/gpu/drm/pl111/pl111_drv.c
+   :doc: ARM PrimeCell PL111 CLCD Driver
index e8f86bc..3e66c28 100644 (file)
@@ -4160,6 +4160,12 @@ F:       include/drm/drm*
 F:     include/uapi/drm/drm*
 F:     include/linux/vga*
 
+DRM DRIVER FOR ARM PL111 CLCD
+M:     Eric Anholt <eric@anholt.net>
+T:     git git://anongit.freedesktop.org/drm/drm-misc
+S:     Supported
+F:     drivers/gpu/drm/pl111/
+
 DRM DRIVER FOR AST SERVER GRAPHICS CHIPS
 M:     Dave Airlie <airlied@redhat.com>
 S:     Odd Fixes
index f57540d..83cb2a8 100644 (file)
@@ -276,6 +276,8 @@ source "drivers/gpu/drm/meson/Kconfig"
 
 source "drivers/gpu/drm/tinydrm/Kconfig"
 
+source "drivers/gpu/drm/pl111/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
index aa62ded..c156fec 100644 (file)
@@ -97,3 +97,4 @@ obj-y                 += hisilicon/
 obj-$(CONFIG_DRM_ZTE)  += zte/
 obj-$(CONFIG_DRM_MXSFB)        += mxsfb/
 obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
+obj-$(CONFIG_DRM_PL111) += pl111/
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig
new file mode 100644 (file)
index 0000000..ede49ef
--- /dev/null
@@ -0,0 +1,12 @@
+config DRM_PL111
+       tristate "DRM Support for PL111 CLCD Controller"
+       depends on DRM
+       depends on ARM || ARM64 || COMPILE_TEST
+       select DRM_KMS_HELPER
+       select DRM_KMS_CMA_HELPER
+       select DRM_GEM_CMA_HELPER
+       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 (file)
index 0000000..01caee7
--- /dev/null
@@ -0,0 +1,5 @@
+pl111_drm-y += pl111_connector.o \
+               pl111_display.o \
+               pl111_drv.o
+
+obj-$(CONFIG_DRM_PL111) += pl111_drm.o
diff --git a/drivers/gpu/drm/pl111/pl111_connector.c b/drivers/gpu/drm/pl111/pl111_connector.c
new file mode 100644 (file)
index 0000000..3f213d7
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * (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
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * pl111_drm_connector.c
+ * Implementation of the connector functions for PL111 DRM
+ */
+#include <linux/amba/clcd-regs.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "pl111_drm.h"
+
+static void pl111_connector_destroy(struct drm_connector *connector)
+{
+       struct pl111_drm_connector *pl111_connector =
+               to_pl111_connector(connector);
+
+       if (pl111_connector->panel)
+               drm_panel_detach(pl111_connector->panel);
+
+       drm_connector_unregister(connector);
+       drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status pl111_connector_detect(struct drm_connector
+                                                       *connector, bool force)
+{
+       struct pl111_drm_connector *pl111_connector =
+               to_pl111_connector(connector);
+
+       return (pl111_connector->panel ?
+               connector_status_connected :
+               connector_status_disconnected);
+}
+
+static int pl111_connector_helper_get_modes(struct drm_connector *connector)
+{
+       struct pl111_drm_connector *pl111_connector =
+               to_pl111_connector(connector);
+
+       if (!pl111_connector->panel)
+               return 0;
+
+       return drm_panel_get_modes(pl111_connector->panel);
+}
+
+const struct drm_connector_funcs connector_funcs = {
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = pl111_connector_destroy,
+       .detect = pl111_connector_detect,
+       .dpms = drm_atomic_helper_connector_dpms,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+const struct drm_connector_helper_funcs connector_helper_funcs = {
+       .get_modes = pl111_connector_helper_get_modes,
+};
+
+/* Walks the OF graph to find the panel node and then asks DRM to look
+ * up the panel.
+ */
+static struct drm_panel *pl111_get_panel(struct device *dev)
+{
+       struct device_node *endpoint, *panel_node;
+       struct device_node *np = dev->of_node;
+       struct drm_panel *panel;
+
+       endpoint = of_graph_get_next_endpoint(np, NULL);
+       if (!endpoint) {
+               dev_err(dev, "no endpoint to fetch panel\n");
+               return NULL;
+       }
+
+       /* don't proceed if we have an endpoint but no panel_node tied to it */
+       panel_node = of_graph_get_remote_port_parent(endpoint);
+       of_node_put(endpoint);
+       if (!panel_node) {
+               dev_err(dev, "no valid panel node\n");
+               return NULL;
+       }
+
+       panel = of_drm_find_panel(panel_node);
+       of_node_put(panel_node);
+
+       return panel;
+}
+
+int pl111_connector_init(struct drm_device *dev)
+{
+       struct pl111_drm_dev_private *priv = dev->dev_private;
+       struct pl111_drm_connector *pl111_connector = &priv->connector;
+       struct drm_connector *connector = &pl111_connector->connector;
+
+       drm_connector_init(dev, connector, &connector_funcs,
+                          DRM_MODE_CONNECTOR_DPI);
+       drm_connector_helper_add(connector, &connector_helper_funcs);
+
+       pl111_connector->panel = pl111_get_panel(dev->dev);
+       if (pl111_connector->panel)
+               drm_panel_attach(pl111_connector->panel, connector);
+
+       return 0;
+}
+
diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c
new file mode 100644 (file)
index 0000000..39a5c33
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * (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
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#include <linux/amba/clcd-regs.h>
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/dma-buf.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.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 u32 pl111_get_fb_offset(struct drm_plane_state *pstate)
+{
+       struct drm_framebuffer *fb = pstate->fb;
+       struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, 0);
+
+       return (obj->paddr +
+               fb->offsets[0] +
+               fb->format->cpp[0] * pstate->src_x +
+               fb->pitches[0] * pstate->src_y);
+}
+
+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 = pl111_get_fb_offset(pstate);
+
+               /* 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_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.connector;
+       u32 cntl;
+       u32 ppl, hsw, hfp, hbp;
+       u32 lpp, vsw, vfp, vbp;
+       u32 cpl;
+       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);
+       /* XXX: We currently always use CLCDCLK with no divisor.  We
+        * could probably reduce power consumption by using HCLK
+        * (apb_pclk) with a divisor when it gets us near our target
+        * pixel clock.
+        */
+       writel(((mode->flags & DRM_MODE_FLAG_NHSYNC) ? TIM2_IHS : 0) |
+              ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? TIM2_IVS : 0) |
+              ((connector->display_info.bus_flags &
+                DRM_BUS_FLAG_DE_LOW) ? TIM2_IOE : 0) |
+              ((connector->display_info.bus_flags &
+                DRM_BUS_FLAG_PIXDATA_NEGEDGE) ? TIM2_IPC : 0) |
+              TIM2_BCD |
+              (cpl << 16),
+              priv->regs + CLCD_TIM2);
+       writel(0, priv->regs + CLCD_TIM3);
+
+       drm_panel_prepare(priv->connector.panel);
+
+       /* Enable and Power Up */
+       cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1);
+
+       /* Note that the the 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.
+        */
+       switch (fb->format->format) {
+       case DRM_FORMAT_ABGR8888:
+       case DRM_FORMAT_XBGR8888:
+               cntl |= CNTL_LCDBPP24;
+               break;
+       case DRM_FORMAT_ARGB8888:
+       case DRM_FORMAT_XRGB8888:
+               cntl |= CNTL_LCDBPP24 | CNTL_BGR;
+               break;
+       case DRM_FORMAT_BGR565:
+               cntl |= CNTL_LCDBPP16_565;
+               break;
+       case DRM_FORMAT_RGB565:
+               cntl |= CNTL_LCDBPP16_565 | CNTL_BGR;
+               break;
+       case DRM_FORMAT_ABGR1555:
+       case DRM_FORMAT_XBGR1555:
+               cntl |= CNTL_LCDBPP16;
+               break;
+       case DRM_FORMAT_ARGB1555:
+       case DRM_FORMAT_XRGB1555:
+               cntl |= CNTL_LCDBPP16 | CNTL_BGR;
+               break;
+       case DRM_FORMAT_ABGR4444:
+       case DRM_FORMAT_XBGR4444:
+               cntl |= CNTL_LCDBPP16_444;
+               break;
+       case DRM_FORMAT_ARGB4444:
+       case DRM_FORMAT_XRGB4444:
+               cntl |= CNTL_LCDBPP16_444 | CNTL_BGR;
+               break;
+       default:
+               WARN_ONCE(true, "Unknown FB format 0x%08x\n",
+                         fb->format->format);
+               break;
+       }
+
+       writel(cntl, priv->regs + CLCD_PL111_CNTL);
+
+       drm_panel_enable(priv->connector.panel);
+
+       drm_crtc_vblank_on(crtc);
+}
+
+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;
+
+       drm_crtc_vblank_off(crtc);
+
+       drm_panel_disable(priv->connector.panel);
+
+       /* Disable and Power Down */
+       writel(0, priv->regs + CLCD_PL111_CNTL);
+
+       drm_panel_unprepare(priv->connector.panel);
+
+       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 = pl111_get_fb_offset(pstate);
+
+               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);
+       }
+}
+
+int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+       struct pl111_drm_dev_private *priv = drm->dev_private;
+
+       writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + CLCD_PL111_IENB);
+
+       return 0;
+}
+
+void pl111_disable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+       struct pl111_drm_dev_private *priv = drm->dev_private;
+
+       writel(0, priv->regs + CLCD_PL111_IENB);
+}
+
+static int pl111_display_prepare_fb(struct drm_simple_display_pipe *pipe,
+                                   struct drm_plane_state *plane_state)
+{
+       return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
+}
+
+const struct drm_simple_display_pipe_funcs pl111_display_funcs = {
+       .check = pl111_display_check,
+       .enable = pl111_display_enable,
+       .disable = pl111_display_disable,
+       .update = pl111_display_update,
+       .prepare_fb = pl111_display_prepare_fb,
+};
+
+int pl111_display_init(struct drm_device *drm)
+{
+       struct pl111_drm_dev_private *priv = drm->dev_private;
+       struct device *dev = drm->dev;
+       struct device_node *endpoint;
+       u32 tft_r0b0g0[3];
+       int ret;
+       static const u32 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,
+       };
+
+       endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+       if (!endpoint)
+               return -ENODEV;
+
+       if (of_property_read_u32_array(endpoint,
+                                      "arm,pl11x,tft-r0g0b0-pads",
+                                      tft_r0b0g0,
+                                      ARRAY_SIZE(tft_r0b0g0)) != 0) {
+               dev_err(dev, "arm,pl11x,tft-r0g0b0-pads should be 3 ints\n");
+               of_node_put(endpoint);
+               return -ENOENT;
+       }
+       of_node_put(endpoint);
+
+       if (tft_r0b0g0[0] != 0 ||
+           tft_r0b0g0[1] != 8 ||
+           tft_r0b0g0[2] != 16) {
+               dev_err(dev, "arm,pl11x,tft-r0g0b0-pads != [0,8,16] not yet supported\n");
+               return -EINVAL;
+       }
+
+       ret = drm_simple_display_pipe_init(drm, &priv->pipe,
+                                          &pl111_display_funcs,
+                                          formats, ARRAY_SIZE(formats),
+                                          &priv->connector.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 (file)
index 0000000..f381593
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *
+ * (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
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#ifndef _PL111_DRM_H_
+#define _PL111_DRM_H_
+
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define CLCD_IRQ_NEXTBASE_UPDATE BIT(2)
+
+struct pl111_drm_connector {
+       struct drm_connector connector;
+       struct drm_panel *panel;
+};
+
+struct pl111_drm_dev_private {
+       struct drm_device *drm;
+
+       struct pl111_drm_connector connector;
+       struct drm_simple_display_pipe pipe;
+       struct drm_fbdev_cma *fbdev;
+
+       void *regs;
+       struct clk *clk;
+};
+
+#define to_pl111_connector(x) \
+       container_of(x, struct pl111_drm_connector, connector)
+
+int pl111_display_init(struct drm_device *dev);
+int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc);
+void pl111_disable_vblank(struct drm_device *drm, unsigned int crtc);
+irqreturn_t pl111_irq(int irq, void *data);
+int pl111_connector_init(struct drm_device *dev);
+int pl111_encoder_init(struct drm_device *dev);
+int pl111_dumb_create(struct drm_file *file_priv,
+                     struct drm_device *dev,
+                     struct drm_mode_create_dumb *args);
+
+#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 (file)
index 0000000..936403f
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * (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
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * DOC: ARM PrimeCell PL111 CLCD Driver
+ *
+ * The PL111 is a simple LCD controller that can support TFT and STN
+ * displays.  This driver exposes a standard KMS interface for them.
+ *
+ * This driver uses the same Device Tree binding as the fbdev CLCD
+ * driver.  While the fbdev driver supports panels that may be
+ * connected to the CLCD internally to the CLCD driver, in DRM the
+ * panels get split out to drivers/gpu/drm/panels/.  This means that,
+ * in converting from using fbdev to using DRM, you also need to write
+ * a panel driver (which may be as simple as an entry in
+ * panel-simple.c).
+ *
+ * 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 X11 software cursor layer.
+ *
+ * TODO:
+ *
+ * - Fix race between setting plane base address and getting IRQ for
+ *   vsync firing the pageflip completion.
+ *
+ * - Expose the correct set of formats we can support based on the
+ *   "arm,pl11x,tft-r0g0b0-pads" DT property.
+ *
+ * - Use the "max-memory-bandwidth" DT property to filter the
+ *   supported formats.
+ *
+ * - Read back hardware state at boot to skip reprogramming the
+ *   hardware when doing a no-op modeset.
+ *
+ * - Use the internal clock divisor to reduce power consumption by
+ *   using HCLK (apb_pclk) when appropriate.
+ */
+
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd-regs.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+
+#include "pl111_drm.h"
+
+#define DRIVER_DESC      "DRM module for PL111"
+
+struct drm_mode_config_funcs mode_config_funcs = {
+       .fb_create = drm_fb_cma_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;
+       int ret = 0;
+
+       drm_mode_config_init(dev);
+       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;
+
+       ret = pl111_connector_init(dev);
+       if (ret) {
+               dev_err(dev->dev, "Failed to create pl111_drm_connector\n");
+               goto out_config;
+       }
+
+       /* Don't actually attach if we didn't find a drm_panel
+        * attached to us.  This will allow a kernel to include both
+        * the fbdev pl111 driver and this one, and choose between
+        * them based on which subsystem has support for the panel.
+        */
+       if (!priv->connector.panel) {
+               dev_info(dev->dev,
+                        "Disabling due to lack of DRM panel device.\n");
+               ret = -ENODEV;
+               goto out_config;
+       }
+
+       ret = pl111_display_init(dev);
+       if (ret != 0) {
+               dev_err(dev->dev, "Failed to init display\n");
+               goto out_config;
+       }
+
+       ret = drm_vblank_init(dev, 1);
+       if (ret != 0) {
+               dev_err(dev->dev, "Failed to init vblank\n");
+               goto out_config;
+       }
+
+       drm_mode_config_reset(dev);
+
+       priv->fbdev = drm_fbdev_cma_init(dev, 32,
+                                        dev->mode_config.num_connector);
+
+       drm_kms_helper_poll_init(dev);
+
+       goto finish;
+
+out_config:
+       drm_mode_config_cleanup(dev);
+finish:
+       return ret;
+}
+
+DEFINE_DRM_GEM_CMA_FOPS(drm_fops);
+
+static void pl111_lastclose(struct drm_device *dev)
+{
+       struct pl111_drm_dev_private *priv = dev->dev_private;
+
+       drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static struct drm_driver pl111_drm_driver = {
+       .driver_features =
+               DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC,
+       .lastclose = pl111_lastclose,
+       .ioctls = NULL,
+       .fops = &drm_fops,
+       .name = "pl111",
+       .desc = DRIVER_DESC,
+       .date = "20170317",
+       .major = 1,
+       .minor = 0,
+       .patchlevel = 0,
+       .dumb_create = drm_gem_cma_dumb_create,
+       .dumb_destroy = drm_gem_dumb_destroy,
+       .dumb_map_offset = drm_gem_cma_dumb_map_offset,
+       .gem_free_object = drm_gem_cma_free_object,
+       .gem_vm_ops = &drm_gem_cma_vm_ops,
+
+       .enable_vblank = pl111_enable_vblank,
+       .disable_vblank = pl111_disable_vblank,
+
+       .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+       .gem_prime_import = drm_gem_prime_import,
+       .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+       .gem_prime_export = drm_gem_prime_export,
+       .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+};
+
+#ifdef CONFIG_ARM_AMBA
+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;
+       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->clk = devm_clk_get(dev, "clcdclk");
+       if (IS_ERR(priv->clk)) {
+               dev_err(dev, "CLCD: unable to get clk.\n");
+               ret = PTR_ERR(priv->clk);
+               goto dev_unref;
+       }
+
+       priv->regs = devm_ioremap_resource(dev, &amba_dev->res);
+       if (!priv->regs) {
+               dev_err(dev, "%s failed mmio\n", __func__);
+               return -EINVAL;
+       }
+
+       /* turn off interrupts before requesting the irq */
+       writel(0, priv->regs + CLCD_PL111_IENB);
+
+       ret = devm_request_irq(dev, amba_dev->irq[0], pl111_irq, 0,
+                              "pl111", 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_unref;
+
+       ret = drm_dev_register(drm, 0);
+       if (ret < 0)
+               goto dev_unref;
+
+       return 0;
+
+dev_unref:
+       drm_dev_unref(drm);
+       return ret;
+}
+
+static int pl111_amba_remove(struct amba_device *amba_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->fbdev)
+               drm_fbdev_cma_fini(priv->fbdev);
+       drm_mode_config_cleanup(drm);
+       drm_dev_unref(drm);
+
+       return 0;
+}
+
+static struct amba_id pl111_id_table[] = {
+       {
+               .id = 0x00041111,
+               .mask = 0x000fffff,
+       },
+       {0, 0},
+};
+
+static struct amba_driver pl111_amba_driver = {
+       .drv = {
+               .name = "drm-clcd-pl111",
+       },
+       .probe = pl111_amba_probe,
+       .remove = pl111_amba_remove,
+       .id_table = pl111_id_table,
+};
+
+module_amba_driver(pl111_amba_driver);
+#endif /* CONFIG_ARM_AMBA */
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("ARM Ltd.");
+MODULE_LICENSE("GPL");