aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2017-03-15 11:50:12 +0000
committerJon Medhurst <tixy@linaro.org>2017-04-03 11:59:00 +0100
commit4aa6c1511e22e868407474996db6178a5bd8c758 (patch)
treea8690dfbe9a37ce54abef8fc85551be07f65633e
parented0a1bd8019af005526c275d57f2c5bee578d73f (diff)
drm: hdlcd: Fork drm_fb_cma_helper -> hdlcd_fb_helper
Because we need to modify it to get Android and Mali working. Files files generated with: cp drivers/gpu/drm/drm_fb_cma_helper.c drivers/gpu/drm/arm/hdlcd_fb_helper.c sed -e s/drm_fb_cma/hdlcd_fb/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.c sed -e s/drm_fbdev_cma/hdlcd_drm_fbdev/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.c sed -e s/fbdev_cma/hdlcd_fbdev/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.c sed -e s/fb_cma/hdlcd_fb/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.c sed -e 's/<drm\/hdlcd_fb_helper\.h>/\"hdlcd_fb_helper\.h\"/' -i drivers/gpu/drm/arm/hdlcd_fb_helper.c cp include/drm/drm_fb_cma_helper.h drivers/gpu/drm/arm/hdlcd_fb_helper.h sed -e s/drm_fb_cma/hdlcd_fb/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.h sed -e s/drm_fbdev_cma/hdlcd_drm_fbdev/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.h sed -e s/fbdev_cma/hdlcd_fbdev/g -i drivers/gpu/drm/arm/hdlcd_fb_helper.h sed -e 's/<drm\/drm_fb_cma_helper\.h>/\"hdlcd_fb_helper\.h\"/' -i drivers/gpu/drm/arm/hdlcd_drv.c sed -e s/drm_fb_cma/hdlcd_fb/g -i drivers/gpu/drm/arm/hdlcd_drv.c sed -e s/drm_fbdev_cma/hdlcd_drm_fbdev/g -i drivers/gpu/drm/arm/hdlcd_drv.c sed -e s/drm_fbdev_cma/hdlcd_drm_fbdev/g -i drivers/gpu/drm/arm/hdlcd_drv.h sed -e 's/<drm\/drm_fb_cma_helper\.h>/\"hdlcd_fb_helper\.h\"/' -i drivers/gpu/drm/arm/hdlcd_crtc.c sed -e s/drm_fb_cma/hdlcd_fb/g -i drivers/gpu/drm/arm/hdlcd_crtc.c sed -e 's/hdlcd_crtc\.o/hdlcd_crtc\.o hdlcd_fb_helper\.o/g' -i drivers/gpu/drm/arm/Makefile Signed-off-by: Jon Medhurst <tixy@linaro.org> # Please enter the commit message for your changes. Lines starting # with '#' will be kept; you may remove them yourself if you want to. # An empty message aborts the commit. # # Date: Wed Mar 15 11:50:12 2017 +0000 # # HEAD detached from 0c05917f761c # Changes to be committed: # modified: drivers/gpu/drm/arm/Makefile # modified: drivers/gpu/drm/arm/hdlcd_crtc.c # modified: drivers/gpu/drm/arm/hdlcd_drv.c # modified: drivers/gpu/drm/arm/hdlcd_drv.h # new file: drivers/gpu/drm/arm/hdlcd_fb_helper.c # new file: drivers/gpu/drm/arm/hdlcd_fb_helper.h #
-rw-r--r--drivers/gpu/drm/arm/Makefile2
-rw-r--r--drivers/gpu/drm/arm/hdlcd_crtc.c4
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.c16
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.h2
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.c641
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.h54
6 files changed, 707 insertions, 12 deletions
diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile
index bb8b158ff90d..6e09ee75681e 100644
--- a/drivers/gpu/drm/arm/Makefile
+++ b/drivers/gpu/drm/arm/Makefile
@@ -1,4 +1,4 @@
-hdlcd-y := hdlcd_drv.o hdlcd_crtc.o
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_fb_helper.o hdlcd_fb_helper.o
obj-$(CONFIG_DRM_HDLCD) += hdlcd.o
mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o
obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o
diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c
index 34ce0dd7cabe..b6010098ba87 100644
--- a/drivers/gpu/drm/arm/hdlcd_crtc.c
+++ b/drivers/gpu/drm/arm/hdlcd_crtc.c
@@ -14,7 +14,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
-#include <drm/drm_fb_cma_helper.h>
+#include "hdlcd_fb_helper.h"
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_plane_helper.h>
@@ -250,7 +250,7 @@ static void hdlcd_plane_atomic_update(struct drm_plane *plane,
src_h = plane->state->src_h >> 16;
dest_w = plane->state->crtc_w;
dest_h = plane->state->crtc_h;
- gem = drm_fb_cma_get_gem_obj(fb, 0);
+ gem = hdlcd_fb_get_gem_obj(fb, 0);
scanout_start = gem->paddr + fb->offsets[0] +
plane->state->crtc_y * fb->pitches[0] +
plane->state->crtc_x *
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
index 96908d6cc9f6..869508c22a60 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.c
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -23,7 +23,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
-#include <drm/drm_fb_cma_helper.h>
+#include "hdlcd_fb_helper.h"
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
@@ -108,11 +108,11 @@ static void hdlcd_fb_output_poll_changed(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
- drm_fbdev_cma_hotplug_event(hdlcd->fbdev);
+ hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev);
}
static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
- .fb_create = drm_fb_cma_create,
+ .fb_create = hdlcd_fb_create,
.output_poll_changed = hdlcd_fb_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
@@ -132,7 +132,7 @@ static void hdlcd_lastclose(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
- drm_fbdev_cma_restore_mode(hdlcd->fbdev);
+ hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev);
}
static irqreturn_t hdlcd_irq(int irq, void *arg)
@@ -253,7 +253,7 @@ static int hdlcd_show_pxlclock(struct seq_file *m, void *arg)
static struct drm_info_list hdlcd_debugfs_list[] = {
{ "interrupt_count", hdlcd_show_underrun_count, 0 },
{ "clocks", hdlcd_show_pxlclock, 0 },
- { "fb", drm_fb_cma_debugfs_show, 0 },
+ { "fb", hdlcd_fb_debugfs_show, 0 },
};
static int hdlcd_debugfs_init(struct drm_minor *minor)
@@ -365,7 +365,7 @@ static int hdlcd_drm_bind(struct device *dev)
preferred_bpp = 32; /* If Mali present, assume 32bpp */
}
- hdlcd->fbdev = drm_fbdev_cma_init(drm, preferred_bpp,
+ hdlcd->fbdev = hdlcd_drm_fbdev_init(drm, preferred_bpp,
drm->mode_config.num_connector);
if (IS_ERR(hdlcd->fbdev)) {
@@ -382,7 +382,7 @@ static int hdlcd_drm_bind(struct device *dev)
err_register:
if (hdlcd->fbdev) {
- drm_fbdev_cma_fini(hdlcd->fbdev);
+ hdlcd_drm_fbdev_fini(hdlcd->fbdev);
hdlcd->fbdev = NULL;
}
err_fbdev:
@@ -410,7 +410,7 @@ static void hdlcd_drm_unbind(struct device *dev)
drm_dev_unregister(drm);
if (hdlcd->fbdev) {
- drm_fbdev_cma_fini(hdlcd->fbdev);
+ hdlcd_drm_fbdev_fini(hdlcd->fbdev);
hdlcd->fbdev = NULL;
}
drm_kms_helper_poll_fini(drm);
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h
index e3950a071152..4d2fa6459031 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.h
+++ b/drivers/gpu/drm/arm/hdlcd_drv.h
@@ -8,7 +8,7 @@
struct hdlcd_drm_private {
void __iomem *mmio;
struct clk *clk;
- struct drm_fbdev_cma *fbdev;
+ struct hdlcd_drm_fbdev *fbdev;
struct drm_crtc crtc;
struct drm_plane *plane;
struct drm_atomic_state *state;
diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c
new file mode 100644
index 000000000000..9e660e3cd855
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c
@@ -0,0 +1,641 @@
+/*
+ * drm kms/fb cma (contiguous memory allocator) helper functions
+ *
+ * Copyright (C) 2012 Analog Device Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Based on udl_fbdev.c
+ * Copyright (C) 2012 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include "hdlcd_fb_helper.h"
+#include <linux/dma-buf.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/reservation.h>
+
+#define DEFAULT_FBDEFIO_DELAY_MS 50
+
+struct hdlcd_fb {
+ struct drm_framebuffer fb;
+ struct drm_gem_cma_object *obj[4];
+};
+
+struct hdlcd_drm_fbdev {
+ struct drm_fb_helper fb_helper;
+ struct hdlcd_fb *fb;
+ const struct drm_framebuffer_funcs *fb_funcs;
+};
+
+/**
+ * DOC: framebuffer cma helper functions
+ *
+ * Provides helper functions for creating a cma (contiguous memory allocator)
+ * backed framebuffer.
+ *
+ * hdlcd_fb_create() is used in the &drm_mode_config_funcs.fb_create
+ * callback function to create a cma backed framebuffer.
+ *
+ * An fbdev framebuffer backed by cma is also available by calling
+ * hdlcd_drm_fbdev_init(). hdlcd_drm_fbdev_fini() tears it down.
+ * If the &drm_framebuffer_funcs.dirty callback is set, fb_deferred_io will be
+ * set up automatically. &drm_framebuffer_funcs.dirty is called by
+ * drm_fb_helper_deferred_io() in process context (&struct delayed_work).
+ *
+ * Example fbdev deferred io code::
+ *
+ * static int driver_fb_dirty(struct drm_framebuffer *fb,
+ * struct drm_file *file_priv,
+ * unsigned flags, unsigned color,
+ * struct drm_clip_rect *clips,
+ * unsigned num_clips)
+ * {
+ * struct drm_gem_cma_object *cma = hdlcd_fb_get_gem_obj(fb, 0);
+ * ... push changes ...
+ * return 0;
+ * }
+ *
+ * static struct drm_framebuffer_funcs driver_fb_funcs = {
+ * .destroy = hdlcd_fb_destroy,
+ * .create_handle = hdlcd_fb_create_handle,
+ * .dirty = driver_fb_dirty,
+ * };
+ *
+ * Initialize::
+ *
+ * fbdev = hdlcd_drm_fbdev_init_with_funcs(dev, 16,
+ * dev->mode_config.num_crtc,
+ * dev->mode_config.num_connector,
+ * &driver_fb_funcs);
+ *
+ */
+
+static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper)
+{
+ return container_of(helper, struct hdlcd_drm_fbdev, fb_helper);
+}
+
+static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb)
+{
+ return container_of(fb, struct hdlcd_fb, fb);
+}
+
+void hdlcd_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (hdlcd_fb->obj[i])
+ drm_gem_object_unreference_unlocked(&hdlcd_fb->obj[i]->base);
+ }
+
+ drm_framebuffer_cleanup(fb);
+ kfree(hdlcd_fb);
+}
+EXPORT_SYMBOL(hdlcd_fb_destroy);
+
+int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned int *handle)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+
+ return drm_gem_handle_create(file_priv,
+ &hdlcd_fb->obj[0]->base, handle);
+}
+EXPORT_SYMBOL(hdlcd_fb_create_handle);
+
+static struct drm_framebuffer_funcs hdlcd_fb_funcs = {
+ .destroy = hdlcd_fb_destroy,
+ .create_handle = hdlcd_fb_create_handle,
+};
+
+static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev,
+ const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_cma_object **obj,
+ unsigned int num_planes, const struct drm_framebuffer_funcs *funcs)
+{
+ struct hdlcd_fb *hdlcd_fb;
+ int ret;
+ int i;
+
+ hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL);
+ if (!hdlcd_fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(dev, &hdlcd_fb->fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ hdlcd_fb->obj[i] = obj[i];
+
+ ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, funcs);
+ if (ret) {
+ dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret);
+ kfree(hdlcd_fb);
+ return ERR_PTR(ret);
+ }
+
+ return hdlcd_fb;
+}
+
+/**
+ * hdlcd_fb_create_with_funcs() - helper function for the
+ * &drm_mode_config_funcs.fb_create
+ * callback
+ * @dev: DRM device
+ * @file_priv: drm file for the ioctl call
+ * @mode_cmd: metadata from the userspace fb creation request
+ * @funcs: vtable to be used for the new framebuffer object
+ *
+ * This can be used to set &drm_framebuffer_funcs for drivers that need the
+ * &drm_framebuffer_funcs.dirty callback. Use hdlcd_fb_create() if you don't
+ * need to change &drm_framebuffer_funcs.
+ */
+struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd,
+ const struct drm_framebuffer_funcs *funcs)
+{
+ const struct drm_format_info *info;
+ struct hdlcd_fb *hdlcd_fb;
+ struct drm_gem_cma_object *objs[4];
+ struct drm_gem_object *obj;
+ int ret;
+ int i;
+
+ info = drm_format_info(mode_cmd->pixel_format);
+ if (!info)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < info->num_planes; i++) {
+ unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
+ unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
+ unsigned int min_size;
+
+ obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]);
+ if (!obj) {
+ dev_err(dev->dev, "Failed to lookup GEM object\n");
+ ret = -ENXIO;
+ goto err_gem_object_unreference;
+ }
+
+ min_size = (height - 1) * mode_cmd->pitches[i]
+ + width * info->cpp[i]
+ + mode_cmd->offsets[i];
+
+ if (obj->size < min_size) {
+ drm_gem_object_unreference_unlocked(obj);
+ ret = -EINVAL;
+ goto err_gem_object_unreference;
+ }
+ objs[i] = to_drm_gem_cma_obj(obj);
+ }
+
+ hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i, funcs);
+ if (IS_ERR(hdlcd_fb)) {
+ ret = PTR_ERR(hdlcd_fb);
+ goto err_gem_object_unreference;
+ }
+
+ return &hdlcd_fb->fb;
+
+err_gem_object_unreference:
+ for (i--; i >= 0; i--)
+ drm_gem_object_unreference_unlocked(&objs[i]->base);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_create_with_funcs);
+
+/**
+ * hdlcd_fb_create() - &drm_mode_config_funcs.fb_create callback function
+ * @dev: DRM device
+ * @file_priv: drm file for the ioctl call
+ * @mode_cmd: metadata from the userspace fb creation request
+ *
+ * If your hardware has special alignment or pitch requirements these should be
+ * checked before calling this function. Use hdlcd_fb_create_with_funcs() if
+ * you need to set &drm_framebuffer_funcs.dirty.
+ */
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ return hdlcd_fb_create_with_funcs(dev, file_priv, mode_cmd,
+ &hdlcd_fb_funcs);
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_create);
+
+/**
+ * hdlcd_fb_get_gem_obj() - Get CMA GEM object for framebuffer
+ * @fb: The framebuffer
+ * @plane: Which plane
+ *
+ * Return the CMA GEM object for given framebuffer.
+ *
+ * This function will usually be called from the CRTC callback functions.
+ */
+struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+
+ if (plane >= 4)
+ return NULL;
+
+ return hdlcd_fb->obj[plane];
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj);
+
+/**
+ * hdlcd_fb_prepare_fb() - Prepare CMA framebuffer
+ * @plane: Which plane
+ * @state: Plane state attach fence to
+ *
+ * This should be set as the &struct drm_plane_helper_funcs.prepare_fb hook.
+ *
+ * This function checks if the plane FB has an dma-buf attached, extracts
+ * the exclusive fence and attaches it to plane state for the atomic helper
+ * to wait on.
+ *
+ * There is no need for cleanup_fb for CMA based framebuffer drivers.
+ */
+int hdlcd_fb_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct dma_buf *dma_buf;
+ struct dma_fence *fence;
+
+ if ((plane->state->fb == state->fb) || !state->fb)
+ return 0;
+
+ dma_buf = hdlcd_fb_get_gem_obj(state->fb, 0)->base.dma_buf;
+ if (dma_buf) {
+ fence = reservation_object_get_excl_rcu(dma_buf->resv);
+ drm_atomic_set_fence_for_plane(state, fence);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_prepare_fb);
+
+#ifdef CONFIG_DEBUG_FS
+static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+ int i;
+
+ seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height,
+ (char *)&fb->format->format);
+
+ for (i = 0; i < fb->format->num_planes; i++) {
+ seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
+ i, fb->offsets[i], fb->pitches[i]);
+ drm_gem_cma_describe(hdlcd_fb->obj[i], m);
+ }
+}
+
+/**
+ * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects
+ * in debugfs.
+ * @m: output file
+ * @arg: private data for the callback
+ */
+int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct drm_framebuffer *fb;
+
+ mutex_lock(&dev->mode_config.fb_lock);
+ drm_for_each_fb(fb, dev)
+ hdlcd_fb_describe(fb, m);
+ mutex_unlock(&dev->mode_config.fb_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show);
+#endif
+
+static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ return dma_mmap_writecombine(info->device, vma, info->screen_base,
+ info->fix.smem_start, info->fix.smem_len);
+}
+
+static struct fb_ops hdlcd_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ DRM_FB_HELPER_DEFAULT_OPS,
+ .fb_fillrect = drm_fb_helper_sys_fillrect,
+ .fb_copyarea = drm_fb_helper_sys_copyarea,
+ .fb_imageblit = drm_fb_helper_sys_imageblit,
+ .fb_mmap = hdlcd_fb_mmap,
+};
+
+static int hdlcd_drm_fbdev_deferred_io_mmap(struct fb_info *info,
+ struct vm_area_struct *vma)
+{
+ fb_deferred_io_mmap(info, vma);
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ return 0;
+}
+
+static int hdlcd_drm_fbdev_defio_init(struct fb_info *fbi,
+ struct drm_gem_cma_object *cma_obj)
+{
+ struct fb_deferred_io *fbdefio;
+ struct fb_ops *fbops;
+
+ /*
+ * Per device structures are needed because:
+ * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap
+ * fbdefio: individual delays
+ */
+ fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
+ fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+ if (!fbdefio || !fbops) {
+ kfree(fbdefio);
+ kfree(fbops);
+ return -ENOMEM;
+ }
+
+ /* can't be offset from vaddr since dirty() uses cma_obj */
+ fbi->screen_buffer = cma_obj->vaddr;
+ /* fb_deferred_io_fault() needs a physical address */
+ fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer));
+
+ *fbops = *fbi->fbops;
+ fbi->fbops = fbops;
+
+ fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS);
+ fbdefio->deferred_io = drm_fb_helper_deferred_io;
+ fbi->fbdefio = fbdefio;
+ fb_deferred_io_init(fbi);
+ fbi->fbops->fb_mmap = hdlcd_drm_fbdev_deferred_io_mmap;
+
+ return 0;
+}
+
+static void hdlcd_drm_fbdev_defio_fini(struct fb_info *fbi)
+{
+ if (!fbi->fbdefio)
+ return;
+
+ fb_deferred_io_cleanup(fbi);
+ kfree(fbi->fbdefio);
+ kfree(fbi->fbops);
+}
+
+static int
+hdlcd_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(helper);
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct drm_device *dev = helper->dev;
+ struct drm_gem_cma_object *obj;
+ struct drm_framebuffer *fb;
+ unsigned int bytes_per_pixel;
+ unsigned long offset;
+ struct fb_info *fbi;
+ size_t size;
+ int ret;
+
+ DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
+ sizes->surface_width, sizes->surface_height,
+ sizes->surface_bpp);
+
+ bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+ obj = drm_gem_cma_create(dev, size);
+ if (IS_ERR(obj))
+ return -ENOMEM;
+
+ fbi = drm_fb_helper_alloc_fbi(helper);
+ if (IS_ERR(fbi)) {
+ ret = PTR_ERR(fbi);
+ goto err_gem_free_object;
+ }
+
+ hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1,
+ hdlcd_fbdev->fb_funcs);
+ if (IS_ERR(hdlcd_fbdev->fb)) {
+ dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(hdlcd_fbdev->fb);
+ goto err_fb_info_destroy;
+ }
+
+ fb = &hdlcd_fbdev->fb->fb;
+ helper->fb = fb;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->fbops = &hdlcd_drm_fbdev_ops;
+
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
+ drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
+
+ offset = fbi->var.xoffset * bytes_per_pixel;
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ dev->mode_config.fb_base = (resource_size_t)obj->paddr;
+ fbi->screen_base = obj->vaddr + offset;
+ fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
+ fbi->screen_size = size;
+ fbi->fix.smem_len = size;
+
+ if (hdlcd_fbdev->fb_funcs->dirty) {
+ ret = hdlcd_drm_fbdev_defio_init(fbi, obj);
+ if (ret)
+ goto err_cma_destroy;
+ }
+
+ return 0;
+
+err_cma_destroy:
+ drm_framebuffer_remove(&hdlcd_fbdev->fb->fb);
+err_fb_info_destroy:
+ drm_fb_helper_release_fbi(helper);
+err_gem_free_object:
+ drm_gem_object_unreference_unlocked(&obj->base);
+ return ret;
+}
+
+static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = {
+ .fb_probe = hdlcd_drm_fbdev_create,
+};
+
+/**
+ * hdlcd_drm_fbdev_init_with_funcs() - Allocate and initializes a hdlcd_drm_fbdev struct
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device
+ * @max_conn_count: Maximum number of connectors
+ * @funcs: fb helper functions, in particular a custom dirty() callback
+ *
+ * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR.
+ */
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count,
+ const struct drm_framebuffer_funcs *funcs)
+{
+ struct hdlcd_drm_fbdev *hdlcd_fbdev;
+ struct drm_fb_helper *helper;
+ int ret;
+
+ hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL);
+ if (!hdlcd_fbdev) {
+ dev_err(dev->dev, "Failed to allocate drm fbdev.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ hdlcd_fbdev->fb_funcs = funcs;
+
+ helper = &hdlcd_fbdev->fb_helper;
+
+ drm_fb_helper_prepare(dev, helper, &hdlcd_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, helper, max_conn_count);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
+ goto err_free;
+ }
+
+ ret = drm_fb_helper_single_add_all_connectors(helper);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to add connectors.\n");
+ goto err_drm_fb_helper_fini;
+
+ }
+
+ ret = drm_fb_helper_initial_config(helper, preferred_bpp);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+ goto err_drm_fb_helper_fini;
+ }
+
+ return hdlcd_fbdev;
+
+err_drm_fb_helper_fini:
+ drm_fb_helper_fini(helper);
+err_free:
+ kfree(hdlcd_fbdev);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init_with_funcs);
+
+/**
+ * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev struct
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device
+ * @num_crtc: Number of CRTCs
+ * @max_conn_count: Maximum number of connectors
+ *
+ * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR.
+ */
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count)
+{
+ return hdlcd_drm_fbdev_init_with_funcs(dev, preferred_bpp,
+ max_conn_count,
+ &hdlcd_fb_funcs);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init);
+
+/**
+ * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct
+ */
+void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper);
+ if (hdlcd_fbdev->fb_helper.fbdev)
+ hdlcd_drm_fbdev_defio_fini(hdlcd_fbdev->fb_helper.fbdev);
+ drm_fb_helper_release_fbi(&hdlcd_fbdev->fb_helper);
+
+ if (hdlcd_fbdev->fb)
+ drm_framebuffer_remove(&hdlcd_fbdev->fb->fb);
+
+ drm_fb_helper_fini(&hdlcd_fbdev->fb_helper);
+ kfree(hdlcd_fbdev);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini);
+
+/**
+ * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ *
+ * This function is usually called from the &drm_driver.lastclose callback.
+ */
+void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode);
+
+/**
+ * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ *
+ * This function is usually called from the &drm_mode_config.output_poll_changed
+ * callback.
+ */
+void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event);
+
+/**
+ * hdlcd_drm_fbdev_set_suspend - wrapper around drm_fb_helper_set_suspend
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * Calls drm_fb_helper_set_suspend, which is a wrapper around
+ * fb_set_suspend implemented by fbdev core.
+ */
+void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_set_suspend(&hdlcd_fbdev->fb_helper, state);
+}
+EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend);
+
+/**
+ * hdlcd_drm_fbdev_set_suspend_unlocked - wrapper around
+ * drm_fb_helper_set_suspend_unlocked
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * Calls drm_fb_helper_set_suspend, which is a wrapper around
+ * fb_set_suspend implemented by fbdev core.
+ */
+void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev,
+ int state)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_set_suspend_unlocked(&hdlcd_fbdev->fb_helper,
+ state);
+}
+EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend_unlocked);
diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.h b/drivers/gpu/drm/arm/hdlcd_fb_helper.h
new file mode 100644
index 000000000000..aee0d9037561
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h
@@ -0,0 +1,54 @@
+#ifndef __DRM_FB_CMA_HELPER_H__
+#define __DRM_FB_CMA_HELPER_H__
+
+struct hdlcd_drm_fbdev;
+struct drm_gem_cma_object;
+
+struct drm_fb_helper_surface_size;
+struct drm_framebuffer_funcs;
+struct drm_fb_helper_funcs;
+struct drm_framebuffer;
+struct drm_fb_helper;
+struct drm_device;
+struct drm_file;
+struct drm_mode_fb_cmd2;
+struct drm_plane;
+struct drm_plane_state;
+
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count,
+ const struct drm_framebuffer_funcs *funcs);
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count);
+void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+
+void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state);
+void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev,
+ int state);
+
+void hdlcd_fb_destroy(struct drm_framebuffer *fb);
+int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned int *handle);
+
+struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd,
+ const struct drm_framebuffer_funcs *funcs);
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
+
+struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane);
+
+int hdlcd_fb_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *state);
+
+#ifdef CONFIG_DEBUG_FS
+struct seq_file;
+
+int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg);
+#endif
+
+#endif
+