[PATCH 2/2] drm/pl111: Initial drm/kms driver for pl111

From: Eric Anholt
Date: Fri Mar 17 2017 - 20:42:36 EST


From: Tom Cooksey <tom.cooksey@xxxxxxx>

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.

Signed-off-by: Tom Cooksey <tom.cooksey@xxxxxxx>
Signed-off-by: Eric Anholt <eric@xxxxxxxxxx>
---
Documentation/gpu/index.rst | 1 +
Documentation/gpu/pl111.rst | 6 +
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/pl111/Kconfig | 12 ++
drivers/gpu/drm/pl111/Makefile | 8 +
drivers/gpu/drm/pl111/pl111_connector.c | 127 ++++++++++++++
drivers/gpu/drm/pl111/pl111_crtc.c | 239 ++++++++++++++++++++++++++
drivers/gpu/drm/pl111/pl111_drm.h | 64 +++++++
drivers/gpu/drm/pl111/pl111_drv.c | 292 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/pl111/pl111_encoder.c | 50 ++++++
drivers/gpu/drm/pl111/pl111_gem.c | 35 ++++
drivers/gpu/drm/pl111/pl111_plane.c | 167 ++++++++++++++++++
13 files changed, 1004 insertions(+)
create mode 100644 Documentation/gpu/pl111.rst
create mode 100644 drivers/gpu/drm/pl111/Kconfig
create mode 100644 drivers/gpu/drm/pl111/Makefile
create mode 100644 drivers/gpu/drm/pl111/pl111_connector.c
create mode 100644 drivers/gpu/drm/pl111/pl111_crtc.c
create mode 100644 drivers/gpu/drm/pl111/pl111_drm.h
create mode 100644 drivers/gpu/drm/pl111/pl111_drv.c
create mode 100644 drivers/gpu/drm/pl111/pl111_encoder.c
create mode 100644 drivers/gpu/drm/pl111/pl111_gem.c
create mode 100644 drivers/gpu/drm/pl111/pl111_plane.c

diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst
index e998ee0d0dd5..71bf510d47e8 100644
--- a/Documentation/gpu/index.rst
+++ b/Documentation/gpu/index.rst
@@ -11,6 +11,7 @@ Linux GPU Driver Developer's Guide
drm-kms-helpers
drm-uapi
i915
+ pl111
tinydrm
vc4
vga-switcheroo
diff --git a/Documentation/gpu/pl111.rst b/Documentation/gpu/pl111.rst
new file mode 100644
index 000000000000..9b03736d33dd
--- /dev/null
+++ b/Documentation/gpu/pl111.rst
@@ -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
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 78d7fc0ebb57..d1c6c12199b7 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -274,6 +274,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
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 59aae43005ee..99810a529bb0 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -95,3 +95,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
index 000000000000..ede49efd531f
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Kconfig
@@ -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
index 000000000000..20a7fd76d513
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Makefile
@@ -0,0 +1,8 @@
+pl111_drm-y += pl111_connector.o \
+ pl111_crtc.o \
+ pl111_drv.o \
+ pl111_encoder.o \
+ pl111_gem.o \
+ pl111_plane.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
index 000000000000..9811d1eadb63
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_connector.c
@@ -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@xxxxxxxx>
+ * 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_create(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_crtc.c b/drivers/gpu/drm/pl111/pl111_crtc.c
new file mode 100644
index 000000000000..4c73bdd3b7e4
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_crtc.c
@@ -0,0 +1,239 @@
+/*
+ * (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@xxxxxxxx>
+ * 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_crtc.c
+ * Implementation of the CRTC functions for PL111 DRM
+ */
+#include <linux/amba/clcd-regs.h>
+#include <linux/clk.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_panel.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->crtc);
+
+ status = IRQ_HANDLED;
+ }
+
+ /* Clear the interrupt once done */
+ writel(irq_stat, priv->regs + CLCD_PL111_ICR);
+
+ return status;
+}
+
+static int pl111_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ const struct drm_display_mode *mode = &state->mode;
+
+ if (!state->active)
+ return 0;
+
+ if (mode->hdisplay % 16)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void pl111_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = crtc->dev->dev_private;
+ const struct drm_display_mode *mode = &crtc->state->mode;
+ struct drm_connector *connector = &priv->connector.connector;
+ unsigned int ppl, hsw, hfp, hbp;
+ unsigned int lpp, vsw, vfp, vbp;
+ unsigned int 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);
+ }
+
+ 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) |
+ TIM2_CLKSEL,
+ priv->regs + CLCD_TIM2);
+ writel(0, priv->regs + CLCD_TIM3);
+}
+
+static void pl111_crtc_helper_enable(struct drm_crtc *crtc)
+{
+ struct pl111_drm_dev_private *priv = crtc->dev->dev_private;
+ u32 cntl;
+
+ clk_prepare_enable(priv->clk);
+
+ drm_panel_prepare(priv->connector.panel);
+
+ /* Enable and Power Up */
+ cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1);
+
+ /* Keep the format that the primary plane had set up. */
+ cntl |= readl(priv->regs + CLCD_PL111_CNTL) & (7 << 1);
+
+ writel(cntl, priv->regs + CLCD_PL111_CNTL);
+
+ drm_panel_enable(priv->connector.panel);
+}
+
+void pl111_crtc_helper_disable(struct drm_crtc *crtc)
+{
+ struct pl111_drm_dev_private *priv = crtc->dev->dev_private;
+
+ drm_panel_disable(priv->connector.panel);
+
+ /* Disable and Power Down */
+ writel(readl(priv->regs + CLCD_PL111_CNTL) & (7 << 1),
+ priv->regs + CLCD_PL111_CNTL);
+
+ drm_panel_unprepare(priv->connector.panel);
+
+ /* Disable clock */
+ clk_disable_unprepare(priv->clk);
+}
+
+static void pl111_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct drm_pending_vblank_event *event = crtc->state->event;
+
+ 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_enable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+
+ clk_prepare_enable(priv->clk);
+
+ writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + CLCD_PL111_IENB);
+
+ return 0;
+}
+
+static void pl111_disable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+
+ writel(0, priv->regs + CLCD_PL111_IENB);
+
+ clk_disable_unprepare(priv->clk);
+}
+
+const struct drm_crtc_funcs crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .enable_vblank = pl111_enable_vblank,
+ .disable_vblank = pl111_disable_vblank,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+ .mode_set_nofb = pl111_crtc_helper_mode_set_nofb,
+ .atomic_check = pl111_crtc_atomic_check,
+ .atomic_flush = pl111_crtc_helper_atomic_flush,
+ .disable = pl111_crtc_helper_disable,
+ .enable = pl111_crtc_helper_enable,
+};
+
+int pl111_crtc_create(struct drm_device *dev)
+{
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ struct drm_crtc *crtc = &priv->crtc;
+
+ drm_crtc_init_with_planes(dev, crtc,
+ &priv->primary, NULL,
+ &crtc_funcs, "primary");
+ drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+ /* XXX: The runtime clock disabling still results in
+ * occasional system hangs, and needs debugging.
+ */
+ clk_prepare_enable(priv->clk);
+
+ 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 000000000000..36e4ce770a6c
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm.h
@@ -0,0 +1,64 @@
+/*
+ *
+ * (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@xxxxxxxx>
+ * 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>
+
+#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_crtc crtc;
+ struct drm_encoder encoder;
+ struct drm_plane primary;
+ struct drm_fbdev_cma *fbdev;
+
+ void *regs;
+ struct clk *clk;
+};
+
+#define to_pl111_connector(x) \
+ container_of(x, struct pl111_drm_connector, connector)
+
+/* CRTC Functions */
+int pl111_crtc_create(struct drm_device *dev);
+irqreturn_t pl111_irq(int irq, void *data);
+
+int pl111_primary_plane_init(struct drm_device *dev);
+
+/* Connector Functions */
+int pl111_connector_create(struct drm_device *dev);
+
+/* Encoder Functions */
+int pl111_encoder_init(struct drm_device *dev);
+
+/* GEM Functions */
+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
index 000000000000..571c296dddf7
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drv.c
@@ -0,0 +1,292 @@
+/*
+ * (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@xxxxxxxx>
+ * 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;
+
+ if (!priv)
+ return -EINVAL;
+
+ 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_primary_plane_init(dev);
+ if (ret != 0) {
+ dev_err(dev->dev, "Failed to init primary plane\n");
+ goto out_config;
+ }
+
+ ret = pl111_crtc_create(dev);
+ if (ret) {
+ dev_err(dev->dev, "Failed to create crtc\n");
+ goto out_config;
+ }
+
+ ret = pl111_connector_create(dev);
+ if (ret) {
+ dev_err(dev->dev, "Failed to create pl111_drm_connector\n");
+ goto out_config;
+ }
+
+ ret = pl111_encoder_init(dev);
+ if (ret) {
+ dev_err(dev->dev, "Failed to create pl111_drm_encoder\n");
+ goto out_config;
+ }
+
+ ret = drm_mode_connector_attach_encoder(&priv->connector.connector,
+ &priv->encoder);
+ if (ret != 0) {
+ dev_err(dev->dev, "Failed to attach encoder\n");
+ goto out_config;
+ }
+
+ priv->connector.connector.encoder = &priv->encoder;
+
+ 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;
+}
+
+static const struct file_operations drm_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .mmap = drm_gem_cma_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+};
+
+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_drm",
+ .desc = DRIVER_DESC,
+ .date = "20170317",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+ .dumb_create = pl111_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,
+
+ .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) {
+ dev_err(dev, "Failed to init modeset\n");
+ 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 = 0x00041110,
+ .mask = 0x000ffffe,
+ },
+ {0, 0},
+};
+
+static struct amba_driver pl111_amba_driver = {
+ .drv = {
+ .name = "clcd-pl11x",
+ },
+ .probe = pl111_amba_probe,
+ .remove = pl111_amba_remove,
+ .id_table = pl111_id_table,
+};
+#endif /* CONFIG_ARM_AMBA */
+
+module_amba_driver(pl111_amba_driver);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("ARM Ltd.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pl111_drm");
diff --git a/drivers/gpu/drm/pl111/pl111_encoder.c b/drivers/gpu/drm/pl111/pl111_encoder.c
new file mode 100644
index 000000000000..78d67ceb46f3
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_encoder.c
@@ -0,0 +1,50 @@
+/*
+ * (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@xxxxxxxx>
+ * 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_encoder.c
+ * Implementation of the encoder 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_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+static const struct drm_encoder_funcs pl111_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+int pl111_encoder_init(struct drm_device *dev)
+{
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ struct drm_encoder *encoder = &priv->encoder;
+ int ret;
+
+ ret = drm_encoder_init(dev, encoder, &pl111_encoder_funcs,
+ DRM_MODE_ENCODER_DPI, NULL);
+ if (ret)
+ return ret;
+
+ encoder->crtc = &priv->crtc;
+ encoder->possible_crtcs = drm_crtc_mask(encoder->crtc);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_gem.c b/drivers/gpu/drm/pl111/pl111_gem.c
new file mode 100644
index 000000000000..b7b0e453dea9
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_gem.c
@@ -0,0 +1,35 @@
+/*
+ * (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@xxxxxxxx>
+ * 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_gem.c
+ * Implementation of the GEM functions for PL111 DRM
+ */
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include "pl111_drm.h"
+
+int pl111_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev, struct drm_mode_create_dumb *args)
+{
+ args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+ return drm_gem_cma_dumb_create_internal(file_priv, dev, args);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_plane.c b/drivers/gpu/drm/pl111/pl111_plane.c
new file mode 100644
index 000000000000..bcdc2f43de46
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_plane.c
@@ -0,0 +1,167 @@
+/*
+ * (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@xxxxxxxx>
+ * 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/of_graph.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "pl111_drm.h"
+
+static int pl111_primary_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ return 0;
+}
+
+static void pl111_primary_plane_atomic_update(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_device *dev = plane->dev;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ struct drm_framebuffer *fb = plane->state->fb;
+ struct drm_gem_cma_object *obj;
+ u32 addr, cntl;
+
+ if (!fb)
+ return;
+
+ obj = drm_fb_cma_get_gem_obj(fb, 0);
+ addr = obj->paddr + fb->offsets[0];
+ addr += fb->format->cpp[0] * plane->state->src_x;
+ addr += fb->pitches[0] * plane->state->src_y;
+
+ writel(addr, priv->regs + CLCD_UBAS);
+ writel(addr + (fb->height - 1 * fb->pitches[0]),
+ priv->regs + CLCD_LBAS);
+
+ cntl = readl(priv->regs + CLCD_PL111_CNTL);
+ cntl &= ~(7 << 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;
+ }
+
+ writel(cntl, priv->regs + CLCD_PL111_CNTL);
+}
+
+static const struct drm_plane_helper_funcs pl111_primary_plane_helper_funcs = {
+ .atomic_check = pl111_primary_plane_atomic_check,
+ .atomic_update = pl111_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs pl111_primary_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int pl111_primary_plane_init(struct drm_device *drm)
+{
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ struct drm_plane *plane = &priv->primary;
+ struct device *dev = drm->dev;
+ 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,
+ };
+ struct device_node *endpoint;
+ u32 tft_r0b0g0[3];
+ int ret;
+
+ 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_universal_plane_init(drm, plane, 0,
+ &pl111_primary_plane_funcs,
+ formats, ARRAY_SIZE(formats),
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret)
+ return ret;
+
+ drm_plane_helper_add(plane, &pl111_primary_plane_helper_funcs);
+
+ return 0;
+}
--
2.11.0