drm: arm: Add support for PRIME and dma_buf buffer sharing.
authorLiviu Dudau <Liviu.Dudau@arm.com>
Mon, 30 Jun 2014 17:36:57 +0000 (18:36 +0100)
committerLiviu Dudau <Liviu.Dudau@arm.com>
Wed, 2 Jul 2014 15:33:22 +0000 (16:33 +0100)
Add support for buffer sharing in HDLCD and implement proprietary
IOCTL from ARM Mali driver to get a file descriptor for the buffer.
This is used by the Mali kernel driver to import the buffers into
DDK and enable double buffering.

Signed-off-by: Liviu Dudau <Liviu.Dudau@arm.com>
drivers/gpu/drm/arm/Kconfig
drivers/gpu/drm/arm/Makefile
drivers/gpu/drm/arm/hdlcd_crtc.c
drivers/gpu/drm/arm/hdlcd_drv.c
drivers/gpu/drm/arm/hdlcd_drv.h
drivers/gpu/drm/arm/hdlcd_fb.c [new file with mode: 0644]

index 8de5b51..acb046f 100644 (file)
@@ -20,7 +20,7 @@ config DRM_HDLCD
          If M is selected the module will be called hdlcd.
 
 config DRM_VIRTUAL_HDLCD
-       bool "Support for virtual HDLCD"
-       depends on DRM_HDLCD
-       help
-         Enable support for virtual HDLCD as emulated by ARM's Fast Models.
+       bool "Support for virtual HDLCD"
+       depends on DRM_HDLCD
+       help
+         Enable support for virtual HDLCD as emulated by ARM's Fast Models.
index 0f28e22..429644c 100644 (file)
@@ -1,4 +1,4 @@
 
-hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o hdlcd_fb.o
 hdlcd-$(CONFIG_DRM_VIRTUAL_HDLCD) += hdlcd_virt_encoder.o
 obj-$(CONFIG_DRM_HDLCD)        += hdlcd.o
index 91ed56c..39cf6c0 100644 (file)
@@ -34,17 +34,20 @@ static void hdlcd_crtc_destroy(struct drm_crtc *crtc)
 void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd)
 {
        struct drm_framebuffer *fb = hdlcd->crtc.primary->fb;
-       struct drm_gem_cma_object *gem;
+       struct hdlcd_bo *bo;
        unsigned int depth, bpp;
        dma_addr_t scanout_start;
 
        drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
-       gem = drm_fb_cma_get_gem_obj(fb, 0);
+       bo = hdlcd->bo;
 
-       scanout_start = gem->paddr + fb->offsets[0] +
+       scanout_start = bo->dma_addr + fb->offsets[0] +
                (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8);
 
-       hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+       if (scanout_start != hdlcd->scanout_buf) {
+               hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+               hdlcd->scanout_buf = scanout_start;
+       }
 }
 
 static int hdlcd_crtc_page_flip(struct drm_crtc *crtc,
@@ -105,6 +108,7 @@ static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc,
 
 static void hdlcd_crtc_prepare(struct drm_crtc *crtc)
 {
+       drm_vblank_pre_modeset(crtc->dev, 0);
        hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
 }
 
@@ -114,6 +118,20 @@ static void hdlcd_crtc_commit(struct drm_crtc *crtc)
        hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
 }
 
+static bool hdlcd_fb_mode_equal(struct drm_framebuffer *oldfb,
+                               struct drm_framebuffer *newfb)
+{
+       if (!oldfb || !newfb)
+               return false;
+
+       if (oldfb->pixel_format == newfb->pixel_format &&
+               oldfb->width == newfb->width &&
+               oldfb->height == newfb->height)
+               return true;
+
+       return false;
+}
+
 static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
                        struct drm_display_mode *mode,
                        struct drm_display_mode *adjusted_mode,
@@ -128,7 +146,12 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
        default_color = 0x00ff000000;
 #endif
 
-       drm_vblank_pre_modeset(crtc->dev, 0);
+       /* This function gets called when the only change is the start of
+          the scanout buffer. Detect that and bail out early */
+       if (hdlcd->initialised && hdlcd_fb_mode_equal(oldfb, crtc->primary->fb)) {
+               hdlcd_set_scanout(hdlcd);
+               return 0;
+       }
 
        /* Preset the number of bits per colour */
        drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp);
@@ -204,7 +227,17 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
        clk_enable(hdlcd->clk);
 
        hdlcd_set_scanout(hdlcd);
+       hdlcd->initialised = true;
+
+       return 0;
+}
+
+int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+                       struct drm_framebuffer *oldfb)
+{
+       struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
 
+       hdlcd_set_scanout(hdlcd);
        return 0;
 }
 
@@ -218,33 +251,17 @@ static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = {
        .prepare        = hdlcd_crtc_prepare,
        .commit         = hdlcd_crtc_commit,
        .mode_set       = hdlcd_crtc_mode_set,
+       .mode_set_base  = hdlcd_crtc_mode_set_base,
        .load_lut       = hdlcd_crtc_load_lut,
 };
 
-static void hdlcd_fb_output_poll_changed(struct drm_device *dev)
-{
-       struct hdlcd_drm_private *hdlcd = dev->dev_private;
-       if (hdlcd->fbdev)
-               drm_fbdev_cma_hotplug_event(hdlcd->fbdev);
-}
-
-static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
-       .fb_create = drm_fb_cma_create,
-       .output_poll_changed = hdlcd_fb_output_poll_changed,
-};
-
 int hdlcd_setup_crtc(struct drm_device *dev)
 {
        struct hdlcd_drm_private *hdlcd = dev->dev_private;
        int ret;
 
        drm_mode_config_init(dev);
-
-       dev->mode_config.min_width = 0;
-       dev->mode_config.min_height = 0;
-       dev->mode_config.max_width = HDLCD_MAX_XRES;
-       dev->mode_config.max_height = HDLCD_MAX_YRES;
-       dev->mode_config.funcs = &hdlcd_mode_config_funcs;
+       hdlcd_drm_mode_config_init(dev);
 
        ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs);
        if (ret < 0)
index 08341ef..62f34a0 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/spinlock.h>
 #include <linux/clk.h>
+#include <linux/completion.h>
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
@@ -29,8 +30,8 @@ static int hdlcd_unload(struct drm_device *dev)
        struct hdlcd_drm_private *hdlcd = dev->dev_private;
 
        drm_kms_helper_poll_fini(dev);
-       if (hdlcd->fbdev)
-               drm_fbdev_cma_fini(hdlcd->fbdev);
+       if (hdlcd->fb_helper)
+               drm_fb_helper_fini(hdlcd->fb_helper);
 
        drm_vblank_cleanup(dev);
        drm_mode_config_cleanup(dev);
@@ -72,6 +73,7 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags)
        atomic_set(&hdlcd->vsync_count, 0);
        atomic_set(&hdlcd->dma_end_count, 0);
 #endif
+       hdlcd->initialised = false;
        dev->dev_private = hdlcd;
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -123,21 +125,29 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags)
         */
        if (hdlcd->slave_node) {
                ret = hdlcd_create_digital_connector(dev, hdlcd);
-               if (ret < 0)
+               if (ret < 0) {
+                       dev_err(dev->dev, "failed to create digital connector, trying board setup: %d\n", ret);
                        ret = hdlcd_create_vexpress_connector(dev, hdlcd);
+               }
 
                if (ret < 0) {
-                       dev_err(dev->dev, "failed to create digital connector\n");
+                       dev_err(dev->dev, "failed to create board connector: %d\n", ret);
                        goto fail;
                }
        } else {
                ret = hdlcd_create_virtual_connector(dev);
                if (ret < 0) {
-                       dev_err(dev->dev, "failed to create virtual connector\n");
+                       dev_err(dev->dev, "failed to create virtual connector: %d\n", ret);
                        goto fail;
                }
        }
 
+       ret = hdlcd_fbdev_init(dev);
+       if (ret < 0) {
+               dev_err(dev->dev, "failed to init the framebuffer (%d)\n", ret);
+               goto fail;
+       }
+
        platform_set_drvdata(pdev, dev);
 
        ret = drm_irq_install(dev);
@@ -146,6 +156,7 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags)
                goto fail;
        }
 
+       init_completion(&hdlcd->vsync_completion);
        ret = drm_vblank_init(dev, 1);
        if (ret < 0) {
                dev_err(dev->dev, "failed to initialise vblank\n");
@@ -154,8 +165,6 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags)
                dev_info(dev->dev, "initialised vblank\n");
        }
 
-       hdlcd->fbdev = drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc,
-                                       dev->mode_config.num_connector);
        drm_kms_helper_poll_init(dev);
 
        return 0;
@@ -172,7 +181,11 @@ static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file)
 static void hdlcd_lastclose(struct drm_device *dev)
 {
        struct hdlcd_drm_private *hdlcd = dev->dev_private;
-       drm_fbdev_cma_restore_mode(hdlcd->fbdev);
+
+       drm_modeset_lock_all(dev);
+       if (hdlcd->fb_helper)
+               drm_fb_helper_restore_fbdev_mode(hdlcd->fb_helper);
+       drm_modeset_unlock_all(dev);
 }
 
 static irqreturn_t hdlcd_irq(int irq, void *arg)
@@ -213,6 +226,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg)
                        drm_vblank_put(dev, 0);
                }
                spin_unlock_irqrestore(&dev->event_lock, flags);
+               complete(&hdlcd->vsync_completion);
        }
 
        /* acknowledge interrupt(s) */
@@ -309,7 +323,7 @@ static void hdlcd_debugfs_cleanup(struct drm_minor *minor)
 }
 #endif
 
-static const struct file_operations fops = {
+static const struct file_operations hdlcd_fops = {
        .owner          = THIS_MODULE,
        .open           = drm_open,
        .release        = drm_release,
@@ -317,10 +331,10 @@ static const struct file_operations fops = {
 #ifdef CONFIG_COMPAT
        .compat_ioctl   = drm_compat_ioctl,
 #endif
+       .mmap           = drm_gem_cma_mmap,
        .poll           = drm_poll,
        .read           = drm_read,
        .llseek         = no_llseek,
-       .mmap           = drm_gem_cma_mmap,
 };
 
 static struct drm_driver hdlcd_driver = {
@@ -337,8 +351,10 @@ static struct drm_driver hdlcd_driver = {
        .get_vblank_counter     = drm_vblank_count,
        .enable_vblank          = hdlcd_enable_vblank,
        .disable_vblank         = hdlcd_disable_vblank,
+
        .gem_free_object        = drm_gem_cma_free_object,
        .gem_vm_ops             = &drm_gem_cma_vm_ops,
+
        .dumb_create            = drm_gem_cma_dumb_create,
        .dumb_map_offset        = drm_gem_cma_dumb_map_offset,
        .dumb_destroy           = drm_gem_dumb_destroy,
@@ -354,7 +370,8 @@ static struct drm_driver hdlcd_driver = {
        .debugfs_init           = hdlcd_debugfs_init,
        .debugfs_cleanup        = hdlcd_debugfs_cleanup,
 #endif
-       .fops                   = &fops,
+       .fops                   = &hdlcd_fops,
+
        .name                   = "hdlcd",
        .desc                   = "ARM HDLCD Controller DRM",
        .date                   = "20130505",
@@ -392,21 +409,11 @@ static struct platform_driver hdlcd_platform_driver = {
 
 static int __init hdlcd_init(void)
 {
-       int err = platform_driver_register(&hdlcd_platform_driver);
-
-#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
-       if (!err)
-               hdlcd_underrun_init();
-#endif
-
-       return err;
+       return platform_driver_register(&hdlcd_platform_driver);
 }
 
 static void __exit hdlcd_exit(void)
 {
-#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
-       hdlcd_underrun_close();
-#endif
        platform_driver_unregister(&hdlcd_platform_driver);
 }
 
index 408e982..cd2923a 100644 (file)
@@ -5,14 +5,22 @@
 #ifndef __HDLCD_DRV_H__
 #define __HDLCD_DRV_H__
 
+struct hdlcd_bo {
+       struct drm_gem_object gem;
+       dma_addr_t dma_addr;
+       void *cpu_addr;
+};
+
 struct hdlcd_drm_private {
        void __iomem                    *mmio;
        struct clk                      *clk;
-       struct drm_fbdev_cma            *fbdev;
-       struct drm_framebuffer          *fb;
+       struct drm_fb_helper            *fb_helper;
+       struct hdlcd_bo                 *bo;
+       dma_addr_t                      scanout_buf;
        struct drm_pending_vblank_event *event;
        struct drm_crtc                 crtc;
        struct device_node              *slave_node;
+       struct completion               vsync_completion;
 #ifdef CONFIG_DEBUG_FS
        atomic_t buffer_underrun_count;
        atomic_t bus_error_count;
@@ -20,8 +28,10 @@ struct hdlcd_drm_private {
        atomic_t dma_end_count;
 #endif
        int dpms;
+       bool initialised;
 };
 
+#define to_hdlcd_bo_obj(x)     container_of(x, struct hdlcd_bo, gem)
 #define crtc_to_hdlcd_priv(x)  container_of(x, struct hdlcd_drm_private, crtc)
 
 static inline void
@@ -64,6 +74,8 @@ static inline int hdlcd_create_virtual_connector(struct drm_device *dev)
 #endif
 
 void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd);
+void hdlcd_drm_mode_config_init(struct drm_device *dev);
+int hdlcd_fbdev_init(struct drm_device *dev);
 
 /* common function used by all connectors */
 extern struct drm_encoder *hdlcd_connector_best_encoder(struct drm_connector *con);
diff --git a/drivers/gpu/drm/arm/hdlcd_fb.c b/drivers/gpu/drm/arm/hdlcd_fb.c
new file mode 100644 (file)
index 0000000..cae947a
--- /dev/null
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2013,2014 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ * Implementation of the DRM fbdev compatibility mode for the HDLCD driver.
+ * Mainly used for doing dumb double buffering as expected by the ARM Mali driver.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-buf.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+#define MAX_FRAMES 2
+
+#define to_hdlcd_fb(x) container_of(x, struct hdlcd_fb, fb)
+
+struct hdlcd_dmabuf_attachment {
+       struct sg_table *sgt;
+       enum dma_data_direction dir;
+};
+
+static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev,
+                                       struct drm_mode_fb_cmd2 *mode_cmd);
+
+static int hdlcd_dmabuf_attach(struct dma_buf *dma_buf,
+               struct device *target_dev, struct dma_buf_attachment *attach)
+{
+       struct hdlcd_dmabuf_attachment *hdlcd_attach;
+
+       hdlcd_attach = kzalloc(sizeof(*hdlcd_attach), GFP_KERNEL);
+       if (!hdlcd_attach)
+               return -ENOMEM;
+
+       hdlcd_attach->dir = DMA_NONE;
+       attach->priv = hdlcd_attach;
+
+       return 0;
+}
+
+static void hdlcd_dmabuf_detach(struct dma_buf *dma_buf,
+                               struct dma_buf_attachment *attach)
+{
+       struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+       struct sg_table *sgt;
+
+       if (!hdlcd_attach)
+               return;
+
+       sgt = hdlcd_attach->sgt;
+       if (sgt) {
+               sg_free_table(sgt);
+       }
+
+       kfree(sgt);
+       kfree(hdlcd_attach);
+       attach->priv = NULL;
+}
+
+static void hdlcd_dmabuf_release(struct dma_buf *dma_buf)
+{
+       struct hdlcd_drm_private *hdlcd = dma_buf->priv;
+       struct drm_gem_object *obj = &hdlcd->bo->gem;
+
+       drm_gem_object_unreference_unlocked(obj);
+}
+
+static struct sg_table *hdlcd_map_dma_buf(struct dma_buf_attachment *attach,
+                                         enum dma_data_direction dir)
+{
+       struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+       struct hdlcd_drm_private *hdlcd = attach->dmabuf->priv;
+       struct sg_table *sgt;
+       int size, ret;
+
+       if (dir == DMA_NONE || !hdlcd_attach)
+               return ERR_PTR(-EINVAL);
+
+       /* return the cached mapping when possible */
+       if (hdlcd_attach->dir == dir)
+               return hdlcd_attach->sgt;
+
+       /* don't allow two different directions for the same attachment */
+       if (hdlcd_attach->dir != DMA_NONE)
+               return ERR_PTR(-EBUSY);
+
+       size = hdlcd->bo->gem.size;
+
+       sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+       if (!sgt) {
+               DRM_ERROR("Failed to allocate sg_table\n");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+       if (ret < 0) {
+               DRM_ERROR("Failed to allocate page table\n");
+               kfree(sgt);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       sg_dma_len(sgt->sgl) = size;
+       sg_set_page(sgt->sgl, pfn_to_page(PFN_DOWN(hdlcd->bo->dma_addr)), size, 0);
+       sg_dma_address(sgt->sgl) = hdlcd->bo->dma_addr;
+       ret = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir);
+       if (!ret) {
+               DRM_ERROR("failed to map sgl with IOMMU\n");
+               sg_free_table(sgt);
+               kfree(sgt);
+               return ERR_PTR(-EIO);
+       }
+       hdlcd_attach->sgt = sgt;
+       hdlcd_attach->dir = dir;
+
+       return sgt;
+}
+
+static void hdlcd_unmap_dma_buf(struct dma_buf_attachment *attach,
+                               struct sg_table *sgt, enum dma_data_direction dir)
+{
+       struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+
+       if (hdlcd_attach->dir != DMA_NONE)
+               dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir);
+       sg_free_table(sgt);
+       kfree(sgt);
+       hdlcd_attach->sgt = NULL;
+       hdlcd_attach->dir = DMA_NONE;
+}
+
+static void *hdlcd_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num)
+{
+       return NULL;
+}
+
+static void hdlcd_dmabuf_kunmap(struct dma_buf *dma_buf,
+                               unsigned long page_num, void *addr)
+{
+}
+
+static int hdlcd_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma)
+{
+       struct hdlcd_drm_private *hdlcd = dma_buf->priv;
+       struct drm_gem_object *obj = &hdlcd->bo->gem;
+       struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj);
+       int ret;
+
+       mutex_lock(&obj->dev->struct_mutex);
+       ret = drm_gem_mmap_obj(obj, obj->size, vma);
+       mutex_unlock(&obj->dev->struct_mutex);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the
+        * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map
+        * the whole buffer.
+        */
+       vma->vm_flags &= ~VM_PFNMAP;
+       vma->vm_pgoff = 0;
+       ret = dma_mmap_writecombine(obj->dev->dev, vma, bo->cpu_addr, bo->dma_addr,
+                                   vma->vm_end - vma->vm_start);
+       if (ret)
+               drm_gem_vm_close(vma);
+
+       return 0;
+}
+
+static void *hdlcd_dmabuf_vmap(struct dma_buf *dma_buf)
+{
+       return ERR_PTR(-EINVAL);
+}
+
+static void hdlcd_dmabuf_vunmap(struct dma_buf *dma_buf, void *cpu_addr)
+{
+}
+
+struct dma_buf_ops hdlcd_buf_ops = {
+       .attach = hdlcd_dmabuf_attach,
+       .detach = hdlcd_dmabuf_detach,
+       .map_dma_buf = hdlcd_map_dma_buf,
+       .unmap_dma_buf = hdlcd_unmap_dma_buf,
+       .release = hdlcd_dmabuf_release,
+       .kmap = hdlcd_dmabuf_kmap,
+       .kmap_atomic = hdlcd_dmabuf_kmap,
+       .kunmap = hdlcd_dmabuf_kunmap,
+       .kunmap_atomic = hdlcd_dmabuf_kunmap,
+       .mmap = hdlcd_dmabuf_mmap,
+       .vmap = hdlcd_dmabuf_vmap,
+       .vunmap = hdlcd_dmabuf_vunmap,
+};
+
+/*
+ * Used for sharing buffers with Mali userspace
+ */
+struct fb_dmabuf_export {
+       uint32_t fd;
+       uint32_t flags;
+};
+
+#define FBIOGET_DMABUF       _IOR('F', 0x21, struct fb_dmabuf_export)
+
+static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd,
+                                 unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       struct fb_dmabuf_export ebuf;
+       struct drm_fb_helper *helper = info->par;
+       struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+       struct dma_buf                  *dma_buf;
+       uint32_t                        fd;
+
+       if (copy_from_user(&ebuf, argp, sizeof(ebuf)))
+               return -EFAULT;
+
+       dma_buf = dma_buf_export(hdlcd, &hdlcd_buf_ops,
+                               info->screen_size, ebuf.flags | O_RDWR);
+       if (!dma_buf) {
+               dev_info(info->dev, "Failed to export DMA buffer\n");
+               goto err_export;
+       }
+
+       fd = dma_buf_fd(dma_buf, O_CLOEXEC);
+       if (fd < 0) {
+               dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n");
+               goto err_export_fd;
+       }
+       ebuf.fd = fd;
+
+       if (copy_to_user(argp, &ebuf, sizeof(ebuf)))
+               goto err_export_fd;
+
+       return 0;
+
+err_export_fd:
+       dma_buf_put(dma_buf);
+err_export:
+       return -EFAULT;
+}
+
+static int hdlcd_wait_for_vsync(struct fb_info *info)
+{
+#if 0
+       struct drm_fb_helper *helper = info->par;
+       struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+       int ret;
+
+       drm_crtc_vblank_on(&hdlcd->crtc);
+       ret = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion,
+                                                       msecs_to_jiffies(100));
+       drm_crtc_vblank_off(&hdlcd->crtc);
+       if (ret)
+               return -ETIMEDOUT;
+#endif
+
+       return 0;
+}
+
+static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+       switch (cmd) {
+       case FBIOGET_DMABUF:
+               return hdlcd_get_dmabuf_ioctl(info, cmd, arg);
+       case FBIO_WAITFORVSYNC:
+               return hdlcd_wait_for_vsync(info);
+       default:
+               printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd);
+       }
+
+       return -EFAULT;
+}
+
+static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       struct drm_fb_helper *helper = info->par;
+       struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+       struct drm_gem_object *obj = &hdlcd->bo->gem;
+       struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj);
+       DEFINE_DMA_ATTRS(attrs);
+       size_t vm_size;
+
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+
+       vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+       vm_size = vma->vm_end - vma->vm_start;
+
+       if (vm_size > bo->gem.size)
+               return -EINVAL;
+
+       return dma_mmap_attrs(helper->dev->dev, vma, bo->cpu_addr,
+                       bo->dma_addr, bo->gem.size, &attrs);
+}
+
+static int hdlcd_fb_check_var(struct fb_var_screeninfo *var,
+                       struct fb_info *info)
+{
+       struct drm_fb_helper *fb_helper = info->par;
+       struct drm_framebuffer *fb = fb_helper->fb;
+       int depth;
+
+       if (var->pixclock != 0 || in_dbg_master())
+               return -EINVAL;
+
+       /* Need to resize the fb object !!! */
+       if (var->bits_per_pixel > fb->bits_per_pixel ||
+           var->xres > fb->width || var->yres > fb->height ||
+           var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) {
+               DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb "
+                         "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n",
+                         var->xres, var->yres, var->bits_per_pixel,
+                         var->xres_virtual, var->yres_virtual,
+                         fb->width, fb->height, fb->bits_per_pixel);
+               return -EINVAL;
+       }
+
+       switch (var->bits_per_pixel) {
+       case 16:
+               depth = (var->green.length == 6) ? 16 : 15;
+               break;
+       case 32:
+               depth = (var->transp.length > 0) ? 32 : 24;
+               break;
+       default:
+               depth = var->bits_per_pixel;
+               break;
+       }
+
+       switch (depth) {
+       case 8:
+               var->red.offset = 0;
+               var->green.offset = 0;
+               var->blue.offset = 0;
+               var->red.length = 8;
+               var->green.length = 8;
+               var->blue.length = 8;
+               var->transp.length = 0;
+               var->transp.offset = 0;
+               break;
+       case 15:
+               var->red.offset = 10;
+               var->green.offset = 5;
+               var->blue.offset = 0;
+               var->red.length = 5;
+               var->green.length = 5;
+               var->blue.length = 5;
+               var->transp.length = 1;
+               var->transp.offset = 15;
+               break;
+       case 16:
+               var->red.offset = 11;
+               var->green.offset = 5;
+               var->blue.offset = 0;
+               var->red.length = 5;
+               var->green.length = 6;
+               var->blue.length = 5;
+               var->transp.length = 0;
+               var->transp.offset = 0;
+               break;
+       case 24:
+               var->red.offset = 16;
+               var->green.offset = 8;
+               var->blue.offset = 0;
+               var->red.length = 8;
+               var->green.length = 8;
+               var->blue.length = 8;
+               var->transp.length = 0;
+               var->transp.offset = 0;
+               break;
+       case 32:
+               var->red.offset = 16;
+               var->green.offset = 8;
+               var->blue.offset = 0;
+               var->red.length = 8;
+               var->green.length = 8;
+               var->blue.length = 8;
+               var->transp.length = 8;
+               var->transp.offset = 24;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static struct fb_ops hdlcd_fb_ops = {
+       .owner  = THIS_MODULE,
+       .fb_fillrect = sys_fillrect,
+       .fb_copyarea = sys_copyarea,
+       .fb_imageblit = sys_imageblit,
+       .fb_check_var = hdlcd_fb_check_var,
+       .fb_set_par = drm_fb_helper_set_par,
+       .fb_blank = drm_fb_helper_blank,
+       .fb_pan_display = drm_fb_helper_pan_display,
+       .fb_setcmap = drm_fb_helper_setcmap,
+       .fb_ioctl = hdlcd_fb_ioctl,
+       .fb_mmap = hdlcd_fb_mmap,
+};
+
+static struct hdlcd_bo *hdlcd_fb_bo_create(struct drm_device *drm, size_t size)
+{
+       int err;
+       struct hdlcd_bo *bo;
+       struct hdlcd_drm_private *hdlcd = drm->dev_private;
+       DEFINE_DMA_ATTRS(attrs);
+
+       bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+       if (!bo)
+               return ERR_PTR(-ENOMEM);
+
+       size = round_up(size, PAGE_SIZE);
+       err = drm_gem_object_init(drm, &bo->gem, size);
+       if (err)
+               goto err_init;
+
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+       bo->cpu_addr = dma_alloc_attrs(drm->dev, size, &bo->dma_addr,
+                                   GFP_KERNEL | __GFP_NOWARN, &attrs);
+
+       if (!bo->cpu_addr) {
+               dev_err(drm->dev, "failed to allocate buffer of size %zu\n", size);
+               err = -ENOMEM;
+               goto err_dma;
+       }
+
+#if 0
+       err = drm_gem_create_mmap_offset(&bo->gem);
+       if (err)
+               goto err_map;
+#endif
+
+       hdlcd->bo = bo;
+       return bo;
+
+#if 0
+err_map:
+       dma_free_attrs(drm->dev, size, bo->cpu_addr, bo->dma_addr, &attrs);
+#endif
+err_dma:
+       drm_gem_object_release(&bo->gem);
+err_init:
+       kfree(bo);
+
+       return ERR_PTR(err);
+}
+
+void hdlcd_fb_bo_free(struct hdlcd_bo *bo)
+{
+       DEFINE_DMA_ATTRS(attrs);
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+
+       drm_gem_object_release(&bo->gem);
+       dma_free_attrs(bo->gem.dev->dev, bo->gem.size, bo->cpu_addr, bo->dma_addr, &attrs);
+       kfree(bo);
+}
+
+static int hdlcd_fb_probe(struct drm_fb_helper *helper,
+                         struct drm_fb_helper_surface_size *sizes)
+{
+       struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+       struct fb_info *info;
+       struct hdlcd_bo *bo = hdlcd->bo;
+       struct drm_mode_fb_cmd2 cmd = { 0 };
+       unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+       unsigned long offset;
+       size_t size;
+       int err;
+
+       cmd.width = sizes->surface_width;
+       cmd.height = sizes->surface_height;
+       cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+       cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+                                                    sizes->surface_depth);
+
+       size = cmd.pitches[0] * cmd.height * MAX_FRAMES;
+
+       bo = hdlcd_fb_bo_create(helper->dev, size);
+       if (IS_ERR(bo))
+               return PTR_ERR(bo);
+
+       info = framebuffer_alloc(0, helper->dev->dev);
+       if (!info) {
+               dev_err(helper->dev->dev, "failed to allocate framebuffer info\n");
+               hdlcd_fb_bo_free(bo);
+               return -ENOMEM;
+       }
+
+       helper->fb = hdlcd_fb_alloc(helper->dev, &cmd);
+       helper->fbdev = info;
+
+       info->par = helper;
+       info->flags = FBINFO_FLAG_DEFAULT;
+       info->fbops = &hdlcd_fb_ops;
+
+       err = fb_alloc_cmap(&info->cmap, 256, 0);
+       if (err < 0) {
+               dev_err(helper->dev->dev, "failed to allocate color map: %d\n", err);
+               goto cleanup;
+       }
+
+       drm_fb_helper_fill_fix(info, helper->fb->pitches[0], helper->fb->depth);
+       drm_fb_helper_fill_var(info, helper, helper->fb->width, sizes->surface_height);
+
+       offset = info->var.xoffset * bytes_per_pixel +
+                       info->var.yoffset * helper->fb->pitches[0];
+
+       helper->dev->mode_config.fb_base = (resource_size_t)bo->dma_addr;
+       info->screen_base = (void __iomem *)bo->cpu_addr + offset;
+       info->screen_size = size;
+       info->var.yres_virtual = info->var.yres * MAX_FRAMES;
+       info->fix.smem_start = (unsigned long)bo->dma_addr + offset;
+       info->fix.smem_len = size;
+
+       return 0;
+
+cleanup:
+       drm_framebuffer_unregister_private(helper->fb);
+       hdlcd_fb_bo_free(bo);
+       framebuffer_release(info);
+       return err;
+}
+
+static struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = {
+       .fb_probe       = hdlcd_fb_probe,
+};
+
+static void hdlcd_fb_destroy(struct drm_framebuffer *fb)
+{
+       struct hdlcd_drm_private *hdlcd = fb->dev->dev_private;
+       drm_gem_object_unreference_unlocked(&hdlcd->bo->gem);
+       drm_framebuffer_cleanup(fb);
+       kfree(hdlcd->bo);
+}
+
+static int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+                                 struct drm_file *file_priv,
+                                 unsigned int *handle)
+{
+       struct hdlcd_drm_private *hdlcd = fb->dev->dev_private;
+       return drm_gem_handle_create(file_priv, &hdlcd->bo->gem, handle);
+}
+
+static int hdlcd_fb_dirty(struct drm_framebuffer *fb,
+                         struct drm_file *file_priv, unsigned flags,
+                         unsigned color, struct drm_clip_rect *clips,
+                         unsigned num_clips)
+{
+       return 0;
+}
+
+static struct drm_framebuffer_funcs hdlcd_fb_funcs = {
+       .destroy        = hdlcd_fb_destroy,
+       .create_handle  = hdlcd_fb_create_handle,
+       .dirty          = hdlcd_fb_dirty,
+};
+
+static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev,
+                                             struct drm_mode_fb_cmd2 *mode_cmd)
+{
+       int err;
+       struct drm_framebuffer *fb;
+
+       dev_info(dev->dev, "Linux is here %s", __func__);
+       fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+       if (!fb)
+               return ERR_PTR(-ENOMEM);
+
+       drm_helper_mode_fill_fb_struct(fb, mode_cmd);
+
+       err = drm_framebuffer_init(dev, fb, &hdlcd_fb_funcs);
+       if (err) {
+               dev_err(dev->dev, "failed to initialize framebuffer.\n");
+               kfree(fb);
+               return ERR_PTR(err);
+       }
+
+       return fb;
+}
+
+static struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+                                              struct drm_file *file_priv,
+                                              struct drm_mode_fb_cmd2 *mode_cmd)
+{
+       struct drm_gem_object *obj;
+       struct hdlcd_bo *bo;
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+       obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+       if (!obj) {
+               dev_err(dev->dev, "failed to lookup GEM object\n");
+               return ERR_PTR(-ENXIO);
+       }
+
+       bo = to_hdlcd_bo_obj(obj);
+       hdlcd->fb_helper->fb = hdlcd_fb_alloc(dev, mode_cmd);
+       if (IS_ERR(hdlcd->fb_helper->fb)) {
+               dev_err(dev->dev, "failed to allocate DRM framebuffer\n");
+       }
+       hdlcd->bo = bo;
+
+       return hdlcd->fb_helper->fb;
+}
+
+int hdlcd_fbdev_init(struct drm_device *dev)
+{
+       int ret;
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+       hdlcd->fb_helper = kzalloc(sizeof(*hdlcd->fb_helper), GFP_KERNEL);
+       if (!hdlcd->fb_helper)
+               return -ENOMEM;
+
+       hdlcd->fb_helper->funcs = &hdlcd_fb_helper_funcs;
+       ret = drm_fb_helper_init(dev, hdlcd->fb_helper, dev->mode_config.num_crtc,
+                                dev->mode_config.num_connector);
+       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(hdlcd->fb_helper);
+       if (ret < 0) {
+               dev_err(dev->dev, "Failed to add connectors.\n");
+               goto err_helper_fini;
+       }
+
+       drm_helper_disable_unused_functions(dev);
+
+       /* disable all the possible outputs/crtcs before entering KMS mode */
+       ret = drm_fb_helper_initial_config(hdlcd->fb_helper, 32);
+       if (ret < 0) {
+               dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+               goto err_helper_fini;
+       }
+
+       return 0;
+
+err_helper_fini:
+       drm_fb_helper_fini(hdlcd->fb_helper);
+
+err_free:
+       kfree(hdlcd->fb_helper);
+       hdlcd->fb_helper = NULL;
+
+       return ret;
+}
+
+static void hdlcd_fb_output_poll_changed(struct drm_device *dev)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       struct drm_fb_helper *fb_helper = hdlcd->fb_helper;
+
+       drm_fb_helper_hotplug_event(fb_helper);
+}
+
+static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
+       .fb_create = hdlcd_fb_create,
+       .output_poll_changed = hdlcd_fb_output_poll_changed,
+};
+
+
+void hdlcd_drm_mode_config_init(struct drm_device *dev)
+{
+       dev->mode_config.min_width = 0;
+       dev->mode_config.min_height = 0;
+       dev->mode_config.max_width = HDLCD_MAX_XRES;
+       dev->mode_config.max_height = HDLCD_MAX_YRES;
+       dev->mode_config.funcs = &hdlcd_mode_config_funcs;
+}