On 01/10/2014 16:53, Boris Brezillon :
> From: Boris BREZILLON <boris.brezillon at free-electrons.com>
> 
> The Atmel HLCDC (HLCD Controller) IP available on some Atmel SoCs (i.e.
> at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
> controller device.
> 
> This display controller supports at least one primary plane and might
> provide several overlays and an hardware cursor depending on the IP
> version.
> 
> At the moment, this driver only implements an RGB connector to interface
> with LCD panels, but support for other kind of external devices might be
> added later.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon at free-electrons.com>
> Reviewed-by: Rob Clark <robdclark at gmail.com>
> Tested-by: Anthony Harivel <anthony.harivel at emtrion.de>
> Tested-by: Ludovic Desroches <ludovic.desroches at atmel.com>
> ---
>  drivers/gpu/drm/Kconfig                          |   2 +
>  drivers/gpu/drm/Makefile                         |   1 +
>  drivers/gpu/drm/atmel-hlcdc/Kconfig              |  13 +
>  drivers/gpu/drm/atmel-hlcdc/Makefile             |   7 +
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c   | 390 +++++++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c     | 531 +++++++++++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h     | 216 ++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c  | 638 +++++++++++++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h  | 394 +++++++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 443 ++++++++++++
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c  | 831 
> +++++++++++++++++++++++
>  11 files changed, 3466 insertions(+)
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/Makefile
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
>  create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index b066bb3..2d97f7e 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -185,6 +185,8 @@ source "drivers/gpu/drm/cirrus/Kconfig"
>  
>  source "drivers/gpu/drm/armada/Kconfig"
>  
> +source "drivers/gpu/drm/atmel-hlcdc/Kconfig"
> +
>  source "drivers/gpu/drm/rcar-du/Kconfig"
>  
>  source "drivers/gpu/drm/shmobile/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 4a55d59..abb4f29 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_DRM_GMA500) += gma500/
>  obj-$(CONFIG_DRM_UDL) += udl/
>  obj-$(CONFIG_DRM_AST) += ast/
>  obj-$(CONFIG_DRM_ARMADA) += armada/
> +obj-$(CONFIG_DRM_ATMEL_HLCDC)        += atmel-hlcdc/
>  obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
>  obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
>  obj-$(CONFIG_DRM_OMAP)       += omapdrm/
> diff --git a/drivers/gpu/drm/atmel-hlcdc/Kconfig 
> b/drivers/gpu/drm/atmel-hlcdc/Kconfig
> new file mode 100644
> index 0000000..942407f
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/Kconfig
> @@ -0,0 +1,13 @@
> +config DRM_ATMEL_HLCDC
> +     tristate "DRM Support for ATMEL HLCDC Display Controller"
> +     depends on DRM && OF && COMMON_CLK
> +     select DRM_GEM_CMA_HELPER
> +     select DRM_KMS_HELPER
> +     select DRM_KMS_FB_HELPER
> +     select DRM_KMS_CMA_HELPER
> +     select DRM_PANEL
> +     select MFD_ATMEL_HLCDC
> +     depends on OF
> +     help
> +       Choose this option if you have an ATMEL SoC with an HLCDC display
> +       controller (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family).
> diff --git a/drivers/gpu/drm/atmel-hlcdc/Makefile 
> b/drivers/gpu/drm/atmel-hlcdc/Makefile
> new file mode 100644
> index 0000000..10ae426
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/Makefile
> @@ -0,0 +1,7 @@
> +atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \
> +             atmel_hlcdc_dc.o \
> +             atmel_hlcdc_layer.o \
> +             atmel_hlcdc_output.o \
> +             atmel_hlcdc_plane.o
> +
> +obj-$(CONFIG_DRM_ATMEL_HLCDC)        += atmel-hlcdc-dc.o
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
> new file mode 100644
> index 0000000..02f7a98
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
> @@ -0,0 +1,390 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + *
> + * Author: Jean-Jacques Hiblot <jjhiblot at traphandler.com>
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drmP.h>
> +
> +#include <video/videomode.h>
> +
> +#include "atmel_hlcdc_dc.h"
> +
> +/**
> + * Atmel HLCDC CRTC structure
> + *
> + * @base: base DRM CRTC structure
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
isn't it dc here ^^^


> + * @event: pointer to the current page flip event
> + * @id: CRTC id (returned by drm_crtc_index)
> + * @dpms: DPMS mode
> + */
> +struct atmel_hlcdc_crtc {
> +     struct drm_crtc base;
> +     struct atmel_hlcdc_dc *dc;
> +     struct drm_pending_vblank_event *event;
> +     int id;
> +     int dpms;
> +};
> +
> +static inline struct atmel_hlcdc_crtc *
> +drm_crtc_to_atmel_hlcdc_crtc(struct drm_crtc *crtc)
> +{
> +     return container_of(crtc, struct atmel_hlcdc_crtc, base);
> +}
> +
> +static void atmel_hlcdc_crtc_dpms(struct drm_crtc *c, int mode)
> +{
> +     struct drm_device *dev = c->dev;
> +     struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> +     struct regmap *regmap = crtc->dc->hlcdc->regmap;
> +     unsigned int status;
> +
> +     if (mode != DRM_MODE_DPMS_ON)
> +             mode = DRM_MODE_DPMS_OFF;
> +
> +     if (crtc->dpms == mode)
> +             return;
> +
> +     pm_runtime_get_sync(dev->dev);
> +
> +     if (mode != DRM_MODE_DPMS_ON) {
> +             regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_DISP);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    (status & ATMEL_HLCDC_DISP))
> +                     cpu_relax();
> +
> +             regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_SYNC);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    (status & ATMEL_HLCDC_SYNC))
> +                     cpu_relax();
> +
> +             regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PIXEL_CLK);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    (status & ATMEL_HLCDC_PIXEL_CLK))
> +                     cpu_relax();
> +
> +             clk_disable_unprepare(crtc->dc->hlcdc->sys_clk);
> +
> +             pm_runtime_allow(dev->dev);
> +     } else {
> +             pm_runtime_forbid(dev->dev);
> +
> +             clk_prepare_enable(crtc->dc->hlcdc->sys_clk);
> +
> +             regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PIXEL_CLK);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    !(status & ATMEL_HLCDC_PIXEL_CLK))
> +                     cpu_relax();
> +
> +
> +             regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_SYNC);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    !(status & ATMEL_HLCDC_SYNC))
> +                     cpu_relax();
> +
> +             regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_DISP);
> +             while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) &&
> +                    !(status & ATMEL_HLCDC_DISP))
> +                     cpu_relax();
> +     }
> +
> +     pm_runtime_put_sync(dev->dev);
> +
> +     crtc->dpms = mode;
> +}
> +
> +static int atmel_hlcdc_crtc_mode_set(struct drm_crtc *c,
> +                                  struct drm_display_mode *mode,
> +                                  struct drm_display_mode *adj,
> +                                  int x, int y,
> +                                  struct drm_framebuffer *old_fb)
> +{
> +     struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> +     struct regmap *regmap = crtc->dc->hlcdc->regmap;
> +     struct drm_plane *plane = c->primary;
> +     struct drm_framebuffer *fb;
> +     unsigned long mode_rate;
> +     struct videomode vm;
> +     unsigned long prate;
> +     unsigned int cfg;
> +     int div;
> +
> +     if (atmel_hlcdc_dc_mode_valid(crtc->dc, adj) != MODE_OK)
> +             return -EINVAL;
> +
> +     vm.vfront_porch = adj->vsync_start - adj->vdisplay;
> +     vm.vback_porch = adj->vtotal - adj->vsync_end;
> +     vm.vsync_len = adj->vsync_end - adj->vsync_start;
> +     vm.hfront_porch = adj->hsync_start - adj->hdisplay;
> +     vm.hback_porch = adj->htotal - adj->hsync_end;
> +     vm.hsync_len = adj->hsync_end - adj->hsync_start;
> +
> +     regmap_write(regmap, ATMEL_HLCDC_CFG(1),
> +                  (vm.hsync_len - 1) | ((vm.vsync_len - 1) << 16));
> +
> +     regmap_write(regmap, ATMEL_HLCDC_CFG(2),
> +                  (vm.vfront_porch - 1) | (vm.vback_porch << 16));
> +
> +     regmap_write(regmap, ATMEL_HLCDC_CFG(3),
> +                  (vm.hfront_porch - 1) | ((vm.hback_porch - 1) << 16));
> +
> +     regmap_write(regmap, ATMEL_HLCDC_CFG(4),
> +                  (adj->hdisplay - 1) | ((adj->vdisplay - 1) << 16));
> +
> +     cfg = ATMEL_HLCDC_CLKPOL;
> +
> +     prate = clk_get_rate(crtc->dc->hlcdc->sys_clk);
> +     mode_rate = mode->clock * 1000;
> +     if ((prate / 2) < mode_rate) {
> +             prate *= 2;
> +             cfg |= ATMEL_HLCDC_CLKSEL;
> +     }
> +
> +     div = DIV_ROUND_UP(prate, mode_rate);
> +     if (div < 2)
> +             div = 2;
> +
> +     cfg |= ATMEL_HLCDC_CLKDIV(div);
> +
> +     regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0),
> +                        ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK |
> +                        ATMEL_HLCDC_CLKPOL, cfg);
> +
> +     cfg = 0;
> +
> +     if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +             cfg |= ATMEL_HLCDC_VSPOL;
> +
> +     if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +             cfg |= ATMEL_HLCDC_HSPOL;
> +
> +     regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5),
> +                        ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL |
> +                        ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE |
> +                        ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY |
> +                        ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO |
> +                        ATMEL_HLCDC_GUARDTIME_MASK,
> +                        cfg);
> +
> +     fb = plane->fb;
> +     plane->fb = old_fb;
> +
> +     return plane->funcs->update_plane(plane, c, fb,
> +                                       0, 0,
> +                                       adj->hdisplay, adj->vdisplay,
> +                                       x << 16, y << 16,
> +                                       adj->hdisplay << 16,
> +                                       adj->vdisplay << 16);
> +}
> +
> +int atmel_hlcdc_crtc_mode_set_base(struct drm_crtc *c, int x, int y,
> +                                struct drm_framebuffer *old_fb)
> +{
> +     struct drm_plane *plane = c->primary;
> +     struct drm_framebuffer *fb = plane->fb;
> +     struct drm_display_mode *mode = &c->hwmode;
> +
> +     plane->fb = old_fb;
> +
> +     return plane->funcs->update_plane(plane, c, fb,
> +                                       0, 0,
> +                                       mode->hdisplay, mode->vdisplay,
> +                                       x << 16, y << 16,
> +                                       mode->hdisplay << 16,
> +                                       mode->vdisplay << 16);
> +}
> +
> +static void atmel_hlcdc_crtc_prepare(struct drm_crtc *crtc)
> +{
> +     atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void atmel_hlcdc_crtc_commit(struct drm_crtc *crtc)
> +{
> +     atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +}
> +
> +static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *crtc,
> +                                     const struct drm_display_mode *mode,
> +                                     struct drm_display_mode *adjusted_mode)
> +{
> +     return true;
> +}
> +
> +
> +static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = {
> +
> +     .mode_fixup = atmel_hlcdc_crtc_mode_fixup,
> +     .dpms = atmel_hlcdc_crtc_dpms,
> +     .mode_set = atmel_hlcdc_crtc_mode_set,
> +     .mode_set_base = atmel_hlcdc_crtc_mode_set_base,
> +     .prepare = atmel_hlcdc_crtc_prepare,
> +     .commit = atmel_hlcdc_crtc_commit,
> +};
> +
> +static void atmel_hlcdc_crtc_destroy(struct drm_crtc *c)
> +{
> +     struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> +
> +     drm_crtc_cleanup(c);
> +     kfree(crtc);
> +}
> +
> +void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *c,
> +                                    struct drm_file *file)
> +{
> +     struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> +     struct drm_pending_vblank_event *event;
> +     struct drm_device *dev = c->dev;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&dev->event_lock, flags);
> +     event = crtc->event;
> +     if (event && event->base.file_priv == file) {
> +             event->base.destroy(&event->base);
> +             drm_vblank_put(dev, crtc->id);
> +             crtc->event = NULL;
> +     }
> +     spin_unlock_irqrestore(&dev->event_lock, flags);
> +}
> +
> +static void atmel_hlcdc_crtc_finish_page_flip(struct atmel_hlcdc_crtc *crtc)
> +{
> +     struct drm_device *dev = crtc->base.dev;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&dev->event_lock, flags);
> +     if (crtc->event) {
> +             drm_send_vblank_event(dev, crtc->id, crtc->event);
> +             drm_vblank_put(dev, crtc->id);
> +             crtc->event = NULL;
> +     }
> +     spin_unlock_irqrestore(&dev->event_lock, flags);
> +}
> +
> +void atmel_hlcdc_crtc_irq(struct drm_crtc *c)
> +{
> +     drm_handle_vblank(c->dev, 0);
> +     atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c));
> +}
> +
> +static int atmel_hlcdc_crtc_page_flip(struct drm_crtc *c,
> +                                   struct drm_framebuffer *fb,
> +                                   struct drm_pending_vblank_event *event,
> +                                   uint32_t page_flip_flags)
> +{
> +     struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> +     struct atmel_hlcdc_plane_update_req req;
> +     struct drm_plane *plane = c->primary;
> +     struct drm_device *dev = c->dev;
> +     unsigned long flags;
> +     int ret = 0;
> +
> +     spin_lock_irqsave(&dev->event_lock, flags);
> +     if (crtc->event)
> +             ret = -EBUSY;
> +     spin_unlock_irqrestore(&dev->event_lock, flags);
> +
> +     if (ret)
> +             return ret;
> +
> +     memset(&req, 0, sizeof(req));
> +     req.crtc_x = 0;
> +     req.crtc_y = 0;
> +     req.crtc_h = c->mode.crtc_vdisplay;
> +     req.crtc_w = c->mode.crtc_hdisplay;
> +     req.src_x = c->x << 16;
> +     req.src_y = c->y << 16;
> +     req.src_w = req.crtc_w << 16;
> +     req.src_h = req.crtc_h << 16;
> +     req.fb = fb;
> +     req.crtc = c;
> +
> +     ret = atmel_hlcdc_plane_prepare_update_req(plane, &req);
> +     if (ret)
> +             return ret;
> +
> +     if (event) {
> +             drm_vblank_get(c->dev, crtc->id);
> +             spin_lock_irqsave(&dev->event_lock, flags);
> +             crtc->event = event;
> +             spin_unlock_irqrestore(&dev->event_lock, flags);
> +     }
> +
> +     ret = atmel_hlcdc_plane_apply_update_req(plane, &req);
> +     if (ret)
> +             crtc->event = NULL;
> +     else
> +             plane->fb = fb;
> +
> +     return ret;
> +}
> +
> +static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = {
> +     .page_flip = atmel_hlcdc_crtc_page_flip,
> +     .set_config = drm_crtc_helper_set_config,
> +     .destroy = atmel_hlcdc_crtc_destroy,
> +};
> +
> +int atmel_hlcdc_crtc_create(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     struct atmel_hlcdc_planes *planes = dc->planes;
> +     struct atmel_hlcdc_crtc *crtc;
> +     int ret;
> +     int i;
> +
> +     crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
> +     if (!crtc)
> +             return -ENOMEM;
> +
> +     crtc->dpms = DRM_MODE_DPMS_OFF;
> +     crtc->dc = dc;
> +
> +     ret = drm_crtc_init_with_planes(dev, &crtc->base,
> +                             &planes->primary->base,
> +                             planes->cursor ? &planes->cursor->base : NULL,
> +                             &atmel_hlcdc_crtc_funcs);
> +     if (ret < 0)
> +             goto fail;
> +
> +     crtc->id = drm_crtc_index(&crtc->base);
> +
> +     if (planes->cursor)
> +             planes->cursor->base.possible_crtcs = 1 << crtc->id;
> +
> +     for (i = 0; i < planes->noverlays; i++)
> +             planes->overlays[i]->base.possible_crtcs = 1 << crtc->id;
> +
> +     drm_crtc_helper_add(&crtc->base, &lcdc_crtc_helper_funcs);
> +
> +     dc->crtc = &crtc->base;
> +
> +     return 0;
> +
> +fail:
> +     atmel_hlcdc_crtc_destroy(&crtc->base);
> +     return ret;
> +}
> +
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
> new file mode 100644
> index 0000000..031bf6b
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
> @@ -0,0 +1,531 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <jjhiblot at traphandler.com>
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "atmel_hlcdc_dc.h"
> +
> +#define ATMEL_HLCDC_LAYER_IRQS_OFFSET                8
> +
> +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d3_layers[] = {
> +     {
> +             .name = "base",
> +             .formats = &atmel_hlcdc_plane_rgb_formats,
> +             .regs_offset = 0x40,
> +             .id = 0,
> +             .type = ATMEL_HLCDC_BASE_LAYER,
> +             .nconfigs = 7,
> +             .layout = {
> +                     .xstride = { 2 },
> +                     .default_color = 3,
> +                     .general_config = 4,
> +                     .disc_pos = 5,
> +                     .disc_size = 6,
> +             },
> +     },
> +     {
> +             .name = "overlay1",
> +             .formats = &atmel_hlcdc_plane_rgb_formats,
> +             .regs_offset = 0x140,
> +             .id = 1,
> +             .type = ATMEL_HLCDC_OVERLAY_LAYER,
> +             .nconfigs = 10,
> +             .layout = {
> +                     .pos = 2,
> +                     .size = 3,
> +                     .xstride = { 4 },
> +                     .pstride = { 5 },
> +                     .default_color = 6,
> +                     .chroma_key = 7,
> +                     .chroma_key_mask = 8,
> +                     .general_config = 9,
> +             },
> +     },
> +     {
> +             .name = "overlay2",
> +             .formats = &atmel_hlcdc_plane_rgb_formats,
> +             .regs_offset = 0x240,
> +             .id = 2,
> +             .type = ATMEL_HLCDC_OVERLAY_LAYER,
> +             .nconfigs = 10,
> +             .layout = {
> +                     .pos = 2,
> +                     .size = 3,
> +                     .xstride = { 4 },
> +                     .pstride = { 5 },
> +                     .default_color = 6,
> +                     .chroma_key = 7,
> +                     .chroma_key_mask = 8,
> +                     .general_config = 9,
> +             },
> +     },
> +     {
> +             .name = "high-end-overlay",
> +             .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats,
> +             .regs_offset = 0x340,
> +             .id = 3,
> +             .type = ATMEL_HLCDC_OVERLAY_LAYER,
> +             .nconfigs = 42,
> +             .layout = {
> +                     .pos = 2,
> +                     .size = 3,
> +                     .memsize = 4,
> +                     .xstride = { 5, 7 },
> +                     .pstride = { 6, 8 },
> +                     .default_color = 9,
> +                     .chroma_key = 10,
> +                     .chroma_key_mask = 11,
> +                     .general_config = 12,
> +                     .csc = 14,
> +             },
> +     },
> +     {
> +             .name = "cursor",
> +             .formats = &atmel_hlcdc_plane_rgb_formats,
> +             .regs_offset = 0x440,
> +             .id = 4,
> +             .type = ATMEL_HLCDC_CURSOR_LAYER,
> +             .nconfigs = 10,
> +             .max_width = 128,
> +             .max_height = 128,
> +             .layout = {
> +                     .pos = 2,
> +                     .size = 3,
> +                     .xstride = { 4 },
> +                     .pstride = { 5 },
> +                     .default_color = 6,
> +                     .chroma_key = 7,
> +                     .chroma_key_mask = 8,
> +                     .general_config = 9,
> +             },
> +     },
> +};
> +
> +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = {
> +     .min_width = 0,
> +     .min_height = 0,
> +     .max_width = 2048,
> +     .max_height = 2048,
> +     .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers),
> +     .layers = atmel_hlcdc_sama5d3_layers,
> +};
> +
> +static const struct of_device_id atmel_hlcdc_of_match[] = {
> +     {
> +             .compatible = "atmel,sama5d3-hlcdc",
> +             .data = &atmel_hlcdc_dc_sama5d3,
> +     },
> +     { /* sentinel */ },
> +};
> +
> +int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
> +                           struct drm_display_mode *mode)
> +{
> +     int vfront_porch = mode->vsync_start - mode->vdisplay;
> +     int vback_porch = mode->vtotal - mode->vsync_end;
> +     int vsync_len = mode->vsync_end - mode->vsync_start;
> +     int hfront_porch = mode->hsync_start - mode->hdisplay;
> +     int hback_porch = mode->htotal - mode->hsync_end;
> +     int hsync_len = mode->hsync_end - mode->hsync_start;
> +
> +     if (hsync_len > 0x40 || hsync_len < 1)
> +             return MODE_HSYNC;
> +
> +     if (vsync_len > 0x40 || vsync_len < 1)
> +             return MODE_VSYNC;
> +
> +     if (hfront_porch > 0x200 || hfront_porch < 1 ||
> +         hback_porch > 0x200 || hback_porch < 1 ||
> +         mode->hdisplay < 1)
> +             return MODE_H_ILLEGAL;
> +
> +     if (vfront_porch > 0x40 || vfront_porch < 1 ||
> +         vback_porch > 0x40 || vback_porch < 0 ||
> +         mode->vdisplay < 1)
> +             return MODE_V_ILLEGAL;
> +
> +     return MODE_OK;
> +}
> +
> +static irqreturn_t atmel_hlcdc_dc_irq_handler(int irq, void *data)
> +{
> +     struct drm_device *dev = data;
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     unsigned long status;
> +     unsigned int imr, isr;
> +     int i;
> +
> +     regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_IMR, &imr);
> +     regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr);
> +     status = imr & isr;
> +     if (!status)
> +             return IRQ_NONE;
> +
> +     if (status & ATMEL_HLCDC_SOF)
> +             atmel_hlcdc_crtc_irq(dc->crtc);
> +
> +     for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) {
> +             struct atmel_hlcdc_layer *layer = dc->layers[i];
> +
> +             if (!(ATMEL_HLCDC_LAYER_STATUS(i) & status) || !layer)
> +                     continue;
> +
> +             atmel_hlcdc_layer_irq(layer);
> +     }
> +
> +     return IRQ_HANDLED;
> +}
> +
> +static struct drm_framebuffer *atmel_hlcdc_fb_create(struct drm_device *dev,
> +             struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
> +{
> +     return drm_fb_cma_create(dev, file_priv, mode_cmd);
> +}
> +
> +static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +
> +     if (dc->fbdev) {
> +             drm_fbdev_cma_hotplug_event(dc->fbdev);
> +     } else {
> +             dc->fbdev = drm_fbdev_cma_init(dev, 24,
> +                             dev->mode_config.num_crtc,
> +                             dev->mode_config.num_connector);
> +             if (IS_ERR(dc->fbdev))
> +                     dc->fbdev = NULL;
> +     }
> +}
> +
> +static const struct drm_mode_config_funcs mode_config_funcs = {
> +     .fb_create = atmel_hlcdc_fb_create,
> +     .output_poll_changed = atmel_hlcdc_fb_output_poll_changed,
> +};
> +
> +static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     struct atmel_hlcdc_planes *planes;
> +     int ret;
> +     int i;
> +
> +     drm_mode_config_init(dev);
> +
> +     ret = atmel_hlcdc_create_outputs(dev);
> +     if (ret) {
> +             dev_err(dev->dev, "failed to create panel: %d\n", ret);
> +             return ret;
> +     }
> +
> +     planes = atmel_hlcdc_create_planes(dev);
> +     if (IS_ERR(planes)) {
> +             dev_err(dev->dev, "failed to create planes\n");
> +             return PTR_ERR(planes);
> +     }
> +
> +     dc->planes = planes;
> +
> +     dc->layers[planes->primary->layer.desc->id] =
> +                                             &planes->primary->layer;
> +
> +     if (planes->cursor)
> +             dc->layers[planes->cursor->layer.desc->id] =
> +                                                     &planes->cursor->layer;
> +
> +     for (i = 0; i < planes->noverlays; i++)
> +             dc->layers[planes->overlays[i]->layer.desc->id] =
> +                                             &planes->overlays[i]->layer;
> +
> +     ret = atmel_hlcdc_crtc_create(dev);
> +     if (ret) {
> +             dev_err(dev->dev, "failed to create crtc\n");
> +             return ret;
> +     }
> +
> +     dev->mode_config.min_width = dc->desc->min_width;
> +     dev->mode_config.min_height = dc->desc->min_height;
> +     dev->mode_config.max_width = dc->desc->max_width;
> +     dev->mode_config.max_height = dc->desc->max_height;
> +     dev->mode_config.funcs = &mode_config_funcs;
> +
> +     return 0;
> +}
> +
> +static int atmel_hlcdc_dc_load(struct drm_device *dev, unsigned long flags)
> +{
> +     struct platform_device *pdev = to_platform_device(dev->dev);
> +     const struct of_device_id *match;
> +     struct atmel_hlcdc_dc *dc;
> +     int ret;
> +
> +     match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node);
> +     if (!match) {
> +             dev_err(&pdev->dev, "invalid compatible string\n");
> +             return -ENODEV;
> +     }
> +
> +     if (!match->data) {
> +             dev_err(&pdev->dev, "invalid hlcdc description\n");
> +             return -EINVAL;
> +     }
> +
> +     dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
> +     if (!dc)
> +             return -ENOMEM;
> +
> +     dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0);
> +     if (!dc->wq)
> +             return -ENOMEM;
> +
> +     dc->desc = match->data;
> +     dc->hlcdc = dev_get_drvdata(dev->dev->parent);
> +     dev->dev_private = dc;
> +
> +     ret = clk_prepare_enable(dc->hlcdc->periph_clk);
> +     if (ret) {
> +             dev_err(dev->dev, "failed to enable periph_clk\n");
> +             goto err_destroy_wq;
> +     }
> +
> +     pm_runtime_enable(dev->dev);
> +
> +     pm_runtime_put_sync(dev->dev);
> +
> +     ret = atmel_hlcdc_dc_modeset_init(dev);
> +     if (ret < 0) {
> +             dev_err(dev->dev, "failed to initialize mode setting\n");
> +             goto err_periph_clk_disable;
> +     }
> +
> +     ret = drm_vblank_init(dev, 1);
> +     if (ret < 0) {
> +             dev_err(dev->dev, "failed to initialize vblank\n");
> +             goto err_periph_clk_disable;
> +     }
> +
> +     pm_runtime_get_sync(dev->dev);
> +     ret = drm_irq_install(dev, dc->hlcdc->irq);
> +     pm_runtime_put_sync(dev->dev);
> +     if (ret < 0) {
> +             dev_err(dev->dev, "failed to install IRQ handler\n");
> +             goto err_periph_clk_disable;
> +     }
> +
> +     platform_set_drvdata(pdev, dev);
> +
> +     drm_kms_helper_poll_init(dev);
> +
> +     /* force connectors detection */
> +     drm_helper_hpd_irq_event(dev);
> +
> +     return 0;
> +
> +err_periph_clk_disable:
> +     clk_disable_unprepare(dc->hlcdc->periph_clk);
> +
> +err_destroy_wq:
> +     destroy_workqueue(dc->wq);
> +
> +     return ret;
> +}
> +
> +static int atmel_hlcdc_dc_unload(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +
> +     drm_kms_helper_poll_fini(dev);
> +     drm_mode_config_cleanup(dev);
> +     drm_vblank_cleanup(dev);
> +
> +     pm_runtime_get_sync(dev->dev);
> +     drm_irq_uninstall(dev);
> +     pm_runtime_put_sync(dev->dev);
> +
> +     dev->dev_private = NULL;
> +
> +     pm_runtime_disable(dev->dev);
> +     clk_disable_unprepare(dc->hlcdc->periph_clk);
> +
> +     flush_workqueue(dc->wq);
> +     destroy_workqueue(dc->wq);
> +
> +     return 0;
> +}
> +
> +static void atmel_hlcdc_dc_preclose(struct drm_device *dev,
> +                                 struct drm_file *file)
> +{
> +     struct drm_crtc *crtc;
> +
> +     list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
> +             atmel_hlcdc_crtc_cancel_page_flip(crtc, file);
> +}
> +
> +static void atmel_hlcdc_dc_lastclose(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +
> +     drm_fbdev_cma_restore_mode(dc->fbdev);
> +}
> +
> +static void atmel_hlcdc_dc_irq_preinstall(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     unsigned int isr;
> +
> +     regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff);
> +     regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr);
> +}
> +
> +static int atmel_hlcdc_dc_irq_postinstall(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     /* Enable SOF (Start Of Frame) interrupt for vblank counting */
> +     unsigned int cfg = ATMEL_HLCDC_SOF;
> +     int i;
> +
> +     /* Enable interrupts on activated layers */
> +     for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) {
> +             if (dc->layers[i])
> +                     cfg |= ATMEL_HLCDC_LAYER_STATUS(i);
> +     }
> +
> +     regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, cfg);
> +
> +     return 0;
> +}
> +
> +static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     unsigned int isr;
> +
> +     regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff);
> +     regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr);
> +}

This looks like the atmel_hlcdc_dc_irq_preinstall(). But well, okay.

> +
> +static int atmel_hlcdc_dc_enable_vblank(struct drm_device *dev, int crtc)
> +{
> +     return 0;
> +}
> +
> +static void atmel_hlcdc_dc_disable_vblank(struct drm_device *dev, int crtc)
> +{
> +}
> +
> +static const struct file_operations 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 atmel_hlcdc_dc_driver = {
> +     .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
> +     .load = atmel_hlcdc_dc_load,
> +     .unload = atmel_hlcdc_dc_unload,
> +     .preclose = atmel_hlcdc_dc_preclose,
> +     .lastclose = atmel_hlcdc_dc_lastclose,
> +     .irq_handler = atmel_hlcdc_dc_irq_handler,
> +     .irq_preinstall = atmel_hlcdc_dc_irq_preinstall,
> +     .irq_postinstall = atmel_hlcdc_dc_irq_postinstall,
> +     .irq_uninstall = atmel_hlcdc_dc_irq_uninstall,
> +     .get_vblank_counter = drm_vblank_count,
> +     .enable_vblank = atmel_hlcdc_dc_enable_vblank,
> +     .disable_vblank = atmel_hlcdc_dc_disable_vblank,
> +     .gem_free_object = drm_gem_cma_free_object,
> +     .gem_vm_ops = &drm_gem_cma_vm_ops,
> +     .dumb_create = drm_gem_cma_dumb_create,
> +     .dumb_map_offset = drm_gem_cma_dumb_map_offset,
> +     .dumb_destroy = drm_gem_dumb_destroy,
> +     .fops = &fops,
> +     .name = "atmel-hlcdc",
> +     .desc = "Atmel HLCD Controller DRM",
> +     .date = "20141504",
> +     .major = 1,
> +     .minor = 0,
> +};
> +
> +static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
> +{
> +     struct drm_device *ddev;
> +     int ret;
> +
> +     ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, &pdev->dev);
> +     if (!ddev)
> +             return -ENOMEM;
> +
> +     ret = drm_dev_set_unique(ddev, dev_name(ddev->dev));
> +     if (ret) {
> +             drm_dev_unref(ddev);
> +             return ret;
> +     }
> +
> +     ret = drm_dev_register(ddev, 0);
> +     if (ret) {
> +             drm_dev_unref(ddev);
> +             return ret;
> +     }
> +
> +     return 0;
> +}
> +
> +static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
> +{
> +     struct drm_device *ddev = platform_get_drvdata(pdev);
> +
> +     drm_dev_unregister(ddev);
> +     drm_dev_unref(ddev);
> +
> +     return 0;
> +}
> +
> +static const struct of_device_id atmel_hlcdc_dc_of_match[] = {
> +     { .compatible = "atmel,hlcdc-display-controller" },
> +     { },
> +};
> +
> +static struct platform_driver atmel_hlcdc_dc_platform_driver = {
> +     .probe  = atmel_hlcdc_dc_drm_probe,
> +     .remove = atmel_hlcdc_dc_drm_remove,
> +     .driver = {
> +             .name   = "atmel-hlcdc-display-controller",
> +             .of_match_table = atmel_hlcdc_dc_of_match,
> +     },
> +};
> +module_platform_driver(atmel_hlcdc_dc_platform_driver);
> +
> +MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot at traphandler.com>");
> +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon at free-electrons.com>");
> +MODULE_DESCRIPTION("Atmel HLCDC Display Controller DRM Driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:atmel-hlcdc-dc");
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
> new file mode 100644
> index 0000000..8194152
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
> @@ -0,0 +1,216 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <jjhiblot at traphandler.com>
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef DRM_ATMEL_HLCDC_H
> +#define DRM_ATMEL_HLCDC_H
> +
> +#include <linux/clk.h>
> +#include <linux/irqdomain.h>
> +#include <linux/pwm.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drmP.h>
> +
> +#include "atmel_hlcdc_layer.h"
> +
> +#define ATMEL_HLCDC_MAX_LAYERS               5
> +
> +/**
> + * Atmel HLCDC Display Controller description structure.
> + *
> + * This structure describe the HLCDC IP capabilities and depends on the
> + * HLCDC IP version (or Atmel SoC family).
> + *
> + * @min_width: minimum width supported by the Display Controller
> + * @min_height: minimum height supported by the Display Controller
> + * @max_width: maximum width supported by the Display Controller
> + * @max_height: maximum height supported by the Display Controller
> + * @layer: a layer description table describing available layers
> + * @nlayers: layer description table size
> + */
> +struct atmel_hlcdc_dc_desc {
> +     int min_width;
> +     int min_height;
> +     int max_width;
> +     int max_height;
> +     const struct atmel_hlcdc_layer_desc *layers;
> +     int nlayers;
> +};
> +
> +/**
> + * Atmel HLCDC Plane properties.
> + *
> + * This structure stores plane property definitions.
> + *
> + * @alpha: alpha blending (or transparency) property
> + * @csc: YUV to RGB conversion factors property
> + */
> +struct atmel_hlcdc_plane_properties {
> +     struct drm_property *alpha;
> +     struct drm_property *rotation;
> +};
> +
> +/**
> + * Atmel HLCDC Plane.
> + *
> + * @base: base DRM plane structure
> + * @layer: HLCDC layer structure
> + * @properties: pointer to the property definitions structure
> + * @alpha: current alpha blending (or transparency) status

I don't see alpha in the structure below. On the other hand, I see
"rotation" which is not described.



> + */
> +struct atmel_hlcdc_plane {
> +     struct drm_plane base;
> +     struct atmel_hlcdc_layer layer;
> +     struct atmel_hlcdc_plane_properties *properties;
> +     unsigned int rotation;
> +};
> +
> +static inline struct atmel_hlcdc_plane *
> +drm_plane_to_atmel_hlcdc_plane(struct drm_plane *p)
> +{
> +     return container_of(p, struct atmel_hlcdc_plane, base);
> +}
> +
> +static inline struct atmel_hlcdc_plane *
> +atmel_hlcdc_layer_to_plane(struct atmel_hlcdc_layer *l)
> +{
> +     return container_of(l, struct atmel_hlcdc_plane, layer);
> +}
> +
> +/**
> + * Atmel HLCDC Plane update request structure.
> + *
> + * @crtc_x: x position of the plane relative to the CRTC
> + * @crtc_y: y position of the plane relative to the CRTC
> + * @crtc_w: visible width of the plane
> + * @crtc_h: visible height of the plane
> + * @src_x: x buffer position
> + * @src_y: y buffer position
> + * @src_w: buffer width
> + * @src_h: buffer height
> + * @pixel_format: pixel format
> + * @gems: GEM object object containing image buffers
> + * @offsets: offsets to apply to the GEM buffers
> + * @pitches: line size in bytes
> + * @crtc: crtc to display on
> + * @finished: finished callback
> + * @finished_data: data passed to the finished callback
> + * @bpp: bytes per pixel deduced from pixel_format
> + * @xstride: value to add to the pixel pointer between each line
> + * @pstride: value to add to the pixel pointer between each pixel
> + * @nplanes: number of planes (deduced from pixel_format)

Ditto: structure documentation not in sync with code.

> + */
> +struct atmel_hlcdc_plane_update_req {
> +     int crtc_x;
> +     int crtc_y;
> +     unsigned int crtc_w;
> +     unsigned int crtc_h;
> +     uint32_t src_x;
> +     uint32_t src_y;
> +     uint32_t src_w;
> +     uint32_t src_h;
> +     struct drm_framebuffer *fb;
> +     struct drm_crtc *crtc;
> +
> +     /* These fields are private and should not be touched */
> +     int bpp[ATMEL_HLCDC_MAX_PLANES];
> +     unsigned int offsets[ATMEL_HLCDC_MAX_PLANES];
> +     int xstride[ATMEL_HLCDC_MAX_PLANES];
> +     int pstride[ATMEL_HLCDC_MAX_PLANES];
> +     int nplanes;
> +};
> +
> +/**
> + * Atmel HLCDC Planes.
> + *
> + * This structure stores the instantiated HLCDC Planes and can be accessed by
> + * the HLCDC Display Controller or the HLCDC CRTC.
> + *
> + * @primary: primary plane
> + * @cursor: hardware cursor plane
> + * @overlays: overlay plane table
> + * @noverlays: number of overlay planes
> + */
> +struct atmel_hlcdc_planes {
> +     struct atmel_hlcdc_plane *primary;
> +     struct atmel_hlcdc_plane *cursor;
> +     struct atmel_hlcdc_plane **overlays;
> +     int noverlays;
> +};
> +
> +/**
> + * Atmel HLCDC Display Controller.
> + *
> + * @desc: HLCDC Display Controller description
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
> + * @fbdev: framebuffer device attached to the Display Controller
> + * @crtc: CRTC provided by the display controller
> + * @planes: instantiated planes
> + * @layers: active HLCDC layer
> + * @wq: display controller workqueue
> + */
> +struct atmel_hlcdc_dc {
> +     const struct atmel_hlcdc_dc_desc *desc;
> +     struct atmel_hlcdc *hlcdc;
> +     struct drm_fbdev_cma *fbdev;
> +     struct drm_crtc *crtc;
> +     struct atmel_hlcdc_planes *planes;
> +     struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
> +     struct workqueue_struct *wq;
> +};
> +
> +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
> +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats;
> +
> +int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
> +                           struct drm_display_mode *mode);
> +
> +struct atmel_hlcdc_planes *
> +atmel_hlcdc_create_planes(struct drm_device *dev);
> +
> +int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
> +                             struct atmel_hlcdc_plane_update_req *req);
> +
> +int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
> +                             struct atmel_hlcdc_plane_update_req *req);
> +
> +void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
> +
> +void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc,
> +                                    struct drm_file *file);
> +
> +int atmel_hlcdc_crtc_create(struct drm_device *dev);
> +
> +int atmel_hlcdc_create_outputs(struct drm_device *dev);
> +
> +struct atmel_hlcdc_pwm_chip *atmel_hlcdc_pwm_create(struct drm_device *dev,
> +                                                 struct clk *slow_clk,
> +                                                 struct clk *sys_clk,
> +                                                 void __iomem *regs);
> +
> +int atmel_hlcdc_pwm_destroy(struct drm_device *dev,
> +                         struct atmel_hlcdc_pwm_chip *chip);
> +
> +#endif /* DRM_ATMEL_HLCDC_H */
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
> new file mode 100644
> index 0000000..4799325
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
> @@ -0,0 +1,638 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +
> +#include "atmel_hlcdc_dc.h"
> +
> +static void
> +atmel_hlcdc_layer_fb_flip_release(struct drm_flip_work *work, void *val)
> +{
> +     struct atmel_hlcdc_layer_fb_flip *flip = val;
> +
> +     if (flip->fb)
> +             drm_framebuffer_unreference(flip->fb);
> +     kfree(flip);
> +}
> +
> +static void
> +atmel_hlcdc_layer_fb_flip_destroy(struct atmel_hlcdc_layer_fb_flip *flip)
> +{
> +     if (flip->fb)
> +             drm_framebuffer_unreference(flip->fb);
> +     kfree(flip->task);
> +     kfree(flip);
> +}
> +
> +static void
> +atmel_hlcdc_layer_fb_flip_release_queue(struct atmel_hlcdc_layer *layer,
> +                                     struct atmel_hlcdc_layer_fb_flip *flip)
> +{
> +     int i;
> +
> +     if (!flip)
> +             return;
> +
> +     for (i = 0; i < layer->max_planes; i++) {
> +             if (!flip->dscrs[i])
> +                     break;
> +
> +             flip->dscrs[i]->status = 0;
> +             flip->dscrs[i] = NULL;
> +     }
> +
> +     drm_flip_work_queue_task(&layer->gc, flip->task);
> +     drm_flip_work_commit(&layer->gc, layer->wq);
> +}
> +
> +static void atmel_hlcdc_layer_update_reset(struct atmel_hlcdc_layer *layer,
> +                                        int id)
> +{
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +
> +     if (id < 0 || id > 1)
> +             return;
> +
> +     slot = &upd->slots[id];
> +     bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> +     memset(slot->configs, 0,
> +            sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> +     if (slot->fb_flip) {
> +             atmel_hlcdc_layer_fb_flip_release_queue(layer, slot->fb_flip);
> +             slot->fb_flip = NULL;
> +     }
> +}
> +
> +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     const struct atmel_hlcdc_layer_desc *desc = layer->desc;
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct regmap *regmap = layer->hlcdc->regmap;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +     struct atmel_hlcdc_layer_fb_flip *fb_flip;
> +     struct atmel_hlcdc_dma_channel_dscr *dscr;
> +     unsigned int cfg;
> +     u32 action = 0;
> +     int i = 0;
> +
> +     if (upd->pending < 0 || upd->pending > 1 ||
> +         dma->status == ATMEL_HLCDC_LAYER_DISABLING)
> +             return;
> +
> +     slot = &upd->slots[upd->pending];
> +
> +     for_each_set_bit(cfg, slot->updated_configs, layer->desc->nconfigs) {
> +             regmap_write(regmap,
> +                          desc->regs_offset +
> +                          ATMEL_HLCDC_LAYER_CFG(layer, cfg),
> +                          slot->configs[cfg]);
> +             action |= ATMEL_HLCDC_LAYER_UPDATE;
> +     }
> +
> +     fb_flip = slot->fb_flip;
> +
> +     if (!fb_flip->fb)
> +             goto apply;
> +
> +     if (dma->status == ATMEL_HLCDC_LAYER_DISABLED) {
> +             for (i = 0; i < fb_flip->ngems; i++) {
> +                     dscr =  fb_flip->dscrs[i];
> +                     dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
> +                                  ATMEL_HLCDC_LAYER_DMA_IRQ |
> +                                  ATMEL_HLCDC_LAYER_ADD_IRQ |
> +                                  ATMEL_HLCDC_LAYER_DONE_IRQ;
> +
> +                     regmap_write(regmap,
> +                                  desc->regs_offset +
> +                                  ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
> +                                  dscr->addr);
> +                     regmap_write(regmap,
> +                                  desc->regs_offset +
> +                                  ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
> +                                  dscr->ctrl);
> +                     regmap_write(regmap,
> +                                  desc->regs_offset +
> +                                  ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
> +                                  dscr->next);
> +             }
> +
> +             action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
> +             dma->status = ATMEL_HLCDC_LAYER_ENABLED;
> +     } else {
> +             for (i = 0; i < fb_flip->ngems; i++) {
> +                     dscr =  fb_flip->dscrs[i];
> +                     dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
> +                                  ATMEL_HLCDC_LAYER_DMA_IRQ |
> +                                  ATMEL_HLCDC_LAYER_DSCR_IRQ |
> +                                  ATMEL_HLCDC_LAYER_DONE_IRQ;
> +
> +                     regmap_write(regmap,
> +                                  desc->regs_offset +
> +                                  ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
> +                                  dscr->next);
> +             }
> +
> +             action |= ATMEL_HLCDC_LAYER_A2Q;
> +     }
> +
> +     /* Release unneeded descriptors */
> +     for (i = fb_flip->ngems; i < layer->max_planes; i++) {
> +             fb_flip->dscrs[i]->status = 0;
> +             fb_flip->dscrs[i] = NULL;
> +     }
> +
> +     dma->queue = fb_flip;
> +     slot->fb_flip = NULL;
> +
> +apply:
> +     if (action)
> +             regmap_write(regmap,
> +                          desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
> +                          action);
> +
> +     atmel_hlcdc_layer_update_reset(layer, upd->pending);
> +
> +     upd->pending = -1;
> +}
> +
> +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     const struct atmel_hlcdc_layer_desc *desc = layer->desc;
> +     struct regmap *regmap = layer->hlcdc->regmap;
> +     struct atmel_hlcdc_layer_fb_flip *flip;
> +     unsigned long flags;
> +     unsigned int isr, imr;
> +     unsigned int status;
> +     unsigned int plane_status;
> +     u32 flip_status;
> +
> +     int i;
> +
> +     regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IMR, &imr);
> +     regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr);
> +     status = imr & isr;
> +     if (!status)
> +             return;
> +
> +     spin_lock_irqsave(&layer->lock, flags);
> +
> +     flip = dma->queue ? dma->queue : dma->cur;
> +
> +     if (!flip) {
> +             spin_unlock_irqrestore(&layer->lock, flags);
> +             return;
> +     }
> +
> +     flip_status = 0;
> +     for (i = 0; i < flip->ngems; i++) {
> +             plane_status = (status >> (8 * i));
> +
> +             if (plane_status &
> +                 (ATMEL_HLCDC_LAYER_ADD_IRQ |
> +                  ATMEL_HLCDC_LAYER_DSCR_IRQ) &
> +                 ~flip->dscrs[i]->ctrl) {
> +                     flip->dscrs[i]->status |=
> +                                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED;
> +                     flip->dscrs[i]->ctrl |=
> +                                     ATMEL_HLCDC_LAYER_ADD_IRQ |
> +                                     ATMEL_HLCDC_LAYER_DSCR_IRQ;
> +             }
> +
> +             if (plane_status &
> +                 ATMEL_HLCDC_LAYER_DONE_IRQ &
> +                 ~flip->dscrs[i]->ctrl) {
> +                     flip->dscrs[i]->status |=
> +                                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
> +                     flip->dscrs[i]->ctrl |=
> +                                     ATMEL_HLCDC_LAYER_DONE_IRQ;
> +             }
> +
> +             if (plane_status & ATMEL_HLCDC_LAYER_OVR_IRQ)
> +                     flip->dscrs[i]->status |=
> +                                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN;
> +
> +             flip_status |= flip->dscrs[i]->status;
> +     }
> +
> +     /* Get changed bits */
> +     flip_status ^= flip->status;
> +     flip->status |= flip_status;
> +
> +     if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED) {
> +             atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
> +             dma->cur = dma->queue;
> +             dma->queue = NULL;
> +     }
> +
> +     if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE) {
> +             atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
> +             dma->cur = NULL;
> +     }
> +
> +     if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN) {
> +             regmap_write(regmap,
> +                          desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
> +                          ATMEL_HLCDC_LAYER_RST);
> +             if (dma->queue)
> +                     atmel_hlcdc_layer_fb_flip_release_queue(layer,
> +                                                             dma->queue);
> +
> +             if (dma->cur)
> +                     atmel_hlcdc_layer_fb_flip_release_queue(layer,
> +                                                             dma->cur);
> +
> +             dma->cur = NULL;
> +             dma->queue = NULL;
> +     }
> +
> +     if (!dma->queue) {
> +             atmel_hlcdc_layer_update_apply(layer);
> +
> +             if (!dma->cur)
> +                     dma->status = ATMEL_HLCDC_LAYER_DISABLED;
> +     }
> +
> +     spin_unlock_irqrestore(&layer->lock, flags);
> +}
> +
> +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct atmel_hlcdc_layer_fb_flip *flip;
> +     unsigned long flags;
> +     int i;
> +
> +     spin_lock_irqsave(&layer->lock, flags);
> +
> +     /*
> +      * First disable DMA transfers. If a DMA transfer has been queued
> +      * we're stopping this one instead of the current one because we
> +      * can't know for sure if queued transfer has been started or not.
> +      */
> +     flip = dma->queue ? dma->queue : dma->cur;
> +     if (flip) {
> +             for (i = 0; i < flip->ngems; i++)
> +                     flip->dscrs[i]->ctrl &= ~(ATMEL_HLCDC_LAYER_DFETCH |
> +                                               ATMEL_HLCDC_LAYER_DONE_IRQ);
> +
> +             dma->status = ATMEL_HLCDC_LAYER_DISABLING;
> +     }
> +
> +     /*
> +      * Then discard the pending update request (if any) to prevent
> +      * DMA irq handler from restarting the DMA channel after it has
> +      * been disabled.
> +      */
> +     if (upd->pending >= 0) {
> +             atmel_hlcdc_layer_update_reset(layer, upd->pending);
> +             upd->pending = -1;
> +     }
> +
> +     spin_unlock_irqrestore(&layer->lock, flags);
> +
> +     return 0;
> +}
> +
> +int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct regmap *regmap = layer->hlcdc->regmap;
> +     struct atmel_hlcdc_layer_fb_flip *fb_flip;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +     unsigned long flags;
> +     int i, j = 0;
> +
> +     fb_flip = kzalloc(sizeof(*fb_flip), GFP_KERNEL);
> +     if (!fb_flip)
> +             return -ENOMEM;
> +
> +     fb_flip->task = drm_flip_work_allocate_task(fb_flip, GFP_KERNEL);
> +     if (!fb_flip->task) {
> +             kfree(fb_flip);
> +             return -ENOMEM;
> +     }
> +
> +     spin_lock_irqsave(&layer->lock, flags);
> +
> +     upd->next = upd->pending ? 0 : 1;
> +
> +     slot = &upd->slots[upd->next];
> +
> +     for (i = 0; i < layer->max_planes * 4; i++) {
> +             if (!dma->dscrs[i].status) {
> +                     fb_flip->dscrs[j++] = &dma->dscrs[i];
> +                     dma->dscrs[i].status =
> +                             ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED;
> +                     if (j == layer->max_planes)
> +                             break;
> +             }
> +     }
> +
> +     if (j < layer->max_planes) {
> +             for (i = 0; i < j; i++)
> +                     fb_flip->dscrs[i]->status = 0;
> +     }
> +
> +     if (j < layer->max_planes) {
> +             spin_unlock_irqrestore(&layer->lock, flags);
> +             atmel_hlcdc_layer_fb_flip_destroy(fb_flip);
> +             return -EBUSY;
> +     }
> +
> +     slot->fb_flip = fb_flip;
> +
> +     if (upd->pending >= 0) {
> +             memcpy(slot->configs,
> +                    upd->slots[upd->pending].configs,
> +                    layer->desc->nconfigs * sizeof(u32));
> +             memcpy(slot->updated_configs,
> +                    upd->slots[upd->pending].updated_configs,
> +                    DIV_ROUND_UP(layer->desc->nconfigs,
> +                                 BITS_PER_BYTE * sizeof(unsigned long)) *
> +                    sizeof(unsigned long));
> +             slot->fb_flip->fb = upd->slots[upd->pending].fb_flip->fb;
> +             if (upd->slots[upd->pending].fb_flip->fb) {
> +                     slot->fb_flip->fb =
> +                             upd->slots[upd->pending].fb_flip->fb;
> +                     slot->fb_flip->ngems =
> +                             upd->slots[upd->pending].fb_flip->ngems;
> +                     drm_framebuffer_reference(slot->fb_flip->fb);
> +             }
> +     } else {
> +             regmap_bulk_read(regmap,
> +                              layer->desc->regs_offset +
> +                              ATMEL_HLCDC_LAYER_CFG(layer, 0),
> +                              upd->slots[upd->next].configs,
> +                              layer->desc->nconfigs);
> +     }
> +
> +     spin_unlock_irqrestore(&layer->lock, flags);
> +
> +     return 0;
> +}
> +
> +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +
> +     atmel_hlcdc_layer_update_reset(layer, upd->next);
> +     upd->next = -1;
> +}
> +
> +void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer,
> +                                  struct drm_framebuffer *fb,
> +                                  unsigned int *offsets)
> +{
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct atmel_hlcdc_layer_fb_flip *fb_flip;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +     struct atmel_hlcdc_dma_channel_dscr *dscr;
> +     struct drm_framebuffer *old_fb;
> +     int nplanes = 0;
> +     int i;
> +
> +     if (upd->next < 0 || upd->next > 1)
> +             return;
> +
> +     if (fb)
> +             nplanes = drm_format_num_planes(fb->pixel_format);
> +
> +     if (nplanes > layer->max_planes)
> +             return;
> +
> +     slot = &upd->slots[upd->next];
> +
> +     fb_flip = slot->fb_flip;
> +     old_fb = slot->fb_flip->fb;
> +
> +     for (i = 0; i < nplanes; i++) {
> +             struct drm_gem_cma_object *gem;
> +
> +             dscr = slot->fb_flip->dscrs[i];
> +             gem = drm_fb_cma_get_gem_obj(fb, i);
> +             dscr->addr = gem->paddr + offsets[i];
> +     }
> +
> +     fb_flip->ngems = nplanes;
> +     fb_flip->fb = fb;
> +
> +     if (fb)
> +             drm_framebuffer_reference(fb);
> +
> +     if (old_fb)
> +             drm_framebuffer_unreference(old_fb);
> +}
> +
> +void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg,
> +                               u32 mask, u32 val)
> +{
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +
> +     if (upd->next < 0 || upd->next > 1)
> +             return;
> +
> +     if (cfg >= layer->desc->nconfigs)
> +             return;
> +
> +     slot = &upd->slots[upd->next];
> +     slot->configs[cfg] &= ~mask;
> +     slot->configs[cfg] |= (val & mask);
> +     set_bit(cfg, slot->updated_configs);
> +}
> +
> +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     struct atmel_hlcdc_layer_update_slot *slot;
> +     unsigned long flags;
> +
> +     if (upd->next < 0  || upd->next > 1)
> +             return;
> +
> +     slot = &upd->slots[upd->next];
> +
> +     spin_lock_irqsave(&layer->lock, flags);
> +
> +     /*
> +      * Release pending update request and replace it by the new one.
> +      */
> +     if (upd->pending >= 0)
> +             atmel_hlcdc_layer_update_reset(layer, upd->pending);
> +
> +     upd->pending = upd->next;
> +     upd->next = -1;
> +
> +     if (!dma->queue)
> +             atmel_hlcdc_layer_update_apply(layer);
> +
> +     spin_unlock_irqrestore(&layer->lock, flags);
> +
> +
> +     upd->next = -1;
> +}
> +
> +static int atmel_hlcdc_layer_dma_init(struct drm_device *dev,
> +                                   struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     dma_addr_t dma_addr;
> +     int i;
> +
> +     dma->dscrs = dma_alloc_coherent(dev->dev,
> +                                     layer->max_planes * 4 *
> +                                     sizeof(*dma->dscrs),
> +                                     &dma_addr, GFP_KERNEL);
> +     if (!dma->dscrs)
> +             return -ENOMEM;
> +
> +     for (i = 0; i < layer->max_planes * 4; i++) {
> +             struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
> +
> +             dscr->next = dma_addr + (i * sizeof(*dscr));
> +     }
> +
> +     return 0;
> +}
> +
> +static void atmel_hlcdc_layer_dma_cleanup(struct drm_device *dev,
> +                                       struct atmel_hlcdc_layer *layer)
> +{
> +     struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +     int i;
> +
> +     for (i = 0; i < layer->max_planes * 4; i++) {
> +             struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
> +
> +             dscr->status = 0;
> +     }
> +
> +     dma_free_coherent(dev->dev, layer->max_planes * 4 *
> +                       sizeof(*dma->dscrs), dma->dscrs,
> +                       dma->dscrs[0].next);
> +}
> +
> +static int atmel_hlcdc_layer_update_init(struct drm_device *dev,
> +                             struct atmel_hlcdc_layer *layer,
> +                             const struct atmel_hlcdc_layer_desc *desc)
> +{
> +     struct atmel_hlcdc_layer_update *upd = &layer->update;
> +     int updated_size;
> +     void *buffer;
> +     int i;
> +
> +     updated_size = DIV_ROUND_UP(desc->nconfigs,
> +                                 BITS_PER_BYTE *
> +                                 sizeof(unsigned long));
> +
> +     buffer = devm_kzalloc(dev->dev,
> +                           ((desc->nconfigs * sizeof(u32)) +
> +                             (updated_size * sizeof(unsigned long))) * 2,
> +                           GFP_KERNEL);
> +     if (!buffer)
> +             return -ENOMEM;
> +
> +     for (i = 0; i < 2; i++) {
> +             upd->slots[i].updated_configs = buffer;
> +             buffer += updated_size * sizeof(unsigned long);
> +             upd->slots[i].configs = buffer;
> +             buffer += desc->nconfigs * sizeof(u32);
> +     }
> +
> +     upd->pending = -1;
> +     upd->next = -1;
> +
> +     return 0;
> +}
> +
> +int atmel_hlcdc_layer_init(struct drm_device *dev,
> +                        struct atmel_hlcdc_layer *layer,
> +                        const struct atmel_hlcdc_layer_desc *desc)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     struct regmap *regmap = dc->hlcdc->regmap;
> +     unsigned int tmp;
> +     int ret;
> +     int i;
> +
> +     layer->hlcdc = dc->hlcdc;
> +     layer->wq = dc->wq;
> +     layer->desc = desc;
> +
> +     regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
> +                  ATMEL_HLCDC_LAYER_RST);
> +     for (i = 0; i < desc->formats->nformats; i++) {
> +             int nplanes = drm_format_num_planes(desc->formats->formats[i]);
> +
> +             if (nplanes > layer->max_planes)
> +                     layer->max_planes = nplanes;
> +     }
> +
> +     spin_lock_init(&layer->lock);
> +     drm_flip_work_init(&layer->gc, desc->name,
> +                        atmel_hlcdc_layer_fb_flip_release);
> +     ret = atmel_hlcdc_layer_dma_init(dev, layer);
> +     if (ret)
> +             return ret;
> +
> +     ret = atmel_hlcdc_layer_update_init(dev, layer, desc);
> +     if (ret)
> +             return ret;
> +
> +     /* Flush Status Register */
> +     regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
> +                  0xffffffff);
> +     regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR,
> +                 &tmp);
> +
> +     tmp = 0;
> +     for (i = 0; i < layer->max_planes; i++)
> +             tmp |= (ATMEL_HLCDC_LAYER_DMA_IRQ |
> +                     ATMEL_HLCDC_LAYER_DSCR_IRQ |
> +                     ATMEL_HLCDC_LAYER_ADD_IRQ |
> +                     ATMEL_HLCDC_LAYER_DONE_IRQ |
> +                     ATMEL_HLCDC_LAYER_OVR_IRQ) << (8 * i);
> +
> +     regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER, tmp);
> +
> +     return 0;
> +}
> +
> +void atmel_hlcdc_layer_cleanup(struct drm_device *dev,
> +                            struct atmel_hlcdc_layer *layer)
> +{
> +     const struct atmel_hlcdc_layer_desc *desc = layer->desc;
> +     struct regmap *regmap = layer->hlcdc->regmap;
> +
> +     regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
> +                  0xffffffff);
> +     regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
> +                  ATMEL_HLCDC_LAYER_RST);
> +
> +     atmel_hlcdc_layer_dma_cleanup(dev, layer);
> +     drm_flip_work_cleanup(&layer->gc);
> +}
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
> new file mode 100644
> index 0000000..01fcf96
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
> @@ -0,0 +1,394 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef DRM_ATMEL_HLCDC_LAYER_H
> +#define DRM_ATMEL_HLCDC_LAYER_H
> +
> +#include <linux/mfd/atmel-hlcdc.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_flip_work.h>
> +#include <drm/drmP.h>
> +
> +#define ATMEL_HLCDC_LAYER_CHER                       0x0
> +#define ATMEL_HLCDC_LAYER_CHDR                       0x4
> +#define ATMEL_HLCDC_LAYER_CHSR                       0x8
> +#define ATMEL_HLCDC_LAYER_DMA_CHAN           BIT(0)
> +#define ATMEL_HLCDC_LAYER_UPDATE             BIT(1)

Can you avoid mixing "BIT()" and "(1 << 16)" below and 0x4/8 above.
Please change all to a coherent declaration.

> +#define ATMEL_HLCDC_LAYER_A2Q                        BIT(2)
> +#define ATMEL_HLCDC_LAYER_RST                        BIT(8)
> +
> +#define ATMEL_HLCDC_LAYER_IER                        0xc
> +#define ATMEL_HLCDC_LAYER_IDR                        0x10
> +#define ATMEL_HLCDC_LAYER_IMR                        0x14
> +#define ATMEL_HLCDC_LAYER_ISR                        0x18
> +#define ATMEL_HLCDC_LAYER_DFETCH             BIT(0)
> +#define ATMEL_HLCDC_LAYER_LFETCH             BIT(1)
> +#define ATMEL_HLCDC_LAYER_DMA_IRQ            BIT(2)
> +#define ATMEL_HLCDC_LAYER_DSCR_IRQ           BIT(3)
> +#define ATMEL_HLCDC_LAYER_ADD_IRQ            BIT(4)
> +#define ATMEL_HLCDC_LAYER_DONE_IRQ           BIT(5)
> +#define ATMEL_HLCDC_LAYER_OVR_IRQ            BIT(6)
> +
> +#define ATMEL_HLCDC_LAYER_PLANE_HEAD(n)              (((n) * 0x10) + 0x1c)
> +#define ATMEL_HLCDC_LAYER_PLANE_ADDR(n)              (((n) * 0x10) + 0x20)
> +#define ATMEL_HLCDC_LAYER_PLANE_CTRL(n)              (((n) * 0x10) + 0x24)
> +#define ATMEL_HLCDC_LAYER_PLANE_NEXT(n)              (((n) * 0x10) + 0x28)
> +#define ATMEL_HLCDC_LAYER_CFG(p, c)          (((c) * 4) + ((p)->max_planes * 
> 0x10) + 0x1c)
> +
> +#define ATMEL_HLCDC_LAYER_DMA_CFG_ID         0
> +#define ATMEL_HLCDC_LAYER_DMA_CFG(p)         ATMEL_HLCDC_LAYER_CFG(p, 
> ATMEL_HLCDC_LAYER_DMA_CFG_ID)
> +#define ATMEL_HLCDC_LAYER_DMA_SIF            BIT(0)
> +#define ATMEL_HLCDC_LAYER_DMA_BLEN_MASK              GENMASK(5, 4)

I don't see the BLEN defined in the driver: can you set it to AHB_INCR4
minimum but maybe better with AHB_INCR16.

> +#define ATMEL_HLCDC_LAYER_DMA_DLBO           BIT(8)
> +#define ATMEL_HLCDC_LAYER_DMA_ROTDIS         BIT(12)
> +#define ATMEL_HLCDC_LAYER_DMA_LOCKDIS                BIT(13)
> +
> +#define ATMEL_HLCDC_LAYER_FORMAT_CFG_ID              1
> +#define ATMEL_HLCDC_LAYER_FORMAT_CFG(p)              
> ATMEL_HLCDC_LAYER_CFG(p, ATMEL_HLCDC_LAYER_FORMAT_CFG_ID)
> +#define ATMEL_HLCDC_LAYER_RGB                        (0 << 0)
> +#define ATMEL_HLCDC_LAYER_CLUT                       (1 << 0)
> +#define ATMEL_HLCDC_LAYER_YUV                        (2 << 0)
> +#define ATMEL_HLCDC_RGB_MODE(m)                      (((m) & 0xf) << 4)
> +#define ATMEL_HLCDC_CLUT_MODE(m)             (((m) & 0x3) << 8)
> +#define ATMEL_HLCDC_YUV_MODE(m)                      (((m) & 0xf) << 12)
> +#define ATMEL_HLCDC_YUV422ROT                        (1 << 16)
> +#define ATMEL_HLCDC_YUV422SWP                        (1 << 17)
> +#define ATMEL_HLCDC_DSCALEOPT                        (1 << 20)
> +
> +#define ATMEL_HLCDC_XRGB4444_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(0))
> +#define ATMEL_HLCDC_ARGB4444_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(1))
> +#define ATMEL_HLCDC_RGBA4444_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(2))
> +#define ATMEL_HLCDC_RGB565_MODE                      (ATMEL_HLCDC_LAYER_RGB 
> | ATMEL_HLCDC_RGB_MODE(3))
> +#define ATMEL_HLCDC_ARGB1555_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(4))
> +#define ATMEL_HLCDC_XRGB8888_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(9))
> +#define ATMEL_HLCDC_RGB888_MODE                      (ATMEL_HLCDC_LAYER_RGB 
> | ATMEL_HLCDC_RGB_MODE(10))
> +#define ATMEL_HLCDC_ARGB8888_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(12))
> +#define ATMEL_HLCDC_RGBA8888_MODE            (ATMEL_HLCDC_LAYER_RGB | 
> ATMEL_HLCDC_RGB_MODE(13))
> +
> +#define ATMEL_HLCDC_AYUV_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(0))
> +#define ATMEL_HLCDC_YUYV_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(1))
> +#define ATMEL_HLCDC_UYVY_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(2))
> +#define ATMEL_HLCDC_YVYU_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(3))
> +#define ATMEL_HLCDC_VYUY_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(4))
> +#define ATMEL_HLCDC_NV61_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(5))
> +#define ATMEL_HLCDC_YUV422_MODE                      (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(6))
> +#define ATMEL_HLCDC_NV21_MODE                        (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(7))
> +#define ATMEL_HLCDC_YUV420_MODE                      (ATMEL_HLCDC_LAYER_YUV 
> | ATMEL_HLCDC_YUV_MODE(8))
> +
> +#define ATMEL_HLCDC_LAYER_POS_CFG(p)         ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.pos)
> +#define ATMEL_HLCDC_LAYER_SIZE_CFG(p)                
> ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.size)
> +#define ATMEL_HLCDC_LAYER_MEMSIZE_CFG(p)     ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.memsize)
> +#define ATMEL_HLCDC_LAYER_XSTRIDE_CFG(p)     ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.xstride)
> +#define ATMEL_HLCDC_LAYER_PSTRIDE_CFG(p)     ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.pstride)
> +#define ATMEL_HLCDC_LAYER_DFLTCOLOR_CFG(p)   ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.default_color)
> +#define ATMEL_HLCDC_LAYER_CRKEY_CFG(p)               
> ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.chroma_key)
> +#define ATMEL_HLCDC_LAYER_CRKEY_MASK_CFG(p)  ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.chroma_key_mask)
> +
> +#define ATMEL_HLCDC_LAYER_GENERAL_CFG(p)     ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.general_config)
> +#define ATMEL_HLCDC_LAYER_CRKEY                      BIT(0)
> +#define ATMEL_HLCDC_LAYER_INV                        BIT(1)
> +#define ATMEL_HLCDC_LAYER_ITER2BL            BIT(2)
> +#define ATMEL_HLCDC_LAYER_ITER                       BIT(3)
> +#define ATMEL_HLCDC_LAYER_REVALPHA           BIT(4)
> +#define ATMEL_HLCDC_LAYER_GAEN                       BIT(5)
> +#define ATMEL_HLCDC_LAYER_LAEN                       BIT(6)
> +#define ATMEL_HLCDC_LAYER_OVR                        BIT(7)
> +#define ATMEL_HLCDC_LAYER_DMA                        BIT(8)
> +#define ATMEL_HLCDC_LAYER_REP                        BIT(9)
> +#define ATMEL_HLCDC_LAYER_DSTKEY             BIT(10)
> +#define ATMEL_HLCDC_LAYER_DISCEN             BIT(11)
> +#define ATMEL_HLCDC_LAYER_GA_MASK            GENMASK(23, 16)

Is this value (16) ^^^^ related to this one vvvv ?

> +#define ATMEL_HLCDC_LAYER_GA_SHIFT           16
> +
> +#define ATMEL_HLCDC_LAYER_CSC_CFG(p, o)              
> ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.csc + o)
> +
> +#define ATMEL_HLCDC_LAYER_DISC_POS_CFG(p)    ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.disc_pos)
> +
> +#define ATMEL_HLCDC_LAYER_DISC_SIZE_CFG(p)   ATMEL_HLCDC_LAYER_CFG(p, 
> (p)->desc->layout.disc_size)
> +
> +#define ATMEL_HLCDC_MAX_PLANES                       3
> +
> +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED        BIT(0)
> +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED  BIT(1)
> +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE    BIT(2)
> +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN BIT(3)
> +
> +/**
> + * Atmel HLCDC Layer registers layout structure
> + *
> + * Each HLCDC layer has its own register organization and a given register
> + * by be placed differently on 2 different layers depending on its

by?

> + * capabilities.
> + * This structure stores common registers layout for a given layer and is
> + * used by HLCDC layer code to chose the appropriate register to write to

s/chose/choose/


> + * or to read from.
> + *
> + * For all fields, a value of zero means "unsupported".
> + *
> + * See Atmel's datasheet for a detailled description of these registers.
> + *
> + * @xstride: xstride registers
> + * @pstride: pstride registers
> + * @pos: position register
> + * @size: displayed size register
> + * @memsize: memory size register
> + * @default_color: default color register
> + * @chroma_key: chroma key register
> + * @chroma_key_mask: chroma key mask register
> + * @general_config: general layer config register
> + * @disc_pos: discard area position register
> + * @disc_size: discard area size register
> + * @csc: color space conversion register
> + */
> +struct atmel_hlcdc_layer_cfg_layout {
> +     int xstride[ATMEL_HLCDC_MAX_PLANES];
> +     int pstride[ATMEL_HLCDC_MAX_PLANES];
> +     int pos;
> +     int size;
> +     int memsize;
> +     int default_color;
> +     int chroma_key;
> +     int chroma_key_mask;
> +     int general_config;
> +     int disc_pos;
> +     int disc_size;
> +     int csc;
> +};
> +
> +/**
> + * Atmel HLCDC framebuffer flip structure
> + *
> + * This structure is allocated when someone asked for a layer update (most
> + * likely a DRM plane update, either primary, overlay or cursor plane) and
> + * released when the layer do not need to reference the framebuffer object
> + * anymore (i.e. the layer was disabled or updated).
> + *
> + * @fb: the referenced framebuffer object.
> + * @refcnt: the number of GEM object still referenced by the layer.
> + *       When no more objects are referenced the fb flip structure is
> + *       added to the garbage collector.
> + * @ngems: number of GEM objects referenced by the fb element.

Please update documentation of this structure.

> + */
> +struct atmel_hlcdc_layer_fb_flip {
> +     struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> +     struct drm_flip_task *task;
> +     struct drm_framebuffer *fb;
> +     int ngems;
> +     u32 status;
> +};
> +
> +/**
> + * Atmel HLCDC DMA descriptor structure
> + *
> + * This structure is used by the HLCDC DMA engine to schedule a DMA transfer.
> + *
> + * The structure fields must remain in this specific order, because they're
> + * used by the HLCDC DMA engine, which expect them in this order.
> + *
> + * @addr: buffer DMA address
> + * @ctrl: DMA transfer options
> + * @next: next DMA descriptor to fetch
> + * @gem_flip: the attached gem_flip operation
> + */
> +struct atmel_hlcdc_dma_channel_dscr {
> +     dma_addr_t addr;
> +     u32 ctrl;
> +     dma_addr_t next;
> +     u32 status;
> +} __aligned(sizeof(u64));

Can you add a comment in the documentation section above about this
alignement constrain (which is needed for sure)?

> +
> +/**
> + * Atmel HLCDC layer types
> + */
> +enum atmel_hlcdc_layer_type {
> +     ATMEL_HLCDC_BASE_LAYER,
> +     ATMEL_HLCDC_OVERLAY_LAYER,
> +     ATMEL_HLCDC_CURSOR_LAYER,
> +     ATMEL_HLCDC_PP_LAYER,
> +};
> +
> +/**
> + * Atmel HLCDC Supported formats structure
> + *
> + * This structure list all the formats supported by a given layer.
> + *
> + * @nformats: number of supported formats
> + * @formats: supported formats
> + */
> +struct atmel_hlcdc_formats {
> +     int nformats;
> +     uint32_t *formats;
> +};
> +
> +/**
> + * Atmel HLCDC Layer description structure
> + *
> + * This structure describe the capabilities provided by a given layer.
> + *
> + * @name: layer name
> + * @type: layer type
> + * @id: layer id
> + * @regs_offset: offset of the layer registers from the HLCDC registers base
> + * @nconfigs: number of config registers provided by this layer

"formats" missing?

> + * @layout: config registers layout
> + * @max_width: maximum width supported by this layer (0 means unlimited)
> + * @max_height: maximum height supported by this layer (0 means unlimited)
> + */
> +struct atmel_hlcdc_layer_desc {
> +     const char *name;
> +     enum atmel_hlcdc_layer_type type;
> +     int id;
> +     int regs_offset;
> +     int nconfigs;
> +     struct atmel_hlcdc_formats *formats;
> +     struct atmel_hlcdc_layer_cfg_layout layout;
> +     int max_width;
> +     int max_height;
> +};
> +
> +/**
> + * Atmel HLCDC Layer Update Slot structure
> + *
> + * This structure stores layer update requests to be applied on next frame.
> + * This is the base structure behind the atomic layer update infrastructure.
> + *
> + * Atomic layer update provides a way to update all layer's parameters
> + * simultaneously. This is needed to avoid incompatible sequential updates
> + * like this one:
> + * 1) update layer format from RGB888 (1 plane/buffer) to YUV422
> + *    (2 planes/buffers)
> + * 2) the format update is applied but the DMA channel for the second
> + *    plane/buffer is not enabled
> + * 3) enable the DMA channel for the second plane
> + *
> + * @dscrs: DMA channel descriptors

^^^ not in structure.

> + * @fb_flip: fb_flip object
> + * @updated_configs: bitmask used to record modified configs
> + * @configs: new config values
> + */
> +struct atmel_hlcdc_layer_update_slot {
> +     struct atmel_hlcdc_layer_fb_flip *fb_flip;
> +     unsigned long *updated_configs;
> +     u32 *configs;
> +};
> +
> +/**
> + * Atmel HLCDC Layer Update structure
> + *
> + * This structure provides a way to queue layer update requests.
> + *
> + * At a given time there is at most:
> + *  - one pending update request, which means the update request has been
> + *    commited (or validated) and is waiting for the DMA channel(s) to be
> + *    available
> + *  - one request being prepared, which means someone started a layer update
> + *    but has not commited it yet. There cannot be more than one started
> + *    request, because the update lock is taken when starting a layer update
> + *    and release when commiting or rolling back the request.
> + *
> + * @slots: update slots. One is used for pending request and the other one
> + *      for started update request
> + * @pending: the pending slot index or -1 if no request is pending
> + * @next: the started update slot index or -1 no update has been started
> + */
> +struct atmel_hlcdc_layer_update {
> +     struct atmel_hlcdc_layer_update_slot slots[2];
> +     int pending;
> +     int next;
> +};
> +
> +enum atmel_hlcdc_layer_dma_channel_status {
> +     ATMEL_HLCDC_LAYER_DISABLED,
> +     ATMEL_HLCDC_LAYER_ENABLED,
> +     ATMEL_HLCDC_LAYER_DISABLING,
> +};
> +
> +/**
> + * Atmel HLCDC Layer DMA channel structure
> + *
> + * This structure stores informations on the DMA channel associated to a
> + * given layer.
> + *
> + * @status: DMA channel status
> + * @cur: current framebuffer
> + * @queue: next framebuffer
> + * @dscrs: allocated DMA descriptors
> + */
> +struct atmel_hlcdc_layer_dma_channel {
> +     enum atmel_hlcdc_layer_dma_channel_status status;
> +     struct atmel_hlcdc_layer_fb_flip *cur;
> +     struct atmel_hlcdc_layer_fb_flip *queue;
> +     struct atmel_hlcdc_dma_channel_dscr *dscrs;
> +};
> +
> +/**
> + * Atmel HLCDC Layer structure
> + *
> + * This structure stores information on the layer instance.
> + *
> + * @desc: layer description
> + * @max_planes: maximum planes/buffers that can be associated with this 
> layer.
> + *          This depends on the supported formats.
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
> + * @dma: dma channel
> + * @gc: fb flip garbage collector
> + * @update: update handler
> + * @lock: layer lock
> + */
> +struct atmel_hlcdc_layer {
> +     const struct atmel_hlcdc_layer_desc *desc;
> +     int max_planes;
> +     struct atmel_hlcdc *hlcdc;
> +     struct workqueue_struct *wq;
> +     struct drm_flip_work gc;
> +     struct atmel_hlcdc_layer_dma_channel dma;
> +     struct atmel_hlcdc_layer_update update;
> +     spinlock_t lock;
> +};
> +
> +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer);
> +
> +int atmel_hlcdc_layer_init(struct drm_device *dev,
> +                        struct atmel_hlcdc_layer *layer,
> +                        const struct atmel_hlcdc_layer_desc *desc);
> +
> +void atmel_hlcdc_layer_cleanup(struct drm_device *dev,
> +                            struct atmel_hlcdc_layer *layer);
> +
> +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer);
> +
> +int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer);
> +
> +void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg,
> +                               u32 mask, u32 val);
> +
> +void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer,
> +                                  struct drm_framebuffer *fb,
> +                                  unsigned int *offsets);
> +
> +void atmel_hlcdc_layer_update_set_finished(struct atmel_hlcdc_layer *layer,
> +                                        void (*finished)(void *data),
> +                                        void *finished_data);
> +
> +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
> +
> +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
> +
> +#endif /* DRM_ATMEL_HLCDC_LAYER_H */
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> new file mode 100644
> index 0000000..8d3a5cb
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> @@ -0,0 +1,443 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <jjhiblot at traphandler.com>
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/of_graph.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_panel.h>
> +
> +#include "atmel_hlcdc_dc.h"
> +
> +/**
> + * Atmel HLCDC RGB output mode
> + */
> +enum atmel_hlcdc_connector_rgb_mode {
> +     ATMEL_HLCDC_CONNECTOR_RGB444,
> +     ATMEL_HLCDC_CONNECTOR_RGB565,
> +     ATMEL_HLCDC_CONNECTOR_RGB666,
> +     ATMEL_HLCDC_CONNECTOR_RGB888,
> +};
> +
> +struct atmel_hlcdc_slave;
> +
> +/**
> + * Atmel HLCDC Slave device operations structure
> + *
> + * This structure defines an abstraction to be implemented by each slave
> + * device type (panel, convertors, ...).
> + *
> + * @enable: Enable the slave device
> + * @disable: Disable the slave device
> + * @get_modes: retrieve modes supported by the slave device

mode_valid missing.

> + * @destroy: detroy the slave device and all associated data
> + */
> +struct atmel_hlcdc_slave_ops {
> +     int (*enable)(struct atmel_hlcdc_slave *slave);
> +     int (*disable)(struct atmel_hlcdc_slave *slave);
> +     int (*get_modes)(struct atmel_hlcdc_slave *slave);
> +     int (*mode_valid)(struct atmel_hlcdc_slave *slave,
> +                       struct drm_display_mode *mode);
> +     void (*destroy)(struct atmel_hlcdc_slave *slave);
> +};
> +
> +/**
> + * Atmel HLCDC Slave device structure
> + *
> + * This structure is the base slave device structure to be overloaded by
> + * each slave device implementation.
> + *
> + * @ops: slave device operations
> + */
> +struct atmel_hlcdc_slave {
> +     const struct atmel_hlcdc_slave_ops *ops;
> +};
> +
> +/**
> + * Atmel HLCDC Panel device structure
> + *
> + * This structure is specialization of the slave device structure to
> + * interface with drm panels.
> + *
> + * @slave: base slave device fields
> + * @panel: drm panel attached to this slave device
> + */
> +struct atmel_hlcdc_panel {
> +     struct atmel_hlcdc_slave slave;
> +     struct drm_panel *panel;
> +};
> +
> +static inline struct atmel_hlcdc_panel *
> +atmel_hlcdc_slave_to_panel(struct atmel_hlcdc_slave *slave)
> +{
> +     return container_of(slave, struct atmel_hlcdc_panel, slave);
> +}
> +
> +/**
> + * Atmel HLCDC RGB connector structure
> + *
> + * This structure stores informations about an DRM panel connected through
> + * the RGB connector.
> + *
> + * @connector: DRM connector
> + * @encoder: DRM encoder
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device

^^^^ it is "dc" now.

> + * @slave: slave device connected to this output
> + * @endpoint: DT endpoint representing this output
> + * @dpms: current DPMS mode
> + */
> +struct atmel_hlcdc_rgb_output {
> +     struct drm_connector connector;
> +     struct drm_encoder encoder;
> +     struct atmel_hlcdc_dc *dc;
> +     struct atmel_hlcdc_slave *slave;
> +     struct of_endpoint endpoint;
> +     int dpms;
> +};
> +
> +static inline struct atmel_hlcdc_rgb_output *
> +drm_connector_to_atmel_hlcdc_rgb_output(struct drm_connector *connector)
> +{
> +     return container_of(connector, struct atmel_hlcdc_rgb_output,
> +                         connector);
> +}
> +
> +static inline struct atmel_hlcdc_rgb_output *
> +drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder)
> +{
> +     return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
> +}
> +
> +static int atmel_hlcdc_panel_enable(struct atmel_hlcdc_slave *slave)
> +{
> +     struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave);
> +
> +     return drm_panel_enable(panel->panel);
> +}
> +
> +static int atmel_hlcdc_panel_disable(struct atmel_hlcdc_slave *slave)
> +{
> +     struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave);
> +
> +     return drm_panel_disable(panel->panel);
> +}
> +
> +static int atmel_hlcdc_panel_get_modes(struct atmel_hlcdc_slave *slave)
> +{
> +     struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave);
> +
> +     return panel->panel->funcs->get_modes(panel->panel);
> +}
> +
> +static int atmel_hlcdc_panel_mode_valid(struct atmel_hlcdc_slave *slave,
> +                                     struct drm_display_mode *mode)
> +{
> +     return MODE_OK;
> +}
> +
> +static void atmel_hlcdc_panel_destroy(struct atmel_hlcdc_slave *slave)
> +{
> +     struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave);
> +
> +     drm_panel_detach(panel->panel);
> +     kfree(panel);
> +}
> +
> +static const struct atmel_hlcdc_slave_ops atmel_hlcdc_panel_ops = {
> +     .enable = atmel_hlcdc_panel_enable,
> +     .disable = atmel_hlcdc_panel_disable,
> +     .get_modes = atmel_hlcdc_panel_get_modes,
> +     .mode_valid = atmel_hlcdc_panel_mode_valid,
> +     .destroy = atmel_hlcdc_panel_destroy,
> +};
> +
> +static struct atmel_hlcdc_slave *
> +atmel_hlcdc_panel_detect(struct atmel_hlcdc_rgb_output *rgb)
> +{
> +     struct device_node *np;
> +     struct drm_panel *p = NULL;
> +     struct atmel_hlcdc_panel *panel;
> +
> +     np = of_graph_get_remote_port_parent(rgb->endpoint.local_node);
> +     if (!np)
> +             return NULL;
> +
> +     p = of_drm_find_panel(np);
> +     of_node_put(np);
> +
> +     if (p) {
> +             panel = kzalloc(sizeof(*panel), GFP_KERNEL);
> +             if (!panel)
> +                     return NULL;
> +
> +             drm_panel_attach(p, &rgb->connector);
> +             panel->panel = p;
> +             panel->slave.ops = &atmel_hlcdc_panel_ops;
> +             return &panel->slave;
> +     }
> +
> +     return NULL;
> +}
> +
> +static void atmel_hlcdc_rgb_encoder_dpms(struct drm_encoder *encoder,
> +                                      int mode)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
> +
> +     if (mode != DRM_MODE_DPMS_ON)
> +             mode = DRM_MODE_DPMS_OFF;
> +
> +     if (mode == rgb->dpms)
> +             return;
> +
> +     if (mode != DRM_MODE_DPMS_ON)
> +             rgb->slave->ops->disable(rgb->slave);
> +     else
> +             rgb->slave->ops->enable(rgb->slave);
> +
> +     rgb->dpms = mode;
> +}
> +
> +static bool
> +atmel_hlcdc_rgb_encoder_mode_fixup(struct drm_encoder *encoder,
> +                                const struct drm_display_mode *mode,
> +                                struct drm_display_mode *adjusted)
> +{
> +     return true;
> +}
> +
> +static void atmel_hlcdc_rgb_encoder_prepare(struct drm_encoder *encoder)
> +{
> +     atmel_hlcdc_rgb_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void atmel_hlcdc_rgb_encoder_commit(struct drm_encoder *encoder)
> +{
> +     atmel_hlcdc_rgb_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
> +}
> +
> +static void
> +atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder,
> +                              struct drm_display_mode *mode,
> +                              struct drm_display_mode *adjusted)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
> +     struct drm_display_info *info = &rgb->connector.display_info;
> +     unsigned int cfg;
> +
> +     cfg = 0;
> +
> +     if (info->num_bus_formats) {
> +             switch (info->bus_formats[0]) {
> +             case VIDEO_BUS_FMT_RGB565_1X16:
> +                     cfg |= ATMEL_HLCDC_CONNECTOR_RGB565 << 8;
> +                     break;
> +             case VIDEO_BUS_FMT_RGB666_1X18:
> +                     cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8;
> +                     break;
> +             case VIDEO_BUS_FMT_RGB888_1X24:
> +                     cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8;
> +                     break;
> +             case VIDEO_BUS_FMT_RGB444_1X12:
> +             default:
> +                     break;
> +             }
> +     }
> +
> +     regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5),
> +                        ATMEL_HLCDC_MODE_MASK,
> +                        cfg);
> +}
> +
> +static struct drm_encoder_helper_funcs atmel_hlcdc_rgb_encoder_helper_funcs 
> = {
> +     .dpms = atmel_hlcdc_rgb_encoder_dpms,
> +     .mode_fixup = atmel_hlcdc_rgb_encoder_mode_fixup,
> +     .prepare = atmel_hlcdc_rgb_encoder_prepare,
> +     .commit = atmel_hlcdc_rgb_encoder_commit,
> +     .mode_set = atmel_hlcdc_rgb_encoder_mode_set,
> +};
> +
> +static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder)
> +{
> +     drm_encoder_cleanup(encoder);
> +     memset(encoder, 0, sizeof(*encoder));
> +}
> +
> +static const struct drm_encoder_funcs atmel_hlcdc_rgb_encoder_funcs = {
> +     .destroy = atmel_hlcdc_rgb_encoder_destroy,
> +};
> +
> +static int atmel_hlcdc_rgb_get_modes(struct drm_connector *connector)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_connector_to_atmel_hlcdc_rgb_output(connector);
> +
> +     return rgb->slave->ops->get_modes(rgb->slave);
> +}
> +
> +static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector,
> +                                   struct drm_display_mode *mode)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_connector_to_atmel_hlcdc_rgb_output(connector);
> +     int ret;
> +
> +     ret = atmel_hlcdc_dc_mode_valid(rgb->dc, mode);
> +     if (ret != MODE_OK)
> +             return ret;
> +
> +     return rgb->slave->ops->mode_valid(rgb->slave, mode);
> +}
> +
> +static struct drm_encoder *
> +atmel_hlcdc_rgb_best_encoder(struct drm_connector *connector)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_connector_to_atmel_hlcdc_rgb_output(connector);
> +
> +     return &rgb->encoder;
> +}
> +
> +static struct drm_connector_helper_funcs 
> atmel_hlcdc_rgb_connector_helper_funcs = {
> +     .get_modes = atmel_hlcdc_rgb_get_modes,
> +     .mode_valid = atmel_hlcdc_rgb_mode_valid,
> +     .best_encoder = atmel_hlcdc_rgb_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +atmel_hlcdc_rgb_connector_detect(struct drm_connector *connector, bool force)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_connector_to_atmel_hlcdc_rgb_output(connector);
> +
> +     if (!rgb->slave) {
> +             /* At the moment we only support panel devices */
> +             rgb->slave = atmel_hlcdc_panel_detect(rgb);
> +     }
> +
> +     if (rgb->slave)
> +             return connector_status_connected;
> +
> +     return connector_status_unknown;
> +}
> +
> +static void
> +atmel_hlcdc_rgb_connector_destroy(struct drm_connector *connector)
> +{
> +     struct atmel_hlcdc_rgb_output *rgb =
> +                     drm_connector_to_atmel_hlcdc_rgb_output(connector);
> +
> +     if (rgb->slave && rgb->slave->ops->destroy)
> +             rgb->slave->ops->destroy(rgb->slave);
> +
> +     drm_connector_unregister(&rgb->connector);
> +     drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs atmel_hlcdc_rgb_connector_funcs = {
> +     .dpms = drm_helper_connector_dpms,
> +     .detect = atmel_hlcdc_rgb_connector_detect,
> +     .fill_modes = drm_helper_probe_single_connector_modes,
> +     .destroy = atmel_hlcdc_rgb_connector_destroy,
> +};
> +
> +static int atmel_hlcdc_create_output(struct drm_device *dev,
> +                                  struct of_endpoint *ep)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     struct atmel_hlcdc_rgb_output *rgb;
> +     int ret;
> +
> +     rgb = devm_kzalloc(dev->dev, sizeof(*rgb), GFP_KERNEL);
> +     if (!rgb)
> +             return -ENOMEM;
> +
> +     rgb->endpoint = *ep;
> +
> +     rgb->dpms = DRM_MODE_DPMS_OFF;
> +
> +     rgb->dc = dc;
> +
> +     drm_encoder_helper_add(&rgb->encoder,
> +                            &atmel_hlcdc_rgb_encoder_helper_funcs);
> +     ret = drm_encoder_init(dev, &rgb->encoder,
> +                            &atmel_hlcdc_rgb_encoder_funcs,
> +                            DRM_MODE_ENCODER_LVDS);
> +     if (ret)
> +             return ret;
> +
> +     rgb->connector.dpms = DRM_MODE_DPMS_OFF;
> +     rgb->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
> +     drm_connector_helper_add(&rgb->connector,
> +                              &atmel_hlcdc_rgb_connector_helper_funcs);
> +     ret = drm_connector_init(dev, &rgb->connector,
> +                              &atmel_hlcdc_rgb_connector_funcs,
> +                              DRM_MODE_CONNECTOR_LVDS);
> +     if (ret)
> +             goto err_encoder_cleanup;
> +
> +     drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder);
> +
> +     ret = drm_connector_register(&rgb->connector);
> +     if (ret)
> +             goto err_connector_cleanup;
> +
> +     rgb->encoder.possible_crtcs = 0x1;
> +
> +     return 0;
> +
> +err_connector_cleanup:
> +     drm_connector_cleanup(&rgb->connector);
> +err_encoder_cleanup:
> +     drm_encoder_cleanup(&rgb->encoder);
> +
> +     return ret;
> +}
> +
> +int atmel_hlcdc_create_outputs(struct drm_device *dev)
> +{
> +     struct device_node *port_np, *np;
> +     struct of_endpoint ep;
> +     int ret;
> +
> +     port_np = of_get_child_by_name(dev->dev->of_node, "port");
> +     if (!port_np)
> +             return -EINVAL;
> +
> +     np = of_get_child_by_name(port_np, "endpoint");
> +     of_node_put(port_np);
> +
> +     if (!np)
> +             return -EINVAL;
> +
> +     ret = of_graph_parse_endpoint(np, &ep);
> +     of_node_put(port_np);
> +
> +     if (ret)
> +             return ret;
> +
> +     ret = atmel_hlcdc_create_output(dev, &ep);
> +     if (ret)
> +             return ret;
> +
> +     return 0;
> +}
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c 
> b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> new file mode 100644
> index 0000000..82bbf93
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> @@ -0,0 +1,831 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <boris.brezillon at free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published 
> by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but 
> WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along 
> with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "atmel_hlcdc_dc.h"
> +
> +#define SUBPIXEL_MASK                        0xffff
> +
> +static uint32_t rgb_formats[] = {
> +     DRM_FORMAT_XRGB4444,
> +     DRM_FORMAT_ARGB4444,
> +     DRM_FORMAT_RGBA4444,
> +     DRM_FORMAT_ARGB1555,
> +     DRM_FORMAT_RGB565,
> +     DRM_FORMAT_RGB888,
> +     DRM_FORMAT_XRGB8888,
> +     DRM_FORMAT_ARGB8888,
> +     DRM_FORMAT_RGBA8888,
> +};
> +
> +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats = {
> +     .formats = rgb_formats,
> +     .nformats = ARRAY_SIZE(rgb_formats),
> +};
> +
> +static uint32_t rgb_and_yuv_formats[] = {
> +     DRM_FORMAT_XRGB4444,
> +     DRM_FORMAT_ARGB4444,
> +     DRM_FORMAT_RGBA4444,
> +     DRM_FORMAT_ARGB1555,
> +     DRM_FORMAT_RGB565,
> +     DRM_FORMAT_RGB888,
> +     DRM_FORMAT_XRGB8888,
> +     DRM_FORMAT_ARGB8888,
> +     DRM_FORMAT_RGBA8888,
> +     DRM_FORMAT_AYUV,
> +     DRM_FORMAT_YUYV,
> +     DRM_FORMAT_UYVY,
> +     DRM_FORMAT_YVYU,
> +     DRM_FORMAT_VYUY,
> +     DRM_FORMAT_NV21,
> +     DRM_FORMAT_NV61,
> +     DRM_FORMAT_YUV422,
> +     DRM_FORMAT_YUV420,
> +};
> +
> +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats = {
> +     .formats = rgb_and_yuv_formats,
> +     .nformats = ARRAY_SIZE(rgb_and_yuv_formats),
> +};
> +
> +static int atmel_hlcdc_format_to_plane_mode(u32 format, u32 *mode)
> +{
> +     switch (format) {
> +     case DRM_FORMAT_XRGB4444:
> +             *mode = ATMEL_HLCDC_XRGB4444_MODE;
> +             break;
> +     case DRM_FORMAT_ARGB4444:
> +             *mode = ATMEL_HLCDC_ARGB4444_MODE;
> +             break;
> +     case DRM_FORMAT_RGBA4444:
> +             *mode = ATMEL_HLCDC_RGBA4444_MODE;
> +             break;
> +     case DRM_FORMAT_RGB565:
> +             *mode = ATMEL_HLCDC_RGB565_MODE;
> +             break;
> +     case DRM_FORMAT_RGB888:
> +             *mode = ATMEL_HLCDC_RGB888_MODE;
> +             break;
> +     case DRM_FORMAT_ARGB1555:
> +             *mode = ATMEL_HLCDC_ARGB1555_MODE;
> +             break;
> +     case DRM_FORMAT_XRGB8888:
> +             *mode = ATMEL_HLCDC_XRGB8888_MODE;
> +             break;
> +     case DRM_FORMAT_ARGB8888:
> +             *mode = ATMEL_HLCDC_ARGB8888_MODE;
> +             break;
> +     case DRM_FORMAT_RGBA8888:
> +             *mode = ATMEL_HLCDC_RGBA8888_MODE;
> +             break;
> +     case DRM_FORMAT_AYUV:
> +             *mode = ATMEL_HLCDC_AYUV_MODE;
> +             break;
> +     case DRM_FORMAT_YUYV:
> +             *mode = ATMEL_HLCDC_YUYV_MODE;
> +             break;
> +     case DRM_FORMAT_UYVY:
> +             *mode = ATMEL_HLCDC_UYVY_MODE;
> +             break;
> +     case DRM_FORMAT_YVYU:
> +             *mode = ATMEL_HLCDC_YVYU_MODE;
> +             break;
> +     case DRM_FORMAT_VYUY:
> +             *mode = ATMEL_HLCDC_VYUY_MODE;
> +             break;
> +     case DRM_FORMAT_NV21:
> +             *mode = ATMEL_HLCDC_NV21_MODE;
> +             break;
> +     case DRM_FORMAT_NV61:
> +             *mode = ATMEL_HLCDC_NV61_MODE;
> +             break;
> +     case DRM_FORMAT_YUV420:
> +             *mode = ATMEL_HLCDC_YUV420_MODE;
> +             break;
> +     case DRM_FORMAT_YUV422:
> +             *mode = ATMEL_HLCDC_YUV422_MODE;
> +             break;
> +     default:
> +             return -ENOTSUPP;
> +     }
> +
> +     return 0;
> +}
> +
> +static bool atmel_hlcdc_format_embedds_alpha(u32 format)
> +{
> +     int i;
> +
> +     for (i = 0; i < sizeof(format); i++) {
> +             char tmp = (format >> (8 * i)) & 0xff;
> +
> +             if (tmp == 'A')
> +                     return true;
> +     }
> +
> +     return false;
> +}
> +
> +static u32 heo_downscaling_xcoef[] = {
> +     0x11343311,
> +     0x000000f7,
> +     0x1635300c,
> +     0x000000f9,
> +     0x1b362c08,
> +     0x000000fb,
> +     0x1f372804,
> +     0x000000fe,
> +     0x24382400,
> +     0x00000000,
> +     0x28371ffe,
> +     0x00000004,
> +     0x2c361bfb,
> +     0x00000008,
> +     0x303516f9,
> +     0x0000000c,
> +};
> +
> +static u32 heo_downscaling_ycoef[] = {
> +     0x00123737,
> +     0x00173732,
> +     0x001b382d,
> +     0x001f3928,
> +     0x00243824,
> +     0x0028391f,
> +     0x002d381b,
> +     0x00323717,
> +};
> +
> +static u32 heo_upscaling_xcoef[] = {
> +     0xf74949f7,
> +     0x00000000,
> +     0xf55f33fb,
> +     0x000000fe,
> +     0xf5701efe,
> +     0x000000ff,
> +     0xf87c0dff,
> +     0x00000000,
> +     0x00800000,
> +     0x00000000,
> +     0x0d7cf800,
> +     0x000000ff,
> +     0x1e70f5ff,
> +     0x000000fe,
> +     0x335ff5fe,
> +     0x000000fb,
> +};
> +
> +static u32 heo_upscaling_ycoef[] = {
> +     0x00004040,
> +     0x00075920,
> +     0x00056f0c,
> +     0x00027b03,
> +     0x00008000,
> +     0x00037b02,
> +     0x000c6f05,
> +     0x00205907,
> +};
> +
> +static void
> +atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     const struct atmel_hlcdc_layer_cfg_layout *layout =
> +                                             &plane->layer.desc->layout;
> +
> +     if (layout->size)
> +             atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                          layout->size,
> +                                          0xffffffff,
> +                                          (req->crtc_w - 1) |
> +                                          ((req->crtc_h - 1) << 16));
> +
> +     if (layout->memsize)
> +             atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                          layout->memsize,
> +                                          0xffffffff,
> +                                          (req->src_w - 1) |
> +                                          ((req->src_h - 1) << 16));
> +
> +     if (layout->pos)
> +             atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                          layout->pos,
> +                                          0xffffffff,
> +                                          req->crtc_x |
> +                                          (req->crtc_y  << 16));
> +
> +     /* TODO: rework the rescaling part */
> +     if (req->crtc_w != req->src_w || req->crtc_h != req->src_h) {
> +             u32 factor_reg = 0;
> +
> +             if (req->crtc_w != req->src_w) {
> +                     int i;
> +                     u32 factor;
> +                     u32 *coeff_tab = heo_upscaling_xcoef;
> +                     u32 max_memsize;
> +
> +                     if (req->crtc_w < req->src_w)
> +                             coeff_tab = heo_downscaling_xcoef;
> +                     for (i = 0; i < ARRAY_SIZE(heo_upscaling_xcoef); i++)
> +                             atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                                          17 + i,
> +                                                          0xffffffff,
> +                                                          coeff_tab[i]);
> +                     factor = ((8 * 256 * req->src_w) - (256 * 4)) /
> +                              req->crtc_w;
> +                     factor++;
> +                     max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
> +                                   2048;
> +                     if (max_memsize > req->src_w)
> +                             factor--;
> +                     factor_reg |= factor | 0x80000000;
> +             }
> +
> +             if (req->crtc_h != req->src_h) {
> +                     int i;
> +                     u32 factor;
> +                     u32 *coeff_tab = heo_upscaling_ycoef;
> +                     u32 max_memsize;
> +
> +                     if (req->crtc_w < req->src_w)
> +                             coeff_tab = heo_downscaling_ycoef;
> +                     for (i = 0; i < ARRAY_SIZE(heo_upscaling_ycoef); i++)
> +                             atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                                          33 + i,
> +                                                          0xffffffff,
> +                                                          coeff_tab[i]);
> +                     factor = ((8 * 256 * req->src_w) - (256 * 4)) /
> +                              req->crtc_w;
> +                     factor++;
> +                     max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
> +                                   2048;
> +                     if (max_memsize > req->src_w)
> +                             factor--;
> +                     factor_reg |= (factor << 16) | 0x80000000;
> +             }
> +
> +             atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
> +                                          factor_reg);
> +     }
> +}
> +
> +static void
> +atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     const struct atmel_hlcdc_layer_cfg_layout *layout =
> +                                             &plane->layer.desc->layout;
> +     unsigned int cfg = ATMEL_HLCDC_LAYER_DMA;
> +
> +     if (plane->base.type != DRM_PLANE_TYPE_PRIMARY) {
> +             cfg |= ATMEL_HLCDC_LAYER_OVR | ATMEL_HLCDC_LAYER_ITER2BL |
> +                    ATMEL_HLCDC_LAYER_ITER;
> +
> +             if (atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format))
> +                     cfg |= ATMEL_HLCDC_LAYER_LAEN;
> +             else
> +                     cfg |= ATMEL_HLCDC_LAYER_GAEN;
> +     }
> +
> +     atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config,
> +                                  ATMEL_HLCDC_LAYER_ITER2BL |
> +                                  ATMEL_HLCDC_LAYER_ITER |
> +                                  ATMEL_HLCDC_LAYER_GAEN |
> +                                  ATMEL_HLCDC_LAYER_LAEN |
> +                                  ATMEL_HLCDC_LAYER_OVR |
> +                                  ATMEL_HLCDC_LAYER_DMA, cfg);
> +}
> +
> +static void atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     u32 cfg;
> +     int ret;
> +
> +     ret = atmel_hlcdc_format_to_plane_mode(req->fb->pixel_format, &cfg);
> +     if (ret)
> +             return;
> +
> +     if ((req->fb->pixel_format == DRM_FORMAT_YUV422 ||
> +          req->fb->pixel_format == DRM_FORMAT_NV61) &&
> +         (plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))))
> +             cfg |= ATMEL_HLCDC_YUV422ROT;
> +
> +     atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                  ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
> +                                  0xffffffff,
> +                                  cfg);
> +
> +     if (req->fb->pixel_format == DRM_FORMAT_RGB888)
> +             cfg = ATMEL_HLCDC_LAYER_DMA_ROTDIS;

Can you add a little comment: why Rotation is disabled? ort a /* TODO */

> +     else
> +             cfg = 0;
> +
> +     atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                  ATMEL_HLCDC_LAYER_DMA_CFG_ID,
> +                                  ATMEL_HLCDC_LAYER_DMA_ROTDIS, cfg);
> +}
> +
> +static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     struct atmel_hlcdc_layer *layer = &plane->layer;
> +     const struct atmel_hlcdc_layer_cfg_layout *layout =
> +                                                     &layer->desc->layout;
> +     int i;
> +
> +     atmel_hlcdc_layer_update_set_fb(&plane->layer, req->fb, req->offsets);
> +
> +     for (i = 0; i < req->nplanes; i++) {
> +             if (layout->xstride[i]) {
> +                     atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                             layout->xstride[i],
> +                                             0xffffffff,
> +                                             req->xstride[i]);
> +             }
> +
> +             if (layout->pstride[i]) {
> +                     atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                             layout->pstride[i],
> +                                             0xffffffff,
> +                                             req->pstride[i]);
> +             }
> +     }
> +}
> +
> +static int atmel_hlcdc_plane_check_update_req(struct drm_plane *p,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +     const struct atmel_hlcdc_layer_cfg_layout *layout =
> +                                             &plane->layer.desc->layout;
> +
> +     if (!layout->size &&
> +         (req->crtc->mode.crtc_hdisplay != req->crtc_w ||
> +          req->crtc->mode.crtc_vdisplay != req->crtc_h))
> +             return -EINVAL;
> +
> +     if (plane->layer.desc->max_height &&
> +         req->crtc_h > plane->layer.desc->max_height)
> +             return -EINVAL;
> +
> +     if (plane->layer.desc->max_width &&
> +         req->crtc_w > plane->layer.desc->max_width)
> +             return -EINVAL;
> +
> +     if ((req->crtc_h != req->src_h || req->crtc_w != req->src_w) &&
> +         (!layout->memsize ||
> +          atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format)))
> +             return -EINVAL;
> +
> +     if (req->crtc_x < 0 || req->crtc_y < 0)
> +             return -EINVAL;
> +
> +     if (req->crtc_w + req->crtc_x > req->crtc->mode.crtc_hdisplay ||
> +         req->crtc_h + req->crtc_y > req->crtc->mode.crtc_vdisplay)
> +             return -EINVAL;
> +
> +     return 0;
> +}
> +
> +int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +     unsigned int patched_crtc_w;
> +     unsigned int patched_crtc_h;
> +     unsigned int patched_src_w;
> +     unsigned int patched_src_h;
> +     unsigned int tmp;
> +     int x_offset = 0;
> +     int y_offset = 0;
> +     int hsub = 1;
> +     int vsub = 1;
> +     int i;
> +
> +     if ((req->src_x | req->src_y | req->src_w | req->src_h) &
> +         SUBPIXEL_MASK)
> +             return -EINVAL;
> +
> +     req->src_x >>= 16;
> +     req->src_y >>= 16;
> +     req->src_w >>= 16;
> +     req->src_h >>= 16;
> +
> +     req->nplanes = drm_format_num_planes(req->fb->pixel_format);
> +     if (req->nplanes > ATMEL_HLCDC_MAX_PLANES)
> +             return -EINVAL;
> +
> +     /*
> +      * Swap width and size in case of 90 or 270 degrees rotation
> +      */
> +     if (plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))) {
> +             tmp = req->crtc_w;
> +             req->crtc_w = req->crtc_h;
> +             req->crtc_h = tmp;
> +             tmp = req->src_w;
> +             req->src_w = req->src_h;
> +             req->src_h = tmp;
> +     }
> +
> +     if (req->crtc_x + req->crtc_w > req->crtc->mode.hdisplay)
> +             patched_crtc_w = req->crtc->mode.hdisplay - req->crtc_x;
> +     else
> +             patched_crtc_w = req->crtc_w;
> +
> +     if (req->crtc_x < 0) {
> +             patched_crtc_w += req->crtc_x;
> +             x_offset = -req->crtc_x;
> +             req->crtc_x = 0;
> +     }
> +
> +     if (req->crtc_y + req->crtc_h > req->crtc->mode.vdisplay)
> +             patched_crtc_h = req->crtc->mode.vdisplay - req->crtc_y;
> +     else
> +             patched_crtc_h = req->crtc_h;
> +
> +     if (req->crtc_y < 0) {
> +             patched_crtc_h += req->crtc_y;
> +             y_offset = -req->crtc_y;
> +             req->crtc_y = 0;
> +     }
> +
> +     patched_src_w = DIV_ROUND_CLOSEST(patched_crtc_w * req->src_w,
> +                                       req->crtc_w);
> +     patched_src_h = DIV_ROUND_CLOSEST(patched_crtc_h * req->src_h,
> +                                       req->crtc_h);
> +
> +     hsub = drm_format_horz_chroma_subsampling(req->fb->pixel_format);
> +     vsub = drm_format_vert_chroma_subsampling(req->fb->pixel_format);
> +
> +     for (i = 0; i < req->nplanes; i++) {
> +             unsigned int offset = 0;
> +             int xdiv = i ? hsub : 1;
> +             int ydiv = i ? vsub : 1;
> +
> +             req->bpp[i] = drm_format_plane_cpp(req->fb->pixel_format, i);
> +             if (!req->bpp[i])
> +                     return -EINVAL;
> +
> +             switch (plane->rotation & 0xf) {
> +             case BIT(DRM_ROTATE_90):
> +                     offset = ((y_offset + req->src_y + patched_src_w - 1) /
> +                               ydiv) * req->fb->pitches[i];
> +                     offset += ((x_offset + req->src_x) / xdiv) *
> +                               req->bpp[i];
> +                     req->xstride[i] = ((patched_src_w - 1) / ydiv) *
> +                                       req->fb->pitches[i];
> +                     req->pstride[i] = -req->fb->pitches[i] - req->bpp[i];
> +                     break;
> +             case BIT(DRM_ROTATE_180):
> +                     offset = ((y_offset + req->src_y + patched_src_h - 1) /
> +                               ydiv) * req->fb->pitches[i];
> +                     offset += ((x_offset + req->src_x + patched_src_w - 1) /
> +                                xdiv) * req->bpp[i];
> +                     req->xstride[i] = ((((patched_src_w - 1) / xdiv) - 1) *
> +                                        req->bpp[i]) - req->fb->pitches[i];
> +                     req->pstride[i] = -2 * req->bpp[i];
> +                     break;
> +             case BIT(DRM_ROTATE_270):
> +                     offset = ((y_offset + req->src_y) / ydiv) *
> +                              req->fb->pitches[i];
> +                     offset += ((x_offset + req->src_x + patched_src_h - 1) /
> +                                xdiv) * req->bpp[i];
> +                     req->xstride[i] = -(((patched_src_w - 1) / ydiv) *
> +                                         req->fb->pitches[i]) -
> +                                       (2 * req->bpp[i]);
> +                     req->pstride[i] = req->fb->pitches[i] - req->bpp[i];
> +                     break;
> +             case BIT(DRM_ROTATE_0):
> +             default:
> +                     offset = ((y_offset + req->src_y) / ydiv) *
> +                              req->fb->pitches[i];
> +                     offset += ((x_offset + req->src_x) / xdiv) *
> +                               req->bpp[i];
> +                     req->xstride[i] = req->fb->pitches[i] -
> +                                       ((patched_src_w / xdiv) *
> +                                        req->bpp[i]);
> +                     req->pstride[i] = 0;
> +                     break;
> +             }
> +
> +             req->offsets[i] = offset + req->fb->offsets[i];
> +     }
> +
> +     req->src_w = patched_src_w;
> +     req->src_h = patched_src_h;
> +     req->crtc_w = patched_crtc_w;
> +     req->crtc_h = patched_crtc_h;
> +
> +     return atmel_hlcdc_plane_check_update_req(p, req);
> +}
> +
> +int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
> +                             struct atmel_hlcdc_plane_update_req *req)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +     int ret;
> +
> +     ret = atmel_hlcdc_layer_update_start(&plane->layer);
> +     if (ret)
> +             return ret;
> +
> +     atmel_hlcdc_plane_update_pos_and_size(plane, req);
> +     atmel_hlcdc_plane_update_general_settings(plane, req);
> +     atmel_hlcdc_plane_update_format(plane, req);
> +     atmel_hlcdc_plane_update_buffers(plane, req);
> +
> +     atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> +     return 0;
> +}
> +
> +static int atmel_hlcdc_plane_update(struct drm_plane *p,
> +                                 struct drm_crtc *crtc,
> +                                 struct drm_framebuffer *fb,
> +                                 int crtc_x, int crtc_y,
> +                                 unsigned int crtc_w, unsigned int crtc_h,
> +                                 uint32_t src_x, uint32_t src_y,
> +                                 uint32_t src_w, uint32_t src_h)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +     struct atmel_hlcdc_plane_update_req req;
> +     int ret = 0;
> +
> +     memset(&req, 0, sizeof(req));
> +     req.crtc_x = crtc_x;
> +     req.crtc_y = crtc_y;
> +     req.crtc_w = crtc_w;
> +     req.crtc_h = crtc_h;
> +     req.src_x = src_x;
> +     req.src_y = src_y;
> +     req.src_w = src_w;
> +     req.src_h = src_h;
> +     req.fb = fb;
> +     req.crtc = crtc;
> +
> +     ret = atmel_hlcdc_plane_prepare_update_req(&plane->base, &req);
> +     if (ret)
> +             return ret;
> +
> +     if (!req.crtc_h || !req.crtc_w)
> +             return atmel_hlcdc_layer_disable(&plane->layer);
> +
> +     return atmel_hlcdc_plane_apply_update_req(&plane->base, &req);
> +}
> +
> +static int atmel_hlcdc_plane_disable(struct drm_plane *p)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +
> +     return atmel_hlcdc_layer_disable(&plane->layer);
> +}
> +
> +static void atmel_hlcdc_plane_destroy(struct drm_plane *p)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +
> +     if (plane->base.fb)
> +             drm_framebuffer_unreference(plane->base.fb);
> +
> +     atmel_hlcdc_layer_cleanup(p->dev, &plane->layer);
> +
> +     drm_plane_cleanup(p);
> +     devm_kfree(p->dev->dev, plane);
> +}
> +
> +static int atmel_hlcdc_plane_set_alpha(struct atmel_hlcdc_plane *plane,
> +                                    u8 alpha)
> +{
> +     atmel_hlcdc_layer_update_start(&plane->layer);
> +     atmel_hlcdc_layer_update_cfg(&plane->layer,
> +                                  plane->layer.desc->layout.general_config,
> +                                  ATMEL_HLCDC_LAYER_GA_MASK,
> +                                  alpha << ATMEL_HLCDC_LAYER_GA_SHIFT);
> +     atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> +     return 0;
> +}
> +
> +static int atmel_hlcdc_plane_set_rotation(struct atmel_hlcdc_plane *plane,
> +                                       unsigned int rotation)
> +{
> +     plane->rotation = rotation;
> +
> +     return 0;
> +}
> +
> +static int atmel_hlcdc_plane_set_property(struct drm_plane *p,
> +                                       struct drm_property *property,
> +                                       uint64_t value)
> +{
> +     struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
> +     struct atmel_hlcdc_plane_properties *props = plane->properties;
> +
> +     if (property == props->alpha)
> +             atmel_hlcdc_plane_set_alpha(plane, value);
> +     else if (property == props->rotation)
> +             atmel_hlcdc_plane_set_rotation(plane, value);
> +     else
> +             return -EINVAL;
> +
> +     return 0;
> +}
> +
> +static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane 
> *plane,
> +                             const struct atmel_hlcdc_layer_desc *desc,
> +                             struct atmel_hlcdc_plane_properties *props)
> +{
> +     struct regmap *regmap = plane->layer.hlcdc->regmap;
> +
> +     if (desc->type == ATMEL_HLCDC_OVERLAY_LAYER ||
> +         desc->type == ATMEL_HLCDC_CURSOR_LAYER) {
> +             drm_object_attach_property(&plane->base.base,
> +                                        props->alpha, 255);
> +
> +             /* Set default alpha value */
> +             regmap_update_bits(regmap,
> +                             desc->regs_offset +
> +                             ATMEL_HLCDC_LAYER_GENERAL_CFG(&plane->layer),
> +                             ATMEL_HLCDC_LAYER_GA_MASK,
> +                             ATMEL_HLCDC_LAYER_GA_MASK);
> +     }
> +
> +     if (desc->layout.xstride && desc->layout.pstride)
> +             drm_object_attach_property(&plane->base.base,
> +                                        props->rotation,
> +                                        BIT(DRM_ROTATE_0));
> +
> +     if (desc->layout.csc) {
> +             /*
> +              * TODO: decare a "yuv-to-rgb-conv-factors" property to let
> +              * userspace modify these factors (using a BLOB property ?).
> +              */
> +             regmap_write(regmap,
> +                          desc->regs_offset +
> +                          ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 0),
> +                          0x4c900091);
> +             regmap_write(regmap,
> +                          desc->regs_offset +
> +                          ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 1),
> +                          0x7a5f5090);
> +             regmap_write(regmap,
> +                          desc->regs_offset +
> +                          ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 2),
> +                          0x40040890);
> +     }
> +}
> +
> +static struct drm_plane_funcs layer_plane_funcs = {
> +     .update_plane = atmel_hlcdc_plane_update,
> +     .disable_plane = atmel_hlcdc_plane_disable,
> +     .set_property = atmel_hlcdc_plane_set_property,
> +     .destroy = atmel_hlcdc_plane_destroy,
> +};
> +
> +static struct atmel_hlcdc_plane *
> +atmel_hlcdc_plane_create(struct drm_device *dev,
> +                      const struct atmel_hlcdc_layer_desc *desc,
> +                      struct atmel_hlcdc_plane_properties *props)
> +{
> +     struct atmel_hlcdc_plane *plane;
> +     enum drm_plane_type type;
> +     int ret;
> +
> +     plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
> +     if (!plane)
> +             return ERR_PTR(-ENOMEM);
> +
> +     ret = atmel_hlcdc_layer_init(dev, &plane->layer, desc);
> +     if (ret)
> +             return ERR_PTR(ret);
> +
> +     if (desc->type == ATMEL_HLCDC_BASE_LAYER)
> +             type = DRM_PLANE_TYPE_PRIMARY;
> +     else if (desc->type == ATMEL_HLCDC_CURSOR_LAYER)
> +             type = DRM_PLANE_TYPE_CURSOR;
> +     else
> +             type = DRM_PLANE_TYPE_OVERLAY;
> +
> +     ret = drm_universal_plane_init(dev, &plane->base, 0,
> +                                    &layer_plane_funcs,
> +                                    desc->formats->formats,
> +                                    desc->formats->nformats, type);
> +     if (ret)
> +             return ERR_PTR(ret);
> +
> +     /* Set default property values*/
> +     atmel_hlcdc_plane_init_properties(plane, desc, props);
> +
> +     return plane;
> +}
> +
> +static struct atmel_hlcdc_plane_properties *
> +atmel_hlcdc_plane_create_properties(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_plane_properties *props;
> +
> +     props = devm_kzalloc(dev->dev, sizeof(*props), GFP_KERNEL);
> +     if (!props)
> +             return ERR_PTR(-ENOMEM);
> +
> +     props->alpha = drm_property_create_range(dev, 0, "alpha", 0, 255);
> +     if (!props->alpha)
> +             return ERR_PTR(-ENOMEM);
> +
> +     props->rotation = drm_mode_create_rotation_property(dev,
> +                                             BIT(DRM_ROTATE_0) |
> +                                             BIT(DRM_ROTATE_90) |
> +                                             BIT(DRM_ROTATE_180) |
> +                                             BIT(DRM_ROTATE_270));
> +     if (!props->rotation)
> +             return ERR_PTR(-ENOMEM);
> +
> +     return props;
> +}
> +
> +struct atmel_hlcdc_planes *
> +atmel_hlcdc_create_planes(struct drm_device *dev)
> +{
> +     struct atmel_hlcdc_dc *dc = dev->dev_private;
> +     struct atmel_hlcdc_plane_properties *props;
> +     struct atmel_hlcdc_planes *planes;
> +     const struct atmel_hlcdc_layer_desc *descs = dc->desc->layers;
> +     int nlayers = dc->desc->nlayers;
> +     int i;
> +
> +     planes = devm_kzalloc(dev->dev, sizeof(*planes), GFP_KERNEL);
> +     if (!planes)
> +             return ERR_PTR(-ENOMEM);
> +
> +     for (i = 0; i < nlayers; i++) {
> +             if (descs[i].type == ATMEL_HLCDC_OVERLAY_LAYER)
> +                     planes->noverlays++;
> +     }
> +
> +     if (planes->noverlays) {
> +             planes->overlays = devm_kzalloc(dev->dev,
> +                                             planes->noverlays *
> +                                             sizeof(*planes->overlays),
> +                                             GFP_KERNEL);
> +             if (!planes->overlays)
> +                     return ERR_PTR(-ENOMEM);
> +     }
> +
> +     props = atmel_hlcdc_plane_create_properties(dev);
> +     if (IS_ERR(props))
> +             return ERR_CAST(props);
> +
> +     planes->noverlays = 0;
> +     for (i = 0; i < nlayers; i++) {
> +             struct atmel_hlcdc_plane *plane;
> +
> +             if (descs[i].type == ATMEL_HLCDC_PP_LAYER)
> +                     continue;
> +
> +             plane = atmel_hlcdc_plane_create(dev, &descs[i], props);
> +             if (IS_ERR(plane))
> +                     return ERR_CAST(plane);
> +
> +             plane->properties = props;
> +
> +             switch (descs[i].type) {
> +             case ATMEL_HLCDC_BASE_LAYER:
> +                     if (planes->primary)
> +                             return ERR_PTR(-EINVAL);
> +                     planes->primary = plane;
> +                     break;
> +
> +             case ATMEL_HLCDC_OVERLAY_LAYER:
> +                     planes->overlays[planes->noverlays++] = plane;
> +                     break;
> +
> +             case ATMEL_HLCDC_CURSOR_LAYER:
> +                     if (planes->cursor)
> +                             return ERR_PTR(-EINVAL);
> +                     planes->cursor = plane;
> +                     break;
> +
> +             default:
> +                     break;
> +             }
> +     }
> +
> +     return planes;
> +}

I'm absolutely not an expert in DRM drivers ;-) but here is my review.
the code is clean and documentation is nice (once updated).

Thanks a lot for your work on this driver!

Once these little things are corected, you can add my:

Acked-by: Nicolas Ferre <nicolas.ferre at atmel.com>

On the driver and associated DT binding.

Thanks, bye,
-- 
Nicolas Ferre

Reply via email to