Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




Den 16.03.2016 16:11, skrev Daniel Vetter:
On Wed, Mar 16, 2016 at 02:34:15PM +0100, Noralf Trønnes wrote:
tinydrm provides a very simplified view of DRM for displays that has
onboard video memory and is connected through a slow bus like SPI/I2C.

Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx>
Yay, it finally happens! I already made a comment on the cover letter
about the fbdev stuff, I think that's the biggest part to split out from
tinydrm here. I'm not entirely sure a detailed code review makes sense
before that part is done (and hey we can start merging already), so just a
high level review for now:

The big story in kms/drm in the past years is that we've rejecting
anything that remotely looks like a midlayer. Instead the preferred design
pattern is a library of helper functions to implement useful default
behaviour, or sometimes just building blocks for useful default behaviour.
And then build up real drivers using these pieces. The benefit of that is
two-fold:
- easier to share code with other drivers that only need part of the
   behaviour (e.g. fbdev deferred io support).
- easier to adapt to special hw that needs exceptions since worst case you
   can just copypaste an entire hook. Or implement the special case and
   call the default helper for the normal cases.

lwn has a good article on this pattern:

https://lwn.net/Articles/336262/

I was afraid you would say "midlayer" :-)

How about creating macros like SIMPLE_DEV_PM_OPS and friends to simplify
the drm_driver boilerplate and use that in the drivers?

#define SET_DRM_DRIVER_GEM_CMA_OPS \
    .gem_free_object    = drm_gem_cma_free_object, \
    .gem_vm_ops        = &drm_gem_cma_vm_ops, \
    .prime_handle_to_fd    = drm_gem_prime_handle_to_fd, \
    .prime_fd_to_handle    = drm_gem_prime_fd_to_handle, \
    .gem_prime_import    = drm_gem_prime_import, \
    .gem_prime_export    = drm_gem_prime_export, \
    .gem_prime_get_sg_table    = drm_gem_cma_prime_get_sg_table, \
    .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, \
    .gem_prime_vmap        = drm_gem_cma_prime_vmap, \
    .gem_prime_vunmap    = drm_gem_cma_prime_vunmap, \
    .gem_prime_mmap        = drm_gem_cma_prime_mmap, \
    .dumb_create        = drm_gem_cma_dumb_create, \
    .dumb_map_offset    = drm_gem_cma_dumb_map_offset, \
    .dumb_destroy        = drm_gem_dumb_destroy,

#define TINYDRM_DRM_DRIVER(name_struct, name_str, desc_str, date_str) \
static struct drm_driver name_struct = { \
    .driver_features    = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME \
                | DRIVER_ATOMIC, \
    .load            = tinydrm_load, \
    .unload            = tinydrm_unload, \
    .lastclose        = tinydrm_lastclose, \
    SET_DRM_DRIVER_GEM_CMA_OPS \
    .fops            = &tinydrm_fops, \
    .name            = name_str, \
    .desc            = desc_str, \
    .date            = date_str, \
    .major            = 1, \
    .minor            = 0, \
}

Now the driver can do this:
TINYDRM_DRM_DRIVER(adafruit_tft, "adafruit-tft", Adafruit TFT", "20160317");

In addition to that, the tinydrm specific parts that make up
tinydrm_load/unload can be exported if someone wants it slightly different.


In the case of tinydrm I think that means we should have a bunch of new
drm helpers, or extensions for existing ones:
- fbdev deferred io support using ->dirtyfb in drm_fb_helper.c.

Are you thinking something like this?

struct drm_fb_helper_funcs {
    int (*dirtyfb)(struct drm_fb_helper *fb_helper,
               struct drm_clip_rect *clip);
};

struct drm_fb_helper {
    spinlock_t dirty_lock;
    struct drm_clip_rect *dirty_clip;
};


Should I extend drm_fb_helper_sys_* or make it explicit with
drm_fb_helper_sys_*_deferred functions?

#ifdef CONFIG_FB_DEFERRED_IO
/* Will just return if info->fbdefio is not set */
void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
                u32 width, u32 height);
#else
static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
                          u32 width, u32 height)
{ }
#endif

void drm_fb_helper_sys_imageblit(struct fb_info *info,
                                 const struct fb_image *image)
{
        sys_imageblit(info, image);
    drm_fb_helper_sys_deferred(info, image->dx, image->dy, image->width,
                   image->height);
}

OR

void drm_fb_helper_sys_imageblit_deferred(struct fb_info *info,
                                          const struct fb_image *image)
{
    drm_fb_helper_sys_imageblit(info, image);
    drm_fb_helper_sys_deferred(info, image->dx, image->dy, image->width,
                   image->height);
}


Initially I used drm_fb_cma_helper.c with some added deferred code.
This worked fine for fbcon, but the deferred mmap part didn't work well.
For instance when using fbtest, I got short random horizontal lines on the
display that didn't contain the latest pixels. I had to write several times
to /dev/fb1 to trigger a display update to get all the previous pixels to go
away and get the current image. Maybe it's some caching issue, I don't know.
The Raspberry Pi doesn't support 16-bit SPI, so tinydrm does a byte swap to
a new buffer before sending it using 8-bit.
Maybe I need to call some kind of DMA sync function?

The dumb buffer uses drm_gem_cma_dumb_create() which is backed by cma, and
that works just fine (I have only tested with David Herrmann's modeset[1]).
A similar byte swapping happens here.

I also had to do this for the deferred io to work:

info->fix.smem_start = __pa(info->screen_base);

drm_fb_cma_helper assigns the dma address to smem_start, but at least on
the Raspberry Pi this bus address can't be used by deferred_io
(fb_deferred_io_fault()). And the ARM version of __pa states that it
shouldn't be used by drivers, so when my vmalloc version worked, I went
with that. But I see that there's a virt_to_phys() function that doesn't
have that statement about not being used by drivers, so maybe this isn't
a show stopper after all?

Any thoughts on this problem? I would rather have a cma backed fbdev
framebuffer since that would give me the same type of memory both for
fbdev and DRM.

I can however live with a vmalloc buffer, because the SPI subsystem uses
the DMA streaming API and supports vmalloc buffers (spi_map_buf()).
The vast majority of these displays are connected through SPI.
Also the dma address at the head of the buffer isn't much use to me since
almost all of these display controllers supports partial updates, so I
can't use it much anyway (partial updates isn't implemented in the current
code yet).

[1] https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c


- Helper to generate a simple display pipeline with 1 plane, 1 crtc, 1
   encoder pointing at a specific drm_connector. There's lots of other
   simple hw that could use this. Maybe create a new
   drm_simple_kms_helper.c for this.
- A helper to create a simple drm_connector from a drm_panel (the
   get_modes hooks you have here), maybe also in drm_simple_kms_helper.c.

How about this:

struct drm_connector *drm_simple_kms_create_panel_connector(struct drm_device *dev,
                                struct drm_panel *panel);

int drm_simple_kms_create_pipeline(struct drm_device *dev,
        const struct drm_plane_helper_funcs *plane_helper_funcs,
        const uint32_t *plane_formats, unsigned int format_count,
        const struct drm_crtc_helper_funcs *crtc_helper_funcs,
        const struct drm_encoder_helper_funcs *encoder_helper_funcs,
        int encoder_type, struct drm_connector *connector);

Or with DRM_MODE_ENCODER_NONE:

int drm_simple_kms_create_pipeline(struct drm_device *dev,
        const struct drm_plane_helper_funcs *plane_helper_funcs,
        const uint32_t *plane_formats, unsigned int format_count,
        const struct drm_crtc_helper_funcs *crtc_helper_funcs,
        struct drm_connector *connector);

- Helpers to handle dirtyfb, like the clip rect merge function you have in
   here.
- Helper maybe for purely software framebuffers that are uploaded using
   cpu access instead of dma.

Then bus-specific support you have in later patches for tinydrm would each
be a separate drm driver, assemebled using those helper blocks. It's a
notch more boilerplate maybe, but all the separate pieces I listed above
would be useful in drivers outside of just tinydrm. And maybe other
drivers would need almost everything, except the drm_framebuffer must be
alloced using the dma api (i.e. those should use the cma helpers), e.g.
when your dumb bus can do dma of some sort.

Anyway first thoughts, I'm really happy that something like this finally
happens!

Cheers, Daniel

Thanks Daniel,

Noralf.

---
  drivers/gpu/drm/Kconfig                            |   2 +
  drivers/gpu/drm/Makefile                           |   1 +
  drivers/gpu/drm/tinydrm/Kconfig                    |  11 +
  drivers/gpu/drm/tinydrm/Makefile                   |   1 +
  drivers/gpu/drm/tinydrm/core/Makefile              |   8 +
  drivers/gpu/drm/tinydrm/core/internal.h            |  43 +++
  drivers/gpu/drm/tinydrm/core/tinydrm-core.c        | 194 ++++++++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c        | 203 ++++++++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c    | 116 +++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c       | 345 +++++++++++++++++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c | 112 +++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  97 ++++++
  drivers/gpu/drm/tinydrm/core/tinydrm-plane.c       |  50 +++
  include/drm/tinydrm/tinydrm.h                      | 142 +++++++++
  14 files changed, 1325 insertions(+)
  create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
  create mode 100644 drivers/gpu/drm/tinydrm/Makefile
  create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
  create mode 100644 drivers/gpu/drm/tinydrm/core/internal.h
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-plane.c
  create mode 100644 include/drm/tinydrm/tinydrm.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index c4bf9a1..3f8ede0 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -266,3 +266,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig"
  source "drivers/gpu/drm/imx/Kconfig"
source "drivers/gpu/drm/vc4/Kconfig"
+
+source "drivers/gpu/drm/tinydrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 1e9ff4c..c7c5c16 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -75,3 +75,4 @@ obj-y			+= i2c/
  obj-y			+= panel/
  obj-y			+= bridge/
  obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
new file mode 100644
index 0000000..f290045
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -0,0 +1,11 @@
+menuconfig DRM_TINYDRM
+	tristate "Support for small TFT LCD display modules"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	select DRM_PANEL
+	select VIDEOMODE_HELPERS
+	help
+	  Choose this option if you have a tinydrm supported display.
+	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
new file mode 100644
index 0000000..7476ed1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DRM_TINYDRM)		+= core/
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
new file mode 100644
index 0000000..03309f4
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,8 @@
+obj-$(CONFIG_DRM_TINYDRM)		+= tinydrm.o
+tinydrm-y				+= tinydrm-core.o
+tinydrm-y				+= tinydrm-crtc.o
+tinydrm-y				+= tinydrm-framebuffer.o
+tinydrm-y				+= tinydrm-plane.o
+tinydrm-y				+= tinydrm-helpers.o
+tinydrm-y				+= tinydrm-deferred.o
+tinydrm-$(CONFIG_DRM_KMS_FB_HELPER)	+= tinydrm-fbdev.o
diff --git a/drivers/gpu/drm/tinydrm/core/internal.h b/drivers/gpu/drm/tinydrm/core/internal.h
new file mode 100644
index 0000000..a126658
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/internal.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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.
+ */
+
+int tinydrm_crtc_create(struct tinydrm_device *tdev);
+
+static inline bool tinydrm_active(struct tinydrm_device *tdev)
+{
+	struct drm_crtc *crtc;
+
+	drm_for_each_crtc(crtc, tdev->base)
+		return crtc->state && crtc->state->active;
+
+	return false;
+}
+
+void tinydrm_mode_config_init(struct tinydrm_device *tdev);
+
+int tinydrm_plane_init(struct tinydrm_device *tdev);
+
+#ifdef CONFIG_DRM_KMS_FB_HELPER
+int tinydrm_fbdev_init(struct tinydrm_device *tdev);
+void tinydrm_fbdev_fini(struct tinydrm_device *tdev);
+void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev);
+#else
+static inline int tinydrm_fbdev_init(struct tinydrm_device *tdev)
+{
+	return 0;
+}
+
+static inline void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
+{
+}
+
+static inline void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev)
+{
+}
+#endif
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
new file mode 100644
index 0000000..cb3cf71
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,194 @@
+//#define DEBUG
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/device.h>
+
+#include "internal.h"
+
+static int tinydrm_load(struct drm_device *ddev, unsigned long flags)
+{
+	struct tinydrm_device *tdev = ddev->dev_private;
+	struct drm_connector *connector;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	tinydrm_mode_config_init(tdev);
+
+	ret = tinydrm_plane_init(tdev);
+	if (ret)
+		return ret;
+
+	ret = tinydrm_crtc_create(tdev);
+	if (ret)
+		return ret;
+
+	connector = list_first_entry(&ddev->mode_config.connector_list,
+				     typeof(*connector), head);
+	connector->status = connector_status_connected;
+
+	drm_panel_init(&tdev->panel);
+	drm_panel_add(&tdev->panel);
+	drm_panel_attach(&tdev->panel, connector);
+
+	drm_mode_config_reset(ddev);
+
+	ret = tinydrm_fbdev_init(tdev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void tinydrm_lastclose(struct drm_device *ddev)
+{
+	struct tinydrm_device *tdev = ddev->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	tinydrm_fbdev_restore_mode(tdev->fbdev);
+}
+
+static const struct file_operations tinydrm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+
+static struct drm_driver tinydrm_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
+				| DRIVER_ATOMIC,
+	.load			= tinydrm_load,
+	.lastclose		= tinydrm_lastclose,
+//	.unload			= tinydrm_unload,
+	.get_vblank_counter	= drm_vblank_count,
+//	.enable_vblank		= tinydrm_enable_vblank,
+//	.disable_vblank		= tinydrm_disable_vblank,
+	.gem_free_object	= drm_gem_cma_free_object,
+	.gem_vm_ops		= &drm_gem_cma_vm_ops,
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
+	.gem_prime_import	= drm_gem_prime_import,
+	.gem_prime_export	= drm_gem_prime_export,
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap,
+	.dumb_create		= drm_gem_cma_dumb_create,
+	.dumb_map_offset	= drm_gem_cma_dumb_map_offset,
+	.dumb_destroy		= drm_gem_dumb_destroy,
+	.fops			= &tinydrm_fops,
+	.name			= "tinydrm",
+	.desc			= "tinydrm",
+	.date			= "20150916",
+	.major			= 1,
+	.minor			= 0,
+};
+
+void tinydrm_release(struct tinydrm_device *tdev)
+{
+	DRM_DEBUG_KMS("\n");
+
+	if (tdev->deferred)
+		cancel_delayed_work_sync(&tdev->deferred->dwork);
+
+	tinydrm_fbdev_fini(tdev);
+
+	drm_panel_detach(&tdev->panel);
+	drm_panel_remove(&tdev->panel);
+
+	drm_mode_config_cleanup(tdev->base);
+	drm_dev_unregister(tdev->base);
+	drm_dev_unref(tdev->base);
+}
+EXPORT_SYMBOL(tinydrm_release);
+
+int tinydrm_register(struct device *dev, struct tinydrm_device *tdev)
+{
+	struct drm_driver *driver = &tinydrm_driver;
+	struct drm_device *ddev;
+	int ret;
+
+	dev_info(dev, "%s\n", __func__);
+
+dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (WARN_ON(!tdev->dirtyfb))
+		return -EINVAL;
+
+	ddev = drm_dev_alloc(driver, dev);
+	if (!ddev)
+		return -ENOMEM;
+
+	tdev->base = ddev;
+	ddev->dev_private = tdev;
+
+	ret = drm_dev_set_unique(ddev, dev_name(ddev->dev));
+	if (ret)
+		goto err_free;
+
+	ret = drm_dev_register(ddev, 0);
+	if (ret)
+		goto err_free;
+
+	DRM_INFO("Device: %s\n", dev_name(dev));
+	DRM_INFO("Initialized %s %d.%d.%d on minor %d\n",
+		 driver->name, driver->major, driver->minor, driver->patchlevel,
+		 ddev->primary->index);
+
+	return 0;
+
+err_free:
+	drm_dev_unref(ddev);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_register);
+
+static void devm_tinydrm_release(struct device *dev, void *res)
+{
+	tinydrm_release(*(struct tinydrm_device **)res);
+}
+
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev)
+{
+	struct tinydrm_device **ptr;
+	int ret;
+
+	ptr = devres_alloc(devm_tinydrm_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return -ENOMEM;
+
+	ret = tinydrm_register(dev, tdev);
+	if (ret) {
+		devres_free(ptr);
+		return ret;
+	}
+
+	*ptr = tdev;
+	devres_add(dev, ptr);
+
+	return 0;
+}
+EXPORT_SYMBOL(devm_tinydrm_register);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c b/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c
new file mode 100644
index 0000000..65b3426
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+static int tinydrm_connector_get_modes(struct drm_connector *connector)
+{
+	struct tinydrm_device *tdev = connector->dev->dev_private;
+	struct drm_display_mode *mode;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+	ret = drm_panel_get_modes(&tdev->panel);
+	if (ret > 0)
+		return ret;
+
+	mode = drm_cvt_mode(connector->dev, tdev->width, tdev->height, 60, false, false, false);
+	if (!mode)
+		return 0;
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static struct drm_encoder *
+tinydrm_connector_best_encoder(struct drm_connector *connector)
+{
+	return drm_encoder_find(connector->dev, connector->encoder_ids[0]);
+}
+
+static const struct drm_connector_helper_funcs tinydrm_connector_helper_funcs = {
+	.get_modes = tinydrm_connector_get_modes,
+	.best_encoder = tinydrm_connector_best_encoder,
+};
+
+static enum drm_connector_status
+tinydrm_connector_detect(struct drm_connector *connector, bool force)
+{
+	DRM_DEBUG_KMS("status = %d\n", connector->status);
+
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static void tinydrm_connector_destroy(struct drm_connector *connector)
+{
+	DRM_DEBUG_KMS("\n");
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+	kfree(connector);
+}
+
+static const struct drm_connector_funcs tinydrm_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = tinydrm_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = tinydrm_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void tinydrm_encoder_disable(struct drm_encoder *encoder)
+{
+}
+
+static void tinydrm_encoder_enable(struct drm_encoder *encoder)
+{
+}
+
+static int tinydrm_encoder_atomic_check(struct drm_encoder *encoder,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs tinydrm_encoder_helper_funcs = {
+	.disable = tinydrm_encoder_disable,
+	.enable = tinydrm_encoder_enable,
+	.atomic_check = tinydrm_encoder_atomic_check,
+};
+
+static void tinydrm_encoder_cleanup(struct drm_encoder *encoder)
+{
+	DRM_DEBUG_KMS("\n");
+	drm_encoder_cleanup(encoder);
+	kfree(encoder);
+}
+
+static const struct drm_encoder_funcs tinydrm_encoder_funcs = {
+	.destroy = tinydrm_encoder_cleanup,
+};
+
+static void tinydrm_crtc_enable(struct drm_crtc *crtc)
+{
+	struct tinydrm_device *tdev = crtc->dev->dev_private;
+
+	DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled);
+
+	/* The panel must be prepared on the first crtc enable after probe */
+	tinydrm_prepare(tdev);
+	/* The panel is enabled after the first display update */
+}
+
+static void tinydrm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct tinydrm_device *tdev = crtc->dev->dev_private;
+
+	DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled);
+
+	tinydrm_disable(tdev);
+}
+
+static const struct drm_crtc_helper_funcs tinydrm_crtc_helper_funcs = {
+	.disable = tinydrm_crtc_disable,
+	.enable = tinydrm_crtc_enable,
+};
+
+static void tinydrm_crtc_cleanup(struct drm_crtc *crtc)
+{
+	DRM_DEBUG_KMS("\n");
+	drm_crtc_cleanup(crtc);
+	kfree(crtc);
+}
+
+static const struct drm_crtc_funcs tinydrm_crtc_funcs = {
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = tinydrm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+int tinydrm_crtc_create(struct tinydrm_device *tdev)
+{
+	struct drm_device *dev = tdev->base;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	struct drm_crtc *crtc;
+	int ret;
+
+	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+	encoder = kzalloc(sizeof(*encoder), GFP_KERNEL);
+	crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
+	if (!connector || !encoder || !crtc) {
+		ret = -ENOMEM;
+		goto error_free;
+	}
+
+	drm_crtc_helper_add(crtc, &tinydrm_crtc_helper_funcs);
+	ret = drm_crtc_init_with_planes(dev, crtc, &tdev->plane, NULL,
+					&tinydrm_crtc_funcs);
+	if (ret)
+		goto error_free;
+
+	encoder->possible_crtcs = 1 << drm_crtc_index(crtc);
+	drm_encoder_helper_add(encoder, &tinydrm_encoder_helper_funcs);
+	ret = drm_encoder_init(dev, encoder, &tinydrm_encoder_funcs,
+			       DRM_MODE_ENCODER_NONE);
+	if (ret)
+		goto error_free;
+
+	drm_connector_helper_add(connector, &tinydrm_connector_helper_funcs);
+	ret = drm_connector_init(dev, connector, &tinydrm_connector_funcs,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret)
+		goto error_free;
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret)
+		goto error_free;
+
+	ret = drm_connector_register(connector);
+	if (ret)
+		goto error_free;
+
+	return 0;
+
+error_free:
+	kfree(crtc);
+	kfree(encoder);
+	kfree(connector);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c b/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c
new file mode 100644
index 0000000..16553a6
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c
@@ -0,0 +1,116 @@
+#include <drm/tinydrm/tinydrm.h>
+
+#include "internal.h"
+
+bool tinydrm_deferred_begin(struct tinydrm_device *tdev,
+			    struct tinydrm_fb_clip *fb_clip)
+{
+	struct tinydrm_deferred *deferred = tdev->deferred;
+
+	spin_lock(&deferred->lock);
+	*fb_clip = deferred->fb_clip;
+	tinydrm_reset_clip(&deferred->fb_clip.clip);
+	deferred->fb_clip.fb = NULL;
+	deferred->fb_clip.vmem = NULL;
+	spin_unlock(&deferred->lock);
+
+	/* The crtc might have been disabled by the time we get here */
+	if (!tinydrm_active(tdev))
+		return false;
+
+	/* On first update make sure to do the entire framebuffer */
+	if (!tdev->enabled) {
+		fb_clip->clip.x1 = 0;
+		fb_clip->clip.x2 = fb_clip->fb->width - 1;
+		fb_clip->clip.y1 = 0;
+		fb_clip->clip.y2 = fb_clip->fb->height - 1;
+	}
+
+	/* TODO: support partial updates */
+	fb_clip->clip.x1 = 0;
+	fb_clip->clip.x2 = fb_clip->fb->width - 1;
+	fb_clip->clip.y1 = 0;
+	fb_clip->clip.y2 = fb_clip->fb->height - 1;
+
+	return true;
+}
+EXPORT_SYMBOL(tinydrm_deferred_begin);
+
+void tinydrm_deferred_end(struct tinydrm_device *tdev)
+{
+	if (tdev->prepared && !tdev->enabled) {
+		drm_panel_enable(&tdev->panel);
+		tdev->enabled = true;
+	}
+}
+EXPORT_SYMBOL(tinydrm_deferred_end);
+
+int tinydrm_dirtyfb(struct drm_framebuffer *fb, void *vmem, unsigned flags,
+		    unsigned color, struct drm_clip_rect *clips,
+		    unsigned num_clips)
+{
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	struct tinydrm_deferred *deferred = tdev->deferred;
+	struct tinydrm_fb_clip *fb_clip = &tdev->deferred->fb_clip;
+
+	bool no_delay = deferred->no_delay;
+	unsigned long delay;
+
+	dev_dbg(tdev->base->dev, "%s(fb = %p, vmem = %p, clips = %p, num_clips = %u, no_delay = %u)\n", __func__, fb, vmem, clips, num_clips, no_delay);
+
+	if (!vmem || !fb)
+		return -EINVAL;
+
+	spin_lock(&deferred->lock);
+	fb_clip->fb = fb;
+	fb_clip->vmem = vmem;
+	tinydrm_merge_clips(&fb_clip->clip, clips, num_clips, flags,
+			    fb->width, fb->height);
+	if (tinydrm_is_full_clip(&fb_clip->clip, fb->width, fb->height))
+		no_delay = true;
+	spin_unlock(&deferred->lock);
+
+	delay = no_delay ? 0 : msecs_to_jiffies(deferred->defer_ms);
+
+	if (schedule_delayed_work(&deferred->dwork, delay))
+		dev_dbg(tdev->base->dev, "%s: Already scheduled\n", __func__);
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_dirtyfb);
+
+void tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *clips, unsigned num_clips,
+			 unsigned flags, u32 width, u32 height)
+{
+	struct drm_clip_rect full_clip = {
+		.x1 = 0,
+		.x2 = width - 1,
+		.y1 = 0,
+		.y2 = height - 1,
+	};
+	int i;
+
+	if (!clips) {
+		clips = &full_clip;
+		num_clips = 1;
+	}
+
+	for (i = 0; i < num_clips; i++) {
+		dst->x1 = min(dst->x1, clips[i].x1);
+		dst->x2 = max(dst->x2, clips[i].x2);
+		dst->y1 = min(dst->y1, clips[i].y1);
+		dst->y2 = max(dst->y2, clips[i].y2);
+
+		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) {
+			i++;
+			dst->x2 = max(dst->x2, clips[i].x2);
+			dst->y2 = max(dst->y2, clips[i].y2);
+		}
+	}
+
+	dst->x2 = min_t(u32, dst->x2, width - 1);
+	dst->y2 = min_t(u32, dst->y2, height - 1);
+}
+EXPORT_SYMBOL(tinydrm_merge_clips);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
new file mode 100644
index 0000000..44b6a95
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
@@ -0,0 +1,345 @@
+//#define DEBUG
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+#include "internal.h"
+
+#define DEFAULT_DEFIO_DELAY HZ/30
+
+struct tinydrm_fbdev {
+	struct drm_fb_helper fb_helper;
+	struct drm_framebuffer fb;
+	void *vmem;
+};
+
+static inline struct tinydrm_fbdev *helper_to_fbdev(struct drm_fb_helper *helper)
+{
+	return container_of(helper, struct tinydrm_fbdev, fb_helper);
+}
+
+static inline struct tinydrm_fbdev *fb_to_fbdev(struct drm_framebuffer *fb)
+{
+	return container_of(fb, struct tinydrm_fbdev, fb);
+}
+
+static void tinydrm_fbdev_dirty(struct fb_info *info,
+				struct drm_clip_rect *clip, bool run_now)
+{
+	struct drm_fb_helper *helper = info->par;
+	struct tinydrm_device *tdev = helper->dev->dev_private;
+	struct drm_framebuffer *fb = helper->fb;
+
+	if (tdev->plane.fb != fb)
+		return;
+
+	if (tdev->deferred)
+		tdev->deferred->no_delay = run_now;
+	tdev->dirtyfb(fb, info->screen_buffer, 0, 0, clip, 1);
+}
+
+static void tinydrm_fbdev_deferred_io(struct fb_info *info,
+				      struct list_head *pagelist)
+{
+	unsigned long start, end, next, min, max;
+	struct drm_clip_rect clip;
+	struct page *page;
+int count = 0;
+
+	min = ULONG_MAX;
+	max = 0;
+	next = 0;
+	list_for_each_entry(page, pagelist, lru) {
+		start = page->index << PAGE_SHIFT;
+		end = start + PAGE_SIZE - 1;
+		min = min(min, start);
+		max = max(max, end);
+count++;
+	}
+
+	if (min < max) {
+		clip.x1 = 0;
+		clip.x2 = info->var.xres - 1;
+		clip.y1 = min / info->fix.line_length;
+		clip.y2 = min_t(u32, max / info->fix.line_length,
+				    info->var.yres - 1);
+		pr_debug("%s: x1=%u, x2=%u, y1=%u, y2=%u, count=%d\n", __func__, clip.x1, clip.x2, clip.y1, clip.y2, count);
+		tinydrm_fbdev_dirty(info, &clip, true);
+	}
+}
+
+static void tinydrm_fbdev_fb_fillrect(struct fb_info *info,
+				      const struct fb_fillrect *rect)
+{
+	struct drm_clip_rect clip = {
+		.x1 = rect->dx,
+		.x2 = rect->dx + rect->width - 1,
+		.y1 = rect->dy,
+		.y2 = rect->dy + rect->height - 1,
+	};
+
+	dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+		__func__, rect->dx, rect->dy, rect->width, rect->height);
+	sys_fillrect(info, rect);
+	tinydrm_fbdev_dirty(info, &clip, false);
+}
+
+static void tinydrm_fbdev_fb_copyarea(struct fb_info *info,
+				      const struct fb_copyarea *area)
+{
+	struct drm_clip_rect clip = {
+		.x1 = area->dx,
+		.x2 = area->dx + area->width - 1,
+		.y1 = area->dy,
+		.y2 = area->dy + area->height - 1,
+	};
+
+	dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+		__func__,  area->dx, area->dy, area->width, area->height);
+	sys_copyarea(info, area);
+	tinydrm_fbdev_dirty(info, &clip, false);
+}
+
+static void tinydrm_fbdev_fb_imageblit(struct fb_info *info,
+				       const struct fb_image *image)
+{
+	struct drm_clip_rect clip = {
+		.x1 = image->dx,
+		.x2 = image->dx + image->width - 1,
+		.y1 = image->dy,
+		.y2 = image->dy + image->height - 1,
+	};
+
+	dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n",
+		__func__,  image->dx, image->dy, image->width, image->height);
+	sys_imageblit(info, image);
+	tinydrm_fbdev_dirty(info, &clip, false);
+}
+
+static ssize_t tinydrm_fbdev_fb_write(struct fb_info *info,
+				      const char __user *buf, size_t count,
+				      loff_t *ppos)
+{
+	struct drm_clip_rect clip = {
+		.x1 = 0,
+		.x2 = info->var.xres - 1,
+		.y1 = 0,
+		.y2 = info->var.yres - 1,
+	};
+	ssize_t ret;
+
+	dev_dbg(info->dev, "%s:\n", __func__);
+	ret = fb_sys_write(info, buf, count, ppos);
+	tinydrm_fbdev_dirty(info, &clip, false);
+
+	return ret;
+}
+
+static void tinydrm_fbdev_fb_destroy(struct drm_framebuffer *fb)
+{
+}
+
+static struct drm_framebuffer_funcs tinydrm_fbdev_fb_funcs = {
+	.destroy = tinydrm_fbdev_fb_destroy,
+};
+
+static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
+				struct drm_fb_helper_surface_size *sizes)
+{
+	struct tinydrm_fbdev *fbdev = helper_to_fbdev(helper);
+	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+	struct drm_device *dev = helper->dev;
+	struct tinydrm_device *tdev = dev->dev_private;
+	struct fb_deferred_io *fbdefio;
+	struct drm_framebuffer *fb;
+	unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+	struct fb_ops *fbops;
+	struct fb_info *fbi;
+	size_t size;
+	char *screen_buffer;
+	int ret;
+
+	mode_cmd.width = sizes->surface_width;
+	mode_cmd.height = sizes->surface_height;
+	mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+							sizes->surface_depth);
+	size = mode_cmd.pitches[0] * mode_cmd.height;
+
+	/*
+	 * A per device fbops structure is needed because
+	 * fb_deferred_io_cleanup() clears fbops.fb_mmap
+	 */
+	fbops = devm_kzalloc(dev->dev, sizeof(*fbops), GFP_KERNEL);
+	if (!fbops) {
+		dev_err(dev->dev, "Failed to allocate fbops\n");
+		return -ENOMEM;
+	}
+
+	/* A per device structure is needed for individual delays */
+	fbdefio = devm_kzalloc(dev->dev, sizeof(*fbdefio), GFP_KERNEL);
+	if (!fbdefio) {
+		dev_err(dev->dev, "Could not allocate fbdefio\n");
+		return -ENOMEM;
+	}
+
+	fbi = drm_fb_helper_alloc_fbi(helper);
+	if (IS_ERR(fbi)) {
+		dev_err(dev->dev, "Could not allocate fbi\n");
+		return PTR_ERR(fbi);
+	}
+
+	screen_buffer = vzalloc(size);
+	if (!screen_buffer) {
+		dev_err(dev->dev, "Failed to allocate fbdev screen buffer.\n");
+		ret = -ENOMEM;
+		goto err_fb_info_destroy;
+	}
+
+	fb = &fbdev->fb;
+	helper->fb = fb;
+	drm_helper_mode_fill_fb_struct(fb, &mode_cmd);
+	ret = drm_framebuffer_init(dev, fb, &tinydrm_fbdev_fb_funcs);
+	if (ret) {
+		dev_err(dev->dev, "failed to init framebuffer: %d\n", ret);
+		vfree(screen_buffer);
+		goto err_fb_info_destroy;
+	}
+
+	DRM_DEBUG_KMS("fbdev FB ID: %d, vmem = %p\n", fb->base.id, fbdev->vmem);
+
+	fbi->par = helper;
+	fbi->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
+	strcpy(fbi->fix.id, "tinydrm");
+
+	fbops->owner          = THIS_MODULE,
+	fbops->fb_fillrect    = tinydrm_fbdev_fb_fillrect,
+	fbops->fb_copyarea    = tinydrm_fbdev_fb_copyarea,
+	fbops->fb_imageblit   = tinydrm_fbdev_fb_imageblit,
+	fbops->fb_write       = tinydrm_fbdev_fb_write,
+	fbops->fb_check_var   = drm_fb_helper_check_var,
+	fbops->fb_set_par     = drm_fb_helper_set_par,
+	fbops->fb_blank       = drm_fb_helper_blank,
+	fbops->fb_setcmap     = drm_fb_helper_setcmap,
+	fbi->fbops = fbops;
+
+	drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+	drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
+
+	fbdev->vmem = screen_buffer;
+	fbi->screen_buffer = screen_buffer;
+	fbi->screen_size = size;
+	fbi->fix.smem_len = size;
+
+	if (tdev->deferred)
+		fbdefio->delay = msecs_to_jiffies(tdev->deferred->defer_ms);
+	else
+		fbdefio->delay = DEFAULT_DEFIO_DELAY;
+	/* delay=0 is turned into delay=HZ, so use 1 as a minimum */
+	if (!fbdefio->delay)
+		fbdefio->delay = 1;
+	fbdefio->deferred_io = tinydrm_fbdev_deferred_io;
+	fbi->fbdefio = fbdefio;
+	fb_deferred_io_init(fbi);
+
+	return 0;
+
+err_fb_info_destroy:
+	drm_fb_helper_release_fbi(helper);
+
+	return ret;
+}
+
+static const struct drm_fb_helper_funcs tinydrm_fb_helper_funcs = {
+	.fb_probe = tinydrm_fbdev_create,
+};
+
+int tinydrm_fbdev_init(struct tinydrm_device *tdev)
+{
+	struct drm_device *dev = tdev->base;
+	struct drm_fb_helper *helper;
+	struct tinydrm_fbdev *fbdev;
+	int ret;
+
+	DRM_DEBUG_KMS("IN\n");
+
+	fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL);
+	if (!fbdev) {
+		dev_err(dev->dev, "Failed to allocate drm fbdev.\n");
+		return -ENOMEM;
+	}
+
+	helper = &fbdev->fb_helper;
+
+	drm_fb_helper_prepare(dev, helper, &tinydrm_fb_helper_funcs);
+
+	ret = drm_fb_helper_init(dev, helper, 1, 1);
+	if (ret < 0) {
+		dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
+		return ret;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(helper);
+	if (ret < 0) {
+		dev_err(dev->dev, "Failed to add connectors.\n");
+		goto err_drm_fb_helper_fini;
+
+	}
+
+	ret = drm_fb_helper_initial_config(helper, 16);
+	if (ret < 0) {
+		dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+		goto err_drm_fb_helper_fini;
+	}
+
+	tdev->fbdev = fbdev;
+	DRM_DEBUG_KMS("OUT\n");
+
+	return 0;
+
+err_drm_fb_helper_fini:
+	drm_fb_helper_fini(helper);
+
+	return ret;
+}
+
+void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
+{
+	struct tinydrm_fbdev *fbdev = tdev->fbdev;
+	struct drm_fb_helper *fb_helper = &fbdev->fb_helper;
+
+	DRM_DEBUG_KMS("IN\n");
+
+	drm_fb_helper_unregister_fbi(fb_helper);
+	fb_deferred_io_cleanup(fb_helper->fbdev);
+	drm_fb_helper_release_fbi(fb_helper);
+	drm_fb_helper_fini(fb_helper);
+
+	drm_framebuffer_unregister_private(&fbdev->fb);
+	drm_framebuffer_cleanup(&fbdev->fb);
+
+	vfree(fbdev->vmem);
+
+	tdev->fbdev = NULL;
+	DRM_DEBUG_KMS("OUT\n");
+}
+
+/* TODO: pass tdev instead ? */
+void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev)
+{
+	if (fbdev)
+		drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->fb_helper);
+}
+EXPORT_SYMBOL(tinydrm_fbdev_restore_mode);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
new file mode 100644
index 0000000..1056bc6
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+static inline struct tinydrm_framebuffer *to_tinydrm_framebuffer(struct drm_framebuffer *fb)
+{
+	return container_of(fb, struct tinydrm_framebuffer, base);
+}
+
+static void tinydrm_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+	struct tinydrm_framebuffer *tinydrm_fb = to_tinydrm_framebuffer(fb);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	DRM_DEBUG_KMS("fb = %p, cma_obj = %p\n", fb, tinydrm_fb->cma_obj);
+
+	if (tdev->deferred)
+		flush_delayed_work(&tdev->deferred->dwork);
+
+	if (tinydrm_fb->cma_obj)
+		drm_gem_object_unreference_unlocked(&tinydrm_fb->cma_obj->base);
+
+	drm_framebuffer_cleanup(fb);
+	kfree(tinydrm_fb);
+}
+
+static int tinydrm_framebuffer_dirty(struct drm_framebuffer *fb,
+				     struct drm_file *file_priv,
+				     unsigned flags, unsigned color,
+				     struct drm_clip_rect *clips,
+				     unsigned num_clips)
+{
+	struct tinydrm_framebuffer *tfb = to_tinydrm_framebuffer(fb);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	dev_dbg(fb->dev->dev, "%s\n", __func__);
+
+	return tdev->dirtyfb(fb, tfb->cma_obj->vaddr, flags, color, clips, num_clips);
+}
+
+static const struct drm_framebuffer_funcs tinydrm_fb_funcs = {
+	.destroy = tinydrm_framebuffer_destroy,
+	.dirty = tinydrm_framebuffer_dirty,
+/*	TODO?
+ *	.create_handle = tinydrm_framebuffer_create_handle, */
+};
+
+static struct drm_framebuffer *
+tinydrm_fb_create(struct drm_device *ddev, struct drm_file *file_priv,
+		  struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct tinydrm_framebuffer *tinydrm_fb;
+	struct drm_gem_object *obj;
+	int ret;
+
+	/* TODO? Validate the pixel format, size and pitches */
+	DRM_DEBUG_KMS("pixel_format=%s\n", drm_get_format_name(mode_cmd->pixel_format));
+	DRM_DEBUG_KMS("width=%u\n", mode_cmd->width);
+	DRM_DEBUG_KMS("height=%u\n", mode_cmd->height);
+	DRM_DEBUG_KMS("pitches[0]=%u\n", mode_cmd->pitches[0]);
+
+	obj = drm_gem_object_lookup(ddev, file_priv, mode_cmd->handles[0]);
+	if (!obj)
+		return NULL;
+
+	tinydrm_fb = kzalloc(sizeof(*tinydrm_fb), GFP_KERNEL);
+	if (!tinydrm_fb)
+		return NULL;
+
+	tinydrm_fb->cma_obj = to_drm_gem_cma_obj(obj);
+
+	ret = drm_framebuffer_init(ddev, &tinydrm_fb->base, &tinydrm_fb_funcs);
+	if (ret) {
+		kfree(tinydrm_fb);
+		drm_gem_object_unreference_unlocked(obj);
+		return NULL;
+	}
+
+	drm_helper_mode_fill_fb_struct(&tinydrm_fb->base, mode_cmd);
+
+	return &tinydrm_fb->base;
+}
+
+static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
+	.fb_create = tinydrm_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+void tinydrm_mode_config_init(struct tinydrm_device *tdev)
+{
+	struct drm_device *ddev = tdev->base;
+
+	drm_mode_config_init(ddev);
+
+	ddev->mode_config.min_width = tdev->width;
+	ddev->mode_config.min_height = tdev->height;
+	ddev->mode_config.max_width = tdev->width;
+	ddev->mode_config.max_height = tdev->height;
+	ddev->mode_config.funcs = &tinydrm_mode_config_funcs;
+}
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
new file mode 100644
index 0000000..8ed9a15
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/backlight.h>
+#include <linux/spi/spi.h>
+
+#include "internal.h"
+
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev)
+{
+	struct backlight_device *backlight;
+	struct device_node *np;
+
+	np = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (!np)
+		return NULL;
+
+	backlight = of_find_backlight_by_node(np);
+	of_node_put(np);
+
+	if (!backlight)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	return backlight;
+}
+EXPORT_SYMBOL(tinydrm_of_find_backlight);
+
+int tinydrm_panel_enable_backlight(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+
+	if (tdev->backlight) {
+		if (tdev->backlight->props.brightness == 0)
+			tdev->backlight->props.brightness =
+					tdev->backlight->props.max_brightness;
+		tdev->backlight->props.state &= ~BL_CORE_SUSPENDED;
+		backlight_update_status(tdev->backlight);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_panel_enable_backlight);
+
+int tinydrm_panel_disable_backlight(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+
+	if (tdev->backlight) {
+		tdev->backlight->props.state |= BL_CORE_SUSPENDED;
+		backlight_update_status(tdev->backlight);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_panel_disable_backlight);
+
+static int __maybe_unused tinydrm_pm_suspend(struct device *dev)
+{
+	struct tinydrm_device *tdev = dev_get_drvdata(dev);
+
+	tinydrm_disable(tdev);
+	tinydrm_unprepare(tdev);
+
+	return 0;
+}
+
+static int __maybe_unused tinydrm_pm_resume(struct device *dev)
+{
+	struct tinydrm_device *tdev = dev_get_drvdata(dev);
+
+	tinydrm_prepare(tdev);
+	/* The panel is enabled after the first display update */
+
+	return 0;
+}
+
+const struct dev_pm_ops tinydrm_simple_pm_ops = {
+        SET_SYSTEM_SLEEP_PM_OPS(tinydrm_pm_suspend, tinydrm_pm_resume)
+};
+EXPORT_SYMBOL(tinydrm_simple_pm_ops);
+
+void tinydrm_spi_shutdown(struct spi_device *spi)
+{
+	struct tinydrm_device *tdev = spi_get_drvdata(spi);
+
+	tinydrm_disable(tdev);
+	tinydrm_unprepare(tdev);
+}
+EXPORT_SYMBOL(tinydrm_spi_shutdown);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c b/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c
new file mode 100644
index 0000000..7774e8c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+/* TODO: Configurable */
+static const uint32_t tinydrm_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+static void tinydrm_plane_atomic_update(struct drm_plane *plane,
+					struct drm_plane_state *old_state)
+{
+	DRM_DEBUG("handle 0x%x, crtc %dx%d+%d+%d\n", 0,
+		  plane->state->crtc_w, plane->state->crtc_h,
+		  plane->state->crtc_x, plane->state->crtc_y);
+}
+
+static const struct drm_plane_helper_funcs tinydrm_plane_helper_funcs = {
+	.atomic_update = tinydrm_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs tinydrm_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+};
+
+int tinydrm_plane_init(struct tinydrm_device *tdev)
+{
+	drm_plane_helper_add(&tdev->plane, &tinydrm_plane_helper_funcs);
+	return drm_universal_plane_init(tdev->base, &tdev->plane, 0,
+					&tinydrm_plane_funcs, tinydrm_formats,
+					ARRAY_SIZE(tinydrm_formats),
+					DRM_PLANE_TYPE_PRIMARY);
+}
diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..695e483
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * 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 __LINUX_TINYDRM_H
+#define __LINUX_TINYDRM_H
+
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_panel.h>
+
+struct tinydrm_deferred;
+struct tinydrm_fbdev;
+struct spi_device;
+struct regulator;
+struct lcdreg;
+
+struct tinydrm_framebuffer {
+	struct drm_framebuffer base;
+	struct drm_gem_cma_object *cma_obj;
+};
+
+struct tinydrm_device {
+	struct drm_device *base;
+	u32 width, height;
+	struct drm_panel panel;
+	struct drm_plane plane;
+	struct tinydrm_fbdev *fbdev;
+	struct tinydrm_deferred *deferred;
+	struct backlight_device *backlight;
+	struct regulator *regulator;
+	struct lcdreg *lcdreg;
+	bool prepared;
+	bool enabled;
+	void *dev_private;
+
+	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
+		       unsigned color, struct drm_clip_rect *clips,
+		       unsigned num_clips);
+};
+
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev);
+int tinydrm_register(struct device *dev, struct tinydrm_device *tdev);
+void tinydrm_release(struct tinydrm_device *tdev);
+
+static inline struct tinydrm_device *tinydrm_from_panel(struct drm_panel *panel)
+{
+	return panel->connector->dev->dev_private;
+}
+
+static inline void tinydrm_prepare(struct tinydrm_device *tdev)
+{
+	if (!tdev->prepared) {
+		drm_panel_prepare(&tdev->panel);
+		tdev->prepared = true;
+	}
+}
+
+static inline void tinydrm_unprepare(struct tinydrm_device *tdev)
+{
+	if (tdev->prepared) {
+		drm_panel_unprepare(&tdev->panel);
+		tdev->prepared = false;
+	}
+}
+
+static inline void tinydrm_enable(struct tinydrm_device *tdev)
+{
+	if (!tdev->enabled) {
+		drm_panel_enable(&tdev->panel);
+		tdev->enabled = true;
+	}
+}
+
+static inline void tinydrm_disable(struct tinydrm_device *tdev)
+{
+	if (tdev->enabled) {
+		drm_panel_disable(&tdev->panel);
+		tdev->enabled = false;
+	}
+}
+
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev);
+int tinydrm_panel_enable_backlight(struct drm_panel *panel);
+int tinydrm_panel_disable_backlight(struct drm_panel *panel);
+extern const struct dev_pm_ops tinydrm_simple_pm_ops;
+void tinydrm_spi_shutdown(struct spi_device *spi);
+
+struct tinydrm_fb_clip {
+	struct drm_framebuffer *fb;
+	struct drm_clip_rect clip;
+	void *vmem;
+};
+
+struct tinydrm_deferred {
+	struct delayed_work dwork;
+	struct tinydrm_fb_clip fb_clip;
+	unsigned defer_ms;
+	spinlock_t lock;
+	bool no_delay;
+};
+
+static inline struct tinydrm_device *work_to_tinydrm(struct work_struct *work)
+{
+	struct tinydrm_deferred *deferred;
+
+	deferred = container_of(work, struct tinydrm_deferred, dwork.work);
+	return deferred->fb_clip.fb->dev->dev_private;
+}
+
+bool tinydrm_deferred_begin(struct tinydrm_device *tdev,
+			    struct tinydrm_fb_clip *fb_clip);
+void tinydrm_deferred_end(struct tinydrm_device *tdev);
+int tinydrm_dirtyfb(struct drm_framebuffer *fb, void *vmem, unsigned flags,
+		    unsigned color, struct drm_clip_rect *clips,
+		    unsigned num_clips);
+
+static inline bool tinydrm_is_full_clip(struct drm_clip_rect *clip, u32 width, u32 height)
+{
+	return clip->x1 == 0 && clip->x2 >= (width - 1) &&
+	       clip->y1 == 0 && clip->y2 >= (height -1);
+}
+
+static inline void tinydrm_reset_clip(struct drm_clip_rect *clip)
+{
+	clip->x1 = ~0;
+	clip->x2 = 0;
+	clip->y1 = ~0;
+	clip->y2 = 0;
+}
+
+void tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *clips, unsigned num_clips,
+			 unsigned flags, u32 width, u32 height);
+
+#endif /* __LINUX_TINYDRM_H */
--
2.2.2

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

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





[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux