This adds generic fbdev emulation for drivers that support the
dumb buffer API. No fbdev code is necessary in the driver.

Differences from drm_fb_helper:
- The backing buffer is created when the first fd is opened.
- Supports changing the mode from userspace.
- Doesn't restore on lastclose if there is no fd/fbcon open.
- Supports changing the buffer size (yres_virtual) from userspace before
  the fd is opened (double/trippel/... buffering).
- Panning is only supported as page flipping, so no partial offset.
- Supports real page flipping with FBIO_WAITFORVSYNC waiting on the
  actual flip.
- Supports framebuffer flushing for fbcon on buffers that doesn't support
  fbdev deferred I/O (shmem). mmap doesn't work but fbcon does.

TODO:
- suspend/resume
- sysrq
- Look more into plane format selection/support.
- Need a way for the driver to say that it wants generic fbdev emulation.
  The client .new hook is run in drm_dev_register() which is before
  drivers set up fbdev themselves. So the client can't look at
  drm_device->fb_helper to find out.
- Do we need to support FB_VISUAL_PSEUDOCOLOR?

TROUBLE:
- fbcon can't handle fb_open returning an error, it just heads on. This
  results in a NULL deref in fbcon_init(). fbcon/vt is awful when it
  comes to error handling. It doesn't look to be easily fixed, so I guess
  a buffer has to be pre-allocated to ensure health and safety.

Signed-off-by: Noralf Trønnes <nor...@tronnes.org>
---
 drivers/gpu/drm/client/Kconfig     |  16 +
 drivers/gpu/drm/client/Makefile    |   2 +
 drivers/gpu/drm/client/drm_fbdev.c | 997 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1015 insertions(+)
 create mode 100644 drivers/gpu/drm/client/drm_fbdev.c

diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
index 4bb8e4655ff7..73902ab44c75 100644
--- a/drivers/gpu/drm/client/Kconfig
+++ b/drivers/gpu/drm/client/Kconfig
@@ -1,4 +1,20 @@
 menu "DRM Clients"
        depends on DRM
 
+config DRM_CLIENT_FBDEV
+       tristate "Generic fbdev emulation"
+       depends on DRM
+       select FB
+       select FRAMEBUFFER_CONSOLE if !EXPERT
+       select FRAMEBUFFER_CONSOLE_DETECT_PRIMARY if FRAMEBUFFER_CONSOLE
+       select FB_SYS_FOPS
+       select FB_SYS_FILLRECT
+       select FB_SYS_COPYAREA
+       select FB_SYS_IMAGEBLIT
+       select FB_DEFERRED_IO
+       select FB_MODE_HELPERS
+       select VIDEOMODE_HELPERS
+       help
+         Generic fbdev emulation
+
 endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
index f66554cd5c45..3ff694429dec 100644
--- a/drivers/gpu/drm/client/Makefile
+++ b/drivers/gpu/drm/client/Makefile
@@ -1 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o
diff --git a/drivers/gpu/drm/client/drm_fbdev.c 
b/drivers/gpu/drm/client/drm_fbdev.c
new file mode 100644
index 000000000000..e28416d72de1
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_fbdev.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/console.h>
+#include <linux/dma-buf.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <video/videomode.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+
+struct drm_fbdev {
+       struct mutex lock;
+
+       struct drm_client_dev *client;
+       struct drm_client_display *display;
+
+       unsigned int open_count;
+       struct drm_client_buffer *buffer;
+       bool page_flip_sent;
+       u32 curr_fb;
+
+       struct fb_info *info;
+       u32 pseudo_palette[17];
+
+       bool flush;
+       bool defio_no_flushing;
+       struct drm_clip_rect dirty_clip;
+       spinlock_t dirty_lock;
+       struct work_struct dirty_work;
+};
+
+static int drm_fbdev_mode_to_fb_mode(struct drm_device *dev,
+                                    struct drm_mode_modeinfo *mode,
+                                    struct fb_videomode *fb_mode)
+{
+       struct drm_display_mode display_mode = { };
+       struct videomode videomode = { };
+       int ret;
+
+       ret = drm_mode_convert_umode(dev, &display_mode, mode);
+       if (ret)
+               return ret;
+
+       memset(fb_mode, 0, sizeof(*fb_mode));
+       drm_display_mode_to_videomode(&display_mode, &videomode);
+       fb_videomode_from_videomode(&videomode, fb_mode);
+
+       return 0;
+}
+
+static void drm_fbdev_destroy_modelist(struct fb_info *info)
+{
+       struct fb_modelist *modelist, *tmp;
+
+       list_for_each_entry_safe(modelist, tmp, &info->modelist, list) {
+               kfree(modelist->mode.name);
+               list_del(&modelist->list);
+               kfree(modelist);
+       }
+}
+
+static void drm_fbdev_use_first_mode(struct fb_info *info)
+{
+       struct fb_modelist *modelist;
+
+       modelist = list_first_entry(&info->modelist, struct fb_modelist, list);
+       fb_videomode_to_var(&info->var, &modelist->mode);
+       info->mode = &modelist->mode;
+}
+
+static struct drm_mode_modeinfo *drm_fbdev_get_drm_mode(struct drm_fbdev 
*fbdev)
+{
+       struct drm_mode_modeinfo *mode_pos, *mode = NULL;
+       struct fb_info *info = fbdev->info;
+       struct fb_videomode tmp;
+
+       mutex_lock(&fbdev->display->modes_lock);
+       drm_client_display_for_each_mode(fbdev->display, mode_pos) {
+               if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode_pos, 
&tmp))
+                       continue;
+               if (fb_mode_is_equal(info->mode, &tmp)) {
+                       mode = mode_pos;
+                       break;
+               }
+       }
+       mutex_unlock(&fbdev->display->modes_lock);
+
+       return mode;
+}
+
+/* Return number of modes or negative error */
+static int drm_fbdev_sync_modes(struct drm_fbdev *fbdev, bool force)
+{
+       struct fb_info *info = fbdev->info;
+       struct drm_mode_modeinfo *mode;
+       struct fb_videomode fb_mode;
+       bool changed;
+
+       struct fb_modelist *fbdev_modelist;
+       int num_modes;
+
+       num_modes = drm_client_display_update_modes(fbdev->display, &changed);
+       if (num_modes <= 0)
+               return num_modes;
+
+       if (!info)
+               return num_modes;
+
+       if (!force && !changed)
+               return num_modes;
+
+       drm_fbdev_destroy_modelist(info);
+
+       mutex_lock(&fbdev->display->modes_lock);
+       drm_client_display_for_each_mode(fbdev->display, mode) {
+               if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode, 
&fb_mode)) {
+                       num_modes--;
+                       continue;
+               }
+
+               fbdev_modelist = kzalloc(sizeof(*fbdev_modelist), GFP_KERNEL);
+               if (!fbdev_modelist) {
+                       drm_fbdev_destroy_modelist(info);
+                       mutex_unlock(&fbdev->display->modes_lock);
+                       return -ENOMEM;
+               }
+
+               fbdev_modelist->mode = fb_mode;
+               fbdev_modelist->mode.name = kstrndup(mode->name,
+                                                    DRM_DISPLAY_MODE_LEN,
+                                                    GFP_KERNEL);
+
+               if (mode->type & DRM_MODE_TYPE_PREFERRED)
+                       fbdev_modelist->mode.flag |= FB_MODE_IS_FIRST;
+
+               list_add_tail(&fbdev_modelist->list, &info->modelist);
+       }
+       mutex_unlock(&fbdev->display->modes_lock);
+
+       if (!fbdev->open_count)
+               drm_fbdev_use_first_mode(info);
+
+       return num_modes;
+}
+
+static void drm_fbdev_format_fill_var(u32 format, struct fb_var_screeninfo 
*var)
+{
+       switch (format) {
+       case DRM_FORMAT_XRGB1555:
+               var->red.offset = 10;
+               var->red.length = 5;
+               var->green.offset = 5;
+               var->green.length = 5;
+               var->blue.offset = 0;
+               var->blue.length = 5;
+               var->transp.offset = 0;
+               var->transp.length = 0;
+               break;
+       case DRM_FORMAT_ARGB1555:
+               var->red.offset = 10;
+               var->red.length = 5;
+               var->green.offset = 5;
+               var->green.length = 5;
+               var->blue.offset = 0;
+               var->blue.length = 5;
+               var->transp.offset = 15;
+               var->transp.length = 1;
+               break;
+       case DRM_FORMAT_RGB565:
+               var->red.offset = 11;
+               var->red.length = 5;
+               var->green.offset = 5;
+               var->green.length = 6;
+               var->blue.offset = 0;
+               var->blue.length = 5;
+               var->transp.offset = 0;
+               var->transp.length = 0;
+               break;
+       case DRM_FORMAT_RGB888:
+       case DRM_FORMAT_XRGB8888:
+               var->red.offset = 16;
+               var->red.length = 8;
+               var->green.offset = 8;
+               var->green.length = 8;
+               var->blue.offset = 0;
+               var->blue.length = 8;
+               var->transp.offset = 0;
+               var->transp.length = 0;
+               break;
+       case DRM_FORMAT_ARGB8888:
+               var->red.offset = 16;
+               var->red.length = 8;
+               var->green.offset = 8;
+               var->green.length = 8;
+               var->blue.offset = 0;
+               var->blue.length = 8;
+               var->transp.offset = 24;
+               var->transp.length = 8;
+               break;
+       default:
+               WARN_ON_ONCE(1);
+               return;
+       }
+
+       var->colorspace = 0;
+       var->grayscale = 0;
+       var->nonstd = 0;
+}
+
+int drm_fbdev_var_to_format(struct fb_var_screeninfo *var, u32 *format)
+{
+       switch (var->bits_per_pixel) {
+       case 15:
+               *format = DRM_FORMAT_ARGB1555;
+               break;
+       case 16:
+               if (var->green.length != 5)
+                       *format = DRM_FORMAT_RGB565;
+               else if (var->transp.length > 0)
+                       *format = DRM_FORMAT_ARGB1555;
+               else
+                       *format = DRM_FORMAT_XRGB1555;
+               break;
+       case 24:
+               *format = DRM_FORMAT_RGB888;
+               break;
+       case 32:
+               if (var->transp.length > 0)
+                       *format = DRM_FORMAT_ARGB8888;
+               else
+                       *format = DRM_FORMAT_XRGB8888;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void drm_fbdev_dirty_work(struct work_struct *work)
+{
+       struct drm_fbdev *fbdev = container_of(work, struct drm_fbdev,
+                                              dirty_work);
+       struct drm_clip_rect *clip = &fbdev->dirty_clip;
+       struct drm_clip_rect clip_copy;
+       unsigned long flags;
+
+       spin_lock_irqsave(&fbdev->dirty_lock, flags);
+       clip_copy = *clip;
+       clip->x1 = clip->y1 = ~0;
+       clip->x2 = clip->y2 = 0;
+       spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+       /* call dirty callback only when it has been really touched */
+       if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2)
+               drm_client_display_flush(fbdev->display, fbdev->curr_fb,
+                                        &clip_copy, 1);
+}
+
+static void drm_fbdev_dirty(struct fb_info *info, u32 x, u32 y,
+                           u32 width, u32 height)
+{
+       struct drm_fbdev *fbdev = info->par;
+       struct drm_clip_rect *clip = &fbdev->dirty_clip;
+       unsigned long flags;
+
+       if (!fbdev->flush)
+               return;
+
+       spin_lock_irqsave(&fbdev->dirty_lock, flags);
+       clip->x1 = min_t(u32, clip->x1, x);
+       clip->y1 = min_t(u32, clip->y1, y);
+       clip->x2 = max_t(u32, clip->x2, x + width);
+       clip->y2 = max_t(u32, clip->y2, y + height);
+       spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+       schedule_work(&fbdev->dirty_work);
+}
+
+static void drm_fbdev_deferred_io(struct fb_info *info,
+                                 struct list_head *pagelist)
+{
+       struct drm_fbdev *fbdev = info->par;
+       unsigned long start, end, min, max;
+       struct page *page;
+       u32 y1, y2;
+
+       /* Is userspace doing explicit pageflip flushing? */
+       if (fbdev->defio_no_flushing)
+               return;
+
+       min = ULONG_MAX;
+       max = 0;
+       list_for_each_entry(page, pagelist, lru) {
+               start = page->index << PAGE_SHIFT;
+               end = start + PAGE_SIZE;
+               min = min(min, start);
+               max = max(max, end);
+       }
+
+       if (min < max) {
+               y1 = min / info->fix.line_length;
+               y2 = DIV_ROUND_UP(max, info->fix.line_length);
+               y2 = min(y2, info->var.yres);
+               drm_fbdev_dirty(info, 0, y1, info->var.xres, y2 - y1);
+       }
+}
+
+static struct fb_deferred_io drm_fbdev_fbdefio = {
+       .delay          = HZ / 20,
+       .deferred_io    = drm_fbdev_deferred_io,
+};
+
+static int
+drm_fbdev_fb_mmap_notsupp(struct fb_info *info, struct vm_area_struct *vma)
+{
+       return -ENOTSUPP;
+}
+
+static void drm_fbdev_delete_buffer(struct drm_fbdev *fbdev)
+{
+       struct fb_info *info = fbdev->info;
+
+       if (info->fbdefio) {
+               /* Stop worker and clear page->mapping */
+               fb_deferred_io_cleanup(info);
+               info->fbdefio = NULL;
+       }
+       if (fbdev->flush) {
+               fbdev->flush = false;
+               cancel_work_sync(&fbdev->dirty_work);
+       }
+
+       drm_client_buffer_rmfb(fbdev->buffer);
+       drm_client_buffer_delete(fbdev->buffer);
+
+       fbdev->buffer = NULL;
+       fbdev->curr_fb = 0;
+       fbdev->page_flip_sent = false;
+       info->screen_buffer = NULL;
+       info->screen_size = 0;
+       info->fix.smem_len = 0;
+       info->fix.line_length = 0;
+}
+
+/* Temporary hack to make tinydrm work before converting to vmalloc buffers */
+static int drm_fbdev_cma_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 drm_fbdev_create_buffer(struct drm_fbdev *fbdev)
+{
+       struct drm_client_dev *client = fbdev->client;
+       struct fb_info *info = fbdev->info;
+       struct drm_client_buffer *buffer;
+       struct drm_mode_modeinfo *mode;
+       u32 format;
+       int ret;
+
+       ret = drm_fbdev_var_to_format(&info->var, &format);
+       if (ret)
+               return ret;
+
+       buffer = drm_client_buffer_create(client, info->var.xres_virtual,
+                                         info->var.yres_virtual, format);
+       if (IS_ERR(buffer))
+               return PTR_ERR(buffer);
+
+       mode = drm_fbdev_get_drm_mode(fbdev);
+       if (!mode)
+               return -EINVAL;
+
+       ret = drm_client_buffer_addfb(buffer, mode);
+       if (ret)
+               goto err_free_buffer;
+
+       fbdev->curr_fb = buffer->fb_ids[0];
+
+       if (drm_mode_can_dirtyfb(client->dev, fbdev->curr_fb, client->file)) {
+               fbdev->flush = true;
+/*             if (is_vmalloc_addr(buffer->vaddr)) { */
+/* Temporary hack for testing on tinydrm before it has moved to vmalloc */
+               if (1) {
+                       fbdev->dirty_clip.x1 = fbdev->dirty_clip.y1 = ~0;
+                       fbdev->dirty_clip.x2 = fbdev->dirty_clip.y2 = 0;
+                       info->fbdefio = &drm_fbdev_fbdefio;
+
+                       /* tinydrm hack */
+                       info->fix.smem_start = 
page_to_phys(virt_to_page(buffer->vaddr));
+
+                       fb_deferred_io_init(info);
+                       /* tinydrm hack */
+                       info->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
+               } else {
+                       info->fbops->fb_mmap = drm_fbdev_fb_mmap_notsupp;
+               }
+       }
+
+       fbdev->buffer = buffer;
+       info->screen_buffer = buffer->vaddr;
+       info->screen_size = buffer->size;
+       info->fix.smem_len = buffer->size;
+       info->fix.line_length = buffer->pitch;
+
+       return 0;
+
+err_free_buffer:
+       drm_client_buffer_delete(buffer);
+
+       return ret;
+}
+
+static int drm_fbdev_fb_open(struct fb_info *info, int user)
+{
+       struct drm_fbdev *fbdev = info->par;
+       int ret = 0;
+
+       DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+
+       mutex_lock(&fbdev->lock);
+
+       if (!fbdev->display) {
+               ret = -ENODEV;
+               goto out_unlock;
+       }
+
+       if (!fbdev->open_count) {
+               /* Pipeline is disabled, make sure it's forced on */
+               info->var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+               ret = drm_fbdev_create_buffer(fbdev);
+               if (ret)
+                       goto out_unlock;
+       }
+
+       fbdev->open_count++;
+
+out_unlock:
+       mutex_unlock(&fbdev->lock);
+
+       if (ret)
+               DRM_DEV_ERROR(fbdev->client->dev->dev, "fb_open failed (%d)\n", 
ret);
+
+       return ret;
+}
+
+static int drm_fbdev_fb_release(struct fb_info *info, int user)
+{
+       struct drm_fbdev *fbdev = info->par;
+
+       DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+       mutex_lock(&fbdev->lock);
+
+       if (--fbdev->open_count == 0) {
+               drm_client_display_dpms(fbdev->display, DRM_MODE_DPMS_OFF);
+               drm_fbdev_delete_buffer(fbdev);
+       }
+
+       fbdev->defio_no_flushing = false;
+
+       mutex_unlock(&fbdev->lock);
+
+       return 0;
+}
+
+static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf,
+                                 size_t count, loff_t *ppos)
+{
+       ssize_t ret;
+
+       ret = fb_sys_write(info, buf, count, ppos);
+       if (ret > 0)
+               drm_fbdev_dirty(info, 0, 0, info->var.xres, info->var.yres);
+
+       return ret;
+}
+
+static void
+drm_fbdev_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+       sys_fillrect(info, rect);
+       drm_fbdev_dirty(info, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void
+drm_fbdev_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+       sys_copyarea(info, area);
+       drm_fbdev_dirty(info, area->dx, area->dy, area->width, area->height);
+}
+
+static void
+drm_fbdev_fb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+       sys_imageblit(info, image);
+       drm_fbdev_dirty(info, image->dx, image->dy, image->width, 
image->height);
+}
+
+static int
+drm_fbdev_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       u32 new_format, old_format, yres_virtual;
+       struct drm_fbdev *fbdev = info->par;
+       const struct fb_videomode *fb_mode;
+       bool is_open;
+       int ret;
+
+       mutex_lock(&fbdev->lock);
+       is_open = fbdev->open_count;
+       mutex_unlock(&fbdev->lock);
+
+       if (!is_open && in_dbg_master())
+               return -EINVAL;
+
+       /* Can be called from sysfs */
+       if (is_open && (var->xres_virtual > fbdev->buffer->width ||
+           var->yres_virtual > fbdev->buffer->height)) {
+               DRM_DEBUG_KMS("Cannot increase virtual resolution while 
open\n");
+               return -EBUSY;
+       }
+
+       if (var->xres > var->xres_virtual || var->yres > var->yres_virtual) {
+               DRM_DEBUG_KMS("Requested width/height to big: %dx%d > virtual 
%dx%d\n",
+                             var->xres, var->yres, var->xres_virtual,
+                             var->yres_virtual);
+               return -EINVAL;
+       }
+
+       ret = drm_fbdev_var_to_format(var, &new_format);
+       if (ret) {
+               DRM_DEBUG_KMS("Unsupported format\n");
+               return -EINVAL;
+       }
+
+       ret = drm_fbdev_var_to_format(&info->var, &old_format);
+       if (ret)
+               return ret;
+
+       if (new_format != old_format && is_open) {
+               DRM_DEBUG_KMS("Cannot change format while open\n");
+               return -EBUSY;
+       }
+
+       drm_fbdev_format_fill_var(new_format, var);
+
+       fb_mode = fb_find_best_mode(var, &info->modelist);
+       if (!fb_mode)
+               return -EINVAL;
+
+       yres_virtual = var->yres_virtual;
+       fb_videomode_to_var(var, fb_mode);
+       var->yres_virtual = yres_virtual;
+
+       return 0;
+}
+
+static int drm_fbdev_fb_set_par(struct fb_info *info)
+{
+       struct drm_fbdev *fbdev = info->par;
+       const struct fb_videomode *fb_mode;
+       struct drm_mode_modeinfo *mode;
+       bool mode_changed;
+       int ret;
+
+       mutex_lock(&fbdev->lock);
+
+       if (!fbdev->open_count) {
+               ret = 0;
+               goto out_unlock;
+       }
+
+       fb_mode = fb_match_mode(&info->var, &info->modelist);
+       if (!fb_mode) {
+               DRM_DEBUG_KMS("Couldn't find var mode\n");
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       mode_changed = !fb_mode_is_equal(info->mode, fb_mode);
+       info->mode = (struct fb_videomode *)fb_mode;
+
+       mode = drm_fbdev_get_drm_mode(fbdev);
+       if (!mode) {
+               DRM_DEBUG_KMS("Couldn't find the matching DRM mode\n");
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       if (mode_changed) {
+               drm_client_buffer_rmfb(fbdev->buffer);
+               fbdev->curr_fb = 0;
+               ret = drm_client_buffer_addfb(fbdev->buffer, mode);
+               if (ret)
+                       goto out_unlock;
+
+               fbdev->curr_fb = fbdev->buffer->fb_ids[0];
+               info->var.yoffset = 0;
+       }
+
+//     info->var.width = drm_mode->width_mm;
+//     info->var.height = drm_mode->height_mm;
+
+       /* Panning is only supported to do page flipping */
+       info->fix.ypanstep = info->var.yres;
+
+       ret = drm_client_display_commit_mode(fbdev->display, fbdev->curr_fb, 
mode);
+
+out_unlock:
+       mutex_unlock(&fbdev->lock);
+
+       return ret;
+}
+
+/*
+ * Do we need to support FB_VISUAL_PSEUDOCOLOR?
+
+static int drm_fbdev_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
+                   unsigned blue, unsigned transp, struct fb_info *info)
+{
+
+}
+*/
+
+static int setcmap_pseudo_palette(struct fb_cmap *cmap, struct fb_info *info)
+{
+       u32 *palette = (u32 *)info->pseudo_palette;
+       int i;
+
+       if (cmap->start + cmap->len > 16)
+               return -EINVAL;
+
+       for (i = 0; i < cmap->len; ++i) {
+               u16 red = cmap->red[i];
+               u16 green = cmap->green[i];
+               u16 blue = cmap->blue[i];
+               u32 value;
+
+               red >>= 16 - info->var.red.length;
+               green >>= 16 - info->var.green.length;
+               blue >>= 16 - info->var.blue.length;
+               value = (red << info->var.red.offset) |
+                       (green << info->var.green.offset) |
+                       (blue << info->var.blue.offset);
+               if (info->var.transp.length > 0) {
+                       u32 mask = (1 << info->var.transp.length) - 1;
+
+                       mask <<= info->var.transp.offset;
+                       value |= mask;
+               }
+               palette[cmap->start + i] = value;
+       }
+
+       return 0;
+}
+
+static int drm_fbdev_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+       if (oops_in_progress)
+               return -EBUSY;
+
+       if (info->fix.visual == FB_VISUAL_TRUECOLOR)
+               return setcmap_pseudo_palette(cmap, info);
+
+       return -EINVAL;
+}
+
+static int drm_fbdev_fb_blank(int blank, struct fb_info *info)
+{
+       struct drm_fbdev *fbdev = info->par;
+       bool is_open;
+       int mode;
+
+       if (oops_in_progress)
+               return -EBUSY;
+
+       mutex_lock(&fbdev->lock);
+       is_open = fbdev->open_count;
+       mutex_unlock(&fbdev->lock);
+
+       if (!is_open)
+               return -EINVAL;
+
+       if (blank == FB_BLANK_UNBLANK)
+               mode = DRM_MODE_DPMS_ON;
+       else
+               mode = DRM_MODE_DPMS_OFF;
+
+       return drm_client_display_dpms(fbdev->display, mode);
+}
+
+static int
+drm_fbdev_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       struct drm_fbdev *fbdev = info->par;
+       struct drm_event *event;
+       unsigned int fb_idx;
+       int ret = 0;
+
+       mutex_lock(&fbdev->lock);
+
+       if (!fbdev->open_count)
+               goto out_unlock;
+
+       fb_idx = var->yoffset / info->var.yres;
+       if (fb_idx >= fbdev->buffer->num_fbs) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       /* Drain previous flip event if userspace didn't care */
+       if (fbdev->page_flip_sent) {
+               event = drm_client_read_event(fbdev->client, false);
+               if (!IS_ERR(event))
+                       kfree(event);
+               fbdev->page_flip_sent = false;
+       }
+
+       if (fbdev->curr_fb == fbdev->buffer->fb_ids[fb_idx])
+               goto out_unlock;
+
+       fbdev->curr_fb = fbdev->buffer->fb_ids[fb_idx];
+       fbdev->defio_no_flushing = true;
+
+       ret = drm_client_display_page_flip(fbdev->display, fbdev->curr_fb, 
true);
+       if (ret)
+               goto out_unlock;
+
+       fbdev->page_flip_sent = true;
+
+out_unlock:
+       mutex_unlock(&fbdev->lock);
+
+       return ret;
+}
+
+static int drm_fbdev_fb_ioctl(struct fb_info *info, unsigned int cmd,
+                             unsigned long arg)
+{
+       struct drm_fbdev *fbdev = info->par;
+       struct drm_event *event;
+       bool page_flip_sent;
+       int ret = 0;
+
+       switch (cmd) {
+//     case FBIOGET_VBLANK:
+//             break;
+       case FBIO_WAITFORVSYNC:
+               mutex_lock(&fbdev->lock);
+               page_flip_sent = fbdev->page_flip_sent;
+               fbdev->page_flip_sent = false;
+               mutex_unlock(&fbdev->lock);
+
+               if (page_flip_sent) {
+                       event = drm_client_read_event(fbdev->client, true);
+                       if (IS_ERR(event))
+                               ret = PTR_ERR(event);
+                       else
+                               kfree(event);
+               } else {
+                       drm_client_display_wait_vblank(fbdev->display);
+               }
+
+               break;
+       default:
+               ret = -ENOTTY;
+       }
+
+       return ret;
+}
+
+static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       struct drm_fbdev *fbdev = info->par;
+
+       return dma_buf_mmap(fbdev->buffer->dma_buf, vma, 0);
+}
+
+static void drm_fbdev_fb_destroy(struct fb_info *info)
+{
+       struct drm_fbdev *fbdev = info->par;
+
+       DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n");
+       drm_client_display_free(fbdev->display);
+       drm_client_free(fbdev->client);
+       kfree(fbdev);
+}
+
+static struct fb_ops drm_fbdev_fb_ops = {
+       .owner          = THIS_MODULE,
+       .fb_open        = drm_fbdev_fb_open,
+       .fb_release     = drm_fbdev_fb_release,
+       .fb_read        = fb_sys_read,
+       .fb_write       = drm_fbdev_fb_write,
+       .fb_check_var   = drm_fbdev_fb_check_var,
+       .fb_set_par     = drm_fbdev_fb_set_par,
+//     .fb_setcolreg   = drm_fbdev_fb_setcolreg,
+       .fb_setcmap     = drm_fbdev_fb_setcmap,
+       .fb_blank       = drm_fbdev_fb_blank,
+       .fb_pan_display = drm_fbdev_fb_pan_display,
+       .fb_fillrect    = drm_fbdev_fb_fillrect,
+       .fb_copyarea    = drm_fbdev_fb_copyarea,
+       .fb_imageblit   = drm_fbdev_fb_imageblit,
+       .fb_ioctl       = drm_fbdev_fb_ioctl,
+       .fb_mmap        = drm_fbdev_fb_mmap,
+       .fb_destroy     = drm_fbdev_fb_destroy,
+};
+
+static int drm_fbdev_register_framebuffer(struct drm_fbdev *fbdev)
+{
+       struct drm_client_display *display;
+       struct fb_info *info;
+       struct fb_ops *fbops;
+       u32 format;
+       int ret;
+
+       display = drm_client_display_get_first_enabled(fbdev->client, false);
+       if (IS_ERR_OR_NULL(display))
+               return PTR_ERR_OR_ZERO(display);
+
+       fbdev->display = display;
+
+       /*
+        * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per instance
+        * version is necessary. We do it for all users since we don't know
+        * yet if the fb has a dirty callback.
+        */
+       fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+       if (!fbops) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       *fbops = drm_fbdev_fb_ops;
+
+       info = framebuffer_alloc(0, fbdev->client->dev->dev);
+       if (!info) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       ret = fb_alloc_cmap(&info->cmap, 256, 0);
+       if (ret)
+               goto err_release;
+
+       info->par = fbdev;
+       info->fbops = fbops;
+       INIT_LIST_HEAD(&info->modelist);
+       info->pseudo_palette = fbdev->pseudo_palette;
+
+       info->fix.type = FB_TYPE_PACKED_PIXELS;
+       info->fix.visual = FB_VISUAL_TRUECOLOR;
+       info->fix.ypanstep = info->var.yres;
+
+       strcpy(info->fix.id, "DRM emulated");
+
+       fbdev->info = info;
+       ret = drm_fbdev_sync_modes(fbdev, true);
+       if (ret < 0)
+               goto err_free_cmap;
+
+       info->var.bits_per_pixel = 
drm_client_display_preferred_depth(fbdev->display);
+       ret = drm_fbdev_var_to_format(&info->var, &format);
+       if (ret) {
+               DRM_WARN("Unsupported bpp, assuming x8r8g8b8 pixel format\n");
+               format = DRM_FORMAT_XRGB8888;
+       }
+       drm_fbdev_format_fill_var(format, &info->var);
+
+       info->var.xres_virtual = info->var.xres;
+       info->var.yres_virtual = info->var.yres;
+
+       info->var.yres_virtual *= CONFIG_DRM_FBDEV_OVERALLOC;
+       info->var.yres_virtual /= 100;
+
+       ret = register_framebuffer(info);
+       if (ret)
+               goto err_free_cmap;
+
+       dev_info(fbdev->client->dev->dev, "fb%d: %s frame buffer device\n",
+                info->node, info->fix.id);
+
+       return 0;
+
+err_free_cmap:
+       fb_dealloc_cmap(&info->cmap);
+err_release:
+       framebuffer_release(info);
+err_free:
+       kfree(fbops);
+       fbdev->info = NULL;
+       drm_client_display_free(fbdev->display);
+       fbdev->display = NULL;
+
+       return ret;
+}
+
+static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
+{
+       struct drm_fbdev *fbdev = client->private;
+       int ret;
+
+       if (!fbdev->info)
+               ret = drm_fbdev_register_framebuffer(fbdev);
+       else
+               ret = drm_fbdev_sync_modes(fbdev, false);
+
+       return ret;
+}
+
+static int drm_fbdev_client_new(struct drm_client_dev *client)
+{
+       struct drm_fbdev *fbdev;
+
+       fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+       if (!fbdev)
+               return -ENOMEM;
+
+       mutex_init(&fbdev->lock);
+       spin_lock_init(&fbdev->dirty_lock);
+       INIT_WORK(&fbdev->dirty_work, drm_fbdev_dirty_work);
+
+       fbdev->client = client;
+       client->private = fbdev;
+
+       /*
+        * vc4 isn't done with it's setup when drm_dev_register() is called.
+        * It should have shouldn't it?
+        * So to keep it from crashing defer setup to hotplug...
+        */
+       if (client->dev->mode_config.max_width)
+               drm_fbdev_client_hotplug(client);
+
+       return 0;
+}
+
+static int drm_fbdev_client_remove(struct drm_client_dev *client)
+{
+       struct drm_fbdev *fbdev = client->private;
+
+       if (!fbdev->info) {
+               kfree(fbdev);
+               return 0;
+       }
+
+       unregister_framebuffer(fbdev->info);
+
+       /* drm_fbdev_fb_destroy() frees the client */
+       return 1;
+}
+
+static int drm_fbdev_client_lastclose(struct drm_client_dev *client)
+{
+       struct drm_fbdev *fbdev = client->private;
+       int ret = -ENOENT;
+
+       if (fbdev->info)
+               ret = fbdev->info->fbops->fb_set_par(fbdev->info);
+
+       return ret;
+}
+
+static const struct drm_client_funcs drm_fbdev_client_funcs = {
+       .name           = "drm_fbdev",
+       .new            = drm_fbdev_client_new,
+       .remove         = drm_fbdev_client_remove,
+       .lastclose      = drm_fbdev_client_lastclose,
+       .hotplug        = drm_fbdev_client_hotplug,
+};
+
+static int __init drm_fbdev_init(void)
+{
+       return drm_client_register(&drm_fbdev_client_funcs);
+}
+module_init(drm_fbdev_init);
+
+static void __exit drm_fbdev_exit(void)
+{
+       drm_client_unregister(&drm_fbdev_client_funcs);
+}
+module_exit(drm_fbdev_exit);
+
+MODULE_DESCRIPTION("DRM Generic fbdev emulation");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to