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 at 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 --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 */
-- 
2.2.2

Reply via email to