All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/5] drm: Add support for tiny LCD displays
@ 2016-03-16 13:34 Noralf Trønnes
  2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
                   ` (6 more replies)
  0 siblings, 7 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

This is an attempt at providing a DRM version of drivers/staging/fbtft.
I'm sending this early before cleaning up the code hoping to get some
feedback in case there are better ways to structure this.

The tinydrm module provides a very simplified view of DRM for displays that
has onboard video memory and is connected through a slow bus like SPI/I2C.
A driver using tinydrm has to provide a function that will be called from
the dirtyfb ioctl and optionally drm_panel functions which can be used to
set up the display controller and control backlight.

tinydrm also has fbdev support using fb_deferred_io which is tied in through
the dirtyfb function call. A driver can use the provided deferred work code
to collect dirtyfb calls and schedule display memory updates if it whishes.
The various display controllers can have library modules that handles
display updates.
Display controllers that have a similar register interface as the MIPI DBI/DCS
controllers can use the lcdreg module for register access.

struct tinydrm_device {
	struct drm_device *base;
	u32 width, height;
	struct drm_panel panel;
[...]
	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
		       unsigned color, struct drm_clip_rect *clips,
		       unsigned num_clips);
};

+------------------------------+---------+
|                              |  fbdev  |
|                  DRM         +------+  |
|                                     |  |
+-------------------------------------+--+
|                                        |
|                tinydrm                 |
|                                        |
+------------------+ .  .  .  .  .  .  . |
|                  |    deferred work    |
|  Display driver  +---------------------+
|                  |  Controller module  |
+------------------+---------------------+
|                 lcdreg                 |
+----------------------------------------+
|     Interface (SPI, I2C, parallel)     |
+----------------------------------------+


Noralf Trønnes (5):
  drm: Add DRM support for tiny LCD displays
  drm/tinydrm: Add lcd register abstraction
  drm/tinydrm/lcdreg: Add SPI support
  drm/tinydrm: Add mipi-dbi support
  drm/tinydrm: Add support for several Adafruit TFT displays

 drivers/gpu/drm/Kconfig                            |   2 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/tinydrm/Kconfig                    |  27 +
 drivers/gpu/drm/tinydrm/Makefile                   |   8 +
 drivers/gpu/drm/tinydrm/adafruit-tft.c             | 256 ++++++++
 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 ++
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig             |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile            |   5 +
 drivers/gpu/drm/tinydrm/lcdreg/internal.h          |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c       | 190 ++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c    | 281 ++++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c        | 720 +++++++++++++++++++++
 drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 231 +++++++
 include/drm/tinydrm/ili9340.h                      |  85 +++
 include/drm/tinydrm/lcdreg-spi.h                   |  63 ++
 include/drm/tinydrm/lcdreg.h                       | 126 ++++
 include/drm/tinydrm/mipi-dbi.h                     |  24 +
 include/drm/tinydrm/tinydrm.h                      | 142 ++++
 26 files changed, 3345 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
 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 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/ili9340.h
 create mode 100644 include/drm/tinydrm/lcdreg-spi.h
 create mode 100644 include/drm/tinydrm/lcdreg.h
 create mode 100644 include/drm/tinydrm/mipi-dbi.h
 create mode 100644 include/drm/tinydrm/tinydrm.h

--
2.2.2

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
@ 2016-03-16 13:34 ` Noralf Trønnes
  2016-03-16 15:11   ` Daniel Vetter
  2016-03-23 17:37   ` David Herrmann
  2016-03-16 13:34 ` [RFC 2/5] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

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              |   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@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC 2/5] drm/tinydrm: Add lcd register abstraction
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
  2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
@ 2016-03-16 13:34 ` Noralf Trønnes
  2016-03-16 13:34 ` [RFC 3/5] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add LCD register abstraction for MIPI DBI/DCS like controllers.
This hides LCD controller interface implementation details.
When built with debugfs, the register is available to userspace.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig                 |   2 +
 drivers/gpu/drm/tinydrm/Makefile                |   1 +
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig          |   3 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile         |   3 +
 drivers/gpu/drm/tinydrm/lcdreg/internal.h       |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c    | 190 ++++++++++++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c | 281 ++++++++++++++++++++++++
 include/drm/tinydrm/lcdreg.h                    | 126 +++++++++++
 8 files changed, 614 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
 create mode 100644 include/drm/tinydrm/lcdreg.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index f290045..671239f 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -9,3 +9,5 @@ menuconfig DRM_TINYDRM
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
+
+source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 7476ed1..f4a92d9 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
+obj-$(CONFIG_LCDREG)			+= lcdreg/
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
new file mode 100644
index 0000000..41383b1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
@@ -0,0 +1,3 @@
+config LCDREG
+	tristate
+	depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
new file mode 100644
index 0000000..c9ea774
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_LCDREG)                   += lcdreg.o
+lcdreg-y                               += lcdreg-core.o
+lcdreg-$(CONFIG_DEBUG_FS)              += lcdreg-debugfs.o
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/internal.h b/drivers/gpu/drm/tinydrm/lcdreg/internal.h
new file mode 100644
index 0000000..140fcfd
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/internal.h
@@ -0,0 +1,8 @@
+
+#ifdef CONFIG_DEBUG_FS
+void lcdreg_debugfs_init(struct lcdreg *reg);
+void lcdreg_debugfs_exit(struct lcdreg *reg);
+#else
+static inline void lcdreg_debugfs_init(struct lcdreg *reg) { }
+static inline void lcdreg_debugfs_exit(struct lcdreg *reg) { }
+#endif
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
new file mode 100644
index 0000000..bda848c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
@@ -0,0 +1,190 @@
+//#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/tinydrm/lcdreg.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+/**
+ * Write to LCD register
+ *
+ * @reg: LCD register
+ * @regnr: Register number
+ * @transfer: Transfer to write
+ */
+int lcdreg_write(struct lcdreg *reg, unsigned regnr,
+		 struct lcdreg_transfer *transfer)
+{
+	if (WARN_ON_ONCE(!reg || !reg->write || !transfer))
+		return -EINVAL;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+
+	dev_dbg(reg->dev,
+		"lcdreg_write: regnr=0x%02x, index=%u, count=%u, width=%u\n",
+		regnr, transfer->index, transfer->count, transfer->width);
+	lcdreg_dbg_transfer_buf(transfer);
+
+	return reg->write(reg, regnr, transfer);
+}
+EXPORT_SYMBOL(lcdreg_write);
+
+/**
+ * Write 32-bit wide buffer to LCD register
+ * @reg: lcdreg
+ * @regnr: Register number
+ * @buf: Buffer to write
+ * @count: Number of words to write
+ */
+int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *buf,
+		       unsigned count)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = reg->def_width,
+		.count = count,
+	};
+	int i, ret;
+
+	if (!buf)
+		return -EINVAL;
+
+	tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	if (reg->def_width <= 8)
+		for (i = 0; i < tr.count; i++)
+			((u8 *)tr.buf)[i] = buf[i];
+	else
+		for (i = 0; i < tr.count; i++)
+			((u16 *)tr.buf)[i] = buf[i];
+	ret = lcdreg_write(reg, regnr, &tr);
+	kfree(tr.buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_write_buf32);
+
+/**
+ * Read from LCD register
+ *
+ * @reg: LCD register
+ * @regnr: Register number
+ * @transfer: Transfer to read into
+ */
+int lcdreg_read(struct lcdreg *reg, unsigned regnr,
+		struct lcdreg_transfer *transfer)
+{
+	int ret;
+
+	if (WARN_ON_ONCE(!reg || !transfer))
+		return -EINVAL;
+
+	if (!reg->read)
+		return -EOPNOTSUPP;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+
+	dev_dbg(reg->dev,
+		"lcdreg_read: regnr=0x%02x, index=%u, count=%u, width=%u\n",
+		regnr, transfer->index, transfer->count, transfer->width);
+
+	ret = reg->read(reg, regnr, transfer);
+
+	lcdreg_dbg_transfer_buf(transfer);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_read);
+
+/**
+ * Read from LCD register into 32-bit wide buffer
+ * @reg: LCD register
+ * @regnr: Register number
+ * @buf: Buffer to read into
+ * @count: Number of words to read
+ */
+int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf,
+			 unsigned count)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.count = count,
+	};
+	int i, ret;
+
+	if (!buf || !count)
+		return -EINVAL;
+
+	tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	ret = lcdreg_read(reg, regnr, &tr);
+	if (ret) {
+		kfree(tr.buf);
+		return ret;
+	}
+
+	if (reg->def_width <= 8)
+		for (i = 0; i < count; i++)
+			buf[i] = ((u8 *)tr.buf)[i];
+	else
+		for (i = 0; i < count; i++)
+			buf[i] = ((u16 *)tr.buf)[i];
+	kfree(tr.buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_readreg_buf32);
+
+static void devm_lcdreg_release(struct device *dev, void *res)
+{
+	struct lcdreg *reg = *(struct lcdreg **)res;
+
+	lcdreg_debugfs_exit(reg);
+	mutex_destroy(&reg->lock);
+}
+
+/**
+ * Device managed lcdreg initialization
+ *
+ * @dev: Device backing the LCD register
+ * @reg: LCD register
+ */
+struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg)
+{
+	struct lcdreg **ptr;
+
+	if (!dev || !reg)
+		return ERR_PTR(-EINVAL);
+
+	ptr = devres_alloc(devm_lcdreg_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	*ptr = reg;
+	devres_add(dev, ptr);
+	reg->dev = dev;
+	mutex_init(&reg->lock);
+	lcdreg_debugfs_init(reg);
+
+	return reg;
+}
+EXPORT_SYMBOL(devm_lcdreg_init);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
new file mode 100644
index 0000000..9fcc13d
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
@@ -0,0 +1,281 @@
+/*
+ * 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/tinydrm/lcdreg.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define READ_RESULT_SIZE 16
+
+static struct dentry *lcdreg_debugfs_root;
+
+static int lcdreg_userbuf_to_u32(const char __user *user_buf, size_t count,
+				 u32 *dest, size_t dest_size)
+{
+	char *buf, *start;
+	int ret = -EINVAL;
+	int i;
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, user_buf, count)) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	/* turn whitespace into end-of-string for number parsing */
+	for (i = 0; i < count; i++)
+		if (buf[i] == ' ' || buf[i] == '\n' || buf[i] == '\t')
+			buf[i] = '\0';
+
+	i = 0;
+	start = buf;
+	while (start < buf + count) {
+		/* skip "whitespace" */
+		if (*start == '\0') {
+			start++;
+			continue;
+		}
+
+		if (i == dest_size) {
+			ret = -EFBIG;
+			break;
+		}
+
+		ret = kstrtou32(start, 0, &dest[i++]);
+		if (ret)
+			break;
+
+		/* move past this number */
+		while (*start != '\0')
+			start++;
+	};
+
+	kfree(buf);
+
+	return ret ? : i;
+}
+
+static ssize_t lcdreg_debugfs_write_file(struct file *file,
+					 const char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+	u32 txbuf[128];
+
+	ret = lcdreg_userbuf_to_u32(user_buf, count, txbuf, ARRAY_SIZE(txbuf));
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&reg->lock);
+	ret = lcdreg_write_buf32(reg, txbuf[0], txbuf + 1, ret - 1);
+	mutex_unlock(&reg->lock);
+
+	return ret ? : count;
+}
+
+static const struct file_operations lcdreg_debugfs_write_fops = {
+	.open = simple_open,
+	.write = lcdreg_debugfs_write_file,
+	.llseek = default_llseek,
+};
+
+static ssize_t lcdreg_debugfs_read_wr(struct file *file,
+				      const char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+
+	ret = lcdreg_userbuf_to_u32(user_buf, count,
+				    &reg->debugfs_read_reg, 1);
+
+	return ret < 0 ? ret : count;
+}
+
+
+static int lcdreg_debugfs_readreg(struct lcdreg *reg)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = reg->debugfs_read_width,
+		.count = 1,
+	};
+	char *buf = reg->debugfs_read_result;
+	int ret;
+
+	tr.buf = kmalloc(lcdreg_bytes_per_word(tr.width), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	mutex_lock(&reg->lock);
+	ret = lcdreg_read(reg, reg->debugfs_read_reg, &tr);
+	mutex_unlock(&reg->lock);
+	if (ret)
+		goto error_out;
+
+	switch (tr.width) {
+	case 8:
+		snprintf(buf, READ_RESULT_SIZE, "0x%02x\n", *(u8 *)tr.buf);
+		break;
+	case 16:
+		snprintf(buf, READ_RESULT_SIZE, "0x%04x\n", *(u16 *)tr.buf);
+		break;
+	case 24:
+	case 32:
+		snprintf(buf, READ_RESULT_SIZE, "0x%08x\n", *(u32 *)tr.buf);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+error_out:
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static ssize_t lcdreg_debugfs_read_rd(struct file *file,
+				      char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+
+	if (*ppos < 0 || !count)
+		return -EINVAL;
+
+	if (*reg->debugfs_read_result == '\0') {
+		ret = lcdreg_debugfs_readreg(reg);
+		if (ret)
+			return ret;
+	}
+
+	if (*ppos >= strlen(reg->debugfs_read_result)) {
+		*reg->debugfs_read_result = '\0';
+		return 0;
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos,
+				       reg->debugfs_read_result,
+				       strlen(reg->debugfs_read_result));
+}
+
+static const struct file_operations lcdreg_debugfs_read_fops = {
+	.open = simple_open,
+	.read = lcdreg_debugfs_read_rd,
+	.write = lcdreg_debugfs_read_wr,
+	.llseek = default_llseek,
+};
+
+static ssize_t lcdreg_debugfs_reset_wr(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+
+	lcdreg_reset(reg);
+
+	return count;
+}
+
+static const struct file_operations lcdreg_debugfs_reset_fops = {
+	.open = simple_open,
+	.write = lcdreg_debugfs_reset_wr,
+	.llseek = default_llseek,
+};
+
+static int lcdreg_debugfs_readwidth_set(void *data, u64 val)
+{
+	struct lcdreg *reg = data;
+
+	reg->debugfs_read_width = val;
+
+	return 0;
+}
+
+static int lcdreg_debugfs_readwidth_get(void *data, u64 *val)
+{
+	struct lcdreg *reg = data;
+
+	/*
+	* def_width is not set when lcdreg_debugfs_init() is run, it's
+	* set later by the controller init code. Hence the need for this
+	* late assignment.
+	*/
+	if (!reg->debugfs_read_width)
+		reg->debugfs_read_width = reg->def_width;
+
+	*val = reg->debugfs_read_width;
+
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(lcdreg_debugfs_readwidth_fops,
+			lcdreg_debugfs_readwidth_get,
+			lcdreg_debugfs_readwidth_set, "%llu\n");
+
+void lcdreg_debugfs_init(struct lcdreg *reg)
+{
+	if (IS_ERR_OR_NULL(lcdreg_debugfs_root))
+		return;
+
+	reg->debugfs_read_result = devm_kzalloc(reg->dev, READ_RESULT_SIZE,
+						GFP_KERNEL);
+	if (!reg->debugfs_read_result)
+		return;
+
+	reg->debugfs = debugfs_create_dir(dev_name(reg->dev),
+					  lcdreg_debugfs_root);
+	if (!reg->debugfs) {
+		dev_warn(reg->dev, "Failed to create debugfs directory\n");
+		return;
+	}
+
+	debugfs_create_file("write", 0220, reg->debugfs, reg,
+			    &lcdreg_debugfs_write_fops);
+	if (reg->read) {
+		debugfs_create_file("read_width", 0660, reg->debugfs, reg,
+				    &lcdreg_debugfs_readwidth_fops);
+		debugfs_create_file("read", 0660, reg->debugfs, reg,
+				    &lcdreg_debugfs_read_fops);
+	}
+	if (reg->reset) {
+		debugfs_create_file("reset", 0220, reg->debugfs, reg,
+				    &lcdreg_debugfs_reset_fops);
+	}
+}
+
+void lcdreg_debugfs_exit(struct lcdreg *reg)
+{
+	debugfs_remove_recursive(reg->debugfs);
+}
+
+static int lcdreg_debugfs_module_init(void)
+{
+	lcdreg_debugfs_root = debugfs_create_dir("lcdreg", NULL);
+	if (!lcdreg_debugfs_root)
+		pr_warn("lcdreg: Failed to create debugfs root\n");
+
+	return 0;
+}
+module_init(lcdreg_debugfs_module_init);
+
+static void lcdreg_debugfs_module_exit(void)
+{
+	debugfs_remove_recursive(lcdreg_debugfs_root);
+}
+module_exit(lcdreg_debugfs_module_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/lcdreg.h b/include/drm/tinydrm/lcdreg.h
new file mode 100644
index 0000000..74e5e50
--- /dev/null
+++ b/include/drm/tinydrm/lcdreg.h
@@ -0,0 +1,126 @@
+/*
+ * 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_LCDREG_H
+#define __LINUX_LCDREG_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+
+/**
+ * struct lcdreg_transfer - LCD register transfer
+ * @index: register index (address)
+ *         Known under the following names:
+ *         D/C (command=0, data=1)
+ *         RS (register selection: index=0, data=1)
+ *         D/I (data/index: index=0, data=1)
+ * @buf: data array to transfer
+ * @count: number of items in array
+ * @width: override default regwidth
+ */
+struct lcdreg_transfer {
+	unsigned index;
+	void *buf;
+	unsigned count;
+	unsigned width;
+};
+
+/**
+ * struct lcdreg - interface to LCD register
+ * @dev: device interface
+ * @lock: mutex for register access locking
+ * @def_width: default register width
+ * @bits_per_word_mask: Bitmask of bits per word supported by the hardware.
+ *                      The driver can emulate more word widths.
+ * @readable: register is readable
+ * @little_endian: register has little endian byte order
+ * @write: write to register
+ * @read: read from register (optional)
+ * @reset: reset controller (optional)
+ */
+struct lcdreg {
+	struct device *dev;
+	struct mutex lock;
+	unsigned def_width;
+	bool readable;
+	bool little_endian;
+	u32 bits_per_word_mask;
+#define LCDREG_BPW_MASK(bits) BIT((bits) - 1)
+
+	int (*write)(struct lcdreg *reg, unsigned regnr,
+		     struct lcdreg_transfer *transfer);
+	int (*read)(struct lcdreg *reg, unsigned regnr,
+		    struct lcdreg_transfer *transfer);
+	void (*reset)(struct lcdreg *reg);
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs;
+	u32 debugfs_read_width;
+	u32 debugfs_read_reg;
+	char *debugfs_read_result;
+#endif
+};
+
+struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg);
+int lcdreg_write(struct lcdreg *reg, unsigned regnr,
+		 struct lcdreg_transfer *transfer);
+int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *data,
+		       unsigned count);
+
+#define lcdreg_writereg(lcdreg, regnr, seq...) \
+({\
+	u32 d[] = { seq };\
+	lcdreg_write_buf32(lcdreg, regnr, d, ARRAY_SIZE(d));\
+})
+
+int lcdreg_read(struct lcdreg *reg, unsigned regnr,
+		struct lcdreg_transfer *transfer);
+int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf,
+			 unsigned count);
+
+static inline void lcdreg_reset(struct lcdreg *reg)
+{
+	if (reg->reset)
+		reg->reset(reg);
+}
+
+static inline bool lcdreg_is_readable(struct lcdreg *reg)
+{
+	return reg->readable;
+}
+
+static inline unsigned lcdreg_bytes_per_word(unsigned bits_per_word)
+{
+	if (bits_per_word <= 8)
+		return 1;
+	else if (bits_per_word <= 16)
+		return 2;
+	else /* bits_per_word <= 32 */
+		return 4;
+}
+
+static inline bool lcdreg_bpw_supported(struct lcdreg *reg, unsigned bpw)
+{
+	return LCDREG_BPW_MASK(bpw) & reg->bits_per_word_mask;
+}
+
+#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG)
+static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr)
+{
+	int groupsize = lcdreg_bytes_per_word(tr->width);
+	size_t len = min_t(size_t, 32, tr->count * groupsize);
+
+	print_hex_dump_debug("    buf=", DUMP_PREFIX_NONE, 32, groupsize,
+			     tr->buf, len, false);
+}
+#else
+static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr) { }
+#endif /* DEBUG || CONFIG_DYNAMIC_DEBUG */
+
+#endif /* __LINUX_LCDREG_H */
-- 
2.2.2

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

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC 3/5] drm/tinydrm/lcdreg: Add SPI support
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
  2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
  2016-03-16 13:34 ` [RFC 2/5] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
@ 2016-03-16 13:34 ` Noralf Trønnes
  2016-03-16 13:34 ` [RFC 4/5] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add SPI bus support to lcdreg.
Supports the following protocols:
- MIPI DBI type C interface option 1 (3-wire) and option 3 (4-wire).
- Option 3 also fits some controllers where all registers are 16-bit.
- 8/16-bit register transfers that need to start with a special startbyte.

It also supports emulation for 16 and 9-bit words if the SPI controller
doesn't support it.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig      |   5 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile     |   2 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c | 720 ++++++++++++++++++++++++++++
 include/drm/tinydrm/lcdreg-spi.h            |  63 +++
 4 files changed, 790 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
 create mode 100644 include/drm/tinydrm/lcdreg-spi.h

diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
index 41383b1..ade465f 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
@@ -1,3 +1,8 @@
 config LCDREG
 	tristate
 	depends on GPIOLIB || COMPILE_TEST
+
+config LCDREG_SPI
+	tristate
+	depends on SPI
+	select LCDREG
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
index c9ea774..4e14571 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Makefile
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_LCDREG)                   += lcdreg.o
 lcdreg-y                               += lcdreg-core.o
 lcdreg-$(CONFIG_DEBUG_FS)              += lcdreg-debugfs.o
+
+obj-$(CONFIG_LCDREG_SPI)               += lcdreg-spi.o
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
new file mode 100644
index 0000000..5e9d8fe1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
@@ -0,0 +1,720 @@
+//#define VERBOSE_DEBUG
+//#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 <asm/unaligned.h>
+#include <drm/tinydrm/lcdreg-spi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+static unsigned txlen;
+module_param(txlen, uint, 0);
+MODULE_PARM_DESC(txlen, "Transmit chunk length");
+
+static unsigned long bpwm;
+module_param(bpwm, ulong, 0);
+MODULE_PARM_DESC(bpwm, "Override SPI master bits_per_word_mask");
+
+struct lcdreg_spi {
+	struct lcdreg reg;
+	enum lcdreg_spi_mode mode;
+unsigned txbuflen;
+	void *txbuf_dc;
+	unsigned id;
+	u32 quirks;
+	u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			bool read);
+	struct gpio_desc *dc;
+	struct gpio_desc *reset;
+};
+
+static inline struct lcdreg_spi *to_lcdreg_spi(struct lcdreg *reg)
+{
+	return reg ? container_of(reg, struct lcdreg_spi, reg) : NULL;
+}
+
+#ifdef VERBOSE_DEBUG
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message *m, u8 *startbyte)
+{
+	struct spi_transfer *tmp;
+	struct list_head *pos;
+	int i = 0;
+
+	if (startbyte)
+		dev_dbg(dev, "spi_message: startbyte=0x%02X\n", startbyte[0]);
+	else
+		dev_dbg(dev, "spi_message:\n");
+
+	list_for_each(pos, &m->transfers) {
+		tmp = list_entry(pos, struct spi_transfer, transfer_list);
+		if (tmp->tx_buf)
+			pr_debug("    tr%i: bpw=%i, len=%u, tx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->tx_buf, tmp->len > 64 ? 64 : tmp->len, tmp->tx_buf);
+		if (tmp->rx_buf)
+			pr_debug("    tr%i: bpw=%i, len=%u, rx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->rx_buf, tmp->len > 64 ? 64 : tmp->len, tmp->rx_buf);
+		i++;
+	}
+}
+#else
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message *m, u8 *startbyte)
+{
+}
+#endif
+
+static int lcdreg_spi_do_transfer(struct lcdreg *reg,
+				  struct lcdreg_transfer *transfer)
+{
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	void *buf = transfer->buf;
+	size_t len = transfer->count * lcdreg_bytes_per_word(transfer->width);
+	size_t max = txlen ? : sdev->master->max_dma_len;
+	size_t room_left_in_page = PAGE_SIZE - offset_in_page(buf);
+	size_t chunk = min_t(size_t, len, max);
+	struct spi_message m;
+	struct spi_transfer *tr;
+	u8 *startbuf = NULL;
+	int ret, i;
+
+	dev_dbg(reg->dev, "%s: index=%u, count=%u, width=%u\n",
+		__func__, transfer->index, transfer->count, transfer->width);
+	lcdreg_dbg_transfer_buf(transfer);
+
+	tr = kzalloc(2 * sizeof(*tr), GFP_KERNEL);
+	if (!tr)
+		return -ENOMEM;
+
+	/* slow down commands? */
+	if (!transfer->index && (spi->quirks & LCDREG_SLOW_INDEX0_WRITE))
+		for (i = 0; i < 2; i++)
+			tr[i].speed_hz = min_t(u32, 2000000,
+					       sdev->max_speed_hz / 2);
+
+	if (spi->mode == LCDREG_SPI_STARTBYTE) {
+		startbuf = kmalloc(1, GFP_KERNEL);
+		if (!startbuf) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		*startbuf = spi->startbyte(reg, transfer, false);
+	}
+
+	/*
+	 * transfer->buf can be unaligned to the page boundary for partial
+	 * updates when videomem is sent directly (no buffering).
+	 * Spi core can sg map the buffer for dma and relies on vmalloc'ed
+	 * memory to be page aligned.
+	 */
+//pr_debug("%s: PAGE_ALIGNED=%d, len > room_left_in_page= %d > %d = %d, chunk=%zu\n", __func__, PAGE_ALIGNED(buf), len, room_left_in_page, len > room_left_in_page, chunk);
+	if (!PAGE_ALIGNED(buf) && len > room_left_in_page) {
+//size_t chunk0 = chunk;
+
+		if (chunk >= room_left_in_page) {
+			chunk = room_left_in_page;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu\n\n", __func__, chunk0, chunk, room_left_in_page);
+		} else {
+			chunk = room_left_in_page % chunk ? : chunk;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu, room_left_in_page %% chunk=%zu\n\n", __func__, chunk0, chunk, room_left_in_page, room_left_in_page % chunk);
+		}
+	}
+
+	do {
+		i = 0;
+		spi_message_init(&m);
+
+		if (spi->mode == LCDREG_SPI_STARTBYTE) {
+			tr[i].tx_buf = startbuf;
+			tr[i].len = 1;
+			tr[i].bits_per_word = 8;
+			spi_message_add_tail(&tr[i++], &m);
+		}
+
+		tr[i].tx_buf = buf;
+		tr[i].len = chunk;
+		tr[i].bits_per_word = transfer->width;
+		buf += chunk;
+		len -= chunk;
+		spi_message_add_tail(&tr[i], &m);
+
+		lcdreg_vdbg_dump_spi(&sdev->dev, &m, startbuf);
+		ret = spi_sync(sdev, &m);
+		if (ret)
+			goto out;
+
+		chunk = min_t(size_t, len, max);
+	} while (len);
+
+out:
+	kfree(tr);
+	kfree(startbuf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer_emulate9(struct lcdreg *reg,
+					struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+		.width = 8,
+		.count = transfer->count,
+	};
+	u16 *src = transfer->buf;
+	unsigned added = 0;
+	int i, ret;
+	u8 *dst;
+
+	if (transfer->count % 8) {
+		dev_err_once(reg->dev,
+			     "transfer->count=%u must be divisible by 8\n",
+			     transfer->count);
+		return -EINVAL;
+	}
+
+	dst = kzalloc(spi->txbuflen, GFP_KERNEL);
+	if (!dst)
+		return -ENOMEM;
+
+	tr.buf = dst;
+
+	for (i = 0; i < transfer->count; i += 8) {
+		u64 tmp = 0;
+		int j, bits = 63;
+
+		for (j = 0; j < 7; j++) {
+			u64 bit9 = (*src & 0x100) ? 1 : 0;
+			u64 val = *src++ & 0xFF;
+
+			tmp |= bit9 << bits;
+			bits -= 8;
+			tmp |= val << bits--;
+		}
+		tmp |= ((*src & 0x100) ? 1 : 0);
+		*(u64 *)dst = cpu_to_be64(tmp);
+		dst += 8;
+		*dst++ = *src++ & 0xFF;
+		added++;
+	}
+	tr.count += added;
+	ret = lcdreg_spi_do_transfer(reg, &tr);
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer_emulate16(struct lcdreg *reg,
+					 struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	unsigned to_copy, remain = transfer->count;
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+		.width = 8,
+	};
+	u16 *data16 = transfer->buf;
+	u16 *txbuf16;
+	int i, ret = 0;
+
+	txbuf16 = kzalloc(spi->txbuflen, GFP_KERNEL);
+	if (!txbuf16)
+		return -ENOMEM;
+
+	tr.buf = txbuf16;
+
+	while (remain) {
+		to_copy = min(remain, spi->txbuflen / 2);
+		dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+					to_copy, remain - to_copy);
+
+		for (i = 0; i < to_copy; i++)
+			txbuf16[i] = swab16(data16[i]);
+
+		data16 = data16 + to_copy;
+		tr.count = to_copy * 2;
+		ret = lcdreg_spi_do_transfer(reg, &tr);
+		if (ret < 0)
+			goto out;
+		remain -= to_copy;
+	}
+
+out:
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer(struct lcdreg *reg,
+			       struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	bool mach_little_endian;
+
+#ifdef __LITTLE_ENDIAN
+	mach_little_endian = true;
+#endif
+	if (spi->dc)
+		gpiod_set_value_cansleep(spi->dc, transfer->index);
+
+	if (lcdreg_bpw_supported(reg, transfer->width))
+		return lcdreg_spi_do_transfer(reg, transfer);
+
+	if (transfer->width == 9)
+		return lcdreg_spi_transfer_emulate9(reg, transfer);
+
+	if ((mach_little_endian == reg->little_endian) &&
+	    (transfer->width % 8 == 0)) {
+		/* the byte order matches */
+		transfer->count *= transfer->width / 8;
+		transfer->width = 8;
+		return lcdreg_spi_do_transfer(reg, transfer);
+	}
+
+	if (mach_little_endian != reg->little_endian && transfer->width == 16)
+		return lcdreg_spi_transfer_emulate16(reg, transfer);
+
+	dev_err_once(reg->dev, "width=%u is not supported (%u:%u)\n",
+		     transfer->width, mach_little_endian, reg->little_endian);
+
+	return -EINVAL;
+}
+
+static int lcdreg_spi_write_9bit_dc(struct lcdreg *reg,
+				    struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+	};
+	u8 *data8 = transfer->buf;
+	u16 *data16 = transfer->buf;
+	unsigned width;
+	u16 *txbuf16;
+	unsigned remain;
+	unsigned tx_array_size;
+	unsigned to_copy;
+	int pad, i, ret;
+
+width = transfer->width;
+
+	if (width != 8 && width != 16) {
+		dev_err(reg->dev, "transfer width %u is not supported\n",
+								width);
+		return -EINVAL;
+	}
+
+	if (!spi->txbuf_dc) {
+		spi->txbuf_dc = devm_kzalloc(reg->dev, spi->txbuflen,
+							GFP_KERNEL);
+		if (!spi->txbuf_dc)
+			return -ENOMEM;
+		dev_info(reg->dev, "allocated %u KiB 9-bit dc buffer\n",
+						spi->txbuflen / 1024);
+	}
+
+	tr.buf = spi->txbuf_dc;
+	txbuf16 = spi->txbuf_dc;
+	remain = transfer->count;
+	if (width == 8)
+		tx_array_size = spi->txbuflen / 2;
+	else
+		tx_array_size = spi->txbuflen / 4;
+
+	/* If we're emulating 9-bit, the buffer has to be divisible by 8.
+	   Pad with no-ops if necessary (assuming here that zero is a no-op)
+	   FIX: If video buf isn't divisible by 8, it will break.
+	 */
+	if (!lcdreg_bpw_supported(reg, 9) && width == 8 &&
+						remain < tx_array_size) {
+		pad = (transfer->count % 8) ? 8 - (transfer->count % 8) : 0;
+		if (transfer->index == 0)
+			for (i = 0; i < pad; i++)
+				*txbuf16++ = 0x000;
+		for (i = 0; i < remain; i++) {
+			*txbuf16 = *data8++;
+			if (transfer->index)
+				*txbuf16++ |= 0x0100;
+		}
+		if (transfer->index == 1)
+			for (i = 0; i < pad; i++)
+				*txbuf16++ = 0x000;
+		tr.width = 9;
+		tr.count = pad + remain;
+		return lcdreg_spi_transfer(reg, &tr);
+	}
+
+	while (remain) {
+		to_copy = remain > tx_array_size ? tx_array_size : remain;
+		remain -= to_copy;
+		dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+					to_copy, remain);
+
+		if (width == 8) {
+			for (i = 0; i < to_copy; i++) {
+				txbuf16[i] = *data8++;
+				if (transfer->index)
+					txbuf16[i] |= 0x0100;
+			}
+		} else {
+			for (i = 0; i < (to_copy * 2); i += 2) {
+				txbuf16[i]     = *data16 >> 8;
+				txbuf16[i + 1] = *data16++ & 0xFF;
+				if (transfer->index) {
+					txbuf16[i]     |= 0x0100;
+					txbuf16[i + 1] |= 0x0100;
+				}
+			}
+		}
+		tr.buf = spi->txbuf_dc;
+		tr.width = 9;
+		tr.count = to_copy * 2;
+		ret = lcdreg_spi_transfer(reg, &tr);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lcdreg_spi_write(struct lcdreg *reg, unsigned regnr,
+			    struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.width = reg->def_width,
+		.count = 1,
+	};
+	int ret;
+
+	tr.buf = kmalloc(sizeof(u32), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	if (reg->def_width <= 8)
+		((u8 *)tr.buf)[0] = regnr;
+	else
+		((u16 *)tr.buf)[0] = regnr;
+
+	if (spi->mode == LCDREG_SPI_3WIRE)
+		ret = lcdreg_spi_write_9bit_dc(reg, &tr);
+	else
+		ret = lcdreg_spi_transfer(reg, &tr);
+	kfree(tr.buf);
+	if (ret || !transfer || !transfer->count)
+		return ret;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+	if (spi->mode == LCDREG_SPI_3WIRE)
+		ret = lcdreg_spi_write_9bit_dc(reg, transfer);
+	else
+		ret = lcdreg_spi_transfer(reg, transfer);
+
+	return ret;
+}
+
+/*
+   <CMD> <DM> <PA>
+   CMD = Command
+   DM = Dummy read
+   PA = Parameter or display data
+
+   ST7735R read:
+     Parallel: <CMD> <DM> <PA>
+     SPI: 8-bit plain, 24- and 32-bit needs 1 dummy clock cycle
+
+   ILI9320:
+     Parallel: no dummy read, page 51 in datasheet
+     SPI (startbyte): One byte of invalid dummy data read after the start byte.
+
+   ILI9340:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   ILI9341:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   SSD1289:
+     Parallel: 1 dummy read
+
+ */
+
+static int lcdreg_spi_read_startbyte(struct lcdreg *reg, unsigned regnr,
+				     struct lcdreg_transfer *transfer)
+{
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct spi_message m;
+	struct spi_transfer trtx = {
+		.speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+		.bits_per_word = 8,
+		.len = 1,
+	};
+	struct spi_transfer trrx = {
+		.speed_hz = trtx.speed_hz,
+		.bits_per_word = 8,
+		.len = (transfer->count * 2) + 1,
+	};
+	u8 *txbuf, *rxbuf;
+	int i, ret;
+
+	if (!reg->readable)
+		return -EACCES;
+
+	if (!transfer || !transfer->count)
+		return -EINVAL;
+
+	transfer->width = transfer->width ? : reg->def_width;
+	if (WARN_ON(transfer->width != 16))
+		return -EINVAL;
+
+	ret = lcdreg_writereg(reg, regnr);
+	if (ret)
+		return ret;
+
+	txbuf = kzalloc(1, GFP_KERNEL);
+	if (!txbuf)
+		return -ENOMEM;
+
+	rxbuf = kzalloc(trrx.len, GFP_KERNEL);
+	if (!rxbuf) {
+		kfree(txbuf);
+		return -ENOMEM;
+	}
+
+	*txbuf = spi->startbyte(reg, transfer, true);
+
+	trtx.tx_buf = txbuf;
+	trrx.rx_buf = rxbuf;
+	spi_message_init(&m);
+	spi_message_add_tail(&trtx, &m);
+	spi_message_add_tail(&trrx, &m);
+	ret = spi_sync(sdev, &m);
+	lcdreg_vdbg_dump_spi(&sdev->dev, &m, txbuf);
+	kfree(txbuf);
+	if (ret) {
+		kfree(rxbuf);
+		return ret;
+	}
+
+	rxbuf++;
+	for (i = 0; i < transfer->count; i++) {
+		((u16 *)transfer->buf)[i] = get_unaligned_be16(rxbuf);
+		rxbuf += 2;
+	}
+	kfree(trrx.rx_buf);
+
+	return 0;
+}
+
+static int lcdreg_spi_read(struct lcdreg *reg, unsigned regnr,
+			   struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct spi_message m;
+	struct spi_transfer trtx = {
+		.speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+		.bits_per_word = reg->def_width,
+		.len = 1,
+	};
+	struct spi_transfer trrx = {
+		.speed_hz = trtx.speed_hz,
+		.rx_buf = transfer->buf,
+		.len = transfer->count,
+	};
+	void *txbuf = NULL;
+	int i, ret;
+
+	transfer->width = transfer->width ? : reg->def_width;
+	if (WARN_ON(transfer->width != reg->def_width || !transfer->count))
+		return -EINVAL;
+
+	if (!reg->readable)
+		return -EACCES;
+
+	txbuf = kzalloc(16, GFP_KERNEL);
+	if (!txbuf)
+		return -ENOMEM;
+
+	spi_message_init(&m);
+	trtx.tx_buf = txbuf;
+	trrx.bits_per_word = transfer->width;
+
+	if (spi->mode == LCDREG_SPI_4WIRE) {
+		if (trtx.bits_per_word == 8) {
+			*(u8 *)txbuf = regnr;
+		} else if (trtx.bits_per_word == 16) {
+			if (lcdreg_bpw_supported(reg, trtx.bits_per_word)) {
+				*(u16 *)txbuf = regnr;
+			} else {
+				*(u16 *)txbuf = cpu_to_be16(regnr);
+				trtx.bits_per_word = 8;
+				trtx.len = 2;
+			}
+		} else {
+			return -EINVAL;
+		}
+		gpiod_set_value_cansleep(spi->dc, 0);
+	} else if (spi->mode == LCDREG_SPI_3WIRE) {
+		if (lcdreg_bpw_supported(reg, 9)) {
+			trtx.bits_per_word = 9;
+			*(u16 *)txbuf = regnr; /* dc=0 */
+		} else {
+			/* 8x 9-bit words, pad with leading zeroes (no-ops) */
+			((u8 *)txbuf)[8] = regnr;
+		}
+	} else {
+		kfree(txbuf);
+		return -EINVAL;
+	}
+	spi_message_add_tail(&trtx, &m);
+
+	if (spi->mode == LCDREG_SPI_4WIRE && transfer->index &&
+	    !(spi->quirks & LCDREG_INDEX0_ON_READ)) {
+		trtx.cs_change = 1; /* not always supported */
+		lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+		ret = spi_sync(sdev, &m);
+		if (ret) {
+			kfree(txbuf);
+			return ret;
+		}
+		gpiod_set_value_cansleep(spi->dc, 1);
+		spi_message_init(&m);
+	}
+
+	spi_message_add_tail(&trrx, &m);
+	ret = spi_sync(sdev, &m);
+	lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+	kfree(txbuf);
+	if (ret)
+		return ret;
+
+	if (!lcdreg_bpw_supported(reg, trrx.bits_per_word) &&
+						(trrx.bits_per_word == 16))
+		for (i = 0; i < transfer->count; i++)
+			((u16 *)transfer->buf)[i] = be16_to_cpu(((u16 *)transfer->buf)[i]);
+
+	return 0;
+}
+
+static void lcdreg_spi_reset(struct lcdreg *reg)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+	if (!spi->reset)
+		return;
+
+	dev_info(reg->dev, "%s()\n", __func__);
+	gpiod_set_value_cansleep(spi->reset, 0);
+	msleep(20);
+	gpiod_set_value_cansleep(spi->reset, 1);
+	msleep(120);
+}
+
+/* Default startbyte implementation: | 0 | 1 | 1 | 1 | 0 | ID | RS | RW | */
+static u8 lcdreg_spi_startbyte(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			       bool read)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+	return 0x70 | (!!spi->id << 2) | (!!tr->index << 1) | read;
+}
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+				    const struct lcdreg_spi_config *config)
+{
+	char *dc_name = config->dc_name ? : "dc";
+	struct device *dev = &sdev->dev;
+	struct lcdreg_spi *spi;
+	struct lcdreg *reg;
+
+	if (txlen) {
+		if (txlen < PAGE_SIZE) {
+			txlen = rounddown_pow_of_two(txlen);
+			if (txlen < 64)
+				txlen = 64;
+		} else {
+			txlen &= PAGE_MASK;
+		}
+	}
+dev_info(dev, "txlen: %u\n", txlen);
+
+	spi = devm_kzalloc(dev, sizeof(*spi), GFP_KERNEL);
+	if (!spi)
+		return ERR_PTR(-ENOMEM);
+
+	reg = &spi->reg;
+	if (bpwm) {
+		reg->bits_per_word_mask = bpwm;
+	} else {
+		if (sdev->master->bits_per_word_mask)
+			reg->bits_per_word_mask = sdev->master->bits_per_word_mask;
+		else
+			reg->bits_per_word_mask = SPI_BPW_MASK(8);
+	}
+	dev_dbg(dev, "bits_per_word_mask: 0x%08x", reg->bits_per_word_mask);
+
+	reg->def_width = config->def_width;
+	reg->readable = config->readable;
+	reg->reset = lcdreg_spi_reset;
+	reg->write = lcdreg_spi_write;
+	reg->read = lcdreg_spi_read;
+
+	spi->mode = config->mode;
+	spi->quirks = config->quirks;
+	spi->id = config->id;
+	if (!spi->txbuflen)
+		spi->txbuflen = PAGE_SIZE;
+
+	switch (spi->mode) {
+	case LCDREG_SPI_4WIRE:
+		spi->dc = devm_gpiod_get(dev, dc_name, GPIOD_OUT_LOW);
+		if (IS_ERR(spi->dc)) {
+			dev_err(dev, "Failed to get gpio '%s'\n", dc_name);
+			return ERR_CAST(spi->dc);
+		}
+		break;
+	case LCDREG_SPI_3WIRE:
+		break;
+	case LCDREG_SPI_STARTBYTE:
+		reg->read = lcdreg_spi_read_startbyte;
+		spi->startbyte = config->startbyte ? : lcdreg_spi_startbyte;
+		break;
+	default:
+		dev_err(dev, "Mode is not supported: %u\n", spi->mode);
+		return ERR_PTR(-EINVAL);
+	}
+
+	spi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(spi->reset)) {
+		dev_err(dev, "Failed to get gpio 'reset'\n");
+		return ERR_CAST(spi->reset);
+	}
+
+	pr_debug("spi->reg.def_width: %u\n", reg->def_width);
+	if (spi->reset)
+		pr_debug("spi->reset: %i\n", desc_to_gpio(spi->reset));
+	if (spi->dc)
+		pr_debug("spi->dc: %i\n", desc_to_gpio(spi->dc));
+	pr_debug("spi->mode: %u\n", spi->mode);
+
+	return devm_lcdreg_init(dev, reg);
+}
+EXPORT_SYMBOL_GPL(devm_lcdreg_spi_init);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/lcdreg-spi.h b/include/drm/tinydrm/lcdreg-spi.h
new file mode 100644
index 0000000..ba5d492
--- /dev/null
+++ b/include/drm/tinydrm/lcdreg-spi.h
@@ -0,0 +1,63 @@
+/*
+ * 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_LCDREG_SPI_H
+#define __LINUX_LCDREG_SPI_H
+
+#include <drm/tinydrm/lcdreg.h>
+#include <linux/spi/spi.h>
+
+/**
+ * enum lcdreg_spi_mode - SPI interface mode
+ * @LCDREG_SPI_4WIRE: 8-bit + D/CX line, MIPI DBI Type C option 3
+ * @LCDREG_SPI_3WIRE: 9-bit inc. D/CX bit, MIPI DBI Type C option 1
+ * @LCDREG_SPI_STARTBYTE: Startbyte header on every transaction (non MIPI)
+ */
+enum lcdreg_spi_mode {
+	LCDREG_SPI_NOMODE = 0,
+	LCDREG_SPI_4WIRE,
+	LCDREG_SPI_3WIRE,
+	LCDREG_SPI_STARTBYTE,
+};
+
+/**
+ * struct lcdreg_spi_config - SPI interface configuration
+ * @mode: Register interface mode
+ * @def_width: Default register width
+ * @readable: Is the register readable, not all displays have MISO wired.
+ * @id: Display id used with LCDREG_SPI_STARTBYTE
+ * @dc_name: Index pin name, usually dc, rs or di (default is 'dc').
+ * @quirks: Deviations from the MIPI DBI standard
+ * @startbyte: Used with LCDREG_SPI_STARTBYTE to get the startbyte
+ *             (default is lcdreg_spi_startbyte).
+ */
+struct lcdreg_spi_config {
+	enum lcdreg_spi_mode mode;
+	unsigned def_width;
+	bool readable;
+	u32 id;
+	char *dc_name;
+	u32 quirks;
+/* slowdown command (index=0) */
+#define LCDREG_SLOW_INDEX0_WRITE	BIT(0)
+/*
+ * The MIPI DBI spec states that D/C should be HIGH during register reading.
+ * However, not all SPI master drivers support cs_change on last transfer and
+ * there are LCD controllers that ignore D/C on read.
+ */
+#define LCDREG_INDEX0_ON_READ		BIT(1)
+
+	u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			bool read);
+};
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+				    const struct lcdreg_spi_config *config);
+
+#endif /* __LINUX_LCDREG_SPI_H */
-- 
2.2.2

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

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC 4/5] drm/tinydrm: Add mipi-dbi support
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (2 preceding siblings ...)
  2016-03-16 13:34 ` [RFC 3/5] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
@ 2016-03-16 13:34 ` Noralf Trønnes
  2016-03-16 13:34 ` [RFC 5/5] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add support for MIPI DBI interfaced controllers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig    |   3 +
 drivers/gpu/drm/tinydrm/Makefile   |   3 +
 drivers/gpu/drm/tinydrm/mipi-dbi.c | 231 +++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/mipi-dbi.h     |  24 ++++
 4 files changed, 261 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/mipi-dbi.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index 671239f..a7929e0 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -10,4 +10,7 @@ menuconfig DRM_TINYDRM
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
 
+config TINYDRM_MIPI_DBI
+	tristate
+
 source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index f4a92d9..35ba822 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1,2 +1,5 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
 obj-$(CONFIG_LCDREG)			+= lcdreg/
+
+# Controllers
+obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
new file mode 100644
index 0000000..3021349
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
@@ -0,0 +1,231 @@
+//#define DEBUG
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 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_gem_cma_helper.h>
+#include <drm/tinydrm/lcdreg.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/swab.h>
+#include <video/mipi_display.h>
+
+#define DCS_POWER_MODE_DISPLAY			BIT(2)
+#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
+#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
+#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
+#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
+#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
+
+/* TODO: Move common functions to a separate module */
+void tinydrm_xrgb8888_to_rgb565(u32 *src, u16 *dst, unsigned num_pixels, bool swap_bytes)
+{
+	int i;
+
+	for (i = 0; i < num_pixels; i++) {
+		*dst = ((*src & 0x00F80000) >> 8) |
+		       ((*src & 0x0000FC00) >> 5) |
+		       ((*src & 0x000000F8) >> 3);
+		if (swap_bytes)
+			*dst = swab16(*dst);
+		src++;
+		dst++;
+	}
+}
+
+// TODO: Pass in regnr
+int tinydrm_update_rgb565_lcdreg(struct tinydrm_device *tdev, struct drm_framebuffer *fb, void *vmem, struct drm_clip_rect *clip)
+{
+	unsigned num_pixels = (clip->x2 - clip->x1 + 1) *
+			      (clip->y2 - clip->y1 + 1);
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = 16,
+		.count = num_pixels
+	};
+	bool byte_swap = false;
+	u16 *buf = NULL;
+	int ret;
+
+	dev_err_once(tdev->base->dev, "pixel_format = %s, bpw = 0x%08x\n", drm_get_format_name(fb->pixel_format), tdev->lcdreg->bits_per_word_mask);
+
+	switch (fb->pixel_format) {
+	case DRM_FORMAT_RGB565:
+		tr.buf = vmem;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		buf = kmalloc(num_pixels * sizeof(u16), GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+#if defined(__LITTLE_ENDIAN)
+		byte_swap = !lcdreg_bpw_supported(tdev->lcdreg, 16);
+#endif
+		tinydrm_xrgb8888_to_rgb565(vmem, buf, num_pixels, byte_swap);
+		tr.buf = buf;
+		if (byte_swap) {
+			tr.width = 8;
+			tr.count *= 2;
+		}
+		break;
+	default:
+		dev_err_once(tdev->base->dev,
+			     "pixel_format '%s' is not supported\n",
+			     drm_get_format_name(fb->pixel_format));
+		return -EINVAL;
+	}
+
+	ret = lcdreg_write(tdev->lcdreg, MIPI_DCS_WRITE_MEMORY_START, &tr);
+	kfree(buf);
+
+	return ret;
+}
+
+static void mipi_dbi_deferred_update(struct work_struct *work)
+{
+	struct tinydrm_device *tdev = work_to_tinydrm(work);
+	struct lcdreg *reg = tdev->lcdreg;
+	struct tinydrm_fb_clip fb_clip;
+	struct drm_clip_rect *clip = &fb_clip.clip;
+	int ret;
+
+	dev_dbg(tdev->base->dev, "%s\n", __func__);
+
+	if (!tinydrm_deferred_begin(tdev, &fb_clip))
+		return;
+
+	dev_dbg(tdev->base->dev, "%s: vmem=%p, x1=%u, x2=%u, y1=%u, y2=%u\n", __func__, fb_clip.vmem, clip->x1, clip->x2, clip->y1, clip->y2);
+
+	lcdreg_writereg(reg, MIPI_DCS_SET_COLUMN_ADDRESS,
+			(clip->x1 >> 8) & 0xFF, clip->x1 & 0xFF,
+			(clip->x2 >> 8) & 0xFF, clip->x2 & 0xFF);
+	lcdreg_writereg(reg, MIPI_DCS_SET_PAGE_ADDRESS,
+			(clip->y1 >> 8) & 0xFF, clip->y1 & 0xFF,
+			(clip->y2 >> 8) & 0xFF, clip->y2 & 0xFF);
+
+	ret = tinydrm_update_rgb565_lcdreg(tdev, fb_clip.fb, fb_clip.vmem, clip);
+	if (ret)
+		dev_err_once(tdev->base->dev, "Failed to update display %d\n", ret);
+
+	tinydrm_deferred_end(tdev);
+}
+
+int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev)
+{
+	tdev->deferred = devm_kzalloc(dev, sizeof(*tdev->deferred),
+				      GFP_KERNEL);
+	if (!tdev->deferred)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&tdev->deferred->dwork, mipi_dbi_deferred_update);
+	tinydrm_reset_clip(&tdev->deferred->fb_clip.clip);
+	tdev->lcdreg->def_width = 8;
+	tdev->dirtyfb = tinydrm_dirtyfb;
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_init);
+
+/* Returns true if the display can be verified to be on */
+bool mipi_dbi_display_is_on(struct lcdreg *reg)
+{
+	u32 val;
+
+	if (!lcdreg_is_readable(reg))
+		return false;
+
+	if (lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, &val, 1))
+		return false;
+
+	val &= ~DCS_POWER_MODE_RESERVED_MASK;
+
+	if (val != (DCS_POWER_MODE_DISPLAY |
+	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
+		return false;
+
+	DRM_DEBUG_DRIVER("Display is ON\n");
+
+	return true;
+}
+EXPORT_SYMBOL(mipi_dbi_display_is_on);
+
+void mipi_dbi_debug_dump_regs(struct lcdreg *reg)
+{
+	u32 val[4];
+	int ret;
+
+	if (!(lcdreg_is_readable(reg) && (drm_debug & DRM_UT_DRIVER)))
+		return;
+
+	ret = lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_ID, val, 3);
+	if (ret) {
+		dev_warn(reg->dev,
+			 "failed to read from controller: %d", ret);
+		return;
+	}
+
+	/* RDDID is not part of the MIPI standard, but seems to be common */
+	DRM_DEBUG_DRIVER("Display ID (%02x): %02x %02x %02x\n",
+			 MIPI_DCS_GET_DISPLAY_ID, val[0], val[1], val[2]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_STATUS, val, 4);
+	DRM_DEBUG_DRIVER("Display status (%02x): %02x %02x %02x %02x\n",
+			 MIPI_DCS_GET_DISPLAY_STATUS, val[0], val[1], val[2], val[3]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Power mode (%02x): %02x\n",
+			 MIPI_DCS_GET_POWER_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_ADDRESS_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Address mode (%02x): %02x\n",
+			 MIPI_DCS_GET_ADDRESS_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_PIXEL_FORMAT, val, 1);
+	DRM_DEBUG_DRIVER("Pixel format (%02x): %02x\n",
+			 MIPI_DCS_GET_PIXEL_FORMAT, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Display mode (%02x): %02x\n",
+			 MIPI_DCS_GET_DISPLAY_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_SIGNAL_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Display signal mode (%02x): %02x\n",
+			 MIPI_DCS_GET_SIGNAL_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DIAGNOSTIC_RESULT, val, 1);
+	DRM_DEBUG_DRIVER("Diagnostic result (%02x): %02x\n",
+			 MIPI_DCS_GET_DIAGNOSTIC_RESULT, val[0]);
+}
+EXPORT_SYMBOL(mipi_dbi_debug_dump_regs);
+
+int mipi_dbi_panel_unprepare(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+	struct lcdreg *reg = tdev->lcdreg;
+
+	/*
+	 * Only do this if we have turned off backlight because if it's on the
+	 * display will in most cases turn all white when the pixels are
+	 * turned off.
+	 */
+	if (tdev->backlight) {
+		lcdreg_writereg(reg, MIPI_DCS_SET_DISPLAY_OFF);
+		lcdreg_writereg(reg, MIPI_DCS_ENTER_SLEEP_MODE);
+	}
+
+	if (tdev->regulator)
+		regulator_disable(tdev->regulator);
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_panel_unprepare);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
new file mode 100644
index 0000000..108a73b
--- /dev/null
+++ b/include/drm/tinydrm/mipi-dbi.h
@@ -0,0 +1,24 @@
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 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_MIPI_DBI_H
+#define __LINUX_MIPI_DBI_H
+
+struct tinydrm_device;
+struct drm_panel;
+struct lcdreg;
+
+int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev);
+bool mipi_dbi_display_is_on(struct lcdreg *reg);
+void mipi_dbi_debug_dump_regs(struct lcdreg *reg);
+int mipi_dbi_panel_unprepare(struct drm_panel *panel);
+
+#endif /* __LINUX_MIPI_DBI_H */
-- 
2.2.2

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

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC 5/5] drm/tinydrm: Add support for several Adafruit TFT displays
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (3 preceding siblings ...)
  2016-03-16 13:34 ` [RFC 4/5] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
@ 2016-03-16 13:34 ` Noralf Trønnes
  2016-03-16 14:50 ` [RFC 0/5] drm: Add support for tiny LCD displays Daniel Vetter
  2016-03-16 18:26 ` Eric Anholt
  6 siblings, 0 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-16 13:34 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add support for Adafruit MIPI DBI compatible SPI displays:
1.8" Color TFT LCD display - ST7735R (#358)
2.2" Color TFT LCD display - HX8340BN, 9-bit (#797)
2.8" PiTFT 320x240 TFT+Touchscreen for Raspberry Pi (#1601)

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig        |  11 ++
 drivers/gpu/drm/tinydrm/Makefile       |   3 +
 drivers/gpu/drm/tinydrm/adafruit-tft.c | 256 +++++++++++++++++++++++++++++++++
 include/drm/tinydrm/ili9340.h          |  85 +++++++++++
 4 files changed, 355 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
 create mode 100644 include/drm/tinydrm/ili9340.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index a7929e0..649f311 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -13,4 +13,15 @@ menuconfig DRM_TINYDRM
 config TINYDRM_MIPI_DBI
 	tristate
 
+config TINYDRM_ADAFRUIT_TFT
+	tristate "DRM driver for Adafruit SPI TFT displays"
+	depends on DRM_TINYDRM && SPI
+	select LCDREG_SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver for the following Adafruit displays:
+	    2.8" PiTFT 320x240 for Raspberry Pi - ILI9340 (#1601)
+	    2.2" Color TFT LCD display - HX8340BN, 9-bit mode (#797)
+	    1.8" Color TFT LCD display - ST7735R (#358)
+
 source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 35ba822..3c00201 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -3,3 +3,6 @@ obj-$(CONFIG_LCDREG)			+= lcdreg/
 
 # Controllers
 obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
+
+# Displays
+obj-$(CONFIG_TINYDRM_ADAFRUIT_TFT)	+= adafruit-tft.o
diff --git a/drivers/gpu/drm/tinydrm/adafruit-tft.c b/drivers/gpu/drm/tinydrm/adafruit-tft.c
new file mode 100644
index 0000000..09049f7
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/adafruit-tft.c
@@ -0,0 +1,256 @@
+#define DEBUG
+
+/*
+ * DRM driver for Adafruit MIPI compatible SPI TFT displays
+ *
+ * Copyright 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/tinydrm/ili9340.h>
+#include <drm/tinydrm/lcdreg-spi.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+enum adafruit_tft_displays {
+	ADAFRUIT_1601 = 1601,
+	ADAFRUIT_797 = 797,
+	ADAFRUIT_358 = 358,
+};
+
+static u32 adafruit_tft_get_rotation(struct device *dev)
+{
+	u32 rotation = 0;
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	return rotation;
+}
+
+static int adafruit_tft_1601_panel_prepare(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+	struct lcdreg *reg = tdev->lcdreg;
+	u8 addr_mode;
+	int ret;
+
+	dev_dbg(tdev->base->dev, "%s\n", __func__);
+
+	if (tdev->regulator) {
+		ret = regulator_enable(tdev->regulator);
+		if (ret) {
+			dev_err(tdev->base->dev,
+				"Failed to enable regulator %d\n", ret);
+			return ret;
+		}
+	}
+
+	mipi_dbi_debug_dump_regs(reg);
+
+	/* Avoid flicker by skipping setup if the bootloader has done it */
+	if (mipi_dbi_display_is_on(reg))
+		return 0;
+
+	lcdreg_reset(reg);
+	ret = lcdreg_writereg(reg, ILI9340_SWRESET);
+	if (ret) {
+		dev_err(tdev->base->dev, "Error writing lcdreg %d\n", ret);
+		return ret;
+	}
+
+	msleep(20);
+
+	/* Undocumented registers */
+	lcdreg_writereg(reg, 0xEF, 0x03, 0x80, 0x02);
+	lcdreg_writereg(reg, 0xCF, 0x00, 0xC1, 0x30);
+	lcdreg_writereg(reg, 0xED, 0x64, 0x03, 0x12, 0x81);
+	lcdreg_writereg(reg, 0xE8, 0x85, 0x00, 0x78);
+	lcdreg_writereg(reg, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02);
+	lcdreg_writereg(reg, 0xF7, 0x20);
+	lcdreg_writereg(reg, 0xEA, 0x00, 0x00);
+
+	lcdreg_writereg(reg, ILI9340_PWCTRL1, 0x23);
+	lcdreg_writereg(reg, ILI9340_PWCTRL2, 0x10);
+	lcdreg_writereg(reg, ILI9340_VMCTRL1, 0x3e, 0x28);
+	lcdreg_writereg(reg, ILI9340_VMCTRL2, 0x86);
+
+	lcdreg_writereg(reg, ILI9340_PIXSET, 0x55);
+	lcdreg_writereg(reg, ILI9340_FRMCTR1, 0x00, 0x18);
+	lcdreg_writereg(reg, ILI9340_DISCTRL, 0x08, 0x82, 0x27);
+
+	/* 3Gamma Function Disable */
+	lcdreg_writereg(reg, 0xF2, 0x00);
+
+	lcdreg_writereg(reg, ILI9340_GAMSET, 0x01);
+	lcdreg_writereg(reg, ILI9340_PGAMCTRL,
+			0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1,
+			0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00);
+	lcdreg_writereg(reg, ILI9340_NGAMCTRL,
+			0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1,
+			0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F);
+
+	switch (adafruit_tft_get_rotation(reg->dev)) {
+		default:
+			addr_mode = ILI9340_MADCTL_MV | ILI9340_MADCTL_MY |
+				    ILI9340_MADCTL_MX;
+			break;
+		case 90:
+			addr_mode = ILI9340_MADCTL_MY;;
+			break;
+		case 180:
+			addr_mode = ILI9340_MADCTL_MV;
+			break;
+		case 270:
+			addr_mode = ILI9340_MADCTL_MX;
+			break;
+	}
+	addr_mode |= ILI9340_MADCTL_BGR;
+	lcdreg_writereg(reg, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+
+	lcdreg_writereg(reg, ILI9340_SLPOUT);
+	msleep(120);
+	lcdreg_writereg(reg, ILI9340_DISPON);
+
+	mipi_dbi_debug_dump_regs(reg);
+
+	return 0;
+}
+
+struct drm_panel_funcs adafruit_tft_1601_funcs = {
+	.prepare = adafruit_tft_1601_panel_prepare,
+	.unprepare = mipi_dbi_panel_unprepare,
+	.enable = tinydrm_panel_enable_backlight,
+	.disable = tinydrm_panel_disable_backlight,
+};
+
+static const struct of_device_id adafruit_tft_of_match[] = {
+	{ .compatible = "adafruit,tft1601", .data = (void *)ADAFRUIT_1601 },
+	{ .compatible = "adafruit,tft797",  .data = (void *)ADAFRUIT_797 },
+	{ .compatible = "adafruit,tft358",  .data = (void *)ADAFRUIT_358 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, adafruit_tft_of_match);
+
+static const struct spi_device_id adafruit_tft_id[] = {
+	{ "tft1601", ADAFRUIT_1601 },
+	{ "tft797",  ADAFRUIT_797 },
+	{ "tft358",  ADAFRUIT_358 },
+        { }
+};
+MODULE_DEVICE_TABLE(spi, adafruit_tft_id);
+
+static int adafruit_tft_probe(struct spi_device *spi)
+{
+	const struct of_device_id *of_id;
+	struct lcdreg_spi_config cfg = {
+		.mode = LCDREG_SPI_4WIRE,
+	};
+	struct device *dev = &spi->dev;
+	struct tinydrm_device *tdev;
+	bool readable = false;
+	struct lcdreg *reg;
+	int id, ret;
+
+	of_id = of_match_device(adafruit_tft_of_match, dev);
+	if (of_id) {
+		id = (int)of_id->data;
+	} else {
+		const struct spi_device_id *spi_id = spi_get_device_id(spi);
+
+		if (!spi_id)
+			return -EINVAL;
+
+		id = spi_id->driver_data;
+	}
+
+	tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
+	if (!tdev)
+		return -ENOMEM;
+
+	tdev->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(tdev->backlight))
+		return PTR_ERR(tdev->backlight);
+
+	tdev->regulator = devm_regulator_get_optional(dev, "power");
+	if (IS_ERR(tdev->regulator)) {
+		if (PTR_ERR(tdev->regulator) != -ENODEV)
+			return PTR_ERR(tdev->regulator);
+		tdev->regulator = NULL;
+	}
+
+	switch (id) {
+	case ADAFRUIT_1601:
+		readable = true;
+		cfg.mode = LCDREG_SPI_4WIRE;
+		tdev->width = 320;
+		tdev->height = 240;
+		tdev->panel.funcs = &adafruit_tft_1601_funcs;
+		break;
+	case ADAFRUIT_797:
+		cfg.mode = LCDREG_SPI_3WIRE;
+		tdev->width = 176;
+		tdev->height = 220;
+		/* TODO: tdev->panel.funcs = &adafruit_tft_797_funcs*/
+		break;
+	case ADAFRUIT_358:
+		tdev->width = 128;
+		tdev->height = 160;
+		/* TODO: tdev->panel.funcs = &adafruit_tft_358_funcs */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	DRM_DEBUG_DRIVER("rotation = %u\n", adafruit_tft_get_rotation(dev));
+	switch (adafruit_tft_get_rotation(dev)) {
+		case 90:
+		case 270:
+			swap(tdev->width, tdev->height);
+			break;
+	}
+
+	reg = devm_lcdreg_spi_init(spi, &cfg);
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	reg->readable = readable;
+	tdev->lcdreg = reg;
+	ret = mipi_dbi_init(dev, tdev);
+	if (ret)
+		return ret;
+
+	/* TODO: Make configurable */
+	tdev->deferred->defer_ms = 40;
+
+	spi_set_drvdata(spi, tdev);
+
+	return devm_tinydrm_register(dev, tdev);
+}
+
+static struct spi_driver adafruit_tft_spi_driver = {
+	.driver = {
+		.name = "ada-mipifb",
+		.owner = THIS_MODULE,
+		.of_match_table = adafruit_tft_of_match,
+		.pm = &tinydrm_simple_pm_ops,
+	},
+	.id_table = adafruit_tft_id,
+	.probe = adafruit_tft_probe,
+	.shutdown = tinydrm_spi_shutdown,
+};
+module_spi_driver(adafruit_tft_spi_driver);
+
+MODULE_DESCRIPTION("Adafruit MIPI compatible SPI displays");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/ili9340.h b/include/drm/tinydrm/ili9340.h
new file mode 100644
index 0000000..ecd2122
--- /dev/null
+++ b/include/drm/tinydrm/ili9340.h
@@ -0,0 +1,85 @@
+/*
+ * ILI9340 LCD controller
+ *
+ * Copyright 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_ILI9340_H
+#define __LINUX_ILI9340_H
+
+
+#define ILI9340_NOP        0x00
+#define ILI9340_SWRESET    0x01
+#define ILI9340_RDDIDIF    0x04
+#define ILI9340_RDDST      0x09
+#define ILI9340_RDDPM      0x0A
+#define ILI9340_RDDMADCTL  0x0B
+#define ILI9340_RDDCOLMOD  0x0C
+#define ILI9340_RDDIM      0x0D
+#define ILI9340_RDDSM      0x0E
+#define ILI9340_RDDSDR     0x0F
+
+#define ILI9340_SLPIN      0x10
+#define ILI9340_SLPOUT     0x11
+#define ILI9340_PTLON      0x12
+#define ILI9340_NORON      0x13
+
+#define ILI9340_DINVOFF    0x20
+#define ILI9340_DINVON     0x21
+#define ILI9340_GAMSET     0x26
+#define ILI9340_DISPOFF    0x28
+#define ILI9340_DISPON     0x29
+#define ILI9340_CASET      0x2A
+#define ILI9340_PASET      0x2B
+#define ILI9340_RAMWR      0x2C
+#define ILI9340_RGBSET     0x2D
+#define ILI9340_RAMRD      0x2E
+
+#define ILI9340_PLTAR      0x30
+#define ILI9340_VSCRDEF    0x33
+#define ILI9340_TEOFF      0x34
+#define ILI9340_TEON       0x35
+#define ILI9340_MADCTL     0x36
+#define ILI9340_VSCRSADD   0x37
+#define ILI9340_IDMOFF     0x38
+#define ILI9340_IDMON      0x39
+#define ILI9340_PIXSET     0x3A
+
+#define ILI9340_FRMCTR1    0xB1
+#define ILI9340_FRMCTR2    0xB2
+#define ILI9340_FRMCTR3    0xB3
+#define ILI9340_INVTR      0xB4
+#define ILI9340_DISCTRL    0xB6
+
+#define ILI9340_PWCTRL1    0xC0
+#define ILI9340_PWCTRL2    0xC1
+#define ILI9340_PWCTRL3    0xC2
+#define ILI9340_PWCTRL4    0xC3
+#define ILI9340_PWCTRL5    0xC4
+#define ILI9340_VMCTRL1    0xC5
+#define ILI9340_VMCTRL2    0xC7
+
+#define ILI9340_RDID1      0xDA
+#define ILI9340_RDID2      0xDB
+#define ILI9340_RDID3      0xDC
+#define ILI9340_RDID4      0xDD
+
+#define ILI9340_PGAMCTRL   0xE0
+#define ILI9340_NGAMCTRL   0xE1
+
+#define ILI9340_IFCTL      0xF6
+
+#define ILI9340_MADCTL_MH  BIT(2)
+#define ILI9340_MADCTL_BGR BIT(3)
+#define ILI9340_MADCTL_ML  BIT(4)
+#define ILI9340_MADCTL_MV  BIT(5)
+#define ILI9340_MADCTL_MX  BIT(6)
+#define ILI9340_MADCTL_MY  BIT(7)
+
+
+#endif /* __LINUX_ILI9340_H */
-- 
2.2.2

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

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (4 preceding siblings ...)
  2016-03-16 13:34 ` [RFC 5/5] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
@ 2016-03-16 14:50 ` Daniel Vetter
  2016-03-16 18:26 ` Eric Anholt
  6 siblings, 0 replies; 23+ messages in thread
From: Daniel Vetter @ 2016-03-16 14:50 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Wed, Mar 16, 2016 at 02:34:14PM +0100, Noralf Trønnes wrote:
> This is an attempt at providing a DRM version of drivers/staging/fbtft.
> I'm sending this early before cleaning up the code hoping to get some
> feedback in case there are better ways to structure this.
> 
> The tinydrm module provides a very simplified view of DRM for displays that
> has onboard video memory and is connected through a slow bus like SPI/I2C.
> A driver using tinydrm has to provide a function that will be called from
> the dirtyfb ioctl and optionally drm_panel functions which can be used to
> set up the display controller and control backlight.
> 
> tinydrm also has fbdev support using fb_deferred_io which is tied in through
> the dirtyfb function call. A driver can use the provided deferred work code
> to collect dirtyfb calls and schedule display memory updates if it whishes.
> The various display controllers can have library modules that handles
> display updates.

fbdev fb_deferred_io should be part of the fbdev helper, not a separate
library. We already have hand-rolled fbdev deferred io in qxl, i915 & udl,
no need to invent yet another one. Well the one in i915 is a bit a hack.

I think the best option would be to extract the code from qxl (since it
already uses a work automatically if needed) and put it into drm_fb_helper.c.
Otherwise the layering goes bust. We already have drm_fb_helper_* hooks
for everything, so only thing to do in drivers is to remove lots of code
\o/

I haven't looked at any of the other parts, just a quick comment on this
part here.
-Daniel

> Display controllers that have a similar register interface as the MIPI DBI/DCS
> controllers can use the lcdreg module for register access.
> 
> struct tinydrm_device {
> 	struct drm_device *base;
> 	u32 width, height;
> 	struct drm_panel panel;
> [...]
> 	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
> 		       unsigned color, struct drm_clip_rect *clips,
> 		       unsigned num_clips);
> };
> 
> +------------------------------+---------+
> |                              |  fbdev  |
> |                  DRM         +------+  |
> |                                     |  |
> +-------------------------------------+--+
> |                                        |
> |                tinydrm                 |
> |                                        |
> +------------------+ .  .  .  .  .  .  . |
> |                  |    deferred work    |
> |  Display driver  +---------------------+
> |                  |  Controller module  |
> +------------------+---------------------+
> |                 lcdreg                 |
> +----------------------------------------+
> |     Interface (SPI, I2C, parallel)     |
> +----------------------------------------+
> 
> 
> Noralf Trønnes (5):
>   drm: Add DRM support for tiny LCD displays
>   drm/tinydrm: Add lcd register abstraction
>   drm/tinydrm/lcdreg: Add SPI support
>   drm/tinydrm: Add mipi-dbi support
>   drm/tinydrm: Add support for several Adafruit TFT displays
> 
>  drivers/gpu/drm/Kconfig                            |   2 +
>  drivers/gpu/drm/Makefile                           |   1 +
>  drivers/gpu/drm/tinydrm/Kconfig                    |  27 +
>  drivers/gpu/drm/tinydrm/Makefile                   |   8 +
>  drivers/gpu/drm/tinydrm/adafruit-tft.c             | 256 ++++++++
>  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 ++
>  drivers/gpu/drm/tinydrm/lcdreg/Kconfig             |   8 +
>  drivers/gpu/drm/tinydrm/lcdreg/Makefile            |   5 +
>  drivers/gpu/drm/tinydrm/lcdreg/internal.h          |   8 +
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c       | 190 ++++++
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c    | 281 ++++++++
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c        | 720 +++++++++++++++++++++
>  drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 231 +++++++
>  include/drm/tinydrm/ili9340.h                      |  85 +++
>  include/drm/tinydrm/lcdreg-spi.h                   |  63 ++
>  include/drm/tinydrm/lcdreg.h                       | 126 ++++
>  include/drm/tinydrm/mipi-dbi.h                     |  24 +
>  include/drm/tinydrm/tinydrm.h                      | 142 ++++
>  26 files changed, 3345 insertions(+)
>  create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
>  create mode 100644 drivers/gpu/drm/tinydrm/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
>  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 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>  create mode 100644 include/drm/tinydrm/ili9340.h
>  create mode 100644 include/drm/tinydrm/lcdreg-spi.h
>  create mode 100644 include/drm/tinydrm/lcdreg.h
>  create mode 100644 include/drm/tinydrm/mipi-dbi.h
>  create mode 100644 include/drm/tinydrm/tinydrm.h
> 
> --
> 2.2.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
@ 2016-03-16 15:11   ` Daniel Vetter
  2016-03-17 21:51     ` Noralf Trønnes
  2016-03-23 17:37   ` David Herrmann
  1 sibling, 1 reply; 23+ messages in thread
From: Daniel Vetter @ 2016-03-16 15:11 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

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@tronnes.org>

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/

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.
- 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.
- 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
> ---
>  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@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (5 preceding siblings ...)
  2016-03-16 14:50 ` [RFC 0/5] drm: Add support for tiny LCD displays Daniel Vetter
@ 2016-03-16 18:26 ` Eric Anholt
  2016-03-17 22:00   ` Noralf Trønnes
  6 siblings, 1 reply; 23+ messages in thread
From: Eric Anholt @ 2016-03-16 18:26 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel; +Cc: thomas.petazzoni


[-- Attachment #1.1: Type: text/plain, Size: 1719 bytes --]

Noralf Trønnes <noralf@tronnes.org> writes:

> This is an attempt at providing a DRM version of drivers/staging/fbtft.
> I'm sending this early before cleaning up the code hoping to get some
> feedback in case there are better ways to structure this.
>
> The tinydrm module provides a very simplified view of DRM for displays that
> has onboard video memory and is connected through a slow bus like SPI/I2C.
> A driver using tinydrm has to provide a function that will be called from
> the dirtyfb ioctl and optionally drm_panel functions which can be used to
> set up the display controller and control backlight.
>
> tinydrm also has fbdev support using fb_deferred_io which is tied in through
> the dirtyfb function call. A driver can use the provided deferred work code
> to collect dirtyfb calls and schedule display memory updates if it whishes.
> The various display controllers can have library modules that handles
> display updates.
> Display controllers that have a similar register interface as the MIPI DBI/DCS
> controllers can use the lcdreg module for register access.
>
> struct tinydrm_device {
> 	struct drm_device *base;
> 	u32 width, height;
> 	struct drm_panel panel;
> [...]
> 	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
> 		       unsigned color, struct drm_clip_rect *clips,
> 		       unsigned num_clips);
> };

This is awesome!

I was wondering, have you considered what it would take to DMA
framebuffer contents from somewhere in memory to these displays?  Does
that look doable to you?

I'd love to be able to do something PRIME-like where vc4's doing the
rendering and we're periodically updating the TFT with the result.

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 818 bytes --]

[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-16 15:11   ` Daniel Vetter
@ 2016-03-17 21:51     ` Noralf Trønnes
  2016-03-18 17:47       ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-17 21:51 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


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@tronnes.org>
> 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@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-16 18:26 ` Eric Anholt
@ 2016-03-17 22:00   ` Noralf Trønnes
  2016-03-18 17:48     ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-17 22:00 UTC (permalink / raw)
  To: Eric Anholt, dri-devel; +Cc: thomas.petazzoni


Den 16.03.2016 19:26, skrev Eric Anholt:
> Noralf Trønnes <noralf@tronnes.org> writes:
>
>> This is an attempt at providing a DRM version of drivers/staging/fbtft.
>> I'm sending this early before cleaning up the code hoping to get some
>> feedback in case there are better ways to structure this.
>>
>> The tinydrm module provides a very simplified view of DRM for displays that
>> has onboard video memory and is connected through a slow bus like SPI/I2C.
>> A driver using tinydrm has to provide a function that will be called from
>> the dirtyfb ioctl and optionally drm_panel functions which can be used to
>> set up the display controller and control backlight.
>>
>> tinydrm also has fbdev support using fb_deferred_io which is tied in through
>> the dirtyfb function call. A driver can use the provided deferred work code
>> to collect dirtyfb calls and schedule display memory updates if it whishes.
>> The various display controllers can have library modules that handles
>> display updates.
>> Display controllers that have a similar register interface as the MIPI DBI/DCS
>> controllers can use the lcdreg module for register access.
>>
>> struct tinydrm_device {
>> 	struct drm_device *base;
>> 	u32 width, height;
>> 	struct drm_panel panel;
>> [...]
>> 	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
>> 		       unsigned color, struct drm_clip_rect *clips,
>> 		       unsigned num_clips);
>> };
> This is awesome!
>
> I was wondering, have you considered what it would take to DMA
> framebuffer contents from somewhere in memory to these displays?  Does
> that look doable to you?

The vast majory of these displays are connected through SPI and the SPI
subsystem maps the buffers using the DMA streaming API including support
for vmalloc buffers. I have some more details in my reply to Daniel.

> I'd love to be able to do something PRIME-like where vc4's doing the
> rendering and we're periodically updating the TFT with the result.

I think I read somewhere that one drm device could do the rendering and
another could scan out the buffer. It would be great if this could be done.
Some use these displays on handhold game emulators and the emulator only
supports OpenGL or some library needing hw rendering. Currently on the
Raspberry Pi this is solved by having a program take snapshots of the gpu
framebuffer and copy this into the fbtft fbdev framebuffer.

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-17 21:51     ` Noralf Trønnes
@ 2016-03-18 17:47       ` Daniel Vetter
  2016-03-23 17:07         ` Noralf Trønnes
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Vetter @ 2016-03-18 17:47 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
> 
> 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@tronnes.org>
> >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, \
> }

Looks like a pretty sweet idea. Maybe even split it up into GEM_PRIME_OPS
and similar sub-groups, which you could roll out into lots of drivers.

> 
> 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.

Just from your sketch here this sounds nifty.

> >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);

We already have a dirty_fb function in
dev->mode_config->funcs->dirty_fb(). This is the official interface native
drm/kms userspace is supposed to use to flush frontbuffer rendering. The
xfree86-video-modesetting driver uses it.

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

Yeah, this part is needed for the delayed work for the fbdev helper.
struct work dirty_fb_work; is missing.

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

Imo extend the existing helpers, adding deferred io is part of the reasons
we added them. Just add a check for mode_config->funcs->dirty_fb to
short-circuit all the deferred io flushing if the driver doesn't need it.

Same for setting up deferred io fbdev driver flags - just check whether
->dirty_fb is there, and only if that is set fill in inof->fbdefio.

> #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?

drm_fb_cma_helper is for creating drm_framebuffer backed by cma allocator
objects. How you create drm_framebuffer is orthogonal to whether you have
a ->dirty_fb hook (and hence needed defio support in fbdev) or not. E.g.
maybe some SPI device has a dma engine, and hence you want to allocate
drm_framebuffer using cma. On others with an i2c bus you want to just
allocate kernel memory, since the cpu will copy the data anyway.

That's why I think we need to make sure this split is still maintained.

> 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.

Hm, tbh I have no clear idea who fbdev fb memory mapping workings. The
above comments are more from a pov of a native kms userspace client. With
fbdev as a clean helper sitting entirely on top of of kms interfaces, with
no need to violate the layering for mmap support. There's some other
thread going on (for the arc driver or whatever it was called) with the
exact same problem. Might be good if you chat directly with Alexey Brodkin
about this topic.

It would be really awesome if we could make this Just Work.

> 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

Ah, I thought you've used vmalloc because your buses need cpu access
anyway and can't dma. I think we really need to look into making the fbdev
helpers work better - does fbdev allow us to implement a special mmap
handler? We could then write one specific to cma buffers to handle this, I
think that's also Alexey's plan now.

> >- 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);

My idea was to go even more radical and nuke all the crtc/plane/encoder
stuff and all the vfuncs entirely:

struct drm_simple_display_pipe {
	struct drm_crtc crtc;
	struct drm_plane plane;

	/* maybe future versions will want to allow connecting to
	 * drm_bridge, but drm_encoder already supports this. */
	struct drm_encoder encoder;
	
	struct drm_simple_display_pipe_funcs *funcs;

	/* here a pointer, since we want flexibility to integrate with
	 * panels and whatever. */
	struct drm_connector *connector;

	/* anything else simple support needs */
};

struct drm_simple_display_pipe_funcs {
	void (*enable)(struct drm_simple_display_pipe *pipe, struct
		       struct drm_crtc_state *crtc_state);
	void (*disable)(struct drm_simple_kms_create_pipeline *pipe);

	/* this would be a combination of dirty_fb + atomic_plane_update */
	void (*plane_update)(struct drm_simple_display_pipe *pipe, 
			     struct drm_plane_state *plane_state);
		  
	/* maybe more? I think this should be enough. */
}

Not entirely sure about the actual interface, but that's the rough idea.
Should be pretty quick to implement using the atomic helpers we have
already.

Then we'd just have
drm_simple_display_pipe_init(struct drm_simple_display_pipe *pipe,
			     struct drm_simple_display_pipe_funcs *funcs,
			     struct drm_connnector *connector);

Simple drivers could then embed their struct into whatever they already
have (maybe right into their overall dev_priv, together with the single
drm_connector). Simple support would provide hooks for all the other
things needed, implemented using the super-simple hooks above.

Note: The above is just typed out without even looking at the details of
your current tinydrm driver. So most likely needs to be adjusted. But the
goal would be to make things _really_ simple. Only flexibility I'd leave
intact is drm_connector, created by the driver directly. And maybe the
option to specify a drm_bridge chain. But we can add a new
_init_with_bridge for that.

Cheers, Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-17 22:00   ` Noralf Trønnes
@ 2016-03-18 17:48     ` Daniel Vetter
  2016-03-26 19:11       ` Noralf Trønnes
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Vetter @ 2016-03-18 17:48 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Thu, Mar 17, 2016 at 11:00:00PM +0100, Noralf Trønnes wrote:
> 
> Den 16.03.2016 19:26, skrev Eric Anholt:
> >Noralf Trønnes <noralf@tronnes.org> writes:
> >
> >>This is an attempt at providing a DRM version of drivers/staging/fbtft.
> >>I'm sending this early before cleaning up the code hoping to get some
> >>feedback in case there are better ways to structure this.
> >>
> >>The tinydrm module provides a very simplified view of DRM for displays that
> >>has onboard video memory and is connected through a slow bus like SPI/I2C.
> >>A driver using tinydrm has to provide a function that will be called from
> >>the dirtyfb ioctl and optionally drm_panel functions which can be used to
> >>set up the display controller and control backlight.
> >>
> >>tinydrm also has fbdev support using fb_deferred_io which is tied in through
> >>the dirtyfb function call. A driver can use the provided deferred work code
> >>to collect dirtyfb calls and schedule display memory updates if it whishes.
> >>The various display controllers can have library modules that handles
> >>display updates.
> >>Display controllers that have a similar register interface as the MIPI DBI/DCS
> >>controllers can use the lcdreg module for register access.
> >>
> >>struct tinydrm_device {
> >>	struct drm_device *base;
> >>	u32 width, height;
> >>	struct drm_panel panel;
> >>[...]
> >>	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
> >>		       unsigned color, struct drm_clip_rect *clips,
> >>		       unsigned num_clips);
> >>};
> >This is awesome!
> >
> >I was wondering, have you considered what it would take to DMA
> >framebuffer contents from somewhere in memory to these displays?  Does
> >that look doable to you?
> 
> The vast majory of these displays are connected through SPI and the SPI
> subsystem maps the buffers using the DMA streaming API including support
> for vmalloc buffers. I have some more details in my reply to Daniel.
> 
> >I'd love to be able to do something PRIME-like where vc4's doing the
> >rendering and we're periodically updating the TFT with the result.
> 
> I think I read somewhere that one drm device could do the rendering and
> another could scan out the buffer. It would be great if this could be done.
> Some use these displays on handhold game emulators and the emulator only
> supports OpenGL or some library needing hw rendering. Currently on the
> Raspberry Pi this is solved by having a program take snapshots of the gpu
> framebuffer and copy this into the fbtft fbdev framebuffer.

Yeah, this is definitely perfect use-case for prime buffer sharing.
CMA/dma buffer support for tinydrm would be great to make this work
seamlessly.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-18 17:47       ` Daniel Vetter
@ 2016-03-23 17:07         ` Noralf Trønnes
  2016-03-23 17:28           ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-23 17:07 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 18.03.2016 18:47, skrev Daniel Vetter:
> On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
>> 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@tronnes.org>
>>> 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:
[...]
>
>>> 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);
> We already have a dirty_fb function in
> dev->mode_config->funcs->dirty_fb(). This is the official interface native
> drm/kms userspace is supposed to use to flush frontbuffer rendering. The
> xfree86-video-modesetting driver uses it.

I couldn't find this dirty_fb() function, but I assume you mean
drm_framebuffer_funcs.dirty().

>> };
>>
>> struct drm_fb_helper {
>>      spinlock_t dirty_lock;
>>      struct drm_clip_rect *dirty_clip;
>> };
> Yeah, this part is needed for the delayed work for the fbdev helper.

> struct work dirty_fb_work; is missing.

This confuses me.
If we have this then there's no need for a fb->funcs->dirty() call,
the driver can just add a work function here instead.

Possible fb dirty() call chain:
Calls to drm_fb_helper_sys_* or mmap page writes will schedule
fb_info->deferred_work. The worker func fb_deferred_io_work() calls
fb_info->fbdefio->deferred_io().
Then deferred_io() can call fb_helper->fb->funcs->dirty().

In my use-case this dirty() function would schedule a delayed_work to run
immediately since it has already been deferred collecting changes.
The regular drm side framebuffer dirty() collects damage and schedules
the same worker to run deferred.

I don't see an easy way for a driver to set the dirty() function in
drm_fb_cma_helper apart from doing this:

  struct drm_fbdev_cma {
          struct drm_fb_helper    fb_helper;
          struct drm_fb_cma       *fb;
+        int (*dirty)(struct drm_framebuffer *framebuffer,
+                     struct drm_file *file_priv, unsigned flags,
+                     unsigned color, struct drm_clip_rect *clips,
+                     unsigned num_clips);
  };


>> 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?
> drm_fb_cma_helper is for creating drm_framebuffer backed by cma allocator
> objects. How you create drm_framebuffer is orthogonal to whether you have
> a ->dirty_fb hook (and hence needed defio support in fbdev) or not. E.g.
> maybe some SPI device has a dma engine, and hence you want to allocate
> drm_framebuffer using cma. On others with an i2c bus you want to just
> allocate kernel memory, since the cpu will copy the data anyway.
>
> That's why I think we need to make sure this split is still maintained.
>
>> 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.
> Hm, tbh I have no clear idea who fbdev fb memory mapping workings. The
> above comments are more from a pov of a native kms userspace client. With
> fbdev as a clean helper sitting entirely on top of of kms interfaces, with
> no need to violate the layering for mmap support. There's some other
> thread going on (for the arc driver or whatever it was called) with the
> exact same problem. Might be good if you chat directly with Alexey Brodkin
> about this topic.

Thanks, that discussion gave me a solution.
My problem goes away if I add this to fb_deferred_io_mmap():

         vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

I have asked on the fbdev mailinglist about this and the physical address:
Problems using fb_deferred_io with drm_fb_cma_helper
http://marc.info/?l=linux-fbdev&m=145874714523971&w=2


Noralf.

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-23 17:07         ` Noralf Trønnes
@ 2016-03-23 17:28           ` Daniel Vetter
  2016-03-25 10:41             ` Noralf Trønnes
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Vetter @ 2016-03-23 17:28 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Wed, Mar 23, 2016 at 06:07:56PM +0100, Noralf Trønnes wrote:
> 
> Den 18.03.2016 18:47, skrev Daniel Vetter:
> >On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
> >>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@tronnes.org>
> >>>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:
> [...]
> >
> >>>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);
> >We already have a dirty_fb function in
> >dev->mode_config->funcs->dirty_fb(). This is the official interface native
> >drm/kms userspace is supposed to use to flush frontbuffer rendering. The
> >xfree86-video-modesetting driver uses it.
> 
> I couldn't find this dirty_fb() function, but I assume you mean
> drm_framebuffer_funcs.dirty().

Yup.

> >>};
> >>
> >>struct drm_fb_helper {
> >>     spinlock_t dirty_lock;
> >>     struct drm_clip_rect *dirty_clip;
> >>};
> >Yeah, this part is needed for the delayed work for the fbdev helper.
> 
> >struct work dirty_fb_work; is missing.
> 
> This confuses me.
> If we have this then there's no need for a fb->funcs->dirty() call,
> the driver can just add a work function here instead.
> 
> Possible fb dirty() call chain:
> Calls to drm_fb_helper_sys_* or mmap page writes will schedule
> fb_info->deferred_work. The worker func fb_deferred_io_work() calls
> fb_info->fbdefio->deferred_io().
> Then deferred_io() can call fb_helper->fb->funcs->dirty().
> 
> In my use-case this dirty() function would schedule a delayed_work to run
> immediately since it has already been deferred collecting changes.
> The regular drm side framebuffer dirty() collects damage and schedules
> the same worker to run deferred.
> 
> I don't see an easy way for a driver to set the dirty() function in
> drm_fb_cma_helper apart from doing this:
> 
>  struct drm_fbdev_cma {
>          struct drm_fb_helper    fb_helper;
>          struct drm_fb_cma       *fb;
> +        int (*dirty)(struct drm_framebuffer *framebuffer,
> +                     struct drm_file *file_priv, unsigned flags,
> +                     unsigned color, struct drm_clip_rect *clips,
> +                     unsigned num_clips);
>  };

Well my point is that drm core already has a canonical interface
(drm_framebuffer_funcs.dirty) to flush out rendering. And it's supposed to
be called from process context, and userspace is supposed to batch up
dirty updates.

What I'd like is that the fbdev emulation uses exactly that interface,
without requiring drivers to write any additional fbdev code (like qxl and
udl currently have). Since the drm_framebuffer_funcs.dirty is already
expected to run in process context I think the only bit we need is the
deferred_work you already added in fbdev, so that we can schedule the
driver's ->dirty() function.

There shouldn't be any need to have another ->dirty() function anywhere
else.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
  2016-03-16 15:11   ` Daniel Vetter
@ 2016-03-23 17:37   ` David Herrmann
  2016-03-25 19:39     ` Noralf Trønnes
  1 sibling, 1 reply; 23+ messages in thread
From: David Herrmann @ 2016-03-23 17:37 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Thomas Petazzoni, dri-devel

Hey

On Wed, Mar 16, 2016 at 2:34 PM, Noralf Trønnes <noralf@tronnes.org> 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@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              |   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",

Can we just drop "date" and "desc" from new drivers? It doesn't add any value.

> +       .major                  = 1,
> +       .minor                  = 0,
> +};
> +
> +void tinydrm_release(struct tinydrm_device *tdev)

We usually prefer "unregister()" to stay consistent with "register()".

> +{
> +       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;

Whatever your .load() callback does, do that here and drop it. It is
really not needed. Optionally do it before calling drm_dev_register(),
depending on which semantics you want.

In general, this looks very similar to what I did with SimpleDRM.
However, I wonder whether we can make it more modular. Right now it
always adds code for fbdev, CMA, backlight, etc., but as Daniel
mentioned those better live in DRM-core helpers.

I'll try forward porting the SimpleDRM drivers to it, and let you know
whether it works out.

Thanks
David

> +
> +       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@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-23 17:28           ` Daniel Vetter
@ 2016-03-25 10:41             ` Noralf Trønnes
  2016-03-29  7:27               ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-25 10:41 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 23.03.2016 18:28, skrev Daniel Vetter:
> On Wed, Mar 23, 2016 at 06:07:56PM +0100, Noralf Trønnes wrote:
>> Den 18.03.2016 18:47, skrev Daniel Vetter:
>>> On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
>>>> 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@tronnes.org>
>>>>> 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:
>> [...]
>>>
>>>>> 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);
>>> We already have a dirty_fb function in
>>> dev->mode_config->funcs->dirty_fb(). This is the official interface native
>>> drm/kms userspace is supposed to use to flush frontbuffer rendering. The
>>> xfree86-video-modesetting driver uses it.
>> I couldn't find this dirty_fb() function, but I assume you mean
>> drm_framebuffer_funcs.dirty().
> Yup.
>
>>>> };
>>>>
>>>> struct drm_fb_helper {
>>>>      spinlock_t dirty_lock;
>>>>      struct drm_clip_rect *dirty_clip;
>>>> };
>>> Yeah, this part is needed for the delayed work for the fbdev helper.
>>> struct work dirty_fb_work; is missing.
>> This confuses me.
>> If we have this then there's no need for a fb->funcs->dirty() call,
>> the driver can just add a work function here instead.
>>
>> Possible fb dirty() call chain:
>> Calls to drm_fb_helper_sys_* or mmap page writes will schedule
>> fb_info->deferred_work. The worker func fb_deferred_io_work() calls
>> fb_info->fbdefio->deferred_io().
>> Then deferred_io() can call fb_helper->fb->funcs->dirty().
>>
>> In my use-case this dirty() function would schedule a delayed_work to run
>> immediately since it has already been deferred collecting changes.
>> The regular drm side framebuffer dirty() collects damage and schedules
>> the same worker to run deferred.
>>
>> I don't see an easy way for a driver to set the dirty() function in
>> drm_fb_cma_helper apart from doing this:
>>
>>   struct drm_fbdev_cma {
>>           struct drm_fb_helper    fb_helper;
>>           struct drm_fb_cma       *fb;
>> +        int (*dirty)(struct drm_framebuffer *framebuffer,
>> +                     struct drm_file *file_priv, unsigned flags,
>> +                     unsigned color, struct drm_clip_rect *clips,
>> +                     unsigned num_clips);
>>   };
> Well my point is that drm core already has a canonical interface
> (drm_framebuffer_funcs.dirty) to flush out rendering. And it's supposed to
> be called from process context, and userspace is supposed to batch up
> dirty updates.

Batched up into one ioctl call?
If that's the case, then I don't have to use delayed_work like I do now.
I can just queue a work_struct to run immediately.

This comment in include/uapi/drm/drm_mode.h made me think that I might
receive multiple calls:

  * The kernel or hardware is free to update more then just the
  * region specified by the clip rects. The kernel or hardware
  * may also delay and/or coalesce several calls to dirty into a
  * single update.

I have assumed that I can't run the whole display update from the ioctl
call since one full display update on the "worst" display takes ~200ms.
But maybe it's fine to run all this in the ioctl call?

> What I'd like is that the fbdev emulation uses exactly that interface,
> without requiring drivers to write any additional fbdev code (like qxl and
> udl currently have). Since the drm_framebuffer_funcs.dirty is already
> expected to run in process context I think the only bit we need is the
> deferred_work you already added in fbdev, so that we can schedule the
> driver's ->dirty() function.
>
> There shouldn't be any need to have another ->dirty() function anywhere
> else.

I'll try and see if I can explain better with some code.
First the drm_fb_helper part:

void drm_fb_helper_deferred_io(struct fb_info *info,
                                struct list_head *pagelist)
{
         struct drm_fb_helper *helper = info->par;
         unsigned long start, end, min, max;
         struct drm_clip_rect clip;
         struct page *page;

         if (!helper->fb->funcs->dirty)
                 return;

         spin_lock(&helper->dirty_lock);
         clip = *helper->dirty_clip;
         /* reset dirty_clip */
         spin_unlock(&helper->dirty_lock);

         min = ULONG_MAX;
         max = 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);
         }

         if (min < max) {
                 /* TODO merge with clip */
         }

         helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
}
EXPORT_SYMBOL(drm_fb_helper_deferred_io);

/* Called by the drm_fb_helper_sys_*() functions */
static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
                                        u32 width, u32 height)
{
         struct drm_fb_helper *helper = info->par;
         struct drm_clip_rect clip;

         if (!info->fbdefio)
                 return;

         clip.x1 = x;
         clip.x2 = x + width -1;
         cliy.y1 = y;
         clip.y2 = y + height - 1;

         spin_lock(&helper->dirty_lock);
         /* TODO merge clip with helper->dirty_clip */
         spin_unlock(&helper->dirty_lock);

         schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
}


So the question I have asked is this: How can the driver set the
dirty() function within drm_fb_cma_helper?

Having looked at the code over and over again, I have a suggestion,
but it assumes that it's allowed to change fb->funcs.

First the necessary drm_fb_cma_helper changes:

EXPORT_SYMBOL(drm_fb_cma_destroy);
EXPORT_SYMBOL(drm_fb_cma_create_handle);
EXPORT_SYMBOL(drm_fbdev_cma_create);

/* This is the drm_fbdev_cma_init() code with one change */
struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
         unsigned int preferred_bpp, unsigned int num_crtc,
         unsigned int max_conn_count, struct drm_framebuffer_funcs *funcs)
{
[...]
         drm_fb_helper_prepare(dev, helper, funcs);
[...]
}

struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
         unsigned int preferred_bpp, unsigned int num_crtc,
         unsigned int max_conn_count)
{
         return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp, num_crtc,
                                              max_conn_count,
&drm_fb_cma_helper_funcs);
}


Now tinydrm should be able to do this:

static int tinydrm_fbdev_dirty(struct drm_framebuffer *fb,
                                struct drm_file *file_priv,
                                unsigned flags, unsigned color,
                                struct drm_clip_rect *clips,
                                unsigned num_clips)
{
         struct drm_fb_helper *helper = info->par;
         struct tinydrm_device *tdev = helper->dev->dev_private;
         struct drm_framebuffer *fb = helper->fb;
         struct drm_gem_cma_object *cma_obj;

         if (tdev->plane.fb != fb)
                 return 0;

         cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
         if (!cma_obj) {
                 dev_err_once(info->dev, "Can't get cma_obj\n");
                 return -EINVAL;
         }

         return tdev->dirtyfb(fb, cma_obj->vaddr, flags, color, clips, 
num_clips);
}

static struct drm_framebuffer_funcs tinydrm_fb_cma_funcs = {
         .destroy        = drm_fb_cma_destroy,
         .create_handle  = drm_fb_cma_create_handle,
         .dirty          = tinydrm_fbdev_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;
         struct fb_deferred_io *fbdefio;
         struct fb_ops *fbops;
         struct fb_info *info;

         /*
          * A per device structure is needed for:
          * - fbops because fb_deferred_io_cleanup() clears fbops.fb_mmap
          * - fbdefio to get per device delay
          */
         fbops = devm_kzalloc(helper->dev->dev, sizeof(*fbops), GFP_KERNEL);
         fbdefio = devm_kzalloc(helper->dev->dev, sizeof(*fbdefio), 
GFP_KERNEL);
         if (!fbops || !fbdefio)
                 return -ENOMEM;

         ret = drm_fbdev_cma_create(helper, sizes);
         if (ret)
                 return ret;

         helper->fb->funcs = &tinydrm_fb_cma_funcs;

         info = fb_helper->fbdev;
         /*
          * fb_deferred_io requires a vmalloc address or a physical address.
          * drm_fbdev_cma_create() sets smem_start to the dma address 
which is
          * a device address. This isn't always a physical address.
          */
         info->smem_start = page_to_phys(virt_to_page(info->screen_buffer));

         *fbops = *info->fbops;
         info->fbops = fbops;

         info->fbdefio = fbdefio;
         fbdefio->deferred_io = drm_fb_helper_deferred_io;
         fbdefio->delay = msecs_to_jiffies(tdev->deferred->defer_ms);
         /* delay=0 is turned into delay=HZ, so use 1 as a minimum */
         if (!fbdefio->delay)
                 fbdefio->delay = 1;
         fb_deferred_io_init(info);

         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 *cma;

         cma = 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(cma))
                 return PTR_ERR(cma);

         tdev->fbdev_cma = cma;

         return 0;
}

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-23 17:37   ` David Herrmann
@ 2016-03-25 19:39     ` Noralf Trønnes
  0 siblings, 0 replies; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-25 19:39 UTC (permalink / raw)
  To: David Herrmann; +Cc: Thomas Petazzoni, dri-devel


Den 23.03.2016 18:37, skrev David Herrmann:
> Hey
>
> On Wed, Mar 16, 2016 at 2:34 PM, Noralf Trønnes <noralf@tronnes.org> 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@tronnes.org>
>> ---

[...]

>> +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",
> Can we just drop "date" and "desc" from new drivers? It doesn't add any value.
>
>> +       .major                  = 1,
>> +       .minor                  = 0,
>> +};
>> +
>> +void tinydrm_release(struct tinydrm_device *tdev)
> We usually prefer "unregister()" to stay consistent with "register()".

Sure.

>> +{
>> +       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;
> Whatever your .load() callback does, do that here and drop it. It is
> really not needed. Optionally do it before calling drm_dev_register(),
> depending on which semantics you want.

Ok.

> In general, this looks very similar to what I did with SimpleDRM.

SimpleDRM was the first drm code I studied and tinydrm started with chunks
of code from it :-)

> However, I wonder whether we can make it more modular. Right now it
> always adds code for fbdev, CMA, backlight, etc., but as Daniel
> mentioned those better live in DRM-core helpers.

Yes I will make the next version more modular.
Instead of trying to guess which parts would fit as core helpers, I just
put everything into one module and posted an RFC hoping to get some 
feedback.
I will try an implement the suggestions Daniel has made.

> I'll try forward porting the SimpleDRM drivers to it, and let you know
> whether it works out.
>
> Thanks
> David


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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-18 17:48     ` Daniel Vetter
@ 2016-03-26 19:11       ` Noralf Trønnes
  2016-03-29  7:29         ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-03-26 19:11 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 18.03.2016 18:48, skrev Daniel Vetter:
> On Thu, Mar 17, 2016 at 11:00:00PM +0100, Noralf Trønnes wrote:
>> Den 16.03.2016 19:26, skrev Eric Anholt:
>>> Noralf Trønnes <noralf@tronnes.org> writes:
>>>
>>>> This is an attempt at providing a DRM version of drivers/staging/fbtft.
>>>> I'm sending this early before cleaning up the code hoping to get some
>>>> feedback in case there are better ways to structure this.
>>>>
>>>> The tinydrm module provides a very simplified view of DRM for displays that
>>>> has onboard video memory and is connected through a slow bus like SPI/I2C.
>>>> A driver using tinydrm has to provide a function that will be called from
>>>> the dirtyfb ioctl and optionally drm_panel functions which can be used to
>>>> set up the display controller and control backlight.
>>>>
>>>> tinydrm also has fbdev support using fb_deferred_io which is tied in through
>>>> the dirtyfb function call. A driver can use the provided deferred work code
>>>> to collect dirtyfb calls and schedule display memory updates if it whishes.
>>>> The various display controllers can have library modules that handles
>>>> display updates.
>>>> Display controllers that have a similar register interface as the MIPI DBI/DCS
>>>> controllers can use the lcdreg module for register access.
>>>>
>>>> struct tinydrm_device {
>>>> 	struct drm_device *base;
>>>> 	u32 width, height;
>>>> 	struct drm_panel panel;
>>>> [...]
>>>> 	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
>>>> 		       unsigned color, struct drm_clip_rect *clips,
>>>> 		       unsigned num_clips);
>>>> };
>>> This is awesome!
>>>
>>> I was wondering, have you considered what it would take to DMA
>>> framebuffer contents from somewhere in memory to these displays?  Does
>>> that look doable to you?
>> The vast majory of these displays are connected through SPI and the SPI
>> subsystem maps the buffers using the DMA streaming API including support
>> for vmalloc buffers. I have some more details in my reply to Daniel.
>>
>>> I'd love to be able to do something PRIME-like where vc4's doing the
>>> rendering and we're periodically updating the TFT with the result.
>> I think I read somewhere that one drm device could do the rendering and
>> another could scan out the buffer. It would be great if this could be done.
>> Some use these displays on handhold game emulators and the emulator only
>> supports OpenGL or some library needing hw rendering. Currently on the
>> Raspberry Pi this is solved by having a program take snapshots of the gpu
>> framebuffer and copy this into the fbtft fbdev framebuffer.
> Yeah, this is definitely perfect use-case for prime buffer sharing.
> CMA/dma buffer support for tinydrm would be great to make this work
> seamlessly.

I have looked at cma prime and it looks like vaddr is not set.
How do I get the virtual address?

drm_prime_fd_to_handle_ioctl
   -> drm_gem_prime_fd_to_handle
      -> drm_gem_prime_import
         -> drm_gem_cma_prime_import_sg_table:

         cma_obj = __drm_gem_cma_create(dev, attach->dmabuf->size);
         cma_obj->paddr = sg_dma_address(sgt->sgl);
         cma_obj->sgt = sgt;

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-25 10:41             ` Noralf Trønnes
@ 2016-03-29  7:27               ` Daniel Vetter
  2016-04-01 19:15                 ` Noralf Trønnes
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Vetter @ 2016-03-29  7:27 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Mar 25, 2016 at 11:41:44AM +0100, Noralf Trønnes wrote:
> 
> Den 23.03.2016 18:28, skrev Daniel Vetter:
> >On Wed, Mar 23, 2016 at 06:07:56PM +0100, Noralf Trønnes wrote:
> >>Den 18.03.2016 18:47, skrev Daniel Vetter:
> >>>On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
> >>>>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@tronnes.org>
> >>>>>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:
> >>[...]
> >>>
> >>>>>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);
> >>>We already have a dirty_fb function in
> >>>dev->mode_config->funcs->dirty_fb(). This is the official interface native
> >>>drm/kms userspace is supposed to use to flush frontbuffer rendering. The
> >>>xfree86-video-modesetting driver uses it.
> >>I couldn't find this dirty_fb() function, but I assume you mean
> >>drm_framebuffer_funcs.dirty().
> >Yup.
> >
> >>>>};
> >>>>
> >>>>struct drm_fb_helper {
> >>>>     spinlock_t dirty_lock;
> >>>>     struct drm_clip_rect *dirty_clip;
> >>>>};
> >>>Yeah, this part is needed for the delayed work for the fbdev helper.
> >>>struct work dirty_fb_work; is missing.
> >>This confuses me.
> >>If we have this then there's no need for a fb->funcs->dirty() call,
> >>the driver can just add a work function here instead.
> >>
> >>Possible fb dirty() call chain:
> >>Calls to drm_fb_helper_sys_* or mmap page writes will schedule
> >>fb_info->deferred_work. The worker func fb_deferred_io_work() calls
> >>fb_info->fbdefio->deferred_io().
> >>Then deferred_io() can call fb_helper->fb->funcs->dirty().
> >>
> >>In my use-case this dirty() function would schedule a delayed_work to run
> >>immediately since it has already been deferred collecting changes.
> >>The regular drm side framebuffer dirty() collects damage and schedules
> >>the same worker to run deferred.
> >>
> >>I don't see an easy way for a driver to set the dirty() function in
> >>drm_fb_cma_helper apart from doing this:
> >>
> >>  struct drm_fbdev_cma {
> >>          struct drm_fb_helper    fb_helper;
> >>          struct drm_fb_cma       *fb;
> >>+        int (*dirty)(struct drm_framebuffer *framebuffer,
> >>+                     struct drm_file *file_priv, unsigned flags,
> >>+                     unsigned color, struct drm_clip_rect *clips,
> >>+                     unsigned num_clips);
> >>  };
> >Well my point is that drm core already has a canonical interface
> >(drm_framebuffer_funcs.dirty) to flush out rendering. And it's supposed to
> >be called from process context, and userspace is supposed to batch up
> >dirty updates.
> 
> Batched up into one ioctl call?
> If that's the case, then I don't have to use delayed_work like I do now.
> I can just queue a work_struct to run immediately.
> 
> This comment in include/uapi/drm/drm_mode.h made me think that I might
> receive multiple calls:
> 
>  * The kernel or hardware is free to update more then just the
>  * region specified by the clip rects. The kernel or hardware
>  * may also delay and/or coalesce several calls to dirty into a
>  * single update.

This just means that userspace must ensure that the entire buffer is still
valid, specifically that regions outside of the cliprects are not garabge.
This way drivers can increase the area that's uploaded (e.g. upload the
entire screen or only 1 rect spanning all cliprects in case that's the
only thing the hw can do).

Userspace otoh is supposed to batch up updates into one ioctl call (and
not one per drawn glyph or something silly like that).

> I have assumed that I can't run the whole display update from the ioctl
> call since one full display update on the "worst" display takes ~200ms.
> But maybe it's fine to run all this in the ioctl call?

Hm, thus far all drivers do their updating synchronously. But then none
yet took 200ms to do that - most have hw dma for the upload itself.

Short term I'd just do the update synchronously, async dirty with this
slow display has all kinds of fun problems I guess with X rendering
getting ahead and reducing interactivity even more. Long term there's talk
about adding cliprects to atomic, and then we could do a helper to
implement dirtyfb as an async atomic update.

> >What I'd like is that the fbdev emulation uses exactly that interface,
> >without requiring drivers to write any additional fbdev code (like qxl and
> >udl currently have). Since the drm_framebuffer_funcs.dirty is already
> >expected to run in process context I think the only bit we need is the
> >deferred_work you already added in fbdev, so that we can schedule the
> >driver's ->dirty() function.
> >
> >There shouldn't be any need to have another ->dirty() function anywhere
> >else.
> 
> I'll try and see if I can explain better with some code.
> First the drm_fb_helper part:
> 
> void drm_fb_helper_deferred_io(struct fb_info *info,
>                                struct list_head *pagelist)
> {
>         struct drm_fb_helper *helper = info->par;
>         unsigned long start, end, min, max;
>         struct drm_clip_rect clip;
>         struct page *page;
> 
>         if (!helper->fb->funcs->dirty)
>                 return;
> 
>         spin_lock(&helper->dirty_lock);
>         clip = *helper->dirty_clip;
>         /* reset dirty_clip */
>         spin_unlock(&helper->dirty_lock);
> 
>         min = ULONG_MAX;
>         max = 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);
>         }
> 
>         if (min < max) {
>                 /* TODO merge with clip */
>         }
> 
>         helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
> }
> EXPORT_SYMBOL(drm_fb_helper_deferred_io);
> 
> /* Called by the drm_fb_helper_sys_*() functions */
> static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
>                                        u32 width, u32 height)
> {
>         struct drm_fb_helper *helper = info->par;
>         struct drm_clip_rect clip;
> 
>         if (!info->fbdefio)
>                 return;
> 
>         clip.x1 = x;
>         clip.x2 = x + width -1;
>         cliy.y1 = y;
>         clip.y2 = y + height - 1;
> 
>         spin_lock(&helper->dirty_lock);
>         /* TODO merge clip with helper->dirty_clip */
>         spin_unlock(&helper->dirty_lock);
> 
>         schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
> }
> 
> 
> So the question I have asked is this: How can the driver set the
> dirty() function within drm_fb_cma_helper?
> 
> Having looked at the code over and over again, I have a suggestion,
> but it assumes that it's allowed to change fb->funcs.
> 
> First the necessary drm_fb_cma_helper changes:
> 
> EXPORT_SYMBOL(drm_fb_cma_destroy);
> EXPORT_SYMBOL(drm_fb_cma_create_handle);
> EXPORT_SYMBOL(drm_fbdev_cma_create);
> 
> /* This is the drm_fbdev_cma_init() code with one change */
> struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
>         unsigned int preferred_bpp, unsigned int num_crtc,
>         unsigned int max_conn_count, struct drm_framebuffer_funcs *funcs)
> {
> [...]
>         drm_fb_helper_prepare(dev, helper, funcs);
> [...]
> }
> 
> struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
>         unsigned int preferred_bpp, unsigned int num_crtc,
>         unsigned int max_conn_count)
> {
>         return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp, num_crtc,
>                                              max_conn_count,
> &drm_fb_cma_helper_funcs);
> }
> 
> 
> Now tinydrm should be able to do this:
> 
> static int tinydrm_fbdev_dirty(struct drm_framebuffer *fb,
>                                struct drm_file *file_priv,
>                                unsigned flags, unsigned color,
>                                struct drm_clip_rect *clips,
>                                unsigned num_clips)
> {
>         struct drm_fb_helper *helper = info->par;
>         struct tinydrm_device *tdev = helper->dev->dev_private;
>         struct drm_framebuffer *fb = helper->fb;
>         struct drm_gem_cma_object *cma_obj;
> 
>         if (tdev->plane.fb != fb)
>                 return 0;
> 
>         cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
>         if (!cma_obj) {
>                 dev_err_once(info->dev, "Can't get cma_obj\n");
>                 return -EINVAL;
>         }
> 
>         return tdev->dirtyfb(fb, cma_obj->vaddr, flags, color, clips,
> num_clips);
> }
> 
> static struct drm_framebuffer_funcs tinydrm_fb_cma_funcs = {
>         .destroy        = drm_fb_cma_destroy,
>         .create_handle  = drm_fb_cma_create_handle,
>         .dirty          = tinydrm_fbdev_dirty,
> };

Ah, cma midlayer strikes again. We've had a similar discussion around the
gem side when vc4 landed for similar reasons iirc - it's hard to overwrite
specific helper functions to customize the behaviour.

I guess the simplest solution would be to export
drm_fb_cma_destroy/create_handle functions, and then add a
drm_fb_cma_create_with_funcs helper which you can use like this:

static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
                                struct drm_fb_helper_surface_size *sizes)
{
	return drm_fb_cma_create_with_funcs(helper, sizes, tinydrm_fb_cma_funcs);
}

Changing fb->funcs after drm_framebuffer_init is called is a no-go, since
that function also registers the framebuffer and makes it userspace
accessible. So after that point other threads can go&look at fb->funcs.

> 
> 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;
>         struct fb_deferred_io *fbdefio;
>         struct fb_ops *fbops;
>         struct fb_info *info;
> 
>         /*
>          * A per device structure is needed for:
>          * - fbops because fb_deferred_io_cleanup() clears fbops.fb_mmap
>          * - fbdefio to get per device delay
>          */
>         fbops = devm_kzalloc(helper->dev->dev, sizeof(*fbops), GFP_KERNEL);
>         fbdefio = devm_kzalloc(helper->dev->dev, sizeof(*fbdefio),
> GFP_KERNEL);
>         if (!fbops || !fbdefio)
>                 return -ENOMEM;
> 
>         ret = drm_fbdev_cma_create(helper, sizes);
>         if (ret)
>                 return ret;
> 
>         helper->fb->funcs = &tinydrm_fb_cma_funcs;

Ok, first thing I've forgotten is that the fbdev cma helper doesn't call
mode_config->funcs->fb_create and so gets the wrong fb->funcs. One thing
I've pondered for different reasons lately is whether the fbdev emulation
should grow a fake drm_file fpriv. With that we could just allocate a dumb
buffer and pass it to ->fb_create (it takes a handle, which means we need
a fake fpriv). I think that would fully demidlayer cma helpers. Or we
create a new helper function to do ->fb_create but with gem objects
instead of ids like what we've done for vc4. Or we just open-code it all.

Not sure which is better here.

> 
>         info = fb_helper->fbdev;
>         /*
>          * fb_deferred_io requires a vmalloc address or a physical address.
>          * drm_fbdev_cma_create() sets smem_start to the dma address which
> is
>          * a device address. This isn't always a physical address.
>          */
>         info->smem_start = page_to_phys(virt_to_page(info->screen_buffer));
> 
>         *fbops = *info->fbops;
>         info->fbops = fbops;
> 
>         info->fbdefio = fbdefio;
>         fbdefio->deferred_io = drm_fb_helper_deferred_io;
>         fbdefio->delay = msecs_to_jiffies(tdev->deferred->defer_ms);
>         /* delay=0 is turned into delay=HZ, so use 1 as a minimum */
>         if (!fbdefio->delay)
>                 fbdefio->delay = 1;
>         fb_deferred_io_init(info);
> 
>         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 *cma;
> 
>         cma = 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(cma))
>                 return PTR_ERR(cma);

I was wondering whether we couldn't avoid the _with_funcs here by setting
up fbdefio iff ->dirty is set. Then you could add the generic defio setup
code to drm_fbdev_cma_create after helper->fb is allocated, but only if
helper->fb->funcs->dirty is set. Makes for a bit less boilerplate.

Or did I miss something?
-Daniel

> 
>         tdev->fbdev_cma = cma;
> 
>         return 0;
> }
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 0/5] drm: Add support for tiny LCD displays
  2016-03-26 19:11       ` Noralf Trønnes
@ 2016-03-29  7:29         ` Daniel Vetter
  0 siblings, 0 replies; 23+ messages in thread
From: Daniel Vetter @ 2016-03-29  7:29 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Sat, Mar 26, 2016 at 08:11:08PM +0100, Noralf Trønnes wrote:
> 
> Den 18.03.2016 18:48, skrev Daniel Vetter:
> >On Thu, Mar 17, 2016 at 11:00:00PM +0100, Noralf Trønnes wrote:
> >>Den 16.03.2016 19:26, skrev Eric Anholt:
> >>>Noralf Trønnes <noralf@tronnes.org> writes:
> >>>
> >>>>This is an attempt at providing a DRM version of drivers/staging/fbtft.
> >>>>I'm sending this early before cleaning up the code hoping to get some
> >>>>feedback in case there are better ways to structure this.
> >>>>
> >>>>The tinydrm module provides a very simplified view of DRM for displays that
> >>>>has onboard video memory and is connected through a slow bus like SPI/I2C.
> >>>>A driver using tinydrm has to provide a function that will be called from
> >>>>the dirtyfb ioctl and optionally drm_panel functions which can be used to
> >>>>set up the display controller and control backlight.
> >>>>
> >>>>tinydrm also has fbdev support using fb_deferred_io which is tied in through
> >>>>the dirtyfb function call. A driver can use the provided deferred work code
> >>>>to collect dirtyfb calls and schedule display memory updates if it whishes.
> >>>>The various display controllers can have library modules that handles
> >>>>display updates.
> >>>>Display controllers that have a similar register interface as the MIPI DBI/DCS
> >>>>controllers can use the lcdreg module for register access.
> >>>>
> >>>>struct tinydrm_device {
> >>>>	struct drm_device *base;
> >>>>	u32 width, height;
> >>>>	struct drm_panel panel;
> >>>>[...]
> >>>>	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
> >>>>		       unsigned color, struct drm_clip_rect *clips,
> >>>>		       unsigned num_clips);
> >>>>};
> >>>This is awesome!
> >>>
> >>>I was wondering, have you considered what it would take to DMA
> >>>framebuffer contents from somewhere in memory to these displays?  Does
> >>>that look doable to you?
> >>The vast majory of these displays are connected through SPI and the SPI
> >>subsystem maps the buffers using the DMA streaming API including support
> >>for vmalloc buffers. I have some more details in my reply to Daniel.
> >>
> >>>I'd love to be able to do something PRIME-like where vc4's doing the
> >>>rendering and we're periodically updating the TFT with the result.
> >>I think I read somewhere that one drm device could do the rendering and
> >>another could scan out the buffer. It would be great if this could be done.
> >>Some use these displays on handhold game emulators and the emulator only
> >>supports OpenGL or some library needing hw rendering. Currently on the
> >>Raspberry Pi this is solved by having a program take snapshots of the gpu
> >>framebuffer and copy this into the fbtft fbdev framebuffer.
> >Yeah, this is definitely perfect use-case for prime buffer sharing.
> >CMA/dma buffer support for tinydrm would be great to make this work
> >seamlessly.
> 
> I have looked at cma prime and it looks like vaddr is not set.
> How do I get the virtual address?
> 
> drm_prime_fd_to_handle_ioctl
>   -> drm_gem_prime_fd_to_handle
>      -> drm_gem_prime_import
>         -> drm_gem_cma_prime_import_sg_table:
> 
>         cma_obj = __drm_gem_cma_create(dev, attach->dmabuf->size);
>         cma_obj->paddr = sg_dma_address(sgt->sgl);
>         cma_obj->sgt = sgt;

sg table is supposed to be for device dma, for kernel access either use
vmap or the kmap functions dma-buf provides. Since no one yet bothered
with kmap I'd just go with vmap ;-)

The reason for this split is that the dma-buf might wrap something that's
totally not normal memory (not struct page backed at least) and hence
kernel mmmaps need special attention.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-03-29  7:27               ` Daniel Vetter
@ 2016-04-01 19:15                 ` Noralf Trønnes
  2016-04-12 10:40                   ` Daniel Vetter
  0 siblings, 1 reply; 23+ messages in thread
From: Noralf Trønnes @ 2016-04-01 19:15 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 29.03.2016 09:27, skrev Daniel Vetter:
> On Fri, Mar 25, 2016 at 11:41:44AM +0100, Noralf Trønnes wrote:
>> Den 23.03.2016 18:28, skrev Daniel Vetter:
>>> On Wed, Mar 23, 2016 at 06:07:56PM +0100, Noralf Trønnes wrote:
>>>> Den 18.03.2016 18:47, skrev Daniel Vetter:
>>>>> On Thu, Mar 17, 2016 at 10:51:55PM +0100, Noralf Trønnes wrote:
>>>>>> 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@tronnes.org>
>>>>>>> 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:
>>>> [...]
>>>>>>> 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);
>>>>> We already have a dirty_fb function in
>>>>> dev->mode_config->funcs->dirty_fb(). This is the official interface native
>>>>> drm/kms userspace is supposed to use to flush frontbuffer rendering. The
>>>>> xfree86-video-modesetting driver uses it.
>>>> I couldn't find this dirty_fb() function, but I assume you mean
>>>> drm_framebuffer_funcs.dirty().
>>> Yup.
>>>
>>>>>> };
>>>>>>
>>>>>> struct drm_fb_helper {
>>>>>>      spinlock_t dirty_lock;
>>>>>>      struct drm_clip_rect *dirty_clip;
>>>>>> };
>>>>> Yeah, this part is needed for the delayed work for the fbdev helper.
>>>>> struct work dirty_fb_work; is missing.
>>>> This confuses me.
>>>> If we have this then there's no need for a fb->funcs->dirty() call,
>>>> the driver can just add a work function here instead.
>>>>
>>>> Possible fb dirty() call chain:
>>>> Calls to drm_fb_helper_sys_* or mmap page writes will schedule
>>>> fb_info->deferred_work. The worker func fb_deferred_io_work() calls
>>>> fb_info->fbdefio->deferred_io().
>>>> Then deferred_io() can call fb_helper->fb->funcs->dirty().
>>>>
>>>> In my use-case this dirty() function would schedule a delayed_work to run
>>>> immediately since it has already been deferred collecting changes.
>>>> The regular drm side framebuffer dirty() collects damage and schedules
>>>> the same worker to run deferred.
>>>>
>>>> I don't see an easy way for a driver to set the dirty() function in
>>>> drm_fb_cma_helper apart from doing this:
>>>>
>>>>   struct drm_fbdev_cma {
>>>>           struct drm_fb_helper    fb_helper;
>>>>           struct drm_fb_cma       *fb;
>>>> +        int (*dirty)(struct drm_framebuffer *framebuffer,
>>>> +                     struct drm_file *file_priv, unsigned flags,
>>>> +                     unsigned color, struct drm_clip_rect *clips,
>>>> +                     unsigned num_clips);
>>>>   };
>>> Well my point is that drm core already has a canonical interface
>>> (drm_framebuffer_funcs.dirty) to flush out rendering. And it's supposed to
>>> be called from process context, and userspace is supposed to batch up
>>> dirty updates.

[...]

>
>>> What I'd like is that the fbdev emulation uses exactly that interface,
>>> without requiring drivers to write any additional fbdev code (like qxl and
>>> udl currently have). Since the drm_framebuffer_funcs.dirty is already
>>> expected to run in process context I think the only bit we need is the
>>> deferred_work you already added in fbdev, so that we can schedule the
>>> driver's ->dirty() function.
>>>
>>> There shouldn't be any need to have another ->dirty() function anywhere
>>> else.
>> I'll try and see if I can explain better with some code.
>> First the drm_fb_helper part:
>>
>> void drm_fb_helper_deferred_io(struct fb_info *info,
>>                                 struct list_head *pagelist)
>> {
>>          struct drm_fb_helper *helper = info->par;
>>          unsigned long start, end, min, max;
>>          struct drm_clip_rect clip;
>>          struct page *page;
>>
>>          if (!helper->fb->funcs->dirty)
>>                  return;
>>
>>          spin_lock(&helper->dirty_lock);
>>          clip = *helper->dirty_clip;
>>          /* reset dirty_clip */
>>          spin_unlock(&helper->dirty_lock);
>>
>>          min = ULONG_MAX;
>>          max = 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);
>>          }
>>
>>          if (min < max) {
>>                  /* TODO merge with clip */
>>          }
>>
>>          helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
>> }
>> EXPORT_SYMBOL(drm_fb_helper_deferred_io);
>>
>> /* Called by the drm_fb_helper_sys_*() functions */
>> static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
>>                                         u32 width, u32 height)
>> {
>>          struct drm_fb_helper *helper = info->par;
>>          struct drm_clip_rect clip;
>>
>>          if (!info->fbdefio)
>>                  return;
>>
>>          clip.x1 = x;
>>          clip.x2 = x + width -1;
>>          cliy.y1 = y;
>>          clip.y2 = y + height - 1;
>>
>>          spin_lock(&helper->dirty_lock);
>>          /* TODO merge clip with helper->dirty_clip */
>>          spin_unlock(&helper->dirty_lock);
>>
>>          schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
>> }
>>
>>
>> So the question I have asked is this: How can the driver set the
>> dirty() function within drm_fb_cma_helper?
>>
>> Having looked at the code over and over again, I have a suggestion,
>> but it assumes that it's allowed to change fb->funcs.
>>
>> First the necessary drm_fb_cma_helper changes:
>>
>> EXPORT_SYMBOL(drm_fb_cma_destroy);
>> EXPORT_SYMBOL(drm_fb_cma_create_handle);
>> EXPORT_SYMBOL(drm_fbdev_cma_create);
>>
>> /* This is the drm_fbdev_cma_init() code with one change */
>> struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
>>          unsigned int preferred_bpp, unsigned int num_crtc,
>>          unsigned int max_conn_count, struct drm_framebuffer_funcs *funcs)
>> {
>> [...]
>>          drm_fb_helper_prepare(dev, helper, funcs);
>> [...]
>> }
>>
>> struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
>>          unsigned int preferred_bpp, unsigned int num_crtc,
>>          unsigned int max_conn_count)
>> {
>>          return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp, num_crtc,
>>                                               max_conn_count,
>> &drm_fb_cma_helper_funcs);
>> }
>>
>>
>> Now tinydrm should be able to do this:
>>
>> static int tinydrm_fbdev_dirty(struct drm_framebuffer *fb,
>>                                 struct drm_file *file_priv,
>>                                 unsigned flags, unsigned color,
>>                                 struct drm_clip_rect *clips,
>>                                 unsigned num_clips)
>> {
>>          struct drm_fb_helper *helper = info->par;
>>          struct tinydrm_device *tdev = helper->dev->dev_private;
>>          struct drm_framebuffer *fb = helper->fb;
>>          struct drm_gem_cma_object *cma_obj;
>>
>>          if (tdev->plane.fb != fb)
>>                  return 0;
>>
>>          cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
>>          if (!cma_obj) {
>>                  dev_err_once(info->dev, "Can't get cma_obj\n");
>>                  return -EINVAL;
>>          }
>>
>>          return tdev->dirtyfb(fb, cma_obj->vaddr, flags, color, clips,
>> num_clips);
>> }
>>
>> static struct drm_framebuffer_funcs tinydrm_fb_cma_funcs = {
>>          .destroy        = drm_fb_cma_destroy,
>>          .create_handle  = drm_fb_cma_create_handle,
>>          .dirty          = tinydrm_fbdev_dirty,
>> };
> Ah, cma midlayer strikes again. We've had a similar discussion around the
> gem side when vc4 landed for similar reasons iirc - it's hard to overwrite
> specific helper functions to customize the behaviour.
>
> I guess the simplest solution would be to export
> drm_fb_cma_destroy/create_handle functions, and then add a
> drm_fb_cma_create_with_funcs helper which you can use like this:
>
> static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
>                                  struct drm_fb_helper_surface_size *sizes)
> {
> 	return drm_fb_cma_create_with_funcs(helper, sizes, tinydrm_fb_cma_funcs);
> }
>
> Changing fb->funcs after drm_framebuffer_init is called is a no-go, since
> that function also registers the framebuffer and makes it userspace
> accessible. So after that point other threads can go&look at fb->funcs.
>
>> 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;
>>          struct fb_deferred_io *fbdefio;
>>          struct fb_ops *fbops;
>>          struct fb_info *info;
>>
>>          /*
>>           * A per device structure is needed for:
>>           * - fbops because fb_deferred_io_cleanup() clears fbops.fb_mmap
>>           * - fbdefio to get per device delay
>>           */
>>          fbops = devm_kzalloc(helper->dev->dev, sizeof(*fbops), GFP_KERNEL);
>>          fbdefio = devm_kzalloc(helper->dev->dev, sizeof(*fbdefio),
>> GFP_KERNEL);
>>          if (!fbops || !fbdefio)
>>                  return -ENOMEM;
>>
>>          ret = drm_fbdev_cma_create(helper, sizes);
>>          if (ret)
>>                  return ret;
>>
>>          helper->fb->funcs = &tinydrm_fb_cma_funcs;
> Ok, first thing I've forgotten is that the fbdev cma helper doesn't call
> mode_config->funcs->fb_create and so gets the wrong fb->funcs. One thing
> I've pondered for different reasons lately is whether the fbdev emulation
> should grow a fake drm_file fpriv. With that we could just allocate a dumb
> buffer and pass it to ->fb_create (it takes a handle, which means we need
> a fake fpriv). I think that would fully demidlayer cma helpers. Or we
> create a new helper function to do ->fb_create but with gem objects
> instead of ids like what we've done for vc4. Or we just open-code it all.
>
> Not sure which is better here.
>
>>          info = fb_helper->fbdev;
>>          /*
>>           * fb_deferred_io requires a vmalloc address or a physical address.
>>           * drm_fbdev_cma_create() sets smem_start to the dma address which
>> is
>>           * a device address. This isn't always a physical address.
>>           */
>>          info->smem_start = page_to_phys(virt_to_page(info->screen_buffer));
>>
>>          *fbops = *info->fbops;
>>          info->fbops = fbops;
>>
>>          info->fbdefio = fbdefio;
>>          fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>          fbdefio->delay = msecs_to_jiffies(tdev->deferred->defer_ms);
>>          /* delay=0 is turned into delay=HZ, so use 1 as a minimum */
>>          if (!fbdefio->delay)
>>                  fbdefio->delay = 1;
>>          fb_deferred_io_init(info);
>>
>>          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 *cma;
>>
>>          cma = 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(cma))
>>                  return PTR_ERR(cma);
> I was wondering whether we couldn't avoid the _with_funcs here by setting
> up fbdefio iff ->dirty is set. Then you could add the generic defio setup
> code to drm_fbdev_cma_create after helper->fb is allocated, but only if
> helper->fb->funcs->dirty is set. Makes for a bit less boilerplate.
>
> Or did I miss something?

I don't see how I can avoid drm_fbdev_cma_init_with_funcs():

static struct drm_framebuffer_funcs tinydrm_fb_cma_funcs = {
         .destroy        = drm_fb_cma_destroy,
         .create_handle  = drm_fb_cma_create_handle,
         .dirty          = tinydrm_fbdev_dirty,
};

static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
                                 struct drm_fb_helper_surface_size *sizes)
{
         return drm_fbdev_cma_create_with_funcs(helper, sizes,
&tinydrm_fb_cma_funcs);
}

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 *cma;

         cma = 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(cma))
                 return PTR_ERR(cma);

         tdev->fbdev_cma = cma;

         return 0;
}


Thanks for your feedback so far Daniel, I quite like the direction this is
taking. I'll try and implement it in a new version of the patchset.


Noralf.

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

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC 1/5] drm: Add DRM support for tiny LCD displays
  2016-04-01 19:15                 ` Noralf Trønnes
@ 2016-04-12 10:40                   ` Daniel Vetter
  0 siblings, 0 replies; 23+ messages in thread
From: Daniel Vetter @ 2016-04-12 10:40 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 01, 2016 at 09:15:45PM +0200, Noralf Trønnes wrote:
> Den 29.03.2016 09:27, skrev Daniel Vetter:
> >I was wondering whether we couldn't avoid the _with_funcs here by setting
> >up fbdefio iff ->dirty is set. Then you could add the generic defio setup
> >code to drm_fbdev_cma_create after helper->fb is allocated, but only if
> >helper->fb->funcs->dirty is set. Makes for a bit less boilerplate.
> >
> >Or did I miss something?
> 
> I don't see how I can avoid drm_fbdev_cma_init_with_funcs():
> 
> static struct drm_framebuffer_funcs tinydrm_fb_cma_funcs = {
>         .destroy        = drm_fb_cma_destroy,
>         .create_handle  = drm_fb_cma_create_handle,
>         .dirty          = tinydrm_fbdev_dirty,
> };
> 
> static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
>                                 struct drm_fb_helper_surface_size *sizes)
> {
>         return drm_fbdev_cma_create_with_funcs(helper, sizes,
> &tinydrm_fb_cma_funcs);
> }
> 
> 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 *cma;
> 
>         cma = 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(cma))
>                 return PTR_ERR(cma);
> 
>         tdev->fbdev_cma = cma;
> 
>         return 0;
> }
> 
> 
> Thanks for your feedback so far Daniel, I quite like the direction this is
> taking. I'll try and implement it in a new version of the patchset.

Yeah I was dense really. One option to avoid most of this might be to add
a framebuffer_create helper function to dev->mode_config->helpers (we
don't yet have a vtable for global helper hooks, so would need to add
that), which instead of the top-level uin32_t -> drm_framebuffer gets a
struct *drm_gem_object[4] array parameter with already decoded buffer
object handles. Drivers could then use that to add their own dirtyfb hooks
and other special sauce to drm_framebuffer, while cma would just use that
hook (if it's set) instead of calling cma_create_fb directly.

So yeah, essentially go back to one of your original proposals. But it
will still not be entirely clean, so whatever you think looks better I'd
say ;-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2016-04-12 10:40 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-16 13:34 [RFC 0/5] drm: Add support for tiny LCD displays Noralf Trønnes
2016-03-16 13:34 ` [RFC 1/5] drm: Add DRM " Noralf Trønnes
2016-03-16 15:11   ` Daniel Vetter
2016-03-17 21:51     ` Noralf Trønnes
2016-03-18 17:47       ` Daniel Vetter
2016-03-23 17:07         ` Noralf Trønnes
2016-03-23 17:28           ` Daniel Vetter
2016-03-25 10:41             ` Noralf Trønnes
2016-03-29  7:27               ` Daniel Vetter
2016-04-01 19:15                 ` Noralf Trønnes
2016-04-12 10:40                   ` Daniel Vetter
2016-03-23 17:37   ` David Herrmann
2016-03-25 19:39     ` Noralf Trønnes
2016-03-16 13:34 ` [RFC 2/5] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
2016-03-16 13:34 ` [RFC 3/5] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
2016-03-16 13:34 ` [RFC 4/5] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
2016-03-16 13:34 ` [RFC 5/5] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
2016-03-16 14:50 ` [RFC 0/5] drm: Add support for tiny LCD displays Daniel Vetter
2016-03-16 18:26 ` Eric Anholt
2016-03-17 22:00   ` Noralf Trønnes
2016-03-18 17:48     ` Daniel Vetter
2016-03-26 19:11       ` Noralf Trønnes
2016-03-29  7:29         ` Daniel Vetter

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.