diff mbox

[RFC,v2,4/8] drm: Add DRM support for tiny LCD displays

Message ID 1460135110-24121-5-git-send-email-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes April 8, 2016, 5:05 p.m. UTC
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@tronnes.org>
---
 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              |   6 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c        | 155 +++++++++++++++++++++
 .../gpu/drm/tinydrm/core/tinydrm-display-pipe.c    |  82 +++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c       |  94 +++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c |  99 +++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  95 +++++++++++++
 include/drm/tinydrm/tinydrm.h                      | 143 +++++++++++++++++++
 11 files changed, 689 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/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.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 include/drm/tinydrm/tinydrm.h
diff mbox

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index cb62cd9..b495dbf 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -276,3 +276,5 @@  source "drivers/gpu/drm/imx/Kconfig"
 source "drivers/gpu/drm/vc4/Kconfig"
 
 source "drivers/gpu/drm/etnaviv/Kconfig"
+
+source "drivers/gpu/drm/tinydrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index ea9bf59..184056e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -75,3 +75,4 @@  obj-y			+= panel/
 obj-y			+= bridge/
 obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
+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..e26e5ed
--- /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_SIMPLE_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_PANEL
+	select VIDEOMODE_HELPERS
+	select FB_DEFERRED_IO if DRM_KMS_FB_HELPER
+	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..11366b4
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,6 @@ 
+obj-$(CONFIG_DRM_TINYDRM)		+= tinydrm.o
+tinydrm-y				+= tinydrm-core.o
+tinydrm-y				+= tinydrm-display-pipe.o
+tinydrm-y				+= tinydrm-framebuffer.o
+tinydrm-y				+= tinydrm-helpers.o
+tinydrm-$(CONFIG_DRM_KMS_FB_HELPER)	+= tinydrm-fbdev.o
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..131a2ac
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,155 @@ 
+//#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/tinydrm/tinydrm.h>
+#include <linux/device.h>
+
+static const uint32_t tinydrm_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
+	.fb_create = tinydrm_fb_cma_dumb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+void tinydrm_lastclose(struct drm_device *dev)
+{
+	struct tinydrm_device *tdev = dev->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	tinydrm_fbdev_restore_mode(tdev);
+}
+EXPORT_SYMBOL(tinydrm_lastclose);
+
+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,
+};
+EXPORT_SYMBOL(tinydrm_fops);
+
+static void tinydrm_unregister(struct tinydrm_device *tdev)
+{
+	DRM_DEBUG_KMS("\n");
+
+	tinydrm_fbdev_fini(tdev);
+
+	drm_mode_config_cleanup(tdev->base);
+	drm_dev_unregister(tdev->base);
+	drm_dev_unref(tdev->base);
+}
+
+static int tinydrm_register(struct device *parent, struct tinydrm_device *tdev,
+			    struct drm_driver *driver)
+{
+	struct drm_device *dev;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	if (WARN_ON(!tdev->dirtyfb))
+		return -EINVAL;
+
+	if (!parent->coherent_dma_mask) {
+		ret = dma_set_coherent_mask(parent, DMA_BIT_MASK(32));
+		if (ret) {
+			DRM_ERROR("Failed to set coherent_dma_mask\n");
+			return ret;
+		}
+	}
+
+	dev = drm_dev_alloc(driver, parent);
+	if (!dev)
+		return -ENOMEM;
+
+	tdev->base = dev;
+	dev->dev_private = tdev;
+
+	ret = drm_dev_set_unique(dev, dev_name(dev->dev));
+	if (ret)
+		goto err_free;
+
+	ret = drm_dev_register(dev, 0);
+	if (ret)
+		goto err_free;
+
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = tdev->width;
+	dev->mode_config.min_height = tdev->height;
+	dev->mode_config.max_width = tdev->width;
+	dev->mode_config.max_height = tdev->height;
+	dev->mode_config.funcs = &tinydrm_mode_config_funcs;
+
+	ret = tinydrm_display_pipe_init(tdev, tinydrm_formats,
+					ARRAY_SIZE(tinydrm_formats));
+	if (ret)
+		goto err_free;
+
+	drm_mode_config_reset(dev);
+
+	ret = tinydrm_fbdev_init(tdev);
+	if (ret)
+		DRM_ERROR("Failed to initialize fbdev: %d\n", ret);
+
+	DRM_INFO("Device: %s\n", dev_name(dev->dev));
+	DRM_INFO("Initialized %s %d.%d.%d on minor %d\n",
+		 driver->name, driver->major, driver->minor, driver->patchlevel,
+		 dev->primary->index);
+
+	return 0;
+
+err_free:
+	drm_dev_unref(dev);
+
+	return ret;
+}
+
+static void devm_tinydrm_release(struct device *dev, void *res)
+{
+	tinydrm_unregister(*(struct tinydrm_device **)res);
+}
+
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev,
+			  struct drm_driver *driver)
+{
+	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, driver);
+	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-display-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.c
new file mode 100644
index 0000000..5e5fa3c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.c
@@ -0,0 +1,82 @@ 
+/*
+ * 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/drm_crtc.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+static void tinydrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+					struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(pipe, struct tinydrm_device, pipe);
+	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_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(pipe, struct tinydrm_device, pipe);
+	DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled);
+
+	tinydrm_disable(tdev);
+}
+
+struct drm_simple_display_pipe_funcs tinydrm_display_pipe_funcs = {
+	.enable = tinydrm_display_pipe_enable,
+	.disable = tinydrm_display_pipe_disable,
+};
+
+int tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			      const uint32_t *formats, unsigned int format_count)
+{
+	struct drm_device *dev = tdev->base;
+	struct drm_connector *connector;
+	int ret;
+
+	connector = drm_simple_kms_panel_connector_create(dev, &tdev->panel,
+						DRM_MODE_CONNECTOR_VIRTUAL);
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
+
+	ret = drm_simple_display_pipe_init(dev, &tdev->pipe,
+				&tinydrm_display_pipe_funcs,
+				formats, format_count,
+				connector);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_init);
+
+int tinydrm_panel_get_modes(struct drm_panel *panel)
+{
+	struct drm_display_mode *mode;
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(panel, struct tinydrm_device, panel);
+// TODO: get width/height somewhere else
+	mode = drm_cvt_mode(panel->connector->dev, tdev->width, tdev->height,
+			    60, false, false, false);
+	if (!mode)
+		return 0;
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(panel->connector, mode);
+
+	return 1;
+}
+EXPORT_SYMBOL(tinydrm_panel_get_modes);
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..73013b4f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
@@ -0,0 +1,94 @@ 
+/*
+ * 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_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+static int tinydrm_fbdev_fb_dirty(struct drm_framebuffer *fb,
+				  struct drm_file *file_priv,
+				  unsigned flags, unsigned color,
+				  struct drm_clip_rect *clips,
+				  unsigned num_clips)
+{
+	struct drm_gem_cma_object *cma = drm_fb_cma_get_gem_obj(fb, 0);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	if (tdev->pipe.plane.fb != fb)
+		return 0;
+
+	return tdev->dirtyfb(fb, cma->vaddr, flags, color, clips, num_clips);
+}
+
+static struct drm_framebuffer_funcs tinydrm_fbdev_fb_funcs = {
+	.destroy	= drm_fb_cma_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= tinydrm_fbdev_fb_dirty,
+};
+
+static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
+				struct drm_fb_helper_surface_size *sizes)
+{
+	struct tinydrm_device *tdev = helper->dev->dev_private;
+	int ret;
+
+	ret = drm_fbdev_cma_create_with_funcs(helper, sizes,
+					      &tinydrm_fbdev_fb_funcs);
+	if (ret)
+		return ret;
+
+	if (tdev->fbdefio_delay_ms) {
+		unsigned long delay;
+
+		delay = msecs_to_jiffies(tdev->fbdefio_delay_ms);
+		helper->fbdev->fbdefio->delay = delay ? delay : 1;
+	}
+
+	return 0;
+}
+
+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_fbdev_cma *fbdev;
+
+	DRM_DEBUG_KMS("IN\n");
+
+	fbdev = drm_fbdev_cma_init_with_funcs(dev, 16,
+					      dev->mode_config.num_crtc,
+					      dev->mode_config.num_connector,
+					      &tinydrm_fb_helper_funcs);
+	if (IS_ERR(fbdev))
+		return PTR_ERR(fbdev);
+
+	tdev->fbdev_cma = fbdev;
+
+	DRM_DEBUG_KMS("OUT\n");
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_fbdev_init);
+
+void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
+{
+	drm_fbdev_cma_fini(tdev->fbdev_cma);
+	tdev->fbdev_cma = NULL;
+}
+EXPORT_SYMBOL(tinydrm_fbdev_fini);
+
+void tinydrm_fbdev_restore_mode(struct tinydrm_device *tdev)
+{
+	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
+}
+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..e167f92
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
@@ -0,0 +1,99 @@ 
+/*
+ * 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>
+
+struct tinydrm_framebuffer {
+	struct drm_framebuffer base;
+	struct drm_gem_cma_object *cma_obj;
+};
+
+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);
+
+	DRM_DEBUG_KMS("fb = %p, cma_obj = %p\n", fb, tinydrm_fb->cma_obj);
+
+	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, */
+};
+
+/*
+ * Maybe this could be turned into drm_fb_cma_dumb_create_with_funcs() and put
+ * alongside drm_fb_cma_create() in drm_fb_cma_helper.c
+ */
+struct drm_framebuffer *tinydrm_fb_cma_dumb_create(struct drm_device *dev,
+					struct drm_file *file_priv,
+					const 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(dev, 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(dev, &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;
+}
+EXPORT_SYMBOL(tinydrm_fb_cma_dumb_create);
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..3545d7f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
@@ -0,0 +1,95 @@ 
+/*
+ * 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>
+
+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/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..5cd5e62
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,143 @@ 
+/*
+ * 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_gem_cma_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct spi_device;
+struct regulator;
+struct lcdreg;
+
+struct tinydrm_device {
+	struct drm_device *base;
+	u32 width, height;
+	struct drm_simple_display_pipe pipe;
+	struct drm_panel panel;
+	struct drm_fbdev_cma *fbdev_cma;
+	unsigned fbdefio_delay_ms;
+	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);
+};
+
+extern const struct file_operations tinydrm_fops;
+void tinydrm_lastclose(struct drm_device *dev);
+
+#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, \
+	.lastclose		= tinydrm_lastclose, \
+	.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			= name_str, \
+	.desc			= desc_str, \
+	.date			= date_str, \
+	.major			= 1, \
+	.minor			= 0, \
+}
+
+struct drm_framebuffer *tinydrm_fb_cma_dumb_create(struct drm_device *dev,
+					struct drm_file *file_priv,
+					const struct drm_mode_fb_cmd2 *mode_cmd);
+int tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			      const uint32_t *formats, unsigned int format_count);
+int tinydrm_panel_get_modes(struct drm_panel *panel);
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev,
+			  struct drm_driver *driver);
+
+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;
+	}
+}
+
+#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_device *tdev);
+#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_device *tdev)
+{
+}
+#endif
+
+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);
+
+#endif /* __LINUX_TINYDRM_H */