+ depends on DRM
+ select DRM_KMS_HELPER
+ help
+ SimpleDRM can run on all systems with pre-initialized graphics
+ hardware. It uses a framebuffer that was initialized during
+ firmware boot. No page-flipping, modesetting or other advanced
+ features are available. However, other DRM drivers can be loaded
+ later and take over from SimpleDRM if they provide real hardware
+ support.
+
+ SimpleDRM supports "simple-framebuffer" DeviceTree objects and
+ compatible platform framebuffers.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called simpledrm.
diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
new file mode 100644
index 0000000..f6a62dc
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -0,0 +1,4 @@
+simpledrm-y := simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \
+ simpledrm_damage.o
+
+obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
new file mode 100644
index 0000000..e841655
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -0,0 +1,87 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#ifndef SDRM_DRV_H
+#define SDRM_DRV_H
+
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct sdrm_device;
+struct sdrm_gem_object;
+struct sdrm_framebuffer;
+
+struct sdrm_device {
+ struct drm_device *ddev;
+ struct drm_simple_display_pipe pipe;
+ struct drm_connector conn;
+
+ /* framebuffer information */
+ const struct simplefb_format *fb_sformat;
+ u32 fb_format;
+ u32 fb_width;
+ u32 fb_height;
+ u32 fb_stride;
+ u32 fb_bpp;
+ unsigned long fb_base;
+ unsigned long fb_size;
+ void *fb_map;
+};
+
+int sdrm_drm_modeset_init(struct sdrm_device *sdrm);
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *clips,
+ unsigned int num_clips);
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm);
+int sdrm_dirty_all_unlocked(struct sdrm_device *sdrm);
+
+struct sdrm_gem_object {
+ struct drm_gem_object base;
+ struct sg_table *sg;
+ struct page **pages;
+ void *vmapping;
+};
+
+#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+ size_t size);
+struct drm_gem_object *sdrm_gem_prime_import(struct drm_device *ddev,
+ struct dma_buf *dma_buf);
+void sdrm_gem_free_object(struct drm_gem_object *obj);
+int sdrm_gem_get_pages(struct sdrm_gem_object *obj);
+
+int sdrm_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
+ struct drm_mode_create_dumb *arg);
+int sdrm_dumb_destroy(struct drm_file *file_priv, struct drm_device *ddev,
+ uint32_t handle);
+int sdrm_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
+ uint32_t handle, uint64_t *offset);
+
+struct sdrm_framebuffer {
+ struct drm_framebuffer base;
+ struct sdrm_gem_object *obj;
+};
+
+#define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
+
+#endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
new file mode 100644
index 0000000..e87dac7
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
@@ -0,0 +1,314 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-buf.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include "simpledrm.h"
+
+static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
+{
+ switch (four_cc) {
+ case DRM_FORMAT_RGB565:
+ r >>= 11;
+ g >>= 10;
+ b >>= 11;
+ put_unaligned((u16)((r << 11) | (g << 5) | b), (u16 *)dst);
+ break;
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ r >>= 11;
+ g >>= 11;
+ b >>= 11;
+ put_unaligned((u16)((r << 10) | (g << 5) | b), (u16 *)dst);
+ break;
+ case DRM_FORMAT_RGB888:
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+#ifdef __LITTLE_ENDIAN
+ dst[2] = r;
+ dst[1] = g;
+ dst[0] = b;
+#elif defined(__BIG_ENDIAN)
+ dst[0] = r;
+ dst[1] = g;
+ dst[2] = b;
+#endif
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ put_unaligned((u32)((r << 16) | (g << 8) | b), (u32 *)dst);
+ break;
+ case DRM_FORMAT_ABGR8888:
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ put_unaligned((u32)((b << 16) | (g << 8) | r), (u32 *)dst);
+ break;
+ case DRM_FORMAT_XRGB2101010:
+ case DRM_FORMAT_ARGB2101010:
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ put_unaligned((u32)((r << 20) | (g << 10) | b), (u32 *)dst);
+ break;
+ }
+}
+
+static void sdrm_blit_from_xrgb8888(const u8 *src, u32 src_stride, u32 src_bpp,
+ u8 *dst, u32 dst_stride, u32 dst_bpp,
+ u32 dst_four_cc, u32 width, u32 height)
+{
+ u32 val, i;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ val = get_unaligned((const u32 *)&src[i * src_bpp]);
+ sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+ (val & 0x00ff0000U) >> 8,
+ (val & 0x0000ff00U),
+ (val & 0x000000ffU) << 8);
+ }
+
+ src += src_stride;
+ dst += dst_stride;
+ }
+}
+
+static void sdrm_blit_from_rgb565(const u8 *src, u32 src_stride, u32 src_bpp,
+ u8 *dst, u32 dst_stride, u32 dst_bpp,
+ u32 dst_four_cc, u32 width, u32 height)
+{
+ u32 val, i;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ val = get_unaligned((const u16 *)&src[i * src_bpp]);
+ sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+ (val & 0xf800),
+ (val & 0x07e0) << 5,
+ (val & 0x001f) << 11);
+ }
+
+ src += src_stride;
+ dst += dst_stride;
+ }
+}
+
+static void sdrm_blit_lines(const u8 *src, u32 src_stride,
+ u8 *dst, u32 dst_stride,
+ u32 bpp, u32 width, u32 height)
+{
+ u32 len;
+
+ len = width * bpp;
+
+ while (height--) {
+ memcpy(dst, src, len);
+ src += src_stride;
+ dst += dst_stride;
+ }
+}
+
+static void sdrm_blit(struct sdrm_framebuffer *sfb, u32 x, u32 y,
+ u32 width, u32 height)
+{
+ struct drm_framebuffer *fb = &sfb->base;
+ struct drm_device *ddev = fb->dev;
+ struct sdrm_device *sdrm = ddev->dev_private;
+ u32 src_bpp, dst_bpp, xoff, yoff, x2, y2;
+ u8 *src, *dst;
+
+ /* already unmapped; ongoing handover? */
+ if (!sdrm->fb_map)
+ return;
+
+ /* empty dirty-region, nothing to do */
+ if (!width || !height)
+ return;
+ if (x >= fb->width || y >= fb->height)
+ return;
+
+ /* sanity checks */
+ if (x + width < x)
+ width = fb->width - x;
+ if (y + height < y)
+ height = fb->height - y;
+
+ /* get scanout offsets */
+ xoff = 0;
+ yoff = 0;
+ if (sdrm->pipe.plane.fb == fb) {
+ xoff = sdrm->pipe.crtc.x;
+ yoff = sdrm->pipe.crtc.y;
+ }
+
+ /* get intersection of dirty region and scan-out region */
+ x2 = min(x + width, xoff + sdrm->fb_width);
+ y2 = min(y + height, yoff + sdrm->fb_height);
+ x = max(x, xoff);
+ y = max(y, yoff);
+ if (x2 <= x || y2 <= y)
+ return;
+ width = x2 - x;
+ height = y2 - y;
+
+ /* get buffer offsets */
+ src = sfb->obj->vmapping;
+ dst = sdrm->fb_map;
+
+ /* bo is guaranteed to be big enough; size checks not needed */
+ src_bpp = (fb->bits_per_pixel + 7) / 8;
+ src += fb->offsets[0] + y * fb->pitches[0] + x * src_bpp;
+
+ dst_bpp = (sdrm->fb_bpp + 7) / 8;
+ dst += (y - yoff) * sdrm->fb_stride + (x - xoff) * dst_bpp;
+
+ /* if formats are identical, do a line-by-line copy.. */
+ if (fb->pixel_format == sdrm->fb_format) {
+ sdrm_blit_lines(src, fb->pitches[0],
+ dst, sdrm->fb_stride,
+ src_bpp, width, height);
+ return;
+ }
+
+ /* ..otherwise call slow blit-function */
+ switch (fb->pixel_format) {
+ case DRM_FORMAT_ARGB8888:
+ /* fallthrough */
+ case DRM_FORMAT_XRGB8888:
+ sdrm_blit_from_xrgb8888(src, fb->pitches[0], src_bpp,
+ dst, sdrm->fb_stride, dst_bpp,
+ sdrm->fb_format, width, height);
+ break;
+ case DRM_FORMAT_RGB565:
+ sdrm_blit_from_rgb565(src, fb->pitches[0], src_bpp,
+ dst, sdrm->fb_stride, dst_bpp,
+ sdrm->fb_format, width, height);
+ break;
+ }
+}
+
+static int sdrm_begin_access(struct sdrm_framebuffer *sfb)
+{
+ int r;
+
+ r = sdrm_gem_get_pages(sfb->obj);
+ if (r)
+ return r;
+
+ if (!sfb->obj->base.import_attach)
+ return 0;
+
+ return dma_buf_begin_cpu_access(sfb->obj->base.import_attach->dmabuf,
+ DMA_FROM_DEVICE);
+}
+
+static void sdrm_end_access(struct sdrm_framebuffer *sfb)
+{
+ if (!sfb->obj->base.import_attach)
+ return;
+
+ dma_buf_end_cpu_access(sfb->obj->base.import_attach->dmabuf,
+ DMA_FROM_DEVICE);
+}
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *clips,
+ unsigned int num_clips)
+{
+ struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+ struct drm_device *ddev = fb->dev;
+ struct sdrm_device *sdrm = ddev->dev_private;
+ struct drm_clip_rect full_clip;
+ unsigned int i;
+ int r;
+
+ if (!clips || !num_clips) {
+ full_clip.x1 = 0;
+ full_clip.x2 = fb->width;
+ full_clip.y1 = 0;
+ full_clip.y2 = fb->height;
+ clips = &full_clip;
+ num_clips = 1;
+ }
+
+ drm_modeset_lock_all(ddev);
+
+ if (sdrm->pipe.plane.fb != fb) {
+ r = 0;
+ goto unlock;
+ }
+
+ r = sdrm_begin_access(sfb);
+ if (r)
+ goto unlock;
+
+ for (i = 0; i < num_clips; i++) {
+ if (clips[i].x2 <= clips[i].x1 ||
+ clips[i].y2 <= clips[i].y1)
+ continue;
+
+ sdrm_blit(sfb, clips[i].x1, clips[i].y1,
+ clips[i].x2 - clips[i].x1,
+ clips[i].y2 - clips[i].y1);
+ }
+
+ sdrm_end_access(sfb);
+
+unlock:
+ drm_modeset_unlock_all(ddev);
+ return 0;
+}
+
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm)
+{
+ struct drm_framebuffer *fb;
+ struct sdrm_framebuffer *sfb;
+ int r;
+
+ fb = sdrm->pipe.plane.fb;
+ if (!fb)
+ return 0;
+
+ sfb = to_sdrm_fb(fb);
+ r = sdrm_begin_access(sfb);
+ if (r)
+ return r;
+
+ sdrm_blit(sfb, 0, 0, fb->width, fb->height);
+
+ sdrm_end_access(sfb);
+
+ return 0;
+}
+
+int sdrm_dirty_all_unlocked(struct sdrm_device *sdrm)
+{
+ int r;
+
+ drm_modeset_lock_all(sdrm->ddev);
+ r = sdrm_dirty_all_locked(sdrm);
+ drm_modeset_unlock_all(sdrm->ddev);
+
+ return r;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
new file mode 100644
index 0000000..26b4ca9
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -0,0 +1,295 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include "simpledrm.h"
+
+static const struct file_operations sdrm_drm_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = sdrm_drm_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+ .release = drm_release,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .llseek = noop_llseek,
+};
+
+static struct drm_driver sdrm_drm_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+ DRIVER_ATOMIC,
+ .fops = &sdrm_drm_fops,
+
+ .gem_free_object = sdrm_gem_free_object,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = sdrm_gem_prime_import,
+
+ .dumb_create = sdrm_dumb_create,
+ .dumb_map_offset = sdrm_dumb_map_offset,
+ .dumb_destroy = sdrm_dumb_destroy,
+
+ .name = "simpledrm",
+ .desc = "Simple firmware framebuffer DRM driver",
+ .date = "20130601",
+ .major = 0,
+ .minor = 0,
+ .patchlevel = 1,
+};
+
+static int parse_dt(struct platform_device *pdev,
+ struct simplefb_platform_data *mode)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const char *format;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ ret = of_property_read_u32(np, "width", &mode->width);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't parse width property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "height", &mode->height);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't parse height property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "stride", &mode->stride);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't parse stride property\n");
+ return ret;
+ }
+
+ ret = of_property_read_string(np, "format", &format);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't parse format property\n");
+ return ret;
+ }
+ mode->format = format;
+
+ return 0;
+}
+
+static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS;
+
+int sdrm_pdev_init(struct sdrm_device *sdrm)
+{
+ struct platform_device *pdev = sdrm->ddev->platformdev;
+ struct simplefb_platform_data *mode = pdev->dev.platform_data;
+ struct simplefb_platform_data pmode;
+ struct resource *mem;
+ unsigned int depth;
+ int ret, i, bpp;
+
+ if (!mode) {
+ mode = &pmode;
+ ret = parse_dt(pdev, mode);
+ if (ret)
+ return ret;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(sdrm->ddev->dev, "No memory resource\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(simplefb_formats); ++i) {
+ if (strcmp(mode->format, simplefb_formats[i].name))
+ continue;
+
+ sdrm->fb_sformat = &simplefb_formats[i];
+ sdrm->fb_format = simplefb_formats[i].fourcc;
+ sdrm->fb_width = mode->width;
+ sdrm->fb_height = mode->height;
+ sdrm->fb_stride = mode->stride;
+ sdrm->fb_base = mem->start;
+ sdrm->fb_size = resource_size(mem);
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(simplefb_formats)) {
+ dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+ return -ENODEV;
+ }
+
+ switch (sdrm->fb_format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XRGB2101010:
+ case DRM_FORMAT_ARGB2101010:
+ /*
+ * You must adjust sdrm_put() whenever you add a new format
+ * here, otherwise, blitting operations will not work.
+ * Furthermore, include/linux/platform_data/simplefb.h needs
+ * to be adjusted so the platform-device actually allows this
+ * format.
+ */
+ break;
+ default:
+ dev_err(sdrm->ddev->dev, "Unsupported format %s\n",
+ mode->format);
+ return -ENODEV;
+ }
+
+ drm_fb_get_bpp_depth(sdrm->fb_format, &depth, &bpp);
+ if (!bpp) {
+ dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+ return -ENODEV;
+ }
+
+ if (sdrm->fb_size < sdrm->fb_stride * sdrm->fb_height) {
+ dev_err(sdrm->ddev->dev, "FB too small\n");
+ return -ENODEV;
+ } else if ((bpp + 7) / 8 * sdrm->fb_width > sdrm->fb_stride) {
+ dev_err(sdrm->ddev->dev, "Invalid stride\n");
+ return -ENODEV;
+ }
+
+ sdrm->fb_bpp = bpp;
+
+ sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
+ if (!sdrm->fb_map) {
+ dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
+ return -EIO;
+ }
+
+ DRM_DEBUG_KMS("format: %s\n", drm_get_format_name(sdrm->fb_format));
+
+ return 0;
+}
+
+void sdrm_pdev_destroy(struct sdrm_device *sdrm)
+{
+ if (sdrm->fb_map) {
+ iounmap(sdrm->fb_map);
+ sdrm->fb_map = NULL;
+ }
+}
+
+static int sdrm_simplefb_probe(struct platform_device *pdev)
+{
+ struct sdrm_device *sdrm;
+ struct drm_device *ddev;
+ int ret;
+
+ ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
+ if (!ddev)
+ return -ENOMEM;
+
+ sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
+ if (!sdrm)
+ goto err_free;
+
+ ddev->platformdev = pdev;
+ ddev->dev_private = sdrm;
+ sdrm->ddev = ddev;
+
+ ret = sdrm_pdev_init(sdrm);
+ if (ret)
+ goto err_free;
+
+ ret = sdrm_drm_modeset_init(sdrm);
+ if (ret)
+ goto err_destroy;
+
+ ret = drm_dev_register(ddev, 0);
+ if (ret)
+ goto err_cleanup;
+
+ platform_set_drvdata(pdev, ddev);
+
+ DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name,
+ ddev->primary->index);
+
+ return 0;
+
+err_cleanup:
+ drm_mode_config_cleanup(ddev);
+err_destroy:
+ sdrm_pdev_destroy(sdrm);
+err_free:
+ drm_dev_unref(ddev);
+ kfree(sdrm);
+
+ return ret;
+}
+
+static int sdrm_simplefb_remove(struct platform_device *pdev)
+{
+ struct drm_device *ddev = platform_get_drvdata(pdev);
+ struct sdrm_device *sdrm = ddev->dev_private;
+
+ drm_dev_unregister(ddev);
+ drm_mode_config_cleanup(ddev);
+
+ /* protect fb_map removal against sdrm_blit() */
+ drm_modeset_lock_all(ddev);
+ sdrm_pdev_destroy(sdrm);
+ drm_modeset_unlock_all(ddev);
+
+ drm_dev_unref(ddev);
+ kfree(sdrm);
+
+ return 0;
+}
+
+static const struct of_device_id simplefb_of_match[] = {
+ { .compatible = "simple-framebuffer", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, simplefb_of_match);
+
+static struct platform_driver sdrm_simplefb_driver = {
+ .probe = sdrm_simplefb_probe,
+ .remove = sdrm_simplefb_remove,
+ .driver = {
+ .name = "simple-framebuffer",
+ .mod_name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .of_match_table = simplefb_of_match,
+ },
+};
+
+static int __init sdrm_init(void)
+{
+ return platform_driver_register(&sdrm_simplefb_driver);
+}
+
+static void __exit sdrm_exit(void)
+{
+ platform_driver_unregister(&sdrm_simplefb_driver);
+}
+
+module_init(sdrm_init);
+module_exit(sdrm_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@xxxxxxxxx>");
+MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
+MODULE_ALIAS("platform:simple-framebuffer");
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
new file mode 100644
index 0000000..2d59632
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
@@ -0,0 +1,276 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_legacy.h>
+#include "simpledrm.h"
+
+int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
+{
+ size_t num, i;
+
+ if (obj->vmapping)
+ return 0;
+
+ if (obj->base.import_attach) {
+ obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf);
+ return !obj->vmapping ? -ENOMEM : 0;
+ }
+
+ num = obj->base.size >> PAGE_SHIFT;
+ obj->pages = drm_malloc_ab(num, sizeof(*obj->pages));
+ if (!obj->pages)
+ return -ENOMEM;
+
+ for (i = 0; i < num; ++i) {
+ obj->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!obj->pages[i])
+ goto error;
+ }
+
+ obj->vmapping = vmap(obj->pages, num, 0, PAGE_KERNEL);
+ if (!obj->vmapping)
+ goto error;
+
+ return 0;
+
+error:
+ while (i > 0)
+ __free_pages(obj->pages[--i], 0);
+
+ drm_free_large(obj->pages);
+ obj->pages = NULL;
+ return -ENOMEM;
+}
+
+static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
+{
+ size_t num, i;
+
+ if (!obj->vmapping)
+ return;
+
+ if (obj->base.import_attach) {
+ dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping);
+ obj->vmapping = NULL;
+ return;
+ }
+
+ vunmap(obj->vmapping);
+ obj->vmapping = NULL;
+
+ num = obj->base.size >> PAGE_SHIFT;
+ for (i = 0; i < num; ++i)
+ __free_pages(obj->pages[i], 0);
+
+ drm_free_large(obj->pages);
+ obj->pages = NULL;
+}
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+ size_t size)
+{
+ struct sdrm_gem_object *obj;
+
+ WARN_ON(!size || (size & ~PAGE_MASK) != 0);
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return NULL;
+
+ drm_gem_private_object_init(ddev, &obj->base, size);
+ return obj;
+}
+
+void sdrm_gem_free_object(struct drm_gem_object *gobj)
+{
+ struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
+ struct drm_device *ddev = gobj->dev;
+
+ if (obj->pages) {
+ /* kill all user-space mappings */
+ drm_vma_node_unmap(&gobj->vma_node,
+ ddev->anon_inode->i_mapping);
+ sdrm_gem_put_pages(obj);
+ }
+
+ if (gobj->import_attach)
+ drm_prime_gem_destroy(gobj, obj->sg);
+
+ drm_gem_free_mmap_offset(gobj);
+ drm_gem_object_release(gobj);
+ kfree(obj);
+}
+
+int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+ struct drm_mode_create_dumb *args)
+{
+ struct sdrm_gem_object *obj;
+ int r;
+
+ if (args->flags)
+ return -EINVAL;
+
+ /* overflow checks are done by DRM core */
+ args->pitch = (args->bpp + 7) / 8 * args->width;
+ args->size = PAGE_ALIGN(args->pitch * args->height);
+
+ obj = sdrm_gem_alloc_object(ddev, args->size);
+ if (!obj)
+ return -ENOMEM;
+
+ r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+ if (r) {
+ drm_gem_object_unreference_unlocked(&obj->base);
+ return r;
+ }
+
+ /* handle owns a reference */
+ drm_gem_object_unreference_unlocked(&obj->base);
+ return 0;
+}
+
+int sdrm_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev,
+ uint32_t handle)
+{
+ return drm_gem_handle_delete(dfile, handle);
+}
+
+int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
+ uint32_t handle, uint64_t *offset)
+{
+ struct drm_gem_object *gobj;
+ int r;
+
+ mutex_lock(&ddev->struct_mutex);
+
+ gobj = drm_gem_object_lookup(dfile, handle);
+ if (!gobj) {
+ r = -ENOENT;
+ goto out_unlock;
+ }
+
+ r = drm_gem_create_mmap_offset(gobj);
+ if (r)
+ goto out_unref;
+
+ *offset = drm_vma_node_offset_addr(&gobj->vma_node);
+
+out_unref:
+ drm_gem_object_unreference(gobj);
+out_unlock:
+ mutex_unlock(&ddev->struct_mutex);
+ return r;
+}
+
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *priv = filp->private_data;
+ struct drm_device *dev = priv->minor->dev;
+ struct drm_vma_offset_node *node;
+ struct drm_gem_object *gobj;
+ struct sdrm_gem_object *obj;
+ size_t size, i, num;
+ int r;
+
+ if (drm_device_is_unplugged(dev))
+ return -ENODEV;
+
+ drm_vma_offset_lock_lookup(dev->vma_offset_manager);
+ node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
+ vma->vm_pgoff,
+ vma_pages(vma));
+ drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
+
+ if (!node)
+ return drm_legacy_mmap(filp, vma);
+ else if (!drm_vma_node_is_allowed(node, filp))
+ return -EACCES;
+
+ gobj = container_of(node, struct drm_gem_object, vma_node);
+ obj = to_sdrm_bo(gobj);
+ size = drm_vma_node_size(node) << PAGE_SHIFT;
+ if (size < vma->vm_end - vma->vm_start)
+ return r;
+
+ r = sdrm_gem_get_pages(obj);
+ if (r < 0)
+ return r;
+
+ /* prevent dmabuf-imported mmap to user-space */
+ if (!obj->pages)
+ return -EACCES;
+
+ vma->vm_flags |= VM_DONTEXPAND;
+ vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+
+ num = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+ for (i = 0; i < num; ++i) {
+ r = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE,
+ obj->pages[i]);
+ if (r < 0) {
+ if (i > 0)
+ zap_vma_ptes(vma, vma->vm_start, i * PAGE_SIZE);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+struct drm_gem_object *sdrm_gem_prime_import(struct drm_device *ddev,
+ struct dma_buf *dma_buf)
+{
+ struct dma_buf_attachment *attach;
+ struct sdrm_gem_object *obj;
+ struct sg_table *sg;
+ int ret;
+
+ /* need to attach */
+ attach = dma_buf_attach(dma_buf, ddev->dev);
+ if (IS_ERR(attach))
+ return ERR_CAST(attach);
+
+ get_dma_buf(dma_buf);
+
+ sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
+ if (IS_ERR(sg)) {
+ ret = PTR_ERR(sg);
+ goto fail_detach;
+ }
+
+ /*
+ * dma_buf_vmap() gives us a page-aligned mapping, so lets bump the
+ * size of the dma-buf to the next page-boundary
+ */
+ obj = sdrm_gem_alloc_object(ddev, PAGE_ALIGN(dma_buf->size));
+ if (!obj) {
+ ret = -ENOMEM;
+ goto fail_unmap;
+ }
+
+ obj->sg = sg;
+ obj->base.import_attach = attach;
+
+ return &obj->base;
+
+fail_unmap:
+ dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL);
+fail_detach:
+ dma_buf_detach(dma_buf, attach);
+ dma_buf_put(dma_buf);
+ return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
new file mode 100644
index 0000000..6295a9f
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -0,0 +1,276 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include "simpledrm.h"
+
+static const uint32_t sdrm_formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+};
+
+static int sdrm_conn_get_modes(struct drm_connector *conn)
+{
+ struct sdrm_device *sdrm = conn->dev->dev_private;
+ struct drm_display_mode *mode;
+
+ mode = drm_cvt_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height,
+ 60, false, false, false);
+ if (!mode)
+ return 0;
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(conn, mode);
+
+ return 1;
+}
+
+static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = {
+ .get_modes = sdrm_conn_get_modes,
+ .best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
+ bool force)
+{
+ /*
+ * We simulate an always connected monitor. simple-fb doesn't
+ * provide any way to detect whether the connector is active. Hence,
+ * signal DRM core that it is always connected.
+ */
+
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sdrm_conn_ops = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = drm_atomic_helper_connector_reset,
+ .detect = sdrm_conn_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static inline struct sdrm_device *
+pipe_to_sdrm(struct drm_simple_display_pipe *pipe)
+{
+ return container_of(pipe, struct sdrm_device, pipe);
+}
+
+static int sdrm_display_pipe_check(struct drm_simple_display_pipe *pipe,
+ struct drm_plane_state *plane_state,
+ struct drm_crtc_state *crtc_state)
+{
+ struct drm_display_mode *mode = &crtc_state->mode;
+ struct sdrm_device *sdrm = pipe_to_sdrm(pipe);
+ struct drm_framebuffer *fb = plane_state->fb;
+ u32 x = plane_state->src_x >> 16;
+ u32 y = plane_state->src_y >> 16;
+
+ if (mode->hdisplay != sdrm->fb_width ||
+ mode->vdisplay != sdrm->fb_height)
+ return -EINVAL;
+ if (fb->width <= x || fb->height <= y ||
+ fb->width - x < sdrm->fb_width || fb->height - y < sdrm->fb_height)
+ return -EINVAL;
+
+ return 0;
+}
+
+void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+ struct drm_plane_state *plane_state)
+{
+ struct drm_framebuffer *fb = pipe->plane.state->fb;
+ struct sdrm_device *sdrm = pipe_to_sdrm(pipe);
+
+ if (fb)
+ sdrm_dirty_all_locked(sdrm);
+}
+
+static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc)
+{
+ if (crtc->state && crtc->state->event) {
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ crtc->state->event = NULL;
+ }
+}
+
+static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+ struct drm_crtc_state *crtc_state)
+{
+ sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+ sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = {
+ .check = sdrm_display_pipe_check,
+ .update = sdrm_display_pipe_update,
+ .enable = sdrm_display_pipe_enable,
+ .disable = sdrm_display_pipe_disable,
+};
+
+static int sdrm_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *dfile,
+ unsigned int *handle)
+{
+ struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+ return drm_gem_handle_create(dfile, &sfb->obj->base, handle);
+}
+
+static void sdrm_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+ drm_framebuffer_cleanup(fb);
+ drm_gem_object_unreference_unlocked(&sfb->obj->base);
+ kfree(sfb);
+}
+
+static const struct drm_framebuffer_funcs sdrm_fb_ops = {
+ .create_handle = sdrm_fb_create_handle,
+ .dirty = sdrm_dirty,
+ .destroy = sdrm_fb_destroy,
+};
+
+static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
+ struct drm_file *dfile,
+ const struct drm_mode_fb_cmd2 *cmd)
+{
+ struct sdrm_framebuffer *fb;
+ struct drm_gem_object *gobj;
+ u32 bpp, size;
+ int ret;
+ void *err;
+
+ if (cmd->flags)
+ return ERR_PTR(-EINVAL);
+
+ gobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
+ if (!gobj)
+ return ERR_PTR(-EINVAL);
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (!fb) {
+ err = ERR_PTR(-ENOMEM);
+ goto err_unref;
+ }
+ fb->obj = to_sdrm_bo(gobj);
+
+ fb->base.pitches[0] = cmd->pitches[0];
+ fb->base.offsets[0] = cmd->offsets[0];
+ fb->base.width = cmd->width;
+ fb->base.height = cmd->height;
+ fb->base.pixel_format = cmd->pixel_format;
+ drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth,
+ &fb->base.bits_per_pixel);
+
+ /*
+ * width/height are already clamped into min/max_width/height range,
+ * so overflows are not possible
+ */
+
+ bpp = (fb->base.bits_per_pixel + 7) / 8;
+ size = cmd->pitches[0] * cmd->height;
+ if (!bpp ||
+ bpp > 4 ||
+ cmd->pitches[0] < bpp * fb->base.width ||
+ cmd->pitches[0] > 0xffffU ||
+ size + fb->base.offsets[0] < size ||
+ size + fb->base.offsets[0] > fb->obj->base.size) {
+ err = ERR_PTR(-EINVAL);
+ goto err_free;
+ }
+
+ ret = drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
+ if (ret < 0) {
+ err = ERR_PTR(ret);
+ goto err_free;
+ }
+
+ DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id,
+ drm_get_format_name(fb->base.pixel_format));
+
+ return &fb->base;
+
+err_free:
+ kfree(fb);
+err_unref:
+ drm_gem_object_unreference_unlocked(gobj);
+
+ return err;
+}
+
+static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
+ .fb_create = sdrm_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+int sdrm_drm_modeset_init(struct sdrm_device *sdrm)
+{
+ struct drm_connector *conn = &sdrm->conn;
+ struct drm_device *ddev = sdrm->ddev;
+ int ret;
+
+ drm_mode_config_init(ddev);
+ ddev->mode_config.min_width = 1;
+ ddev->mode_config.min_height = 1;
+ ddev->mode_config.max_width = 8192;
+ ddev->mode_config.max_height = 8192;
+ ddev->mode_config.preferred_depth = sdrm->fb_bpp;
+ ddev->mode_config.funcs = &sdrm_mode_config_ops;
+
+ drm_connector_helper_add(conn, &sdrm_conn_hfuncs);
+ ret = drm_connector_init(ddev, conn, &sdrm_conn_ops,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret)
+ goto err_cleanup;
+
+ ret = drm_mode_create_dirty_info_property(ddev);
+ if (ret)
+ goto err_cleanup;
+
+ drm_object_attach_property(&conn->base,
+ ddev->mode_config.dirty_info_property,
+ DRM_MODE_DIRTY_ON);
+
+ ret = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs,
+ sdrm_formats,
+ ARRAY_SIZE(sdrm_formats), conn);
+ if (ret)
+ goto err_cleanup;
+
+ drm_mode_config_reset(ddev);
+
+ return 0;
+
+err_cleanup:
+ drm_mode_config_cleanup(ddev);
+
+ return ret;
+}
--
2.8.2