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