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

David Herrmann dh.herrmann at gmail.com
Wed Mar 23 17:37:52 UTC 2016


Hey

On Wed, Mar 16, 2016 at 2:34 PM, Noralf Trønnes <noralf at 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 at tronnes.org>
> ---
>  drivers/gpu/drm/Kconfig                            |   2 +
>  drivers/gpu/drm/Makefile                           |   1 +
>  drivers/gpu/drm/tinydrm/Kconfig                    |  11 +
>  drivers/gpu/drm/tinydrm/Makefile                   |   1 +
>  drivers/gpu/drm/tinydrm/core/Makefile              |   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 at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel


More information about the dri-devel mailing list