video: adf: add fbdev compatibility helper
authorGreg Hackmann <ghackmann@google.com>
Tue, 9 Jul 2013 20:07:26 +0000 (13:07 -0700)
committerGreg Hackmann <ghackmann@google.com>
Fri, 11 Oct 2013 23:47:47 +0000 (16:47 -0700)
Change-Id: I2b82bb625f805e8edb27799743b290dda5befb97
Signed-off-by: Greg Hackmann <ghackmann@google.com>
drivers/video/adf/Kconfig
drivers/video/adf/Makefile
drivers/video/adf/adf_fbdev.c [new file with mode: 0644]
include/video/adf_fbdev.h [new file with mode: 0644]

index fcd725fb93d838f38ab101a5c468608af140b520..33858b73d8bb99e64af9a1040e47e1ad5ddead34 100644 (file)
@@ -3,6 +3,11 @@ menuconfig ADF
        depends on DMA_SHARED_BUFFER
        tristate "Atomic Display Framework"
 
+menuconfig ADF_FBDEV
+       depends on ADF
+       depends on FB
+       tristate "Helper for implementing the fbdev API in ADF drivers"
+
 menuconfig ADF_MEMBLOCK
        depends on ADF
        depends on HAVE_MEMBLOCK
index 4118f324f65480dae105bf6500e1bc4e34a18ca5..78d0915122f41d17da383317f99b5fbed34dde7a 100644 (file)
@@ -10,4 +10,6 @@ obj-$(CONFIG_ADF) += adf.o \
 
 obj-$(CONFIG_COMPAT) += adf_fops32.o
 
+obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o
+
 obj-$(CONFIG_ADF_MEMBLOCK) += adf_memblock.o
diff --git a/drivers/video/adf/adf_fbdev.c b/drivers/video/adf/adf_fbdev.c
new file mode 100644 (file)
index 0000000..477abd6
--- /dev/null
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 <linux/vmalloc.h>
+
+#include <video/adf.h>
+#include <video/adf_client.h>
+#include <video/adf_fbdev.h>
+#include <video/adf_format.h>
+
+#include "adf.h"
+
+struct adf_fbdev_format {
+       u32 fourcc;
+       u32 bpp;
+       u32 r_length;
+       u32 g_length;
+       u32 b_length;
+       u32 a_length;
+       u32 r_offset;
+       u32 g_offset;
+       u32 b_offset;
+       u32 a_offset;
+};
+
+static const struct adf_fbdev_format format_table[] = {
+       {DRM_FORMAT_RGB332, 8, 3, 3, 2, 0, 5, 2, 0, 0},
+       {DRM_FORMAT_BGR233, 8, 3, 3, 2, 0, 0, 3, 5, 0},
+
+       {DRM_FORMAT_XRGB4444, 16, 4, 4, 4, 0, 8, 4, 0, 0},
+       {DRM_FORMAT_XBGR4444, 16, 4, 4, 4, 0, 0, 4, 8, 0},
+       {DRM_FORMAT_RGBX4444, 16, 4, 4, 4, 0, 12, 8, 4, 0},
+       {DRM_FORMAT_BGRX4444, 16, 4, 4, 4, 0, 0, 4, 8, 0},
+
+       {DRM_FORMAT_ARGB4444, 16, 4, 4, 4, 4, 8, 4, 0, 12},
+       {DRM_FORMAT_ABGR4444, 16, 4, 4, 4, 4, 0, 4, 8, 12},
+       {DRM_FORMAT_RGBA4444, 16, 4, 4, 4, 4, 12, 8, 4, 0},
+       {DRM_FORMAT_BGRA4444, 16, 4, 4, 4, 4, 0, 4, 8, 0},
+
+       {DRM_FORMAT_XRGB1555, 16, 5, 5, 5, 0, 10, 5, 0, 0},
+       {DRM_FORMAT_XBGR1555, 16, 5, 5, 5, 0, 0, 5, 10, 0},
+       {DRM_FORMAT_RGBX5551, 16, 5, 5, 5, 0, 11, 6, 1, 0},
+       {DRM_FORMAT_BGRX5551, 16, 5, 5, 5, 0, 1, 6, 11, 0},
+
+       {DRM_FORMAT_ARGB1555, 16, 5, 5, 5, 1, 10, 5, 0, 15},
+       {DRM_FORMAT_ABGR1555, 16, 5, 5, 5, 1, 0, 5, 10, 15},
+       {DRM_FORMAT_RGBA5551, 16, 5, 5, 5, 1, 11, 6, 1, 0},
+       {DRM_FORMAT_BGRA5551, 16, 5, 5, 5, 1, 1, 6, 11, 0},
+
+       {DRM_FORMAT_RGB565, 16, 5, 6, 5, 0, 11, 5, 0, 0},
+       {DRM_FORMAT_BGR565, 16, 5, 6, 5, 0, 0, 5, 11, 0},
+
+       {DRM_FORMAT_RGB888, 24, 8, 8, 8, 0, 16, 8, 0, 0},
+       {DRM_FORMAT_BGR888, 24, 8, 8, 8, 0, 0, 8, 16, 0},
+
+       {DRM_FORMAT_XRGB8888, 32, 8, 8, 8, 0, 16, 8, 0, 0},
+       {DRM_FORMAT_XBGR8888, 32, 8, 8, 8, 0, 0, 8, 16, 0},
+       {DRM_FORMAT_RGBX8888, 32, 8, 8, 8, 0, 24, 16, 8, 0},
+       {DRM_FORMAT_BGRX8888, 32, 8, 8, 8, 0, 8, 16, 24, 0},
+
+       {DRM_FORMAT_ARGB8888, 32, 8, 8, 8, 8, 16, 8, 0, 24},
+       {DRM_FORMAT_ABGR8888, 32, 8, 8, 8, 8, 0, 8, 16, 24},
+       {DRM_FORMAT_RGBA8888, 32, 8, 8, 8, 8, 24, 16, 8, 0},
+       {DRM_FORMAT_BGRA8888, 32, 8, 8, 8, 8, 8, 16, 24, 0},
+
+       {DRM_FORMAT_XRGB2101010, 32, 10, 10, 10, 0, 20, 10, 0, 0},
+       {DRM_FORMAT_XBGR2101010, 32, 10, 10, 10, 0, 0, 10, 20, 0},
+       {DRM_FORMAT_RGBX1010102, 32, 10, 10, 10, 0, 22, 12, 2, 0},
+       {DRM_FORMAT_BGRX1010102, 32, 10, 10, 10, 0, 2, 12, 22, 0},
+
+       {DRM_FORMAT_ARGB2101010, 32, 10, 10, 10, 2, 20, 10, 0, 30},
+       {DRM_FORMAT_ABGR2101010, 32, 10, 10, 10, 2, 0, 10, 20, 30},
+       {DRM_FORMAT_RGBA1010102, 32, 10, 10, 10, 2, 22, 12, 2, 0},
+       {DRM_FORMAT_BGRA1010102, 32, 10, 10, 10, 2, 2, 12, 22, 0},
+};
+
+static u32 drm_fourcc_from_fb_var(struct fb_var_screeninfo *var)
+{
+       size_t i;
+       for (i = 0; i < ARRAY_SIZE(format_table); i++) {
+               const struct adf_fbdev_format *f = &format_table[i];
+               if (var->red.length == f->r_length &&
+                       var->red.offset == f->r_offset &&
+                       var->green.length == f->g_length &&
+                       var->green.offset == f->g_offset &&
+                       var->blue.length == f->b_length &&
+                       var->blue.offset == f->b_offset &&
+                       var->transp.length == f->a_length &&
+                       (var->transp.length == 0 ||
+                                       var->transp.offset == f->a_offset))
+                       return f->fourcc;
+       }
+
+       return 0;
+}
+
+static const struct adf_fbdev_format *fbdev_format_info(u32 format)
+{
+       size_t i;
+       for (i = 0; i < ARRAY_SIZE(format_table); i++) {
+               const struct adf_fbdev_format *f = &format_table[i];
+               if (f->fourcc == format)
+                       return f;
+       }
+
+       BUG();
+}
+
+void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode,
+               struct fb_videomode *vmode)
+{
+       memset(vmode, 0, sizeof(*vmode));
+
+       vmode->refresh = mode->vrefresh;
+
+       vmode->xres = mode->hdisplay;
+       vmode->yres = mode->vdisplay;
+
+       vmode->pixclock = mode->clock ? KHZ2PICOS(mode->clock) : 0;
+       vmode->left_margin = mode->htotal - mode->hsync_end;
+       vmode->right_margin = mode->hsync_start - mode->hdisplay;
+       vmode->upper_margin = mode->vtotal - mode->vsync_end;
+       vmode->lower_margin = mode->vsync_start - mode->vdisplay;
+       vmode->hsync_len = mode->hsync_end - mode->hsync_start;
+       vmode->vsync_len = mode->vsync_end - mode->vsync_start;
+
+       vmode->sync = 0;
+       if (mode->flags | DRM_MODE_FLAG_PHSYNC)
+               vmode->sync |= FB_SYNC_HOR_HIGH_ACT;
+       if (mode->flags | DRM_MODE_FLAG_PVSYNC)
+               vmode->sync |= FB_SYNC_VERT_HIGH_ACT;
+       if (mode->flags | DRM_MODE_FLAG_PCSYNC)
+               vmode->sync |= FB_SYNC_COMP_HIGH_ACT;
+       if (mode->flags | DRM_MODE_FLAG_BCAST)
+               vmode->sync |= FB_SYNC_BROADCAST;
+
+       vmode->vmode = 0;
+       if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+               vmode->vmode |= FB_VMODE_INTERLACED;
+       if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+               vmode->vmode |= FB_VMODE_DOUBLE;
+}
+EXPORT_SYMBOL(adf_modeinfo_to_fb_videomode);
+
+void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode,
+               struct drm_mode_modeinfo *mode)
+{
+       memset(mode, 0, sizeof(*mode));
+
+       mode->hdisplay = vmode->xres;
+       mode->hsync_start = mode->hdisplay + vmode->right_margin;
+       mode->hsync_end = mode->hsync_start + vmode->hsync_len;
+       mode->htotal = mode->hsync_end + vmode->left_margin;
+
+       mode->vdisplay = vmode->yres;
+       mode->vsync_start = mode->vdisplay + vmode->lower_margin;
+       mode->vsync_end = mode->vsync_start + vmode->vsync_len;
+       mode->vtotal = mode->vsync_end + vmode->upper_margin;
+
+       mode->clock = vmode->pixclock ? PICOS2KHZ(vmode->pixclock) : 0;
+
+       mode->flags = 0;
+       if (vmode->sync & FB_SYNC_HOR_HIGH_ACT)
+               mode->flags |= DRM_MODE_FLAG_PHSYNC;
+       if (vmode->sync & FB_SYNC_VERT_HIGH_ACT)
+               mode->flags |= DRM_MODE_FLAG_PVSYNC;
+       if (vmode->sync & FB_SYNC_COMP_HIGH_ACT)
+               mode->flags |= DRM_MODE_FLAG_PCSYNC;
+       if (vmode->sync & FB_SYNC_BROADCAST)
+               mode->flags |= DRM_MODE_FLAG_BCAST;
+       if (vmode->vmode & FB_VMODE_INTERLACED)
+               mode->flags |= DRM_MODE_FLAG_INTERLACE;
+       if (vmode->vmode & FB_VMODE_DOUBLE)
+               mode->flags |= DRM_MODE_FLAG_DBLSCAN;
+
+       if (vmode->refresh)
+               mode->vrefresh = vmode->refresh;
+       else
+               adf_modeinfo_set_vrefresh(mode);
+
+       if (vmode->name)
+               strlcpy(mode->name, vmode->name, sizeof(mode->name));
+       else
+               adf_modeinfo_set_name(mode);
+}
+EXPORT_SYMBOL(adf_modeinfo_from_fb_videomode);
+
+static int adf_fbdev_post(struct adf_fbdev *fbdev)
+{
+       struct adf_buffer buf;
+       struct sync_fence *complete_fence;
+       int ret = 0;
+
+       memset(&buf, 0, sizeof(buf));
+       buf.overlay_engine = fbdev->eng;
+       buf.w = fbdev->info->var.xres;
+       buf.h = fbdev->info->var.yres;
+       buf.format = fbdev->format;
+       buf.dma_bufs[0] = fbdev->dma_buf;
+       buf.offset[0] = fbdev->offset +
+                       fbdev->info->var.yoffset * fbdev->pitch +
+                       fbdev->info->var.xoffset *
+                       (fbdev->info->var.bits_per_pixel / 8);
+       buf.pitch[0] = fbdev->pitch;
+       buf.n_planes = 1;
+
+       complete_fence = adf_interface_simple_post(fbdev->intf, &buf);
+       if (IS_ERR(complete_fence)) {
+               ret = PTR_ERR(complete_fence);
+               goto done;
+       }
+
+       sync_fence_put(complete_fence);
+done:
+       return ret;
+}
+
+static const u16 vga_palette[][3] = {
+       {0x0000, 0x0000, 0x0000},
+       {0x0000, 0x0000, 0xAAAA},
+       {0x0000, 0xAAAA, 0x0000},
+       {0x0000, 0xAAAA, 0xAAAA},
+       {0xAAAA, 0x0000, 0x0000},
+       {0xAAAA, 0x0000, 0xAAAA},
+       {0xAAAA, 0x5555, 0x0000},
+       {0xAAAA, 0xAAAA, 0xAAAA},
+       {0x5555, 0x5555, 0x5555},
+       {0x5555, 0x5555, 0xFFFF},
+       {0x5555, 0xFFFF, 0x5555},
+       {0x5555, 0xFFFF, 0xFFFF},
+       {0xFFFF, 0x5555, 0x5555},
+       {0xFFFF, 0x5555, 0xFFFF},
+       {0xFFFF, 0xFFFF, 0x5555},
+       {0xFFFF, 0xFFFF, 0xFFFF},
+};
+
+static int adf_fb_alloc(struct adf_fbdev *fbdev)
+{
+       int ret;
+
+       ret = adf_interface_simple_buffer_alloc(fbdev->intf,
+                       fbdev->default_xres_virtual,
+                       fbdev->default_yres_virtual,
+                       fbdev->default_format,
+                       &fbdev->dma_buf, &fbdev->offset, &fbdev->pitch);
+       if (ret < 0) {
+               dev_err(fbdev->info->dev, "allocating fb failed: %d\n", ret);
+               return ret;
+       }
+
+       fbdev->vaddr = dma_buf_vmap(fbdev->dma_buf);
+       if (!fbdev->vaddr) {
+               ret = -ENOMEM;
+               dev_err(fbdev->info->dev, "vmapping fb failed\n");
+               goto err_vmap;
+       }
+       fbdev->info->fix.line_length = fbdev->pitch;
+       fbdev->info->var.xres_virtual = fbdev->default_xres_virtual;
+       fbdev->info->var.yres_virtual = fbdev->default_yres_virtual;
+       fbdev->info->fix.smem_len = fbdev->dma_buf->size;
+       fbdev->info->screen_base = fbdev->vaddr;
+
+       return 0;
+
+err_vmap:
+       dma_buf_put(fbdev->dma_buf);
+       return ret;
+}
+
+static void adf_fb_destroy(struct adf_fbdev *fbdev)
+{
+       dma_buf_vunmap(fbdev->dma_buf, fbdev->vaddr);
+       dma_buf_put(fbdev->dma_buf);
+}
+
+static void adf_fbdev_set_format(struct adf_fbdev *fbdev, u32 format)
+{
+       size_t i;
+       const struct adf_fbdev_format *info = fbdev_format_info(format);
+       for (i = 0; i < ARRAY_SIZE(vga_palette); i++) {
+               u16 r = vga_palette[i][0];
+               u16 g = vga_palette[i][1];
+               u16 b = vga_palette[i][2];
+
+               r >>= (16 - info->r_length);
+               g >>= (16 - info->g_length);
+               b >>= (16 - info->b_length);
+
+               fbdev->pseudo_palette[i] =
+                       (r << info->r_offset) |
+                       (g << info->g_offset) |
+                       (b << info->b_offset);
+
+               if (info->a_length) {
+                       u16 a = BIT(info->a_length) - 1;
+                       fbdev->pseudo_palette[i] |= (a << info->a_offset);
+               }
+       }
+
+       fbdev->info->var.bits_per_pixel = adf_format_bpp(format);
+       fbdev->info->var.red.length = info->r_length;
+       fbdev->info->var.red.offset = info->r_offset;
+       fbdev->info->var.green.length = info->g_length;
+       fbdev->info->var.green.offset = info->g_offset;
+       fbdev->info->var.blue.length = info->b_length;
+       fbdev->info->var.blue.offset = info->b_offset;
+       fbdev->info->var.transp.length = info->a_length;
+       fbdev->info->var.transp.offset = info->a_offset;
+       fbdev->format = format;
+}
+
+static void adf_fbdev_fill_modelist(struct adf_fbdev *fbdev)
+{
+       struct drm_mode_modeinfo *modelist;
+       struct fb_videomode fbmode;
+       size_t n_modes, i;
+       int ret = 0;
+
+       n_modes = adf_interface_modelist(fbdev->intf, NULL, 0);
+       modelist = kzalloc(sizeof(modelist[0]) * n_modes, GFP_KERNEL);
+       if (!modelist) {
+               dev_warn(fbdev->info->dev, "allocating new modelist failed; keeping old modelist\n");
+               return;
+       }
+       adf_interface_modelist(fbdev->intf, modelist, n_modes);
+
+       fb_destroy_modelist(&fbdev->info->modelist);
+
+       for (i = 0; i < n_modes; i++) {
+               adf_modeinfo_to_fb_videomode(&modelist[i], &fbmode);
+               ret = fb_add_videomode(&fbmode, &fbdev->info->modelist);
+               if (ret < 0)
+                       dev_warn(fbdev->info->dev, "adding mode %s to modelist failed: %d\n",
+                                       modelist[i].name, ret);
+       }
+
+       kfree(modelist);
+}
+
+/**
+ * adf_fbdev_open - default implementation of fbdev open op
+ */
+int adf_fbdev_open(struct fb_info *info, int user)
+{
+       struct adf_fbdev *fbdev = info->par;
+       int ret;
+
+       if (!fbdev->open) {
+               struct drm_mode_modeinfo mode;
+               struct fb_videomode fbmode;
+               struct adf_device *dev = adf_interface_parent(fbdev->intf);
+
+               ret = adf_device_attach(dev, fbdev->eng, fbdev->intf);
+               if (ret < 0 && ret != -EALREADY)
+                       return ret;
+
+               ret = adf_fb_alloc(fbdev);
+               if (ret < 0)
+                       return ret;
+
+               adf_interface_current_mode(fbdev->intf, &mode);
+               adf_modeinfo_to_fb_videomode(&mode, &fbmode);
+               fb_videomode_to_var(&fbdev->info->var, &fbmode);
+
+               adf_fbdev_set_format(fbdev, fbdev->default_format);
+               adf_fbdev_fill_modelist(fbdev);
+       }
+
+       ret = adf_fbdev_post(fbdev);
+       if (ret < 0) {
+               if (!fbdev->open)
+                       adf_fb_destroy(fbdev);
+               return ret;
+       }
+
+       fbdev->open = true;
+       return 0;
+}
+EXPORT_SYMBOL(adf_fbdev_open);
+
+/**
+ * adf_fbdev_release - default implementation of fbdev release op
+ */
+int adf_fbdev_release(struct fb_info *info, int user)
+{
+       struct adf_fbdev *fbdev = info->par;
+       adf_fb_destroy(fbdev);
+       fbdev->open = false;
+       return 0;
+}
+EXPORT_SYMBOL(adf_fbdev_release);
+
+/**
+ * adf_fbdev_check_var - default implementation of fbdev check_var op
+ */
+int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       struct adf_fbdev *fbdev = info->par;
+       bool valid_format = true;
+       u32 format = drm_fourcc_from_fb_var(var);
+       u32 pitch = var->xres_virtual * var->bits_per_pixel / 8;
+
+       if (!format) {
+               dev_dbg(info->dev, "%s: unrecognized format\n", __func__);
+               valid_format = false;
+       }
+
+       if (valid_format && var->grayscale) {
+               dev_dbg(info->dev, "%s: grayscale modes not supported\n",
+                               __func__);
+               valid_format = false;
+       }
+
+       if (valid_format && var->nonstd) {
+               dev_dbg(info->dev, "%s: nonstandard formats not supported\n",
+                               __func__);
+               valid_format = false;
+       }
+
+       if (valid_format && !adf_overlay_engine_supports_format(fbdev->eng,
+                       format)) {
+               char format_str[ADF_FORMAT_STR_SIZE];
+               adf_format_str(format, format_str);
+               dev_dbg(info->dev, "%s: format %s not supported by overlay engine %s\n",
+                               __func__, format_str, fbdev->eng->base.name);
+               valid_format = false;
+       }
+
+       if (valid_format && pitch > fbdev->pitch) {
+               dev_dbg(info->dev, "%s: fb pitch too small for var (pitch = %u, xres_virtual = %u, bits_per_pixel = %u)\n",
+                               __func__, fbdev->pitch, var->xres_virtual,
+                               var->bits_per_pixel);
+               valid_format = false;
+       }
+
+       if (valid_format && var->yres_virtual > fbdev->default_yres_virtual) {
+               dev_dbg(info->dev, "%s: fb height too small for var (h = %u, yres_virtual = %u)\n",
+                               __func__, fbdev->default_yres_virtual,
+                               var->yres_virtual);
+               valid_format = false;
+       }
+
+       if (valid_format) {
+               var->activate = info->var.activate;
+               var->height = info->var.height;
+               var->width = info->var.width;
+               var->accel_flags = info->var.accel_flags;
+               var->rotate = info->var.rotate;
+               var->colorspace = info->var.colorspace;
+               /* userspace can't change these */
+       } else {
+               /* if any part of the format is invalid then fixing it up is
+                  impractical, so save just the modesetting bits and
+                  overwrite everything else */
+               struct fb_videomode mode;
+               fb_var_to_videomode(&mode, var);
+               memcpy(var, &info->var, sizeof(*var));
+               fb_videomode_to_var(var, &mode);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(adf_fbdev_check_var);
+
+/**
+ * adf_fbdev_set_par - default implementation of fbdev set_par op
+ */
+int adf_fbdev_set_par(struct fb_info *info)
+{
+       struct adf_fbdev *fbdev = info->par;
+       struct adf_interface *intf = fbdev->intf;
+       struct fb_videomode vmode;
+       struct drm_mode_modeinfo mode;
+       int ret;
+       u32 format = drm_fourcc_from_fb_var(&info->var);
+
+       fb_var_to_videomode(&vmode, &info->var);
+       adf_modeinfo_from_fb_videomode(&vmode, &mode);
+       ret = adf_interface_set_mode(intf, &mode);
+       if (ret < 0)
+               return ret;
+
+       ret = adf_fbdev_post(fbdev);
+       if (ret < 0)
+               return ret;
+
+       if (format != fbdev->format)
+               adf_fbdev_set_format(fbdev, format);
+
+       return 0;
+}
+EXPORT_SYMBOL(adf_fbdev_set_par);
+
+/**
+ * adf_fbdev_blank - default implementation of fbdev blank op
+ */
+int adf_fbdev_blank(int blank, struct fb_info *info)
+{
+       struct adf_fbdev *fbdev = info->par;
+       struct adf_interface *intf = fbdev->intf;
+       u8 dpms_state;
+
+       switch (blank) {
+       case FB_BLANK_UNBLANK:
+               dpms_state = DRM_MODE_DPMS_ON;
+               break;
+       case FB_BLANK_NORMAL:
+               dpms_state = DRM_MODE_DPMS_STANDBY;
+               break;
+       case FB_BLANK_VSYNC_SUSPEND:
+               dpms_state = DRM_MODE_DPMS_STANDBY;
+               break;
+       case FB_BLANK_HSYNC_SUSPEND:
+               dpms_state = DRM_MODE_DPMS_SUSPEND;
+               break;
+       case FB_BLANK_POWERDOWN:
+               dpms_state = DRM_MODE_DPMS_OFF;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return adf_interface_blank(intf, dpms_state);
+}
+EXPORT_SYMBOL(adf_fbdev_blank);
+
+/**
+ * adf_fbdev_pan_display - default implementation of fbdev pan_display op
+ */
+int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       struct adf_fbdev *fbdev = info->par;
+       return adf_fbdev_post(fbdev);
+}
+EXPORT_SYMBOL(adf_fbdev_pan_display);
+
+/**
+ * adf_fbdev_mmap - default implementation of fbdev mmap op
+ */
+int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       struct adf_fbdev *fbdev = info->par;
+
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+       return dma_buf_mmap(fbdev->dma_buf, vma, 0);
+}
+EXPORT_SYMBOL(adf_fbdev_mmap);
+
+/**
+ * adf_fbdev_init - initialize helper to wrap ADF device in fbdev API
+ *
+ * @fbdev: the fbdev helper
+ * @interface: the ADF interface that will display the framebuffer
+ * @eng: the ADF overlay engine that will scan out the framebuffer
+ * @xres_virtual: the virtual width of the framebuffer
+ * @yres_virtual: the virtual height of the framebuffer
+ * @format: the format of the framebuffer
+ * @fbops: the device's fbdev ops
+ * @fmt: formatting for the framebuffer identification string
+ * @...: variable arguments
+ *
+ * @format must be a standard, non-indexed RGB format, i.e.,
+ * adf_format_is_rgb(@format) && @format != @DRM_FORMAT_C8.
+ *
+ * Returns 0 on success or -errno on failure.
+ */
+int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface,
+               struct adf_overlay_engine *eng,
+               u16 xres_virtual, u16 yres_virtual, u32 format,
+               struct fb_ops *fbops, const char *fmt, ...)
+{
+       struct adf_device *parent = adf_interface_parent(interface);
+       struct device *dev = &parent->base.dev;
+       u16 width_mm, height_mm;
+       va_list args;
+       int ret;
+
+       if (!adf_format_is_rgb(format) ||
+                       format == DRM_FORMAT_C8) {
+               dev_err(dev, "fbdev helper does not support format %u\n",
+                               format);
+               return -EINVAL;
+       }
+
+       memset(fbdev, 0, sizeof(*fbdev));
+       fbdev->intf = interface;
+       fbdev->eng = eng;
+       fbdev->info = framebuffer_alloc(0, dev);
+       if (!fbdev->info) {
+               dev_err(dev, "allocating framebuffer device failed\n");
+               return -ENOMEM;
+       }
+       fbdev->default_xres_virtual = xres_virtual;
+       fbdev->default_yres_virtual = yres_virtual;
+       fbdev->default_format = format;
+
+       fbdev->info->flags = FBINFO_FLAG_DEFAULT;
+       ret = adf_interface_get_screen_size(interface, &width_mm, &height_mm);
+       if (ret < 0) {
+               width_mm = 0;
+               height_mm = 0;
+       }
+       fbdev->info->var.width = width_mm;
+       fbdev->info->var.height = height_mm;
+       fbdev->info->var.activate = FB_ACTIVATE_VBL;
+       va_start(args, fmt);
+       vsnprintf(fbdev->info->fix.id, sizeof(fbdev->info->fix.id), fmt, args);
+       va_end(args);
+       fbdev->info->fix.type = FB_TYPE_PACKED_PIXELS;
+       fbdev->info->fix.visual = FB_VISUAL_TRUECOLOR;
+       fbdev->info->fix.xpanstep = 1;
+       fbdev->info->fix.ypanstep = 1;
+       INIT_LIST_HEAD(&fbdev->info->modelist);
+       fbdev->info->fbops = fbops;
+       fbdev->info->pseudo_palette = fbdev->pseudo_palette;
+       fbdev->info->par = fbdev;
+
+       ret = register_framebuffer(fbdev->info);
+       if (ret < 0) {
+               dev_err(dev, "registering framebuffer failed: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(adf_fbdev_init);
+
+/**
+ * adf_fbdev_destroy - destroy helper to wrap ADF device in fbdev API
+ *
+ * @fbdev: the fbdev helper
+ */
+void adf_fbdev_destroy(struct adf_fbdev *fbdev)
+{
+       unregister_framebuffer(fbdev->info);
+       if (WARN_ON(fbdev->open))
+               adf_fb_destroy(fbdev);
+       framebuffer_release(fbdev->info);
+}
+EXPORT_SYMBOL(adf_fbdev_destroy);
diff --git a/include/video/adf_fbdev.h b/include/video/adf_fbdev.h
new file mode 100644 (file)
index 0000000..9c34914
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef _VIDEO_ADF_FBDEV_H_
+#define _VIDEO_ADF_FBDEV_H_
+
+#include <linux/fb.h>
+#include <video/adf.h>
+
+struct adf_fbdev {
+       struct adf_interface *intf;
+       struct adf_overlay_engine *eng;
+       struct fb_info *info;
+       u32 pseudo_palette[16];
+
+       bool open;
+
+       struct dma_buf *dma_buf;
+       u32 offset;
+       u32 pitch;
+       void *vaddr;
+       u32 format;
+
+       u16 default_xres_virtual;
+       u16 default_yres_virtual;
+       u32 default_format;
+};
+
+void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode,
+               struct fb_videomode *vmode);
+void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode,
+               struct drm_mode_modeinfo *mode);
+
+int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface,
+               struct adf_overlay_engine *eng,
+               u16 xres_virtual, u16 yres_virtual, u32 format,
+               struct fb_ops *fbops, const char *fmt, ...);
+void adf_fbdev_destroy(struct adf_fbdev *fbdev);
+
+int adf_fbdev_open(struct fb_info *info, int user);
+int adf_fbdev_release(struct fb_info *info, int user);
+int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
+int adf_fbdev_set_par(struct fb_info *info);
+int adf_fbdev_blank(int blank, struct fb_info *info);
+int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info);
+int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma);
+
+#endif /* _VIDEO_ADF_FBDEV_H_ */