diff mbox

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

Message ID 1458135259-31050-2-git-send-email-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes March 16, 2016, 1:34 p.m. UTC
tinydrm provides a very simplified view of DRM for displays that has
onboard video memory and is connected through a slow bus like SPI/I2C.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/Kconfig                            |   2 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/tinydrm/Kconfig                    |  11 +
 drivers/gpu/drm/tinydrm/Makefile                   |   1 +
 drivers/gpu/drm/tinydrm/core/Makefile              |   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

Comments

Daniel Vetter March 16, 2016, 3:11 p.m. UTC | #1
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
Noralf Trønnes March 17, 2016, 9:51 p.m. UTC | #2
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
Daniel Vetter March 18, 2016, 5:47 p.m. UTC | #3
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
Noralf Trønnes March 23, 2016, 5:07 p.m. UTC | #4
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.
Daniel Vetter March 23, 2016, 5:28 p.m. UTC | #5
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
David Herrmann March 23, 2016, 5:37 p.m. UTC | #6
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
Noralf Trønnes March 25, 2016, 10:41 a.m. UTC | #7
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;
}
Noralf Trønnes March 25, 2016, 7:39 p.m. UTC | #8
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
Daniel Vetter March 29, 2016, 7:27 a.m. UTC | #9
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;
> }
>
Noralf Trønnes April 1, 2016, 7:15 p.m. UTC | #10
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.
Daniel Vetter April 12, 2016, 10:40 a.m. UTC | #11
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
diff mbox

Patch

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