diff mbox

[4/9] drm: Add DRM support for tiny LCD displays

Message ID 20170122181117.8210-5-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes Jan. 22, 2017, 6:11 p.m. UTC
tinydrm provides helpers for very simple displays that can use
CMA backed framebuffers and need flushing on changes.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 Documentation/gpu/drm-kms-helpers.rst       |  15 ++
 MAINTAINERS                                 |   7 +
 drivers/gpu/drm/Kconfig                     |   2 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/tinydrm/Kconfig             |   8 +
 drivers/gpu/drm/tinydrm/Makefile            |   1 +
 drivers/gpu/drm/tinydrm/core/Makefile       |   3 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 374 ++++++++++++++++++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 244 ++++++++++++++++++
 include/drm/tinydrm/tinydrm.h               | 115 +++++++++
 10 files changed, 770 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
 create mode 100644 include/drm/tinydrm/tinydrm.h

Comments

Daniel Vetter Jan. 23, 2017, 9:28 a.m. UTC | #1
On Sun, Jan 22, 2017 at 07:11:12PM +0100, Noralf Trønnes wrote:
> tinydrm provides helpers for very simple displays that can use
> CMA backed framebuffers and need flushing on changes.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Looks all pretty. A bunch of ideas below, but all optional. For merging I
think simplest to first get the core patches in through drm-misc, and then
you can submit a pull request to Dave for tinydrm+backends (just needs an
ack for the dt parts from dt maintainers), including MAINTAINERS entry.
Ack from my side.

Thanks, Daniel

> ---
>  Documentation/gpu/drm-kms-helpers.rst       |  15 ++
>  MAINTAINERS                                 |   7 +
>  drivers/gpu/drm/Kconfig                     |   2 +
>  drivers/gpu/drm/Makefile                    |   1 +
>  drivers/gpu/drm/tinydrm/Kconfig             |   8 +
>  drivers/gpu/drm/tinydrm/Makefile            |   1 +
>  drivers/gpu/drm/tinydrm/core/Makefile       |   3 +
>  drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 374 ++++++++++++++++++++++++++++
>  drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 244 ++++++++++++++++++
>  include/drm/tinydrm/tinydrm.h               | 115 +++++++++
>  10 files changed, 770 insertions(+)
>  create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
>  create mode 100644 drivers/gpu/drm/tinydrm/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
>  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
>  create mode 100644 include/drm/tinydrm/tinydrm.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 03040aa..a86bd7f 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -272,3 +272,18 @@ Auxiliary Modeset Helpers
>  
>  .. kernel-doc:: drivers/gpu/drm/drm_modeset_helper.c
>     :export:
> +
> +tinydrm Helper Reference
> +========================
> +
> +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
> +   :doc: overview
> +
> +.. kernel-doc:: include/drm/tinydrm/tinydrm.h
> +   :internal:
> +
> +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
> +   :export:
> +
> +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
> +   :export:

Since tinydrm is more like a special driver than a helper I think it would
make sense to move the docs into the driver section, next to the include
for the i915 stuff. That means a new tinydrm.rst and including it in
gpu/index.rst.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 741f35f..817e0fe 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4365,6 +4365,13 @@ DRM DRIVER FOR TDFX VIDEO CARDS
>  S:	Orphan / Obsolete
>  F:	drivers/gpu/drm/tdfx/
>  
> +DRM DRIVERS FOR TINY DISPLAYS
> +M:	Noralf Trønnes <noralf@tronnes.org>
> +W:	https://github.com/notro/tinydrm/wiki
> +S:	Maintained
> +F:	drivers/gpu/drm/tinydrm/
> +F:	include/drm/tinydrm/
> +
>  DRM DRIVER FOR USB DISPLAYLINK VIDEO ADAPTERS
>  M:	Dave Airlie <airlied@redhat.com>
>  S:	Odd Fixes
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 90bc65d..88e01e08e 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -263,6 +263,8 @@ source "drivers/gpu/drm/mxsfb/Kconfig"
>  
>  source "drivers/gpu/drm/meson/Kconfig"
>  
> +source "drivers/gpu/drm/tinydrm/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 92de399..3ee9579 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -94,3 +94,4 @@ obj-$(CONFIG_DRM_ARCPGU)+= arc/
>  obj-y			+= hisilicon/
>  obj-$(CONFIG_DRM_ZTE)	+= zte/
>  obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
> +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..ffb873f
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/Kconfig
> @@ -0,0 +1,8 @@
> +menuconfig DRM_TINYDRM
> +	tristate "Support for simple displays"
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	select DRM_KMS_CMA_HELPER
> +	help
> +	  Choose this option if you have a tinydrm supported display.
> +	  If M is selected the module will be called tinydrm.
> diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
> new file mode 100644
> index 0000000..7476ed1
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_DRM_TINYDRM)		+= core/
> diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
> new file mode 100644
> index 0000000..4f14a0f
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/core/Makefile
> @@ -0,0 +1,3 @@
> +tinydrm-y := tinydrm-core.o tinydrm-pipe.o
> +
> +obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
> diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
> new file mode 100644
> index 0000000..492b478
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
> @@ -0,0 +1,374 @@
> +/*
> + * Copyright (C) 2016 Noralf Trønnes
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/tinydrm/tinydrm.h>
> +#include <linux/device.h>
> +#include <linux/dma-buf.h>
> +
> +/**
> + * DOC: overview
> + *
> + * This library provides driver helpers for very simple display hardware.
> + *
> + * It is based on &drm_simple_display_pipe coupled with a &drm_connector which
> + * has only one fixed &drm_display_mode. The framebuffers are backed by the
> + * cma helper and have support for framebuffer flushing (dirty).
> + * fbdev support is also included.
> + *
> + * The driver allocates &tinydrm_device, initializes it using
> + * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
> + * and registers the DRM device using devm_tinydrm_register().
> + */
> +
> +/**
> + * tinydrm_lastclose - DRM lastclose helper
> + * @drm: DRM device
> + *
> + * This function ensures that fbdev is restored when drm_lastclose() is called
> + * on the last drm_release(). Drivers can use this as their
> + * &drm_driver->lastclose callback.
> + */
> +void tinydrm_lastclose(struct drm_device *drm)
> +{
> +	struct tinydrm_device *tdev = drm->dev_private;
> +
> +	DRM_DEBUG_KMS("\n");
> +	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
> +}
> +EXPORT_SYMBOL(tinydrm_lastclose);

I wonder whether we should have a pointer in dev->mode_config for the
fbdev stuff, and then a helper for generic kms lastclose in the fbdev
helper (so that it's useable for both cma and non-cma drivers). We
copy-paste this one-liner wrapper sooooo many times.

Just an idea for a follow-up/separate series.

> +
> +/**
> + * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from
> + *     another driver's scatter/gather table of pinned pages
> + * @drm: DRM device to import into
> + * @attach: DMA-BUF attachment
> + * @sgt: Scatter/gather table of pinned pages
> + *
> + * This function imports a scatter/gather table exported via DMA-BUF by
> + * another driver using drm_gem_cma_prime_import_sg_table(). It sets the
> + * kernel virtual address on the CMA object. Drivers should use this as their
> + * &drm_driver->gem_prime_import_sg_table callback if they need the virtual
> + * address. tinydrm_gem_cma_free_object() should be used in combination with
> + * this function.
> + *
> + * Returns:
> + * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
> + * error code on failure.
> + */
> +struct drm_gem_object *
> +tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
> +				      struct dma_buf_attachment *attach,
> +				      struct sg_table *sgt)
> +{
> +	struct drm_gem_cma_object *cma_obj;
> +	struct drm_gem_object *obj;
> +	void *vaddr;
> +
> +	vaddr = dma_buf_vmap(attach->dmabuf);
> +	if (!vaddr) {
> +		DRM_ERROR("Failed to vmap PRIME buffer\n");
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
> +	if (IS_ERR(obj)) {
> +		dma_buf_vunmap(attach->dmabuf, vaddr);
> +		return obj;
> +	}
> +
> +	cma_obj = to_drm_gem_cma_obj(obj);
> +	cma_obj->vaddr = vaddr;
> +
> +	return obj;
> +}
> +EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table);
> +
> +/**
> + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
> + *                               object
> + * @gem_obj: GEM object to free
> + *
> + * This function frees the backing memory of the CMA GEM object, cleans up the
> + * GEM object state and frees the memory used to store the object itself using
> + * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel
> + * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers
> + * can use this as their &drm_driver->gem_free_object callback.
> + */
> +void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
> +{
> +	if (gem_obj->import_attach) {
> +		struct drm_gem_cma_object *cma_obj;
> +
> +		cma_obj = to_drm_gem_cma_obj(gem_obj);
> +		dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
> +		cma_obj->vaddr = NULL;
> +	}
> +
> +	drm_gem_cma_free_object(gem_obj);
> +}
> +EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object);
> +
> +const struct file_operations tinydrm_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= drm_open,
> +	.release	= drm_release,
> +	.unlocked_ioctl	= drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl	= drm_compat_ioctl,
> +#endif
> +	.poll		= drm_poll,
> +	.read		= drm_read,
> +	.llseek		= no_llseek,
> +	.mmap		= drm_gem_cma_mmap,
> +};
> +EXPORT_SYMBOL(tinydrm_fops);
> +
> +static struct drm_framebuffer *
> +tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
> +		  const struct drm_mode_fb_cmd2 *mode_cmd)
> +{
> +	struct tinydrm_device *tdev = drm->dev_private;
> +
> +	return drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd,
> +					    tdev->fb_funcs);
> +}
> +
> +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,
> +};
> +
> +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
> +			const struct drm_framebuffer_funcs *fb_funcs,
> +			struct drm_driver *driver)
> +{
> +	struct drm_device *drm;
> +
> +	mutex_init(&tdev->dirty_lock);
> +	tdev->fb_funcs = fb_funcs;
> +
> +	/*
> +	 * We don't embed drm_device, because that prevent us from using
> +	 * devm_kzalloc() to allocate tinydrm_device in the driver since
> +	 * drm_dev_unref() frees the structure. The devm_ functions provide
> +	 * for easy error handling.
> +	 */
> +	drm = drm_dev_alloc(driver, parent);

There's a patch series from Chris Wilson to add a ->release callback and
properly fix this.

> +	if (IS_ERR(drm))
> +		return PTR_ERR(drm);
> +
> +	tdev->drm = drm;
> +	drm->dev_private = tdev;
> +	drm_mode_config_init(drm);
> +	drm->mode_config.funcs = &tinydrm_mode_config_funcs;
> +
> +	return 0;
> +}
> +
> +static void tinydrm_fini(struct tinydrm_device *tdev)
> +{
> +	DRM_DEBUG_KMS("\n");
> +
> +	drm_mode_config_cleanup(tdev->drm);
> +	mutex_destroy(&tdev->dirty_lock);
> +	tdev->drm->dev_private = NULL;
> +	drm_dev_unref(tdev->drm);
> +}
> +
> +static void devm_tinydrm_release(void *data)
> +{
> +	tinydrm_fini(data);
> +}
> +
> +/**
> + * devm_tinydrm_init - Initialize tinydrm device
> + * @parent: Parent device object
> + * @tdev: tinydrm device
> + * @fb_funcs: Framebuffer functions
> + * @driver: DRM driver
> + *
> + * This function initializes @tdev, the underlying DRM device and it's
> + * mode_config. Resources will be automatically freed on driver detach (devres)
> + * using drm_mode_config_cleanup() and drm_dev_unref().
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
> +		      const struct drm_framebuffer_funcs *fb_funcs,
> +		      struct drm_driver *driver)
> +{
> +	int ret;
> +
> +	ret = tinydrm_init(parent, tdev, fb_funcs, driver);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action(parent, devm_tinydrm_release, tdev);
> +	if (ret)
> +		tinydrm_fini(tdev);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(devm_tinydrm_init);
> +
> +static int tinydrm_register(struct tinydrm_device *tdev)
> +{
> +	struct drm_device *drm = tdev->drm;
> +	int bpp = drm->mode_config.preferred_depth;
> +	struct drm_fbdev_cma *fbdev;
> +	int ret;
> +
> +	ret = drm_dev_register(tdev->drm, 0);
> +	if (ret)
> +		return ret;
> +
> +	fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
> +					      drm->mode_config.num_crtc,
> +					      drm->mode_config.num_connector,
> +					      tdev->fb_funcs);
> +	if (IS_ERR(fbdev))
> +		DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
> +	else
> +		tdev->fbdev_cma = fbdev;
> +
> +	return 0;
> +}
> +
> +static void tinydrm_unregister(struct tinydrm_device *tdev)
> +{
> +	DRM_DEBUG_KMS("\n");
> +
> +	drm_crtc_force_disable_all(tdev->drm);
> +
> +	if (tdev->fbdev_cma)
> +		drm_fbdev_cma_fini(tdev->fbdev_cma);
> +
> +	drm_dev_unregister(tdev->drm);
> +}
> +
> +static void devm_tinydrm_register_release(void *data)
> +{
> +	tinydrm_unregister(data);
> +}
> +
> +/**
> + * devm_tinydrm_register - Register tinydrm device
> + * @tdev: tinydrm device
> + *
> + * This function registers the underlying DRM device and fbdev.
> + * These resources will be automatically unregistered on driver detach (devres)
> + * and the display pipeline will be disabled.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int devm_tinydrm_register(struct tinydrm_device *tdev)
> +{
> +	struct device *dev = tdev->drm->dev;
> +	int ret;
> +
> +	ret = tinydrm_register(tdev);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
> +	if (ret)
> +		tinydrm_unregister(tdev);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(devm_tinydrm_register);
> +
> +/**
> + * tinydrm_shutdown - Shutdown tinydrm
> + * @tdev: tinydrm device
> + *
> + * This function makes sure that the display pipeline is disabled.
> + * Used by drivers in their shutdown callback to turn off the display
> + * on machine shutdown and reboot.
> + */
> +void tinydrm_shutdown(struct tinydrm_device *tdev)
> +{
> +	drm_crtc_force_disable_all(tdev->drm);
> +}
> +EXPORT_SYMBOL(tinydrm_shutdown);
> +
> +/**
> + * tinydrm_suspend - Suspend tinydrm
> + * @tdev: tinydrm device
> + *
> + * Used in driver PM operations to suspend tinydrm.
> + * Suspends fbdev and DRM.
> + * Resume with tinydrm_resume().
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int tinydrm_suspend(struct tinydrm_device *tdev)
> +{
> +	struct drm_atomic_state *state;
> +
> +	if (tdev->suspend_state) {
> +		DRM_ERROR("Failed to suspend: state already set\n");
> +		return -EINVAL;
> +	}
> +
> +	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1);

Hm, random idea for faster suspend: On resume we can use the worker to
avoid stalling on console_lock. On suspedn we could use a
set_suspend_async/complete pair, which first launches the worker and later
on waits for it to complete. If we do this around the (usually rather
slow) call to drm_atomic_helper_suspend, then we could hide all that
latency.

> +	state = drm_atomic_helper_suspend(tdev->drm);
> +	if (IS_ERR(state)) {
> +		drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
> +		return PTR_ERR(state);
> +	}
> +
> +	tdev->suspend_state = state;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tinydrm_suspend);
> +
> +/**
> + * tinydrm_resume - Resume tinydrm
> + * @tdev: tinydrm device
> + *
> + * Used in driver PM operations to resume tinydrm.
> + * Suspend with tinydrm_suspend().
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int tinydrm_resume(struct tinydrm_device *tdev)
> +{
> +	struct drm_atomic_state *state = tdev->suspend_state;
> +	int ret;
> +
> +	if (!state) {
> +		DRM_ERROR("Failed to resume: state is not set\n");
> +		return -EINVAL;
> +	}
> +
> +	tdev->suspend_state = NULL;
> +
> +	ret = drm_atomic_helper_resume(tdev->drm, state);
> +	if (ret) {
> +		DRM_ERROR("Error resuming state: %d\n", ret);
> +		return ret;
> +	}
> +
> +	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tinydrm_resume);
> +
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
> new file mode 100644
> index 0000000..4327b04
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
> @@ -0,0 +1,244 @@
> +/*
> + * Copyright (C) 2016 Noralf Trønnes
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_modes.h>
> +#include <drm/tinydrm/tinydrm.h>
> +
> +struct tinydrm_connector {
> +	struct drm_connector base;
> +	const struct drm_display_mode *mode;
> +};
> +
> +static inline struct tinydrm_connector *
> +to_tinydrm_connector(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct tinydrm_connector, base);
> +}
> +
> +static int tinydrm_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_duplicate(connector->dev, tconn->mode);
> +	if (!mode) {
> +		DRM_ERROR("Failed to duplicate mode\n");
> +		return 0;
> +	}
> +
> +	if (mode->name[0] == '\0')
> +		drm_mode_set_name(mode);
> +
> +	mode->type |= DRM_MODE_TYPE_PREFERRED;
> +	drm_mode_probed_add(connector, mode);
> +
> +	if (mode->width_mm) {
> +		connector->display_info.width_mm = mode->width_mm;
> +		connector->display_info.height_mm = mode->height_mm;
> +	}
> +
> +	return 1;
> +}
> +
> +static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = {
> +	.get_modes = tinydrm_connector_get_modes,
> +	.best_encoder = drm_atomic_helper_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +tinydrm_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	if (drm_device_is_unplugged(connector->dev))
> +		return connector_status_disconnected;
> +
> +	return connector->status;
> +}
> +
> +static void tinydrm_connector_destroy(struct drm_connector *connector)
> +{
> +	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
> +
> +	drm_connector_cleanup(connector);
> +	kfree(tconn);
> +}
> +
> +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,
> +};
> +
> +struct drm_connector *
> +tinydrm_connector_create(struct drm_device *drm,
> +			 const struct drm_display_mode *mode,
> +			 int connector_type)
> +{
> +	struct tinydrm_connector *tconn;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	tconn = kzalloc(sizeof(*tconn), GFP_KERNEL);
> +	if (!tconn)
> +		return ERR_PTR(-ENOMEM);
> +
> +	tconn->mode = mode;
> +	connector = &tconn->base;
> +
> +	drm_connector_helper_add(connector, &tinydrm_connector_hfuncs);
> +	ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs,
> +				 connector_type);
> +	if (ret) {
> +		kfree(tconn);
> +		return ERR_PTR(ret);
> +	}
> +
> +	connector->status = connector_status_connected;
> +
> +	return connector;
> +}
> +
> +/**
> + * tinydrm_display_pipe_update - Display pipe update helper
> + * @pipe: Simple display pipe
> + * @old_state: Old plane state
> + *
> + * This function does a full framebuffer flush if the plane framebuffer
> + * has changed. It also handles vblank events. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->update callback.
> + */
> +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
> +				 struct drm_plane_state *old_state)
> +{
> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
> +	struct drm_framebuffer *fb = pipe->plane.state->fb;
> +	struct drm_crtc *crtc = &tdev->pipe.crtc;
> +
> +	if (!fb)
> +		DRM_DEBUG_KMS("fb unset\n");
> +	else if (!old_state->fb)
> +		DRM_DEBUG_KMS("fb set\n");
> +	else if (fb != old_state->fb)
> +		DRM_DEBUG_KMS("fb changed\n");
> +	else
> +		DRM_DEBUG_KMS("No fb change\n");
> +
> +	if (fb && (fb != old_state->fb)) {
> +		pipe->plane.fb = fb;
> +		if (fb->funcs->dirty)
> +			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);

I like the idea, but my cunning long-term plan is that we'd extend the
atomic support to support a dirty rectangle. Together with the
non-blocking stuff we could then implement fb->funcs->dirty in terms of
atomic. But here you implement atomic in terms of ->dirty, and we'd end up
with a loop.

Personally I'd just drop this helper here and move this part into the
backend modules ...

> +	}
> +
> +	if (crtc->state->event) {
> +		DRM_DEBUG_KMS("crtc event\n");
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		drm_crtc_send_vblank_event(crtc, crtc->state->event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +		crtc->state->event = NULL;

... because this here is kinda a hack, since it's not synchronized with
the screen update. Otoh these tiny panels are kinda special.

> +	}
> +}
> +EXPORT_SYMBOL(tinydrm_display_pipe_update);
> +
> +/**
> + * tinydrm_display_pipe_prepare_fb - Display pipe prepare_fb helper
> + * @pipe: Simple display pipe
> + * @plane_state: Plane state
> + *
> + * This function uses drm_fb_cma_prepare_fb() to check if the plane FB has an
> + * dma-buf attached, extracts the exclusive fence and attaches it to plane
> + * state for the atomic helper to wait on. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->prepare_fb callback.
> + */
> +int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
> +				    struct drm_plane_state *plane_state)
> +{
> +	return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
> +}
> +EXPORT_SYMBOL(tinydrm_display_pipe_prepare_fb);
> +
> +static int tinydrm_rotate_mode(struct drm_display_mode *mode,
> +			       unsigned int rotation)
> +{
> +	if (rotation == 0 || rotation == 180) {
> +		return 0;
> +	} else if (rotation == 90 || rotation == 270) {
> +		swap(mode->hdisplay, mode->vdisplay);
> +		swap(mode->hsync_start, mode->vsync_start);
> +		swap(mode->hsync_end, mode->vsync_end);
> +		swap(mode->htotal, mode->vtotal);
> +		swap(mode->width_mm, mode->height_mm);
> +		return 0;
> +	} else {
> +		return -EINVAL;
> +	}
> +}
> +
> +/**
> + * tinydrm_display_pipe_init - Initialize display pipe
> + * @tdev: tinydrm device
> + * @funcs: Display pipe functions
> + * @connector_type: Connector type
> + * @formats: Array of supported formats (DRM_FORMAT\_\*)
> + * @format_count: Number of elements in @formats
> + * @mode: Supported mode
> + * @rotation: Initial @mode rotation in degrees Counter Clock Wise
> + *
> + * This function sets up a &drm_simple_display_pipe with a &drm_connector that
> + * has one fixed &drm_display_mode which is rotated according to @rotation.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int
> +tinydrm_display_pipe_init(struct tinydrm_device *tdev,
> +			  const struct drm_simple_display_pipe_funcs *funcs,
> +			  int connector_type,
> +			  const uint32_t *formats,
> +			  unsigned int format_count,
> +			  const struct drm_display_mode *mode,
> +			  unsigned int rotation)
> +{
> +	struct drm_device *drm = tdev->drm;
> +	struct drm_display_mode *mode_copy;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL);
> +	if (!mode_copy)
> +		return -ENOMEM;
> +
> +	*mode_copy = *mode;
> +	ret = tinydrm_rotate_mode(mode_copy, rotation);
> +	if (ret) {
> +		DRM_ERROR("Illegal rotation value %u\n", rotation);
> +		return -EINVAL;
> +	}
> +
> +	drm->mode_config.min_width = mode_copy->hdisplay;
> +	drm->mode_config.max_width = mode_copy->hdisplay;
> +	drm->mode_config.min_height = mode_copy->vdisplay;
> +	drm->mode_config.max_height = mode_copy->vdisplay;
> +
> +	connector = tinydrm_connector_create(drm, mode_copy, connector_type);
> +	if (IS_ERR(connector))
> +		return PTR_ERR(connector);
> +
> +	ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
> +					   format_count, connector);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tinydrm_display_pipe_init);
> diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
> new file mode 100644
> index 0000000..cf9ca20
> --- /dev/null
> +++ b/include/drm/tinydrm/tinydrm.h
> @@ -0,0 +1,115 @@
> +/*
> + * 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/drm_gem_cma_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +/**
> + * struct tinydrm_device - tinydrm device
> + * @drm: DRM device
> + * @pipe: Display pipe structure
> + * @dirty_lock: Serializes framebuffer flushing
> + * @fbdev_cma: CMA fbdev structure
> + * @suspend_state: Atomic state when suspended
> + * @fb_funcs: Framebuffer functions used when creating framebuffers
> + */
> +struct tinydrm_device {
> +	struct drm_device *drm;
> +	struct drm_simple_display_pipe pipe;
> +	struct mutex dirty_lock;
> +	struct drm_fbdev_cma *fbdev_cma;
> +	struct drm_atomic_state *suspend_state;
> +	const struct drm_framebuffer_funcs *fb_funcs;
> +};
> +
> +static inline struct tinydrm_device *
> +pipe_to_tinydrm(struct drm_simple_display_pipe *pipe)
> +{
> +	return container_of(pipe, struct tinydrm_device, pipe);
> +}
> +
> +/**
> + * TINYDRM_GEM_DRIVER_OPS - default tinydrm gem operations
> + *
> + * This macro provides a shortcut for setting the tinydrm GEM operations in
> + * the &drm_driver structure.
> + */
> +#define TINYDRM_GEM_DRIVER_OPS \
> +	.gem_free_object	= tinydrm_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 = tinydrm_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
> +
> +/**
> + * TINYDRM_MODE - tinydrm display mode
> + * @hd: Horizontal resolution, width
> + * @vd: Vertical resolution, height
> + * @hd_mm: Display width in millimeters
> + * @vd_mm: Display height in millimeters
> + *
> + * This macro creates a &drm_display_mode for use with tinydrm.
> + */
> +#define TINYDRM_MODE(hd, vd, hd_mm, vd_mm) \
> +	.hdisplay = (hd), \
> +	.hsync_start = (hd), \
> +	.hsync_end = (hd), \
> +	.htotal = (hd), \
> +	.vdisplay = (vd), \
> +	.vsync_start = (vd), \
> +	.vsync_end = (vd), \
> +	.vtotal = (vd), \
> +	.width_mm = (hd_mm), \
> +	.height_mm = (vd_mm), \
> +	.type = DRM_MODE_TYPE_DRIVER, \
> +	.clock = 1 /* pass validation */
> +
> +extern const struct file_operations tinydrm_fops;
> +void tinydrm_lastclose(struct drm_device *drm);
> +void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj);
> +struct drm_gem_object *
> +tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
> +				      struct dma_buf_attachment *attach,
> +				      struct sg_table *sgt);
> +int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
> +		      const struct drm_framebuffer_funcs *fb_funcs,
> +		      struct drm_driver *driver);
> +int devm_tinydrm_register(struct tinydrm_device *tdev);
> +void tinydrm_shutdown(struct tinydrm_device *tdev);
> +int tinydrm_suspend(struct tinydrm_device *tdev);
> +int tinydrm_resume(struct tinydrm_device *tdev);
> +
> +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
> +				 struct drm_plane_state *old_state);
> +int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
> +				    struct drm_plane_state *plane_state);
> +int
> +tinydrm_display_pipe_init(struct tinydrm_device *tdev,
> +			  const struct drm_simple_display_pipe_funcs *funcs,
> +			  int connector_type,
> +			  const uint32_t *formats,
> +			  unsigned int format_count,
> +			  const struct drm_display_mode *mode,
> +			  unsigned int rotation);
> +
> +#endif /* __LINUX_TINYDRM_H */
> -- 
> 2.10.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Noralf Trønnes Jan. 24, 2017, 4:35 p.m. UTC | #2
Den 23.01.2017 10.28, skrev Daniel Vetter:
> On Sun, Jan 22, 2017 at 07:11:12PM +0100, Noralf Trønnes wrote:
>> tinydrm provides helpers for very simple displays that can use
>> CMA backed framebuffers and need flushing on changes.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Looks all pretty. A bunch of ideas below, but all optional. For merging I
> think simplest to first get the core patches in through drm-misc, and then
> you can submit a pull request to Dave for tinydrm+backends (just needs an
> ack for the dt parts from dt maintainers), including MAINTAINERS entry.
> Ack from my side.

So when the tinydrm core patches are in:
- tinydrm patches are posted to dri-devel.
- DT patches need ack.
- If there's no comments or resolved, I send a pull request to Dave.

How often and when do I send pull requests?
(I have seen *-next, *-fixes, * for 4.xx)

Do drivers _always_ need a MAINTANERS entry, or is it preferred,
nice to have, not so important, ...


>> +/**
>> + * tinydrm_display_pipe_update - Display pipe update helper
>> + * @pipe: Simple display pipe
>> + * @old_state: Old plane state
>> + *
>> + * This function does a full framebuffer flush if the plane framebuffer
>> + * has changed. It also handles vblank events. Drivers can use this as their
>> + * &drm_simple_display_pipe_funcs->update callback.
>> + */
>> +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
>> +				 struct drm_plane_state *old_state)
>> +{
>> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
>> +	struct drm_framebuffer *fb = pipe->plane.state->fb;
>> +	struct drm_crtc *crtc = &tdev->pipe.crtc;
>> +
>> +	if (!fb)
>> +		DRM_DEBUG_KMS("fb unset\n");
>> +	else if (!old_state->fb)
>> +		DRM_DEBUG_KMS("fb set\n");
>> +	else if (fb != old_state->fb)
>> +		DRM_DEBUG_KMS("fb changed\n");
>> +	else
>> +		DRM_DEBUG_KMS("No fb change\n");
>> +
>> +	if (fb && (fb != old_state->fb)) {
>> +		pipe->plane.fb = fb;
>> +		if (fb->funcs->dirty)
>> +			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
> I like the idea, but my cunning long-term plan is that we'd extend the
> atomic support to support a dirty rectangle. Together with the
> non-blocking stuff we could then implement fb->funcs->dirty in terms of
> atomic. But here you implement atomic in terms of ->dirty, and we'd end up
> with a loop.
>
> Personally I'd just drop this helper here and move this part into the
> backend modules ...
>
>> +	}
>> +
>> +	if (crtc->state->event) {
>> +		DRM_DEBUG_KMS("crtc event\n");
>> +		spin_lock_irq(&crtc->dev->event_lock);
>> +		drm_crtc_send_vblank_event(crtc, crtc->state->event);
>> +		spin_unlock_irq(&crtc->dev->event_lock);
>> +		crtc->state->event = NULL;
> ... because this here is kinda a hack, since it's not synchronized with
> the screen update. Otoh these tiny panels are kinda special.

Yeah, you're right it's only synchronized if the framebuffer changes.
So this won't catch events that's not page flip events.
afaict DRM_EVENT_VBLANK is the only other event. When is that used?

How about if I check for events as well so the fb is always flushed
if someone wants know?

void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
                  struct drm_plane_state *old_state)
{
     struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
     struct drm_framebuffer *fb = pipe->plane.state->fb;
     struct drm_crtc *crtc = &tdev->pipe.crtc;

     if (fb && (fb != old_state->fb || crtc->state->event)) {
         pipe->plane.fb = fb;
         if (fb->funcs->dirty)
             fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
     }

     if (crtc->state->event) {
         spin_lock_irq(&crtc->dev->event_lock);
         drm_crtc_send_vblank_event(crtc, crtc->state->event);
         spin_unlock_irq(&crtc->dev->event_lock);
         crtc->state->event = NULL;
     }
}


Or maybe I should send the event in the dirty() function instead?

void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
                  struct drm_plane_state *old_state)
{
     struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
     struct drm_framebuffer *fb = pipe->plane.state->fb;
     struct drm_crtc *crtc = &tdev->pipe.crtc;

     if (fb && (fb != old_state->fb || crtc->state->event)) {
         pipe->plane.fb = fb;
         if (fb->funcs->dirty)
             fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
     }
}

void tinydrm_send_pending_event(struct tinydrm_device *tdev)
{
     struct drm_crtc *crtc = &tdev->pipe.crtc;

     if (!crtc->state->event)
         return;

     spin_lock_irq(&crtc->dev->event_lock);
     drm_crtc_send_vblank_event(crtc, crtc->state->event);
     spin_unlock_irq(&crtc->dev->event_lock);
     crtc->state->event = NULL;
}

static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
                  struct drm_file *file_priv,
                  unsigned int flags, unsigned int color,
                  struct drm_clip_rect *clips,
                  unsigned int num_clips)
{
     <snip>
     mutex_lock(&tdev->dirty_lock);

     <flush>

     tinydrm_send_pending_event(tdev);

     mutex_unlock(&tdev->dirty_lock);

     return ret;
}


Noralf.
Jani Nikula Jan. 24, 2017, 5:48 p.m. UTC | #3
On Tue, 24 Jan 2017, Noralf Trønnes <noralf@tronnes.org> wrote:
> Den 23.01.2017 10.28, skrev Daniel Vetter:
>> On Sun, Jan 22, 2017 at 07:11:12PM +0100, Noralf Trønnes wrote:
>>> tinydrm provides helpers for very simple displays that can use
>>> CMA backed framebuffers and need flushing on changes.
>>>
>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> Looks all pretty. A bunch of ideas below, but all optional. For merging I
>> think simplest to first get the core patches in through drm-misc, and then
>> you can submit a pull request to Dave for tinydrm+backends (just needs an
>> ack for the dt parts from dt maintainers), including MAINTAINERS entry.
>> Ack from my side.
>
> So when the tinydrm core patches are in:
> - tinydrm patches are posted to dri-devel.
> - DT patches need ack.
> - If there's no comments or resolved, I send a pull request to Dave.
>
> How often and when do I send pull requests?
> (I have seen *-next, *-fixes, * for 4.xx)

For DRM the cutoff for new features in the next merge window is around
-rc5 of the previous development kernels, i.e. the deadline for v4.11
merge window is around v4.10-rc5, pretty much now.

Pick up and send fixes pulls for current -rc kernels as needed. My flow
for i915 is to try to pick up stuff early in the week, let the stuff
simmer and go through our CI, and send the pull request to Dave by
Thursday.

> Do drivers _always_ need a MAINTANERS entry, or is it preferred,
> nice to have, not so important, ...

If you want the patches to flow through your tree, you need
it. Otherwise it'll fall under drm and drm-misc.


BR,
Jani.


>
>
>>> +/**
>>> + * tinydrm_display_pipe_update - Display pipe update helper
>>> + * @pipe: Simple display pipe
>>> + * @old_state: Old plane state
>>> + *
>>> + * This function does a full framebuffer flush if the plane framebuffer
>>> + * has changed. It also handles vblank events. Drivers can use this as their
>>> + * &drm_simple_display_pipe_funcs->update callback.
>>> + */
>>> +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
>>> +				 struct drm_plane_state *old_state)
>>> +{
>>> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
>>> +	struct drm_framebuffer *fb = pipe->plane.state->fb;
>>> +	struct drm_crtc *crtc = &tdev->pipe.crtc;
>>> +
>>> +	if (!fb)
>>> +		DRM_DEBUG_KMS("fb unset\n");
>>> +	else if (!old_state->fb)
>>> +		DRM_DEBUG_KMS("fb set\n");
>>> +	else if (fb != old_state->fb)
>>> +		DRM_DEBUG_KMS("fb changed\n");
>>> +	else
>>> +		DRM_DEBUG_KMS("No fb change\n");
>>> +
>>> +	if (fb && (fb != old_state->fb)) {
>>> +		pipe->plane.fb = fb;
>>> +		if (fb->funcs->dirty)
>>> +			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
>> I like the idea, but my cunning long-term plan is that we'd extend the
>> atomic support to support a dirty rectangle. Together with the
>> non-blocking stuff we could then implement fb->funcs->dirty in terms of
>> atomic. But here you implement atomic in terms of ->dirty, and we'd end up
>> with a loop.
>>
>> Personally I'd just drop this helper here and move this part into the
>> backend modules ...
>>
>>> +	}
>>> +
>>> +	if (crtc->state->event) {
>>> +		DRM_DEBUG_KMS("crtc event\n");
>>> +		spin_lock_irq(&crtc->dev->event_lock);
>>> +		drm_crtc_send_vblank_event(crtc, crtc->state->event);
>>> +		spin_unlock_irq(&crtc->dev->event_lock);
>>> +		crtc->state->event = NULL;
>> ... because this here is kinda a hack, since it's not synchronized with
>> the screen update. Otoh these tiny panels are kinda special.
>
> Yeah, you're right it's only synchronized if the framebuffer changes.
> So this won't catch events that's not page flip events.
> afaict DRM_EVENT_VBLANK is the only other event. When is that used?
>
> How about if I check for events as well so the fb is always flushed
> if someone wants know?
>
> void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
>                   struct drm_plane_state *old_state)
> {
>      struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
>      struct drm_framebuffer *fb = pipe->plane.state->fb;
>      struct drm_crtc *crtc = &tdev->pipe.crtc;
>
>      if (fb && (fb != old_state->fb || crtc->state->event)) {
>          pipe->plane.fb = fb;
>          if (fb->funcs->dirty)
>              fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
>      }
>
>      if (crtc->state->event) {
>          spin_lock_irq(&crtc->dev->event_lock);
>          drm_crtc_send_vblank_event(crtc, crtc->state->event);
>          spin_unlock_irq(&crtc->dev->event_lock);
>          crtc->state->event = NULL;
>      }
> }
>
>
> Or maybe I should send the event in the dirty() function instead?
>
> void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
>                   struct drm_plane_state *old_state)
> {
>      struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
>      struct drm_framebuffer *fb = pipe->plane.state->fb;
>      struct drm_crtc *crtc = &tdev->pipe.crtc;
>
>      if (fb && (fb != old_state->fb || crtc->state->event)) {
>          pipe->plane.fb = fb;
>          if (fb->funcs->dirty)
>              fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
>      }
> }
>
> void tinydrm_send_pending_event(struct tinydrm_device *tdev)
> {
>      struct drm_crtc *crtc = &tdev->pipe.crtc;
>
>      if (!crtc->state->event)
>          return;
>
>      spin_lock_irq(&crtc->dev->event_lock);
>      drm_crtc_send_vblank_event(crtc, crtc->state->event);
>      spin_unlock_irq(&crtc->dev->event_lock);
>      crtc->state->event = NULL;
> }
>
> static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
>                   struct drm_file *file_priv,
>                   unsigned int flags, unsigned int color,
>                   struct drm_clip_rect *clips,
>                   unsigned int num_clips)
> {
>      <snip>
>      mutex_lock(&tdev->dirty_lock);
>
>      <flush>
>
>      tinydrm_send_pending_event(tdev);
>
>      mutex_unlock(&tdev->dirty_lock);
>
>      return ret;
> }
>
>
> Noralf.
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
diff mbox

Patch

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 03040aa..a86bd7f 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -272,3 +272,18 @@  Auxiliary Modeset Helpers
 
 .. kernel-doc:: drivers/gpu/drm/drm_modeset_helper.c
    :export:
+
+tinydrm Helper Reference
+========================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :doc: overview
+
+.. kernel-doc:: include/drm/tinydrm/tinydrm.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :export:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
+   :export:
diff --git a/MAINTAINERS b/MAINTAINERS
index 741f35f..817e0fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4365,6 +4365,13 @@  DRM DRIVER FOR TDFX VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/tdfx/
 
+DRM DRIVERS FOR TINY DISPLAYS
+M:	Noralf Trønnes <noralf@tronnes.org>
+W:	https://github.com/notro/tinydrm/wiki
+S:	Maintained
+F:	drivers/gpu/drm/tinydrm/
+F:	include/drm/tinydrm/
+
 DRM DRIVER FOR USB DISPLAYLINK VIDEO ADAPTERS
 M:	Dave Airlie <airlied@redhat.com>
 S:	Odd Fixes
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 90bc65d..88e01e08e 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -263,6 +263,8 @@  source "drivers/gpu/drm/mxsfb/Kconfig"
 
 source "drivers/gpu/drm/meson/Kconfig"
 
+source "drivers/gpu/drm/tinydrm/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 92de399..3ee9579 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -94,3 +94,4 @@  obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
 obj-$(CONFIG_DRM_ZTE)	+= zte/
 obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
+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..ffb873f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -0,0 +1,8 @@ 
+menuconfig DRM_TINYDRM
+	tristate "Support for simple displays"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	help
+	  Choose this option if you have a tinydrm supported display.
+	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
new file mode 100644
index 0000000..7476ed1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_DRM_TINYDRM)		+= core/
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
new file mode 100644
index 0000000..4f14a0f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,3 @@ 
+tinydrm-y := tinydrm-core.o tinydrm-pipe.o
+
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
new file mode 100644
index 0000000..492b478
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,374 @@ 
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/device.h>
+#include <linux/dma-buf.h>
+
+/**
+ * DOC: overview
+ *
+ * This library provides driver helpers for very simple display hardware.
+ *
+ * It is based on &drm_simple_display_pipe coupled with a &drm_connector which
+ * has only one fixed &drm_display_mode. The framebuffers are backed by the
+ * cma helper and have support for framebuffer flushing (dirty).
+ * fbdev support is also included.
+ *
+ * The driver allocates &tinydrm_device, initializes it using
+ * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
+ * and registers the DRM device using devm_tinydrm_register().
+ */
+
+/**
+ * tinydrm_lastclose - DRM lastclose helper
+ * @drm: DRM device
+ *
+ * This function ensures that fbdev is restored when drm_lastclose() is called
+ * on the last drm_release(). Drivers can use this as their
+ * &drm_driver->lastclose callback.
+ */
+void tinydrm_lastclose(struct drm_device *drm)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
+}
+EXPORT_SYMBOL(tinydrm_lastclose);
+
+/**
+ * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from
+ *     another driver's scatter/gather table of pinned pages
+ * @drm: DRM device to import into
+ * @attach: DMA-BUF attachment
+ * @sgt: Scatter/gather table of pinned pages
+ *
+ * This function imports a scatter/gather table exported via DMA-BUF by
+ * another driver using drm_gem_cma_prime_import_sg_table(). It sets the
+ * kernel virtual address on the CMA object. Drivers should use this as their
+ * &drm_driver->gem_prime_import_sg_table callback if they need the virtual
+ * address. tinydrm_gem_cma_free_object() should be used in combination with
+ * this function.
+ *
+ * Returns:
+ * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
+ * error code on failure.
+ */
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt)
+{
+	struct drm_gem_cma_object *cma_obj;
+	struct drm_gem_object *obj;
+	void *vaddr;
+
+	vaddr = dma_buf_vmap(attach->dmabuf);
+	if (!vaddr) {
+		DRM_ERROR("Failed to vmap PRIME buffer\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
+	if (IS_ERR(obj)) {
+		dma_buf_vunmap(attach->dmabuf, vaddr);
+		return obj;
+	}
+
+	cma_obj = to_drm_gem_cma_obj(obj);
+	cma_obj->vaddr = vaddr;
+
+	return obj;
+}
+EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table);
+
+/**
+ * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
+ *                               object
+ * @gem_obj: GEM object to free
+ *
+ * This function frees the backing memory of the CMA GEM object, cleans up the
+ * GEM object state and frees the memory used to store the object itself using
+ * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel
+ * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers
+ * can use this as their &drm_driver->gem_free_object callback.
+ */
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+{
+	if (gem_obj->import_attach) {
+		struct drm_gem_cma_object *cma_obj;
+
+		cma_obj = to_drm_gem_cma_obj(gem_obj);
+		dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
+		cma_obj->vaddr = NULL;
+	}
+
+	drm_gem_cma_free_object(gem_obj);
+}
+EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object);
+
+const struct file_operations tinydrm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+EXPORT_SYMBOL(tinydrm_fops);
+
+static struct drm_framebuffer *
+tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+		  const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	return drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd,
+					    tdev->fb_funcs);
+}
+
+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,
+};
+
+static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+			const struct drm_framebuffer_funcs *fb_funcs,
+			struct drm_driver *driver)
+{
+	struct drm_device *drm;
+
+	mutex_init(&tdev->dirty_lock);
+	tdev->fb_funcs = fb_funcs;
+
+	/*
+	 * We don't embed drm_device, because that prevent us from using
+	 * devm_kzalloc() to allocate tinydrm_device in the driver since
+	 * drm_dev_unref() frees the structure. The devm_ functions provide
+	 * for easy error handling.
+	 */
+	drm = drm_dev_alloc(driver, parent);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+
+	tdev->drm = drm;
+	drm->dev_private = tdev;
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &tinydrm_mode_config_funcs;
+
+	return 0;
+}
+
+static void tinydrm_fini(struct tinydrm_device *tdev)
+{
+	DRM_DEBUG_KMS("\n");
+
+	drm_mode_config_cleanup(tdev->drm);
+	mutex_destroy(&tdev->dirty_lock);
+	tdev->drm->dev_private = NULL;
+	drm_dev_unref(tdev->drm);
+}
+
+static void devm_tinydrm_release(void *data)
+{
+	tinydrm_fini(data);
+}
+
+/**
+ * devm_tinydrm_init - Initialize tinydrm device
+ * @parent: Parent device object
+ * @tdev: tinydrm device
+ * @fb_funcs: Framebuffer functions
+ * @driver: DRM driver
+ *
+ * This function initializes @tdev, the underlying DRM device and it's
+ * mode_config. Resources will be automatically freed on driver detach (devres)
+ * using drm_mode_config_cleanup() and drm_dev_unref().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver)
+{
+	int ret;
+
+	ret = tinydrm_init(parent, tdev, fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(parent, devm_tinydrm_release, tdev);
+	if (ret)
+		tinydrm_fini(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_init);
+
+static int tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct drm_device *drm = tdev->drm;
+	int bpp = drm->mode_config.preferred_depth;
+	struct drm_fbdev_cma *fbdev;
+	int ret;
+
+	ret = drm_dev_register(tdev->drm, 0);
+	if (ret)
+		return ret;
+
+	fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
+					      drm->mode_config.num_crtc,
+					      drm->mode_config.num_connector,
+					      tdev->fb_funcs);
+	if (IS_ERR(fbdev))
+		DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
+	else
+		tdev->fbdev_cma = fbdev;
+
+	return 0;
+}
+
+static void tinydrm_unregister(struct tinydrm_device *tdev)
+{
+	DRM_DEBUG_KMS("\n");
+
+	drm_crtc_force_disable_all(tdev->drm);
+
+	if (tdev->fbdev_cma)
+		drm_fbdev_cma_fini(tdev->fbdev_cma);
+
+	drm_dev_unregister(tdev->drm);
+}
+
+static void devm_tinydrm_register_release(void *data)
+{
+	tinydrm_unregister(data);
+}
+
+/**
+ * devm_tinydrm_register - Register tinydrm device
+ * @tdev: tinydrm device
+ *
+ * This function registers the underlying DRM device and fbdev.
+ * These resources will be automatically unregistered on driver detach (devres)
+ * and the display pipeline will be disabled.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct device *dev = tdev->drm->dev;
+	int ret;
+
+	ret = tinydrm_register(tdev);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
+	if (ret)
+		tinydrm_unregister(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_register);
+
+/**
+ * tinydrm_shutdown - Shutdown tinydrm
+ * @tdev: tinydrm device
+ *
+ * This function makes sure that the display pipeline is disabled.
+ * Used by drivers in their shutdown callback to turn off the display
+ * on machine shutdown and reboot.
+ */
+void tinydrm_shutdown(struct tinydrm_device *tdev)
+{
+	drm_crtc_force_disable_all(tdev->drm);
+}
+EXPORT_SYMBOL(tinydrm_shutdown);
+
+/**
+ * tinydrm_suspend - Suspend tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to suspend tinydrm.
+ * Suspends fbdev and DRM.
+ * Resume with tinydrm_resume().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_suspend(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state;
+
+	if (tdev->suspend_state) {
+		DRM_ERROR("Failed to suspend: state already set\n");
+		return -EINVAL;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1);
+	state = drm_atomic_helper_suspend(tdev->drm);
+	if (IS_ERR(state)) {
+		drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+		return PTR_ERR(state);
+	}
+
+	tdev->suspend_state = state;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_suspend);
+
+/**
+ * tinydrm_resume - Resume tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to resume tinydrm.
+ * Suspend with tinydrm_suspend().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_resume(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state = tdev->suspend_state;
+	int ret;
+
+	if (!state) {
+		DRM_ERROR("Failed to resume: state is not set\n");
+		return -EINVAL;
+	}
+
+	tdev->suspend_state = NULL;
+
+	ret = drm_atomic_helper_resume(tdev->drm, state);
+	if (ret) {
+		DRM_ERROR("Error resuming state: %d\n", ret);
+		return ret;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_resume);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
new file mode 100644
index 0000000..4327b04
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
@@ -0,0 +1,244 @@ 
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.h>
+#include <drm/tinydrm/tinydrm.h>
+
+struct tinydrm_connector {
+	struct drm_connector base;
+	const struct drm_display_mode *mode;
+};
+
+static inline struct tinydrm_connector *
+to_tinydrm_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct tinydrm_connector, base);
+}
+
+static int tinydrm_connector_get_modes(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, tconn->mode);
+	if (!mode) {
+		DRM_ERROR("Failed to duplicate mode\n");
+		return 0;
+	}
+
+	if (mode->name[0] == '\0')
+		drm_mode_set_name(mode);
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	if (mode->width_mm) {
+		connector->display_info.width_mm = mode->width_mm;
+		connector->display_info.height_mm = mode->height_mm;
+	}
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = {
+	.get_modes = tinydrm_connector_get_modes,
+	.best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status
+tinydrm_connector_detect(struct drm_connector *connector, bool force)
+{
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static void tinydrm_connector_destroy(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+
+	drm_connector_cleanup(connector);
+	kfree(tconn);
+}
+
+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,
+};
+
+struct drm_connector *
+tinydrm_connector_create(struct drm_device *drm,
+			 const struct drm_display_mode *mode,
+			 int connector_type)
+{
+	struct tinydrm_connector *tconn;
+	struct drm_connector *connector;
+	int ret;
+
+	tconn = kzalloc(sizeof(*tconn), GFP_KERNEL);
+	if (!tconn)
+		return ERR_PTR(-ENOMEM);
+
+	tconn->mode = mode;
+	connector = &tconn->base;
+
+	drm_connector_helper_add(connector, &tinydrm_connector_hfuncs);
+	ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs,
+				 connector_type);
+	if (ret) {
+		kfree(tconn);
+		return ERR_PTR(ret);
+	}
+
+	connector->status = connector_status_connected;
+
+	return connector;
+}
+
+/**
+ * tinydrm_display_pipe_update - Display pipe update helper
+ * @pipe: Simple display pipe
+ * @old_state: Old plane state
+ *
+ * This function does a full framebuffer flush if the plane framebuffer
+ * has changed. It also handles vblank events. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->update callback.
+ */
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct drm_crtc *crtc = &tdev->pipe.crtc;
+
+	if (!fb)
+		DRM_DEBUG_KMS("fb unset\n");
+	else if (!old_state->fb)
+		DRM_DEBUG_KMS("fb set\n");
+	else if (fb != old_state->fb)
+		DRM_DEBUG_KMS("fb changed\n");
+	else
+		DRM_DEBUG_KMS("No fb change\n");
+
+	if (fb && (fb != old_state->fb)) {
+		pipe->plane.fb = fb;
+		if (fb->funcs->dirty)
+			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+	}
+
+	if (crtc->state->event) {
+		DRM_DEBUG_KMS("crtc event\n");
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_update);
+
+/**
+ * tinydrm_display_pipe_prepare_fb - Display pipe prepare_fb helper
+ * @pipe: Simple display pipe
+ * @plane_state: Plane state
+ *
+ * This function uses drm_fb_cma_prepare_fb() to check if the plane FB has an
+ * dma-buf attached, extracts the exclusive fence and attaches it to plane
+ * state for the atomic helper to wait on. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->prepare_fb callback.
+ */
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state)
+{
+	return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_prepare_fb);
+
+static int tinydrm_rotate_mode(struct drm_display_mode *mode,
+			       unsigned int rotation)
+{
+	if (rotation == 0 || rotation == 180) {
+		return 0;
+	} else if (rotation == 90 || rotation == 270) {
+		swap(mode->hdisplay, mode->vdisplay);
+		swap(mode->hsync_start, mode->vsync_start);
+		swap(mode->hsync_end, mode->vsync_end);
+		swap(mode->htotal, mode->vtotal);
+		swap(mode->width_mm, mode->height_mm);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+
+/**
+ * tinydrm_display_pipe_init - Initialize display pipe
+ * @tdev: tinydrm device
+ * @funcs: Display pipe functions
+ * @connector_type: Connector type
+ * @formats: Array of supported formats (DRM_FORMAT\_\*)
+ * @format_count: Number of elements in @formats
+ * @mode: Supported mode
+ * @rotation: Initial @mode rotation in degrees Counter Clock Wise
+ *
+ * This function sets up a &drm_simple_display_pipe with a &drm_connector that
+ * has one fixed &drm_display_mode which is rotated according to @rotation.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation)
+{
+	struct drm_device *drm = tdev->drm;
+	struct drm_display_mode *mode_copy;
+	struct drm_connector *connector;
+	int ret;
+
+	mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL);
+	if (!mode_copy)
+		return -ENOMEM;
+
+	*mode_copy = *mode;
+	ret = tinydrm_rotate_mode(mode_copy, rotation);
+	if (ret) {
+		DRM_ERROR("Illegal rotation value %u\n", rotation);
+		return -EINVAL;
+	}
+
+	drm->mode_config.min_width = mode_copy->hdisplay;
+	drm->mode_config.max_width = mode_copy->hdisplay;
+	drm->mode_config.min_height = mode_copy->vdisplay;
+	drm->mode_config.max_height = mode_copy->vdisplay;
+
+	connector = tinydrm_connector_create(drm, mode_copy, connector_type);
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
+
+	ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
+					   format_count, connector);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_init);
diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..cf9ca20
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,115 @@ 
+/*
+ * 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/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+/**
+ * struct tinydrm_device - tinydrm device
+ * @drm: DRM device
+ * @pipe: Display pipe structure
+ * @dirty_lock: Serializes framebuffer flushing
+ * @fbdev_cma: CMA fbdev structure
+ * @suspend_state: Atomic state when suspended
+ * @fb_funcs: Framebuffer functions used when creating framebuffers
+ */
+struct tinydrm_device {
+	struct drm_device *drm;
+	struct drm_simple_display_pipe pipe;
+	struct mutex dirty_lock;
+	struct drm_fbdev_cma *fbdev_cma;
+	struct drm_atomic_state *suspend_state;
+	const struct drm_framebuffer_funcs *fb_funcs;
+};
+
+static inline struct tinydrm_device *
+pipe_to_tinydrm(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct tinydrm_device, pipe);
+}
+
+/**
+ * TINYDRM_GEM_DRIVER_OPS - default tinydrm gem operations
+ *
+ * This macro provides a shortcut for setting the tinydrm GEM operations in
+ * the &drm_driver structure.
+ */
+#define TINYDRM_GEM_DRIVER_OPS \
+	.gem_free_object	= tinydrm_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 = tinydrm_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
+
+/**
+ * TINYDRM_MODE - tinydrm display mode
+ * @hd: Horizontal resolution, width
+ * @vd: Vertical resolution, height
+ * @hd_mm: Display width in millimeters
+ * @vd_mm: Display height in millimeters
+ *
+ * This macro creates a &drm_display_mode for use with tinydrm.
+ */
+#define TINYDRM_MODE(hd, vd, hd_mm, vd_mm) \
+	.hdisplay = (hd), \
+	.hsync_start = (hd), \
+	.hsync_end = (hd), \
+	.htotal = (hd), \
+	.vdisplay = (vd), \
+	.vsync_start = (vd), \
+	.vsync_end = (vd), \
+	.vtotal = (vd), \
+	.width_mm = (hd_mm), \
+	.height_mm = (vd_mm), \
+	.type = DRM_MODE_TYPE_DRIVER, \
+	.clock = 1 /* pass validation */
+
+extern const struct file_operations tinydrm_fops;
+void tinydrm_lastclose(struct drm_device *drm);
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj);
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt);
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver);
+int devm_tinydrm_register(struct tinydrm_device *tdev);
+void tinydrm_shutdown(struct tinydrm_device *tdev);
+int tinydrm_suspend(struct tinydrm_device *tdev);
+int tinydrm_resume(struct tinydrm_device *tdev);
+
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state);
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state);
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation);
+
+#endif /* __LINUX_TINYDRM_H */