The SimpleDRM driver binds to simple-framebuffer devices and provides a
DRM/KMS API. It provides only a single CRTC+encoder+connector combination
plus one initial mode.

Userspace can create one dumb-buffer and attach it to the CRTC. Only if
the buffer is destroyed, a new buffer can be created. The buffer is
directly mapped into user-space, so we have only resources for a single
buffer. Otherwise, shadow buffers plus damage-request would be needed.

Signed-off-by: David Herrmann <dh.herrm...@gmail.com>
Tested-by: Stephen Warren <swar...@nvidia.com>
---
 MAINTAINERS                                |   8 +
 drivers/gpu/drm/Kconfig                    |   2 +
 drivers/gpu/drm/Makefile                   |   1 +
 drivers/gpu/drm/simpledrm/Kconfig          |  18 ++
 drivers/gpu/drm/simpledrm/Makefile         |   3 +
 drivers/gpu/drm/simpledrm/simpledrm.h      |  89 ++++++++
 drivers/gpu/drm/simpledrm/simpledrm_drv.c  | 225 ++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_main.c | 328 +++++++++++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_mem.c  | 242 +++++++++++++++++++++
 9 files changed, 916 insertions(+)
 create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
 create mode 100644 drivers/gpu/drm/simpledrm/Makefile
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_drv.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_main.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_mem.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a26b10e..35c2fab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7435,6 +7435,14 @@ S:       Odd Fixes
 F:     drivers/media/platform/sh_vou.c
 F:     include/media/sh_vou.h
 
+SIMPLE DRM DRIVER
+M:     David Herrmann <dh.herrm...@gmail.com>
+L:     dri-devel@lists.freedesktop.org
+T:     git git://people.freedesktop.org/~dvdhrm/linux
+S:     Maintained
+F:     drivers/gpu/drm/simpledrm
+F:     include/linux/platform_data/simpledrm.h
+
 SIMPLE FIRMWARE INTERFACE (SFI)
 M:     Len Brown <l...@kernel.org>
 L:     sfi-de...@simplefirmware.org
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 955555d..33c1765 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -236,3 +236,5 @@ source "drivers/gpu/drm/tilcdc/Kconfig"
 source "drivers/gpu/drm/qxl/Kconfig"
 
 source "drivers/gpu/drm/msm/Kconfig"
+
+source "drivers/gpu/drm/simpledrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index f089adf..fe23d6f 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -55,4 +55,5 @@ obj-$(CONFIG_DRM_OMAP)        += omapdrm/
 obj-$(CONFIG_DRM_TILCDC)       += tilcdc/
 obj-$(CONFIG_DRM_QXL) += qxl/
 obj-$(CONFIG_DRM_MSM) += msm/
+obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm/
 obj-y                  += i2c/
diff --git a/drivers/gpu/drm/simpledrm/Kconfig 
b/drivers/gpu/drm/simpledrm/Kconfig
new file mode 100644
index 0000000..35bcce8
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -0,0 +1,18 @@
+config DRM_SIMPLEDRM
+       tristate "Simple firmware framebuffer DRM driver"
+       depends on DRM && (FB_SIMPLE = n)
+       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..ceb97eb
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -0,0 +1,3 @@
+simpledrm-y := simpledrm_drv.o simpledrm_main.o simpledrm_mem.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..977b344
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -0,0 +1,89 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrm...@gmail.com>
+ *
+ * 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>
+
+struct sdrm_device;
+struct sdrm_gem_object;
+struct sdrm_framebuffer;
+
+/* simpledrm devices */
+
+struct sdrm_device {
+       struct drm_device *ddev;
+
+       /* 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;
+
+       /* mode-setting objects */
+       struct sdrm_gem_object *fb_obj;
+       struct drm_crtc crtc;
+       struct drm_encoder enc;
+       struct drm_connector conn;
+       struct drm_display_mode *mode;
+};
+
+int sdrm_drm_load(struct drm_device *ddev, unsigned long flags);
+int sdrm_drm_unload(struct drm_device *ddev);
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+int sdrm_pdev_init(struct sdrm_device *sdrm);
+void sdrm_pdev_destroy(struct sdrm_device *sdrm);
+
+/* simpledrm gem objects */
+
+struct sdrm_gem_object {
+       struct drm_gem_object base;
+       unsigned long fb_base;
+       unsigned long fb_size;
+};
+
+#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
+
+int sdrm_gem_init_object(struct drm_gem_object *obj);
+void sdrm_gem_free_object(struct drm_gem_object *obj);
+void sdrm_gem_unmap_object(struct sdrm_gem_object *obj);
+
+/* dumb buffers */
+
+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);
+
+/* simpledrm framebuffers */
+
+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_drv.c 
b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
new file mode 100644
index 0000000..8a34051
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -0,0 +1,225 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrm...@gmail.com>
+ *
+ * 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_MODESET | DRIVER_GEM,
+       .load = sdrm_drm_load,
+       .unload = sdrm_drm_unload,
+       .fops = &sdrm_drm_fops,
+
+       .gem_init_object = sdrm_gem_init_object,
+       .gem_free_object = sdrm_gem_free_object,
+
+       .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;
+       }
+
+       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;
+
+       if (!request_mem_region(sdrm->fb_base, sdrm->fb_size,
+                               "simple-framebuffer")) {
+               dev_err(sdrm->ddev->dev, "cannot reserve VMEM\n");
+               return -EIO;
+       }
+
+       sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
+       if (!sdrm->fb_map) {
+               dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
+               ret = -EIO;
+               goto err_region;
+       }
+
+       return 0;
+
+err_region:
+       release_mem_region(sdrm->fb_base, sdrm->fb_size);
+       return ret;
+}
+
+void sdrm_pdev_destroy(struct sdrm_device *sdrm)
+{
+       if (sdrm->fb_map) {
+               iounmap(sdrm->fb_map);
+               release_mem_region(sdrm->fb_base, sdrm->fb_size);
+               sdrm->fb_map = NULL;
+       }
+}
+
+static int sdrm_simplefb_probe(struct platform_device *pdev)
+{
+       return drm_platform_init(&sdrm_drm_driver, pdev);
+}
+
+static int sdrm_simplefb_remove(struct platform_device *pdev)
+{
+       drm_platform_exit(&sdrm_drm_driver, pdev);
+
+       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.herrm...@gmail.com>");
+MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_main.c 
b/drivers/gpu/drm/simpledrm/simpledrm_main.c
new file mode 100644
index 0000000..ae507e3
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_main.c
@@ -0,0 +1,328 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrm...@gmail.com>
+ *
+ * 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_crtc.h>
+#include "simpledrm.h"
+
+/* crtcs */
+
+static int sdrm_crtc_set_config(struct drm_mode_set *set)
+{
+       struct drm_device *ddev;
+       struct sdrm_device *sdrm;
+       struct sdrm_framebuffer *fb;
+
+       if (!set || !set->crtc)
+               return -EINVAL;
+
+       ddev = set->crtc->dev;
+       sdrm = ddev->dev_private;
+
+       if (set->crtc != &sdrm->crtc)
+               return -EINVAL;
+
+       if (!set->mode || !set->fb || !set->num_connectors) {
+               sdrm->conn.encoder = NULL;
+               sdrm->conn.dpms = DRM_MODE_DPMS_OFF;
+               sdrm->enc.crtc = NULL;
+               sdrm->crtc.fb = NULL;
+               sdrm->crtc.enabled = false;
+               return 0;
+       }
+
+       fb = to_sdrm_fb(set->fb);
+
+       if (set->num_connectors != 1 || set->connectors[0] != &sdrm->conn)
+               return -EINVAL;
+       if (set->x || set->y)
+               return -EINVAL;
+       if (set->mode->hdisplay != sdrm->fb_width ||
+           set->mode->vdisplay != sdrm->fb_height)
+               return -EINVAL;
+
+       sdrm->conn.encoder = &sdrm->enc;
+       sdrm->conn.dpms = DRM_MODE_DPMS_ON;
+       sdrm->enc.crtc = &sdrm->crtc;
+       sdrm->crtc.fb = set->fb;
+       sdrm->crtc.enabled = true;
+       sdrm->crtc.mode = *set->mode;
+       sdrm->crtc.hwmode = *set->mode;
+       sdrm->crtc.x = 0;
+       sdrm->crtc.y = 0;
+
+       drm_calc_timestamping_constants(&sdrm->crtc);
+       return 0;
+}
+
+static const struct drm_crtc_funcs sdrm_crtc_ops = {
+       .set_config = sdrm_crtc_set_config,
+       .destroy = drm_crtc_cleanup,
+};
+
+/* encoders */
+
+static const struct drm_encoder_funcs sdrm_enc_ops = {
+       .destroy = drm_encoder_cleanup,
+};
+
+/* connectors */
+
+static void sdrm_conn_dpms(struct drm_connector *conn, int mode)
+{
+       conn->dpms = mode;
+}
+
+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 int sdrm_conn_fill_modes(struct drm_connector *conn, uint32_t max_x,
+                               uint32_t max_y)
+{
+       struct sdrm_device *sdrm = conn->dev->dev_private;
+       struct drm_display_mode *mode;
+       int ret;
+
+       if (conn->force == DRM_FORCE_ON)
+               conn->status = connector_status_connected;
+       else if (conn->force)
+               conn->status = connector_status_disconnected;
+       else
+               conn->status = connector_status_connected;
+
+       list_for_each_entry(mode, &conn->modes, head)
+               mode->status = MODE_UNVERIFIED;
+
+       mode = drm_gtf_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height,
+                           60, 0, 0);
+       if (mode) {
+               mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+               drm_mode_probed_add(conn, mode);
+               sdrm->mode = mode;
+               drm_mode_connector_list_update(conn);
+               ret = 1;
+       } else {
+               ret = 0;
+       }
+
+       if (max_x && max_y)
+               drm_mode_validate_size(conn->dev, &conn->modes,
+                                      max_x, max_y, 0);
+
+       drm_mode_prune_invalid(conn->dev, &conn->modes, false);
+       if (list_empty(&conn->modes))
+               return 0;
+
+       drm_mode_sort(&conn->modes);
+
+       list_for_each_entry(mode, &conn->modes, head) {
+               mode->vrefresh = drm_mode_vrefresh(mode);
+               drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+       }
+
+       return ret;
+}
+
+static void sdrm_conn_destroy(struct drm_connector *conn)
+{
+       /* Remove the fake-connector from sysfs and then let the DRM core
+        * clean up all associated resources. */
+       if (device_is_registered(&conn->kdev))
+               drm_sysfs_connector_remove(conn);
+       drm_connector_cleanup(conn);
+}
+
+static const struct drm_connector_funcs sdrm_conn_ops = {
+       .dpms = sdrm_conn_dpms,
+       .detect = sdrm_conn_detect,
+       .fill_modes = sdrm_conn_fill_modes,
+       .destroy = sdrm_conn_destroy,
+};
+
+/* framebuffers */
+
+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,
+       .destroy = sdrm_fb_destroy,
+};
+
+static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
+                                             struct drm_file *dfile,
+                                             struct drm_mode_fb_cmd2 *cmd)
+{
+       struct sdrm_device *sdrm = ddev->dev_private;
+       struct sdrm_framebuffer *fb;
+       struct drm_gem_object *gobj;
+       int ret, i;
+       void *err;
+
+       if (cmd->flags || cmd->pixel_format != sdrm->fb_format)
+               return ERR_PTR(-EINVAL);
+       if (cmd->height != sdrm->fb_height || cmd->width != sdrm->fb_width)
+               return ERR_PTR(-EINVAL);
+       if (cmd->offsets[0] || cmd->pitches[0] != sdrm->fb_stride)
+               return ERR_PTR(-EINVAL);
+
+       gobj = drm_gem_object_lookup(ddev, 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];
+       for (i = 1; i < 4; i++) {
+               fb->base.pitches[i] = 0;
+               fb->base.offsets[i] = 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);
+
+       ret = drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
+       if (ret < 0) {
+               err = ERR_PTR(ret);
+               goto err_free;
+       }
+
+       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,
+};
+
+/* initialization */
+
+int sdrm_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+       struct sdrm_device *sdrm;
+       int ret;
+
+       sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
+       if (!sdrm)
+               return -ENOMEM;
+
+       sdrm->ddev = ddev;
+       ddev->dev_private = sdrm;
+
+       ddev->devname = kstrdup("simpledrm", GFP_KERNEL);
+       if (!ddev->devname) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       ret = sdrm_pdev_init(sdrm);
+       if (ret)
+               goto err_name;
+
+       drm_mode_config_init(ddev);
+       ddev->mode_config.min_width = 0;
+       ddev->mode_config.min_height = 0;
+       ddev->mode_config.max_width = 8192;
+       ddev->mode_config.max_height = 8192;
+       ddev->mode_config.funcs = &sdrm_mode_config_ops;
+
+       ret = drm_crtc_init(ddev, &sdrm->crtc, &sdrm_crtc_ops);
+       if (ret)
+               goto err_cleanup;
+
+       sdrm->enc.possible_crtcs = 1;
+       sdrm->enc.possible_clones = 0;
+       ret = drm_encoder_init(ddev, &sdrm->enc, &sdrm_enc_ops,
+                              DRM_MODE_ENCODER_VIRTUAL);
+       if (ret)
+               goto err_cleanup;
+
+       sdrm->conn.display_info.width_mm = 0;
+       sdrm->conn.display_info.height_mm = 0;
+       sdrm->conn.interlace_allowed = false;
+       sdrm->conn.doublescan_allowed = false;
+       sdrm->conn.polled = 0;
+       ret = drm_connector_init(ddev, &sdrm->conn, &sdrm_conn_ops,
+                                DRM_MODE_CONNECTOR_VIRTUAL);
+       if (ret)
+               goto err_cleanup;
+
+       ret = drm_mode_connector_attach_encoder(&sdrm->conn, &sdrm->enc);
+       if (ret)
+               goto err_cleanup;
+
+       ret = drm_sysfs_connector_add(&sdrm->conn);
+       if (ret)
+               goto err_cleanup;
+
+       return 0;
+
+err_cleanup:
+       drm_mode_config_cleanup(ddev);
+       sdrm_pdev_destroy(sdrm);
+err_name:
+       kfree(ddev->devname);
+       ddev->devname = NULL;
+err_free:
+       kfree(sdrm);
+       return ret;
+}
+
+int sdrm_drm_unload(struct drm_device *ddev)
+{
+       struct sdrm_device *sdrm = ddev->dev_private;
+
+       drm_mode_config_cleanup(ddev);
+       sdrm_pdev_destroy(sdrm);
+       kfree(sdrm);
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_mem.c 
b/drivers/gpu/drm/simpledrm/simpledrm_mem.c
new file mode 100644
index 0000000..1bcd3f1
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_mem.c
@@ -0,0 +1,242 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrm...@gmail.com>
+ *
+ * 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 "simpledrm.h"
+
+/*
+ * Create GEM Object
+ * Allocates a new GEM object to manage the physical memory at @fb_base with
+ * size @fb_size. Both parameters must be page-aligned and point to the
+ * physical memory of the framebuffer to manage. They must not have a
+ * "struct page" and have to be reserved before.
+ * It is the callers responsibility to create only one object per framebuffer.
+ */
+static struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+                                                    unsigned long fb_base,
+                                                    unsigned long fb_size)
+{
+       struct sdrm_gem_object *obj;
+
+       WARN_ON((fb_base & ~PAGE_MASK) != 0);
+       WARN_ON((fb_size & ~PAGE_MASK) != 0);
+
+       /* align to page-size */
+       fb_size = fb_size + (fb_base & ~PAGE_MASK);
+       fb_base = fb_base & PAGE_MASK;
+       fb_size = PAGE_ALIGN(fb_size);
+
+       if (fb_base + fb_size < fb_base)
+               return NULL;
+
+       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+       if (!obj)
+               return NULL;
+       obj->fb_base = fb_base;
+       obj->fb_size = fb_size;
+
+       drm_gem_private_object_init(ddev, &obj->base, fb_size);
+
+       return obj;
+}
+
+/* drm_gem_object_alloc() is not supported */
+int sdrm_gem_init_object(struct drm_gem_object *gobj)
+{
+       return -EINVAL;
+}
+
+/*
+ * Unmap GEM Object
+ * Destroy any memory-mappings that user-space created on this object. Note
+ * that this will cause SIGBUS errors if user-space continues writing to it.
+ * There is no way to remap the pages in fault-handlers as this is not what
+ * we want. You should destroy the mappings only when destroying the object
+ * so no remapping will be needed.
+ * It's the callers responsibility to prevent any further mappings. This only
+ * destroys all current mappings.
+ */
+void sdrm_gem_unmap_object(struct sdrm_gem_object *obj)
+{
+       struct drm_device *ddev = obj->base.dev;
+
+       drm_vma_node_unmap(&obj->base.vma_node, ddev->dev_mapping);
+}
+
+/*
+ * Free GEM Object
+ * Frees the given GEM object. It does not release the framebuffer memory that
+ * was passed during allocation, but destroys all user-space mappings.
+ */
+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;
+       struct sdrm_device *sdrm = ddev->dev_private;
+
+       if (sdrm->fb_obj == obj)
+               sdrm->fb_obj = NULL;
+
+       sdrm_gem_unmap_object(obj);
+
+       drm_gem_free_mmap_offset(gobj);
+       drm_gem_object_release(gobj);
+       kfree(obj);
+}
+
+/*
+ * Create Dumb Buffer
+ * IOCTL backend for dumb-buffers. We only support one framebuffer per
+ * simple-DRM device so this function fails if there is already a framebuffer
+ * allocated. If not, an initial GEM-object plus framebuffer is created and
+ * forwarded to the caller.
+ *
+ * We could try to kill off the previous framebuffer and create a new one for
+ * the caller. However, user-space often allocates two buffers in a row to
+ * allow double-buffering. If we kill the previous buffer, user-space would
+ * have no chance to notice that only one buffer is available.
+ *
+ * So user-space must make sure they either destroy their buffer when dropping
+ * DRM-Master or leave the CRTC intact and let others share the buffer via
+ * drmModeGetFB().
+ *
+ * The buffer parameters must be the same as from the default-mode of the CRTC.
+ * No other sizes can be supported!
+ */
+int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+                    struct drm_mode_create_dumb *args)
+{
+       struct drm_device *dev = dfile->minor->dev;
+       struct sdrm_device *sdrm = dev->dev_private;
+       struct sdrm_gem_object *obj;
+       int ret;
+
+       /* only allow one framebuffer at a time */
+       if (sdrm->fb_obj)
+               return -ENOMEM;
+
+       if (args->width != sdrm->fb_width ||
+           args->height != sdrm->fb_height ||
+           args->bpp != sdrm->fb_bpp ||
+           args->flags)
+               return -EINVAL;
+
+       args->pitch = sdrm->fb_stride;
+       args->size = sdrm->fb_size;
+       obj = sdrm_gem_alloc_object(ddev, sdrm->fb_base, sdrm->fb_size);
+       if (!obj)
+               return -ENOMEM;
+
+       ret = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+       if (ret) {
+               drm_gem_object_unreference(&obj->base);
+               return ret;
+       }
+
+       /* fb_obj is cleared by sdrm_gem_free_object() */
+       sdrm->fb_obj = obj;
+       drm_gem_object_unreference(&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 ret;
+
+       mutex_lock(&ddev->struct_mutex);
+
+       gobj = drm_gem_object_lookup(ddev, dfile, handle);
+       if (!gobj) {
+               ret = -ENOENT;
+               goto out_unlock;
+       }
+
+       ret = drm_gem_create_mmap_offset(gobj);
+       if (ret)
+               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 ret;
+}
+
+/*
+ * mmap ioctl
+ * We simply map the physical range of the FB into user-space as requested. We
+ * perform few sanity-checks and then let io_remap_pfn_range() do all the work.
+ * No vma_ops are needed this way as pages are either cleared or present.
+ */
+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_gem_mm *mm = dev->mm_private;
+       struct drm_vma_offset_node *node;
+       struct drm_gem_object *gobj;
+       struct sdrm_gem_object *obj;
+       int ret;
+
+       if (drm_device_is_unplugged(dev))
+               return -ENODEV;
+
+       mutex_lock(&dev->struct_mutex);
+
+       node = drm_vma_offset_exact_lookup(&mm->vma_manager, vma->vm_pgoff,
+                                          vma_pages(vma));
+       if (!node) {
+               mutex_unlock(&dev->struct_mutex);
+               return drm_mmap(filp, vma);
+       } else if (!drm_vma_node_is_allowed(node, filp)) {
+               mutex_unlock(&dev->struct_mutex);
+               return -EACCES;
+       }
+
+       /* verify mapping size */
+       if (vma_pages(vma) > drm_vma_node_size(node)) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       gobj = container_of(node, struct drm_gem_object, vma_node);
+       obj = to_sdrm_bo(gobj);
+
+       vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+       vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+       /* FIXME: do we need fb_pgprotect() here? */
+
+       /* This object is _not_ referenced here. Therefore, we _must_ destroy
+        * the mapping before destroying the bo! We do this in
+        * sdrm_gem_free_object(). */
+
+       ret = io_remap_pfn_range(vma, vma->vm_start, obj->fb_base >> PAGE_SHIFT,
+                                obj->fb_size, vma->vm_page_prot);
+
+out_unlock:
+       mutex_unlock(&dev->struct_mutex);
+       return ret;
+}
-- 
1.8.4

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

Reply via email to