lkml.org 
[lkml]   [2014]   [Jul]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    Date
    From
    SubjectRe: [PATCH v2 3/7] drm: add Atmel HLCDC Display Controller support
    Hello,

    It's been almost a month and I only got reviews pointing minor issues.

    I'd really like to get feedback/reviews from DRM/KMS maintainers and/or
    experienced developers (this is my first DRM/KMS driver I'm pretty sure
    there are things to fix).

    Best Regards,

    Boris

    On Mon, 9 Jun 2014 18:04:16 +0200
    Boris BREZILLON <boris.brezillon@free-electrons.com> wrote:

    > The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
    > at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
    > controller device.
    >
    > This display controller support at least one primary plane and might
    > provide several overlays and an hardware cursor depending on the IP
    > version.
    >
    > Signed-off-by: Boris BREZILLON <boris.brezillon@free-electrons.com>
    > ---
    > .../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 59 ++
    > drivers/gpu/drm/Kconfig | 2 +
    > drivers/gpu/drm/Makefile | 1 +
    > drivers/gpu/drm/atmel-hlcdc/Kconfig | 11 +
    > drivers/gpu/drm/atmel-hlcdc/Makefile | 7 +
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 529 ++++++++++++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
    > drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
    > drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
    > drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
    > 14 files changed, 3410 insertions(+)
    > create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
    > 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_panel.c
    > create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
    > create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
    > create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
    >
    > diff --git a/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
    > new file mode 100644
    > index 0000000..594bdb2
    > --- /dev/null
    > +++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
    > @@ -0,0 +1,59 @@
    > +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) DRM driver
    > +
    > +The Atmel HLCDC Display Controller is subdevice of the HLCDC MFD device.
    > +See Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt for more details.
    > +
    > +Required properties:
    > + - compatible: value should be one of the following:
    > + "atmel,hlcdc-dc"
    > + - interrupts: the HLCDC interrupt definition
    > + - pinctrl-names: the pin control state names. Should contain "default",
    > + "rgb-444", "rgb-565", "rgb-666" and "rgb-888".
    > + - pinctrl-[0-4]: should contain the pinctrl states described by pinctrl
    > + names.
    > + - atmel,panel: Should contain a phandle with 2 parameters.
    > + The first cell is a phandle to a DRM panel device
    > + The second cell encodes the RGB mode, which can take the following values:
    > + * 0: RGB444
    > + * 1: RGB565
    > + * 2: RGB666
    > + * 3: RGB888
    > + The third cell encodes specific flags describing LCD signals configuration
    > + (see Atmel's datasheet for a full description of these fields):
    > + * bit 0: HSPOL: Horizontal Synchronization Pulse Polarity
    > + * bit 1: VSPOL: Vertical Synchronization Pulse Polarity
    > + * bit 2: VSPDLYS: Vertical Synchronization Pulse Start
    > + * bit 3: VSPDLYE: Vertical Synchronization Pulse End
    > + * bit 4: DISPPOL: Display Signal Polarity
    > + * bit 7: DISPDLY: LCD Controller Display Power Signal Synchronization
    > + * bit 12: VSPSU: LCD Controller Vertical synchronization Pulse Setup Configuration
    > + * bit 13: VSPHO: LCD Controller Vertical synchronization Pulse Hold Configuration
    > + * bit 16-20: GUARDTIME: LCD DISPLAY Guard Time
    > +
    > +Example:
    > +
    > + hlcdc: hlcdc@f0030000 {
    > + compatible = "atmel,sama5d3-hlcdc";
    > + reg = <0xf0030000 0x2000>;
    > + clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>;
    > + clock-names = "periph_clk","sys_clk", "slow_clk";
    > + status = "disabled";
    > +
    > + hlcdc-display-controller {
    > + compatible = "atmel,hlcdc-dc";
    > + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
    > + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
    > + pinctrl-0 = <&pinctrl_lcd_base>;
    > + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
    > + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
    > + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
    > + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
    > + };
    > +
    > + hlcdc_pwm: hlcdc-pwm {
    > + compatible = "atmel,hlcdc-pwm";
    > + pinctrl-names = "default";
    > + pinctrl-0 = <&pinctrl_lcd_pwm>;
    > + #pwm-cells = <3>;
    > + };
    > + };
    > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
    > index d1cc2f6..df6f0c1 100644
    > --- a/drivers/gpu/drm/Kconfig
    > +++ b/drivers/gpu/drm/Kconfig
    > @@ -182,6 +182,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 48e38ba..28c8a61 100644
    > --- a/drivers/gpu/drm/Makefile
    > +++ b/drivers/gpu/drm/Makefile
    > @@ -54,6 +54,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..bc07315
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/Kconfig
    > @@ -0,0 +1,11 @@
    > +config DRM_ATMEL_HLCDC
    > + tristate "DRM Support for ATMEL HLCDC Display Controller"
    > + depends on DRM && OF && MFD_ATMEL_HLCDC && COMMON_CLK
    > + select DRM_GEM_CMA_HELPER
    > + select DRM_KMS_HELPER
    > + select DRM_KMS_FB_HELPER
    > + select DRM_KMS_CMA_HELPER
    > + select DRM_PANEL
    > + 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..bf9fe0b
    > --- /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_panel.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..a18492e
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
    > @@ -0,0 +1,529 @@
    > +/*
    > + * Copyright (C) 2014 Traphandler
    > + * Copyright (C) 2014 Free Electrons
    > + *
    > + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
    > + * Author: Boris BREZILLON <boris.brezillon@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"
    > +
    > +/**
    > + * Structure storing hardware cursor informations like its position, its size
    > + * or the GEM object currently used to display the HW cursor.
    > + *
    > + * @gem: the cursor GEM object
    > + * @pitch: GEM object pitch
    > + * @height: cursor height in pixels
    > + * @width: cursor width in pixels
    > + * @x: cursor x position
    > + * @y: cursor y position
    > + * @offset: start offset from the provided buffer
    > + * @patched_height: patched height value after adapting the image size
    > + * depending on cursor position
    > + * @patched_width: patched width value after adapting the image size depending
    > + * on cursor position
    > + * @patched_x: patched x value after adapting to cursor position (positive
    > + * value)
    > + * @patched_y: patched x value after adapting to cursor position (positive
    > + * value)
    > + */
    > +struct atmel_hlcdc_crtc_cursor_info {
    > + struct drm_gem_cma_object *gem;
    > + unsigned int pitch;
    > + u32 height;
    > + u32 width;
    > + int x;
    > + int y;
    > +
    > + /*
    > + * These fields are automatically calculated by
    > + * atmel_hlcdc_crtc_cursor_prepare_req.
    > + */
    > + unsigned int offset;
    > + u32 patched_height;
    > + u32 patched_width;
    > + int patched_x;
    > + int patched_y;
    > +};
    > +
    > +/**
    > + * Structure storing HW cursor status.
    > + *
    > + * @status: the current cursor status
    > + * @req: the requested cursor changes
    > + * @plane: the hardware cursor plane
    > + * @lock: cursor lock held when modifying cursor req or status
    > + */
    > +struct atmel_hlcdc_crtc_cursor {
    > + struct atmel_hlcdc_crtc_cursor_info status;
    > + struct atmel_hlcdc_crtc_cursor_info req;
    > + struct atmel_hlcdc_plane *plane;
    > + struct mutex lock;
    > +};
    > +
    > +/**
    > + * Atmel HLCDC CRTC structure
    > + *
    > + * @base: base DRM CRTC structure
    > + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
    > + * @event: pointer to the current page flip event
    > + * @id: CRTC id (returned by drm_crtc_index)
    > + * @dpms: DPMS mode
    > + * @cursor: hardware cursor status
    > + */
    > +struct atmel_hlcdc_crtc {
    > + struct drm_crtc base;
    > + struct atmel_hlcdc *hlcdc;
    > + struct drm_pending_vblank_event *event;
    > + int id;
    > + int dpms;
    > + struct atmel_hlcdc_crtc_cursor cursor;
    > +};
    > +
    > +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;
    > +
    > + if (mode != DRM_MODE_DPMS_ON)
    > + mode = DRM_MODE_DPMS_OFF;
    > +
    > + pm_runtime_get_sync(dev->dev);
    > +
    > + if (mode == DRM_MODE_DPMS_ON)
    > + pm_runtime_forbid(dev->dev);
    > + else
    > + pm_runtime_allow(dev->dev);
    > +
    > + pm_runtime_put_sync(dev->dev);
    > +}
    > +
    > +static int atmel_hlcdc_crtc_mode_set(struct drm_crtc *c,
    > + struct drm_display_mode *mode,
    > + struct drm_display_mode *adjusted,
    > + 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->hlcdc->regmap;
    > + struct drm_plane *plane = c->primary;
    > + struct drm_framebuffer *fb;
    > + struct videomode vm;
    > +
    > + vm.vfront_porch = mode->vsync_start - mode->vdisplay;
    > + vm.vback_porch = mode->vtotal - mode->vsync_end;
    > + vm.vsync_len = mode->vsync_end - mode->vsync_start;
    > + vm.hfront_porch = mode->hsync_start - mode->hdisplay;
    > + vm.hback_porch = mode->htotal - mode->hsync_end;
    > + vm.hsync_len = mode->hsync_end - mode->hsync_start;
    > +
    > + if (vm.hsync_len > 0x40 || vm.hsync_len < 0 ||
    > + vm.vsync_len > 0x40 || vm.vsync_len < 0 ||
    > + vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
    > + vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
    > + vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
    > + vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
    > + mode->hdisplay > 2048 || mode->hdisplay < 0 ||
    > + mode->vdisplay > 2048 || mode->vdisplay < 0)
    > + return -EINVAL;
    > +
    > + 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 - 1) << 16));
    > +
    > + regmap_write(regmap, ATMEL_HLCDC_CFG(3),
    > + (vm.hfront_porch - 1) | ((vm.hback_porch - 1) << 16));
    > +
    > + regmap_write(regmap, ATMEL_HLCDC_CFG(4),
    > + (mode->hdisplay - 1) | ((mode->vdisplay - 1) << 16));
    > +
    > + fb = plane->fb;
    > + plane->fb = old_fb;
    > +
    > + return plane->funcs->update_plane(plane, c, fb,
    > + 0, 0,
    > + mode->hdisplay, mode->vdisplay,
    > + c->x << 16, c->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,
    > + .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(void *data)
    > +{
    > + struct atmel_hlcdc_crtc *crtc = data;
    > + 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);
    > +}
    > +
    > +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 drm_plane *plane = c->primary;
    > +
    > + if (crtc->event)
    > + return -EBUSY;
    > +
    > + if (event) {
    > + crtc->event = event;
    > + drm_vblank_get(c->dev, crtc->id);
    > + }
    > +
    > + return plane->funcs->update_plane(plane, c, fb,
    > + 0, 0,
    > + c->mode.hdisplay, c->mode.vdisplay,
    > + c->x << 16, c->y << 16,
    > + c->mode.hdisplay << 16,
    > + c->mode.vdisplay << 16);
    > +}
    > +
    > +static void atmel_hlcdc_crtc_cursor_prepare_req(struct atmel_hlcdc_crtc *crtc)
    > +{
    > + int bpp = drm_format_plane_cpp(DRM_FORMAT_ARGB8888, 0);
    > + struct atmel_hlcdc_crtc_cursor_info *info = &crtc->cursor.req;
    > + int x_offset = 0;
    > + int y_offset = 0;
    > +
    > + if (!info->gem) {
    > + info->width = 0;
    > + info->height = 0;
    > + info->pitch = 0;
    > + info->offset = 0;
    > + return;
    > + }
    > +
    > + info->pitch = info->width * bpp;
    > +
    > + if (info->x + info->width > crtc->base.mode.hdisplay)
    > + info->patched_width = crtc->base.mode.hdisplay - info->x;
    > + else
    > + info->patched_width = info->width;
    > +
    > + if (info->x < 0) {
    > + info->patched_width += info->x;
    > + x_offset = -info->x;
    > + info->patched_x = 0;
    > + } else {
    > + info->patched_x = info->x;
    > + }
    > +
    > + if (info->y + info->height > crtc->base.mode.vdisplay)
    > + info->patched_height = crtc->base.mode.vdisplay - info->y;
    > + else
    > + info->patched_height = info->height;
    > +
    > + if (info->y < 0) {
    > + info->patched_height += info->y;
    > + y_offset = -info->y;
    > + info->patched_y = 0;
    > + } else {
    > + info->patched_y = info->y;
    > + }
    > +
    > + info->offset = (x_offset * bpp) +
    > + (y_offset * info->pitch);
    > +}
    > +
    > +static int atmel_hlcdc_crtc_cursor_apply_req(struct atmel_hlcdc_crtc *crtc)
    > +{
    > + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
    > + struct atmel_hlcdc_crtc_cursor_info *req = &crtc->cursor.req;
    > + struct atmel_hlcdc_crtc_cursor_info *status = &crtc->cursor.status;
    > + int ret;
    > +
    > + if (unlikely(!plane))
    > + return -ENOTSUPP;
    > +
    > + if (!req->gem ||
    > + !req->patched_width || !req->patched_height) {
    > + ret = plane->base.funcs->disable_plane(&plane->base);
    > +
    > + if (!ret)
    > + goto out;
    > + else
    > + goto err;
    > + }
    > +
    > + ret = atmel_hlcdc_layer_update_start(&plane->layer);
    > + if (ret)
    > + return ret;
    > +
    > + atmel_hlcdc_plane_update_general_settings(plane, DRM_FORMAT_ARGB8888);
    > +
    > + ret = atmel_hlcdc_plane_update_format(plane, DRM_FORMAT_ARGB8888);
    > + if (ret)
    > + goto err;
    > +
    > + ret = atmel_hlcdc_plane_update_pos_and_size(plane, &crtc->base,
    > + req->patched_x,
    > + req->patched_y,
    > + req->patched_width,
    > + req->patched_height,
    > + 0, 0,
    > + req->patched_width,
    > + req->patched_height);
    > + if (ret)
    > + goto err;
    > +
    > + ret = atmel_hlcdc_plane_update_buffers(plane,
    > + DRM_FORMAT_ARGB8888,
    > + &req->gem,
    > + &req->pitch,
    > + &req->offset,
    > + 0, 0,
    > + req->patched_width,
    > + req->height);
    > + if (ret)
    > + goto err;
    > +
    > + if (!plane->base.crtc)
    > + plane->base.crtc = &crtc->base;
    > +
    > + atmel_hlcdc_layer_update_commit(&plane->layer);
    > +
    > +out:
    > + if (req->gem)
    > + drm_gem_object_reference(&req->gem->base);
    > +
    > + if (status->gem)
    > + drm_gem_object_unreference_unlocked(&status->gem->base);
    > +
    > + *status = *req;
    > +
    > + return 0;
    > +
    > +err:
    > + atmel_hlcdc_layer_update_rollback(&plane->layer);
    > + return ret;
    > +}
    > +
    > +static int atmel_hlcdc_crtc_cursor_set(struct drm_crtc *c,
    > + struct drm_file *file_priv,
    > + uint32_t handle,
    > + uint32_t width, uint32_t height)
    > +{
    > + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
    > + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
    > + struct drm_gem_cma_object *cma_gem = NULL;
    > + int ret;
    > +
    > + if (unlikely(!plane))
    > + return -ENOTSUPP;
    > +
    > + mutex_lock(&crtc->cursor.lock);
    > +
    > + if (handle) {
    > + struct drm_gem_object *gem;
    > +
    > + gem = drm_gem_object_lookup(c->dev, file_priv, handle);
    > + if (unlikely(!gem)) {
    > + ret = -ENOENT;
    > + goto out;
    > + }
    > +
    > + cma_gem = to_drm_gem_cma_obj(gem);
    > + }
    > +
    > + crtc->cursor.req = crtc->cursor.status;
    > +
    > + crtc->cursor.req.gem = cma_gem;
    > + crtc->cursor.req.width = width;
    > + crtc->cursor.req.height = height;
    > + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
    > +
    > + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
    > + if (cma_gem)
    > + drm_gem_object_unreference_unlocked(&cma_gem->base);
    > +
    > +out:
    > + mutex_unlock(&crtc->cursor.lock);
    > +
    > + return ret;
    > +}
    > +
    > +static int atmel_hlcdc_crtc_cursor_move(struct drm_crtc *c, int x, int y)
    > +{
    > + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
    > + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
    > + int ret;
    > +
    > + if (unlikely(!plane))
    > + return -ENOTSUPP;
    > +
    > + mutex_lock(&crtc->cursor.lock);
    > + /*
    > + * If there's no cursor update pending, get the current
    > + * cursor size and buffer.
    > + */
    > + crtc->cursor.req = crtc->cursor.status;
    > +
    > + crtc->cursor.req.x = x;
    > + crtc->cursor.req.y = y;
    > + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
    > +
    > + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
    > + mutex_unlock(&crtc->cursor.lock);
    > +
    > + 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,
    > +};
    > +
    > +static const struct drm_crtc_funcs atmel_hlcdc_crtc_with_cursor_funcs = {
    > + .page_flip = atmel_hlcdc_crtc_page_flip,
    > + .set_config = drm_crtc_helper_set_config,
    > + .destroy = atmel_hlcdc_crtc_destroy,
    > + .cursor_set = atmel_hlcdc_crtc_cursor_set,
    > + .cursor_move = atmel_hlcdc_crtc_cursor_move,
    > +};
    > +
    > +int atmel_hlcdc_crtc_create(struct drm_device *dev)
    > +{
    > + struct atmel_hlcdc_dc *dc = dev->dev_private;
    > + struct atmel_hlcdc_planes *planes = dc->planes;
    > + const struct drm_crtc_funcs *funcs;
    > + struct atmel_hlcdc_crtc *crtc;
    > + int ret;
    > + int i;
    > +
    > + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
    > + if (!crtc) {
    > + dev_err(dev->dev, "allocation failed\n");
    > + return -ENOMEM;
    > + }
    > +
    > + mutex_init(&crtc->cursor.lock);
    > + crtc->hlcdc = dc->hlcdc;
    > + crtc->cursor.plane = planes->cursor;
    > +
    > + if (planes->cursor)
    > + funcs = &atmel_hlcdc_crtc_with_cursor_funcs;
    > + else
    > + funcs = &atmel_hlcdc_crtc_funcs;
    > +
    > + ret = drm_crtc_init_with_planes(dev, &crtc->base,
    > + &planes->primary->base,
    > + planes->cursor ? &planes->cursor->base : NULL,
    > + funcs);
    > + if (ret < 0)
    > + goto fail;
    > +
    > + atmel_hlcdc_layer_set_finished(&planes->primary->layer,
    > + atmel_hlcdc_crtc_finish_page_flip,
    > + crtc);
    > +
    > + 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);
    > +
    > + 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..e4ce24e
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
    > @@ -0,0 +1,477 @@
    > +/*
    > + * Copyright (C) 2014 Traphandler
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
    > + * Author: Boris BREZILLON <boris.brezillon@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 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 bit;
    > +
    > + 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;
    > +
    > + bit = ATMEL_HLCDC_LAYER_IRQS_OFFSET;
    > + for_each_set_bit_from(bit, &status, ATMEL_HLCDC_LAYER_IRQS_OFFSET +
    > + ATMEL_HLCDC_MAX_LAYERS) {
    > + int layerid = bit - ATMEL_HLCDC_LAYER_IRQS_OFFSET;
    > + struct atmel_hlcdc_layer *layer = dc->layers[layerid];
    > +
    > + if (layer)
    > + 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);
    > +}
    > +
    > +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_panel_create(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 = dev->platformdev;
    > + const struct atmel_hlcdc_dc_desc *desc;
    > + struct atmel_hlcdc_dc *dc;
    > + int ret;
    > +
    > + desc = platform_get_drvdata(pdev);
    > +
    > + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
    > + if (!dc) {
    > + dev_err(dev->dev, "failed to allocate private data\n");
    > + return -ENOMEM;
    > + }
    > +
    > + dc->desc = platform_get_drvdata(pdev);
    > + 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");
    > + return ret;
    > + }
    > +
    > + 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);
    > + 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);
    > +
    > + dc->fbdev = drm_fbdev_cma_init(dev, 24,
    > + dev->mode_config.num_crtc,
    > + dev->mode_config.num_connector);
    > +
    > + if (IS_ERR(dc->fbdev)) {
    > + ret = PTR_ERR(dc->fbdev);
    > + goto err_periph_clk_disable;
    > + }
    > +
    > + return 0;
    > +
    > +err_periph_clk_disable:
    > + clk_disable_unprepare(dc->hlcdc->periph_clk);
    > +
    > + 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);
    > +
    > + 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;
    > + int i;
    > +
    > + /* Enable interrupts on activated layers */
    > + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) {
    > + if (dc->layers[i])
    > + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER,
    > + BIT(i + 8));
    > + }
    > +
    > + return 0;
    > +}
    > +
    > +static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev)
    > +{
    > +
    > +}
    > +
    > +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 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,
    > + },
    > + },
    > + {
    > + .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 */ },
    > +};
    > +
    > +static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
    > +{
    > + const struct of_device_id *match;
    > + int ret;
    > +
    > + match = of_match_node(atmel_hlcdc_of_match, pdev->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;
    > + }
    > +
    > + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
    > + if (ret)
    > + return ret;
    > +
    > + platform_set_drvdata(pdev, (void *)match->data);
    > +
    > + ret = drm_platform_init(&atmel_hlcdc_dc_driver, pdev);
    > + if (ret)
    > + return ret;
    > +
    > + return 0;
    > +}
    > +
    > +static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
    > +{
    > + drm_put_dev(platform_get_drvdata(pdev));
    > +
    > + return 0;
    > +}
    > +
    > +static const struct of_device_id atmel_hlcdc_dc_of_match[] = {
    > + { .compatible = "atmel,hlcdc-dc" },
    > + { },
    > +};
    > +
    > +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-dc",
    > + .owner = THIS_MODULE,
    > + .of_match_table = atmel_hlcdc_dc_of_match,
    > + },
    > +};
    > +module_platform_driver(atmel_hlcdc_dc_platform_driver);
    > +
    > +MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>");
    > +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@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..5d2919e
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
    > @@ -0,0 +1,178 @@
    > +/*
    > + * Copyright (C) 2014 Traphandler
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
    > + * Author: Boris BREZILLON <boris.brezillon@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
    > + */
    > +struct atmel_hlcdc_plane_properties {
    > + struct drm_property *alpha;
    > +};
    > +
    > +/**
    > + * 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
    > + */
    > +struct atmel_hlcdc_plane {
    > + struct drm_plane base;
    > + struct atmel_hlcdc_layer layer;
    > + struct atmel_hlcdc_plane_properties *properties;
    > + u8 alpha;
    > +};
    > +
    > +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 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
    > + * @planes: instantiated planes
    > + * @layers: active HLCDC layer
    > + */
    > +struct atmel_hlcdc_dc {
    > + struct atmel_hlcdc_dc_desc *desc;
    > + struct atmel_hlcdc *hlcdc;
    > + struct drm_fbdev_cma *fbdev;
    > + struct atmel_hlcdc_planes *planes;
    > + struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
    > +};
    > +
    > +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
    > +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats;
    > +
    > +struct atmel_hlcdc_planes *
    > +atmel_hlcdc_create_planes(struct drm_device *dev);
    > +
    > +int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
    > + u32 pixel_format,
    > + struct drm_gem_cma_object **gems,
    > + unsigned int *pitches,
    > + unsigned int *offsets,
    > + uint32_t src_x, uint32_t src_y,
    > + uint32_t src_w, uint32_t src_h);
    > +
    > +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
    > + u32 format);
    > +
    > +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
    > + u32 format);
    > +
    > +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
    > + struct drm_crtc *crtc,
    > + 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);
    > +
    > +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_panel_create(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..b449fe1
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
    > @@ -0,0 +1,701 @@
    > +/*
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Boris BREZILLON <boris.brezillon@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_gem_flip_release(struct atmel_hlcdc_layer *layer,
    > + struct atmel_hlcdc_layer_gem_flip *flip)
    > +{
    > + int i;
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (!flip->gems[i])
    > + break;
    > +
    > + drm_gem_object_unreference_unlocked(flip->gems[i]);
    > + }
    > +
    > + kfree(flip);
    > +}
    > +
    > +static void atmel_hlcdc_layer_gc_work(struct work_struct *work)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip_gc *gc =
    > + container_of(work,
    > + struct atmel_hlcdc_layer_gem_flip_gc,
    > + work);
    > + struct atmel_hlcdc_layer *layer =
    > + container_of(gc , struct atmel_hlcdc_layer, gc);
    > +
    > + while (true) {
    > + struct atmel_hlcdc_layer_gem_flip *flip;
    > + unsigned long flags;
    > +
    > + spin_lock_irqsave(&gc->list_lock, flags);
    > + flip = list_first_entry_or_null(&gc->list,
    > + struct atmel_hlcdc_layer_gem_flip,
    > + node);
    > + if (flip)
    > + list_del(&flip->node);
    > + spin_unlock_irqrestore(&gc->list_lock, flags);
    > +
    > + if (!flip)
    > + break;
    > +
    > + atmel_hlcdc_layer_gem_flip_release(layer, flip);
    > +
    > + mutex_lock(&gc->finished_lock);
    > + if (gc->finished)
    > + gc->finished(gc->finished_data);
    > + mutex_unlock(&gc->finished_lock);
    > + }
    > +}
    > +
    > +static void
    > +atmel_hlcdc_layer_gem_flip_put(struct atmel_hlcdc_layer *layer,
    > + struct atmel_hlcdc_layer_gem_flip *flip)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
    > + unsigned long flags;
    > +
    > + if (--flip->remaining_gems <= 0) {
    > + spin_lock_irqsave(&gc->list_lock, flags);
    > + list_add_tail(&flip->node,
    > + &gc->list);
    > + spin_unlock_irqrestore(&gc->list_lock, flags);
    > + schedule_work(&gc->work);
    > + }
    > +}
    > +
    > +static void atmel_hlcdc_layer_start_queue(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;
    > + int i;
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + dma->cur[i] = dma->queue[i];
    > + if (!dma->queue[i])
    > + continue;
    > + dma->queue[i] = NULL;
    > +
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
    > + dma->cur[i]->addr);
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
    > + dma->cur[i]->ctrl);
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
    > + dma->cur[i]->next);
    > + }
    > +}
    > +
    > +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
    > +{
    > + 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;
    > + unsigned int cfg;
    > + u32 action = 0;
    > + int i;
    > +
    > + if (upd->pending < 0 || upd->pending > 1)
    > + 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;
    > + }
    > +
    > + if (slot->gem_flip && slot->gem_flip->remaining_gems) {
    > + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + struct atmel_hlcdc_dma_channel_dscr *dscr;
    > +
    > + if (!slot->gem_flip->gems[i])
    > + break;
    > +
    > + dscr = slot->dscrs[i];
    > + slot->dscrs[i] = NULL;
    > +
    > + if (!dma->enabled) {
    > + action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
    > + 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);
    > + dma->cur[i] = dscr;
    > + } else {
    > + action |= ATMEL_HLCDC_LAYER_A2Q;
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
    > + dscr->next);
    > + dma->queue[i] = dscr;
    > + }
    > + }
    > +
    > + dma->enabled = true;
    > + slot->gem_flip = NULL;
    > + }
    > +
    > + if (action)
    > + regmap_write(regmap,
    > + desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
    > + action);
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (!slot->dscrs[i])
    > + continue;
    > +
    > + slot->dscrs[i]->gem_flip = NULL;
    > + slot->dscrs[i] = NULL;
    > + }
    > +
    > + if (slot->gem_flip) {
    > + atmel_hlcdc_layer_gem_flip_put(layer, slot->gem_flip);
    > + slot->gem_flip = NULL;
    > + }
    > +
    > + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
    > + memset(slot->configs, 0,
    > + sizeof(*slot->configs) * layer->desc->nconfigs);
    > +
    > + upd->pending = -1;
    > +}
    > +
    > +static bool
    > +atmel_hlcdc_layer_dma_channel_active(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
    > + int i;
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (dma->cur[i])
    > + return true;
    > + }
    > +
    > + return false;
    > +}
    > +
    > +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip *flip_gc[ATMEL_HLCDC_MAX_PLANES];
    > + 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_dma_channel_dscr *dscr;
    > + unsigned long flags;
    > + unsigned int isr, imr;
    > + unsigned int 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;
    > +
    > + memset(flip_gc, 0, sizeof(flip_gc));
    > +
    > + spin_lock_irqsave(&dma->lock, flags);
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if ((status & ATMEL_HLCDC_LAYER_DONE_IRQ(i)) ||
    > + (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i))) {
    > + dscr = dma->cur[i];
    > + dma->cur[i] = NULL;
    > + flip_gc[i] = dscr->gem_flip;
    > + dscr->gem_flip = NULL;
    > + }
    > +
    > + if (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i)) {
    > + dma->cur[i] = dma->queue[i];
    > + dma->queue[i] = NULL;
    > + }
    > + }
    > +
    > + /*
    > + * The DMA channel might have been disabled before we were able to
    > + * add the new frame to the DMA transfer queue.
    > + * Try to re-enable the channel in this case.
    > + */
    > + if (!atmel_hlcdc_layer_dma_channel_active(layer)) {
    > + if (atmel_hlcdc_layer_dma_channel_busy(layer)) {
    > + atmel_hlcdc_layer_start_queue(layer);
    > +
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_CHDR,
    > + ATMEL_HLCDC_LAYER_A2Q);
    > + regmap_write(regmap,
    > + desc->regs_offset +
    > + ATMEL_HLCDC_LAYER_CHER,
    > + ATMEL_HLCDC_LAYER_DMA_CHAN);
    > + } else {
    > + dma->enabled = false;
    > + }
    > + }
    > +
    > + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
    > + struct atmel_hlcdc_layer_update *upd = &layer->update;
    > +
    > + spin_lock(&upd->pending_lock);
    > + atmel_hlcdc_layer_update_apply(layer);
    > + spin_unlock(&upd->pending_lock);
    > + }
    > + spin_unlock_irqrestore(&dma->lock, flags);
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (!flip_gc[i])
    > + break;
    > +
    > + atmel_hlcdc_layer_gem_flip_put(layer, flip_gc[i]);
    > + }
    > +}
    > +
    > +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
    > + unsigned long flags;
    > + int i;
    > +
    > + spin_lock_irqsave(&dma->lock, flags);
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (!dma->cur[i])
    > + break;
    > +
    > + dma->cur[i]->ctrl = 0;
    > + }
    > + spin_unlock_irqrestore(&dma->lock, flags);
    > +
    > + return 0;
    > +}
    > +
    > +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
    > + void (*finished)(void *data),
    > + void *data)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
    > +
    > + mutex_lock(&gc->finished_lock);
    > + gc->finished = finished;
    > + gc->finished_data = data;
    > + mutex_unlock(&gc->finished_lock);
    > +}
    > +
    > +static void atmel_hlcdc_layer_update_reset(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;
    > + int i;
    > +
    > + if (upd->next < 0 || upd->next > 1)
    > + return;
    > +
    > + slot = &upd->slots[upd->next];
    > + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
    > + memset(slot->configs, 0,
    > + sizeof(*slot->configs) * layer->desc->nconfigs);
    > +
    > + spin_lock_irqsave(&dma->lock, flags);
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (!slot->dscrs[i])
    > + break;
    > + slot->dscrs[i]->gem_flip = NULL;
    > + slot->dscrs[i] = NULL;
    > + }
    > + spin_unlock_irqrestore(&layer->dma.lock, flags);
    > +
    > + if (slot->gem_flip) {
    > + atmel_hlcdc_layer_gem_flip_release(layer, slot->gem_flip);
    > + slot->gem_flip = NULL;
    > + }
    > +
    > + upd->next = -1;
    > +}
    > +
    > +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_gem_flip *gem_flip;
    > + struct atmel_hlcdc_layer_update_slot *slot;
    > + unsigned long flags;
    > + int i, j = 0;
    > + int pending;
    > +
    > + gem_flip = kzalloc(sizeof(*gem_flip), GFP_KERNEL);
    > + if (!gem_flip)
    > + return -ENOMEM;
    > +
    > + mutex_lock(&upd->lock);
    > +
    > + spin_lock_irqsave(&upd->pending_lock, flags);
    > + pending = upd->pending;
    > + spin_unlock_irqrestore(&upd->pending_lock, flags);
    > +
    > + upd->next = pending ? 0 : 1;
    > +
    > + slot = &upd->slots[upd->next];
    > +
    > + spin_lock_irqsave(&dma->lock, flags);
    > + for (i = 0; i < layer->max_planes * 4; i++) {
    > + if (!dma->dscrs[i].gem_flip) {
    > + slot->dscrs[j++] = &dma->dscrs[i];
    > + dma->dscrs[i].gem_flip = gem_flip;
    > + if (j == layer->max_planes)
    > + break;
    > + }
    > + }
    > +
    > + if (j < layer->max_planes) {
    > + for (i = 0; i < j; i++)
    > + slot->dscrs[i]->gem_flip = NULL;
    > + }
    > + spin_unlock_irqrestore(&layer->dma.lock, flags);
    > +
    > + if (j < layer->max_planes) {
    > + mutex_unlock(&upd->lock);
    > + kfree(gem_flip);
    > + return -EBUSY;
    > + }
    > +
    > + slot->gem_flip = gem_flip;
    > +
    > + spin_lock_irqsave(&upd->pending_lock, flags);
    > + pending = upd->pending;
    > + if (pending >= 0) {
    > + memcpy(upd->slots[upd->next].configs,
    > + upd->slots[upd->pending].configs,
    > + layer->desc->nconfigs * sizeof(u32));
    > + memcpy(upd->slots[upd->next].updated_configs,
    > + upd->slots[upd->pending].updated_configs,
    > + DIV_ROUND_UP(layer->desc->nconfigs,
    > + BITS_PER_BYTE * sizeof(unsigned long)) *
    > + sizeof(unsigned long));
    > + }
    > + spin_unlock_irqrestore(&upd->pending_lock, flags);
    > +
    > + if (pending < 0)
    > + regmap_bulk_read(regmap, ATMEL_HLCDC_LAYER_CFG(layer, 0),
    > + upd->slots[upd->next].configs,
    > + layer->desc->nconfigs);
    > +
    > + 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);
    > + mutex_unlock(&upd->lock);
    > +}
    > +
    > +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
    > + int plane_id,
    > + struct drm_gem_cma_object *gem,
    > + unsigned int offset)
    > +{
    > + struct atmel_hlcdc_layer_update *upd = &layer->update;
    > + struct atmel_hlcdc_layer_gem_flip *gem_flip;
    > + struct atmel_hlcdc_layer_update_slot *slot;
    > + struct atmel_hlcdc_dma_channel_dscr *dscr;
    > + struct drm_gem_object *old_gem;
    > +
    > + if (upd->next < 0 || upd->next > 1)
    > + return -EINVAL;
    > +
    > + if (plane_id >= layer->max_planes || plane_id < 0)
    > + return -EINVAL;
    > +
    > + slot = &upd->slots[upd->next];
    > + dscr = slot->dscrs[plane_id];
    > + dscr->addr = gem->paddr + offset;
    > + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH;
    > + gem_flip = dscr->gem_flip;
    > +
    > + old_gem = gem_flip->gems[plane_id];
    > +
    > + if (gem) {
    > + drm_gem_object_reference(&gem->base);
    > + gem_flip->gems[plane_id] = &gem->base;
    > + gem_flip->remaining_gems++;
    > + } else {
    > + gem_flip->gems[plane_id] = NULL;
    > + }
    > +
    > + if (old_gem) {
    > + drm_gem_object_unreference_unlocked(old_gem);
    > + gem_flip->remaining_gems--;
    > + }
    > +
    > + return 0;
    > +}
    > +
    > +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;
    > + int pending;
    > +
    > + if (upd->next < 0 || upd->next > 1)
    > + return;
    > +
    > + slot = &upd->slots[upd->next];
    > +
    > + spin_lock_irqsave(&upd->pending_lock, flags);
    > + pending = upd->pending;
    > + upd->pending = upd->next;
    > + upd->next = pending;
    > + if (pending >= 0 && !slot->gem_flip->remaining_gems) {
    > + struct atmel_hlcdc_layer_gem_flip *gem_flip = slot->gem_flip;
    > + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
    > +
    > + memcpy(dscrs, slot->dscrs, sizeof(dscrs));
    > + slot->gem_flip = upd->slots[pending].gem_flip;
    > + memcpy(slot->dscrs, upd->slots[pending].dscrs,
    > + sizeof(slot->dscrs));
    > + upd->slots[pending].gem_flip = gem_flip;
    > + memcpy(upd->slots[pending].dscrs, dscrs, sizeof(dscrs));
    > + }
    > + spin_unlock_irqrestore(&upd->pending_lock, flags);
    > +
    > + if (pending >= 0) {
    > + atmel_hlcdc_layer_update_reset(layer);
    > + mutex_unlock(&upd->lock);
    > + return;
    > + }
    > +
    > + spin_lock_irqsave(&dma->lock, flags);
    > + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
    > + spin_lock(&upd->pending_lock);
    > + atmel_hlcdc_layer_update_apply(layer);
    > + spin_unlock(&upd->pending_lock);
    > + }
    > + spin_unlock_irqrestore(&dma->lock, flags);
    > +
    > + atmel_hlcdc_layer_update_reset(layer);
    > + mutex_unlock(&upd->lock);
    > +}
    > +
    > +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));
    > + }
    > +
    > + spin_lock_init(&dma->lock);
    > +
    > + 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];
    > +
    > + if (!dscr->gem_flip)
    > + continue;
    > +
    > + atmel_hlcdc_layer_gem_flip_put(layer, dscr->gem_flip);
    > + }
    > +
    > + dma_free_coherent(dev->dev, layer->max_planes * 4 *
    > + sizeof(*dma->dscrs), dma->dscrs,
    > + dma->dscrs[0].next);
    > +}
    > +
    > +static void atmel_hlcdc_layer_gc_init(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
    > +
    > + INIT_LIST_HEAD(&gc->list);
    > + spin_lock_init(&gc->list_lock);
    > + INIT_WORK(&gc->work, atmel_hlcdc_layer_gc_work);
    > + mutex_init(&gc->finished_lock);
    > +}
    > +
    > +static void atmel_hlcdc_layer_gc_cleanup(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
    > +
    > + flush_work(&gc->work);
    > +}
    > +
    > +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;
    > + spin_lock_init(&upd->pending_lock);
    > + mutex_init(&upd->lock);
    > +
    > + 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->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;
    > + }
    > +
    > + atmel_hlcdc_layer_gc_init(layer);
    > + 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);
    > +
    > + for (i = 0; i < layer->max_planes; i++)
    > + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER,
    > + ATMEL_HLCDC_LAYER_ADD_IRQ(i) |
    > + ATMEL_HLCDC_LAYER_DONE_IRQ(i));
    > +
    > + 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);
    > + atmel_hlcdc_layer_gc_cleanup(layer);
    > +}
    > 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..868f444
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
    > @@ -0,0 +1,417 @@
    > +/*
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Boris BREZILLON <boris.brezillon@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/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)
    > +#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(n) BIT(2 + ((n) * 8))
    > +#define ATMEL_HLCDC_LAYER_DSCR_IRQ(n) BIT(3 + ((n) * 8))
    > +#define ATMEL_HLCDC_LAYER_ADD_IRQ(n) BIT(4 + ((n) * 8))
    > +#define ATMEL_HLCDC_LAYER_DONE_IRQ(n) BIT(5 + ((n) * 8))
    > +#define ATMEL_HLCDC_LAYER_OVR_IRQ(n) BIT(6 + ((n) * 8))
    > +
    > +#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_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_GA_MASK GENMASK(23, 16)
    > +#define ATMEL_HLCDC_LAYER_GA_SHIFT 16
    > +
    > +#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
    > +
    > +/**
    > + * 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
    > + * 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
    > + * 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
    > + * @color_space_conv: color spave 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 color_space_conv;
    > +};
    > +
    > +/**
    > + * Atmel HLCDC GEM 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 the reference GEM objects anymore
    > + * (i.e. the layer was disabled or updated).
    > + *
    > + * @node: list element structure. Used to attach the GEM flip structure to
    > + * the garbage collector when the layer no longer need the referenced
    > + * GEM objects.
    > + * @gems: the referenced GEM objects. The number of GEM object referenced
    > + * depends on the chosen format.
    > + * @remaining_gems: the number of GEM object still referenced by the layer.
    > + * When no more objects are referenced the GEM flip structure
    > + * is added to the garbage collector.
    > + */
    > +struct atmel_hlcdc_layer_gem_flip {
    > + struct list_head node;
    > + struct drm_gem_object *gems[ATMEL_HLCDC_MAX_PLANES];
    > + int remaining_gems;
    > +};
    > +
    > +/**
    > + * 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;
    > + struct atmel_hlcdc_layer_gem_flip *gem_flip;
    > +} __aligned(sizeof(u64));
    > +
    > +/**
    > + * 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
    > + * @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
    > + *@gem_flip: gem_flip object
    > + *@updated_configs: bitmask used to record modified configs
    > + *@configs: new config values
    > + */
    > +struct atmel_hlcdc_layer_update_slot {
    > + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
    > + struct atmel_hlcdc_layer_gem_flip *gem_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
    > + *@pending_lock: lock to access the pending field
    > + *@lock: layer update lock
    > + */
    > +struct atmel_hlcdc_layer_update {
    > + struct atmel_hlcdc_layer_update_slot slots[2];
    > + int pending;
    > + int next;
    > + spinlock_t pending_lock;
    > + struct mutex lock;
    > +};
    > +
    > +/**
    > + * Atmel HLCDC Layer GEM flip garbage collector structure
    > + *
    > + * This structure is used to schedule GEM object release when we are in
    > + * interrupt context (within atmel_hlcdc_layer_irq function).
    > + *
    > + *@list: GEM flip objects to release
    > + *@list_lock: lock to access the GEM flip list
    > + *@work: work queue scheduled when there are GEM flip to collect
    > + *@finished: action to execute the GEM flip and all attached objects have been
    > + * released
    > + *@finished_data: data passed to the finished callback
    > + *@finished_lock: lock to access finished related fields
    > + */
    > +struct atmel_hlcdc_layer_gem_flip_gc {
    > + struct list_head list;
    > + spinlock_t list_lock;
    > + struct work_struct work;
    > + void (*finished)(void *data);
    > + void *finished_data;
    > + struct mutex finished_lock;
    > +};
    > +
    > +/**
    > + * Atmel HLCDC Layer DMA channel structure
    > + *
    > + * This structure stores informations on the DMA channel associated to a
    > + * given layer.
    > + *
    > + *@enabled: DMA channel status
    > + *@lock: lock to access the DMA channel fields
    > + *@cur: current DMA transfers (one for each plane/buffer)
    > + *@queue: next DMA transfers, to be launch on next frame update
    > + *@dscrs: allocated DMA descriptors
    > + */
    > +struct atmel_hlcdc_layer_dma_channel {
    > + bool enabled;
    > + spinlock_t lock;
    > + struct atmel_hlcdc_dma_channel_dscr *cur[ATMEL_HLCDC_MAX_PLANES];
    > + struct atmel_hlcdc_dma_channel_dscr *queue[ATMEL_HLCDC_MAX_PLANES];
    > + 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: GEM flip garbage collector
    > + *@update: update handler
    > + */
    > +struct atmel_hlcdc_layer {
    > + const struct atmel_hlcdc_layer_desc *desc;
    > + int max_planes;
    > + struct atmel_hlcdc *hlcdc;
    > + struct atmel_hlcdc_layer_dma_channel dma;
    > + struct atmel_hlcdc_layer_gem_flip_gc gc;
    > + struct atmel_hlcdc_layer_update update;
    > +};
    > +
    > +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);
    > +
    > +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
    > + void (*finished)(void *data),
    > + void *data);
    > +
    > +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);
    > +
    > +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
    > + int plane_id,
    > + struct drm_gem_cma_object *gem,
    > + unsigned int offset);
    > +
    > +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
    > +
    > +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
    > +
    > +static inline bool
    > +atmel_hlcdc_layer_dma_channel_busy(struct atmel_hlcdc_layer *layer)
    > +{
    > + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
    > + int i;
    > +
    > + for (i = 0; i < layer->max_planes; i++) {
    > + if (dma->queue[i])
    > + return true;
    > + }
    > +
    > + return false;
    > +}
    > +
    > +#endif /* DRM_ATMEL_HLCDC_LAYER_H */
    > diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
    > new file mode 100644
    > index 0000000..3295021
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
    > @@ -0,0 +1,351 @@
    > +/*
    > + * Copyright (C) 2014 Traphandler
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
    > + * Author: Boris BREZILLON <boris.brezillon@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 <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,
    > +};
    > +
    > +/**
    > + * Atmel HLCDC Panel structure
    > + *
    > + * This structure stores informations about an DRM panel connected through
    > + * the RGB connector.
    > + *
    > + * @mode: RGB output mode
    > + * @connector: DRM connector
    > + * @encoder: DRM encoder
    > + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
    > + * @panel: pointer to the attached DRM panel
    > + * @pinctrl: pinctrl state used by the current RGB output mode
    > + * @np: DRM panel DT node
    > + * @dpms: current DPMS mode
    > + */
    > +struct atmel_hlcdc_panel {
    > + enum atmel_hlcdc_connector_rgb_mode mode;
    > + struct drm_connector connector;
    > + struct drm_encoder encoder;
    > + struct atmel_hlcdc *hlcdc;
    > + struct drm_panel *panel;
    > + struct pinctrl *pinctrl;
    > + struct device_node *np;
    > + int dpms;
    > +};
    > +
    > +static const char * const pin_state_names[] = {
    > + [ATMEL_HLCDC_CONNECTOR_RGB444] = "rgb-444",
    > + [ATMEL_HLCDC_CONNECTOR_RGB565] = "rgb-565",
    > + [ATMEL_HLCDC_CONNECTOR_RGB666] = "rgb-666",
    > + [ATMEL_HLCDC_CONNECTOR_RGB888] = "rgb-888",
    > +};
    > +
    > +static inline struct atmel_hlcdc_panel *
    > +drm_connector_to_atmel_hlcdc_panel(struct drm_connector *connector)
    > +{
    > + return container_of(connector, struct atmel_hlcdc_panel, connector);
    > +}
    > +
    > +static inline struct atmel_hlcdc_panel *
    > +drm_encoder_to_atmel_hlcdc_panel(struct drm_encoder *encoder)
    > +{
    > + return container_of(encoder, struct atmel_hlcdc_panel, encoder);
    > +}
    > +
    > +static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
    > + int mode)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_encoder_to_atmel_hlcdc_panel(encoder);
    > + struct regmap *regmap = panel->hlcdc->regmap;
    > + unsigned int status;
    > +
    > + if (mode != DRM_MODE_DPMS_ON)
    > + mode = DRM_MODE_DPMS_OFF;
    > +
    > + if (mode == panel->dpms)
    > + return;
    > +
    > + 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(panel->hlcdc->sys_clk);
    > + if (panel->panel)
    > + drm_panel_disable(panel->panel);
    > + } else {
    > + if (panel->panel)
    > + drm_panel_enable(panel->panel);
    > + clk_prepare_enable(panel->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();
    > + }
    > +
    > + panel->dpms = mode;
    > +}
    > +
    > +static bool
    > +atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
    > + const struct drm_display_mode *mode,
    > + struct drm_display_mode *adjusted)
    > +{
    > + return true;
    > +}
    > +
    > +static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
    > +{
    > + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
    > +}
    > +
    > +static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
    > +{
    > + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
    > +}
    > +
    > +static void
    > +atmel_hlcdc_panel_encoder_mode_set(struct drm_encoder *encoder,
    > + struct drm_display_mode *mode,
    > + struct drm_display_mode *adjusted)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_encoder_to_atmel_hlcdc_panel(encoder);
    > + unsigned long prate = clk_get_rate(panel->hlcdc->sys_clk);
    > + unsigned long mode_rate = mode->clock * 1000;
    > + int div;
    > + u32 cfg0 = 0;
    > +
    > + if ((prate / 2) < mode_rate) {
    > + prate *= 2;
    > + cfg0 |= ATMEL_HLCDC_CLKSEL;
    > + }
    > +
    > + div = DIV_ROUND_UP(prate, mode_rate);
    > + if (div < 2)
    > + div = 2;
    > +
    > + cfg0 |= ATMEL_HLCDC_CLKDIV(div);
    > +
    > + regmap_update_bits(panel->hlcdc->regmap, ATMEL_HLCDC_CFG(0),
    > + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg0);
    > +}
    > +
    > +static struct drm_encoder_helper_funcs encoder_helper_funcs = {
    > + .dpms = atmel_hlcdc_panel_encoder_dpms,
    > + .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
    > + .prepare = atmel_hlcdc_panel_encoder_prepare,
    > + .commit = atmel_hlcdc_panel_encoder_commit,
    > + .mode_set = atmel_hlcdc_panel_encoder_mode_set,
    > +};
    > +
    > +static void atmel_hlcdc_panel_encoder_destroy(struct drm_encoder *encoder)
    > +{
    > + drm_encoder_cleanup(encoder);
    > + memset(encoder, 0, sizeof(*encoder));
    > +}
    > +
    > +static const struct drm_encoder_funcs encoder_funcs = {
    > + .destroy = atmel_hlcdc_panel_encoder_destroy,
    > +};
    > +
    > +static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_connector_to_atmel_hlcdc_panel(connector);
    > + int ret;
    > +
    > + if (!panel->panel)
    > + return -ENOENT;
    > +
    > + ret = panel->panel->funcs->get_modes(panel->panel);
    > + return ret;
    > +}
    > +
    > +static int atmel_hlcdc_panel_mode_valid(struct drm_connector *connector,
    > + struct drm_display_mode *mode)
    > +{
    > + return MODE_OK;
    > +}
    > +
    > +static struct drm_encoder *
    > +atmel_hlcdc_panel_best_encoder(struct drm_connector *connector)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_connector_to_atmel_hlcdc_panel(connector);
    > +
    > + return &panel->encoder;
    > +}
    > +
    > +static struct drm_connector_helper_funcs connector_helper_funcs = {
    > + .get_modes = atmel_hlcdc_panel_get_modes,
    > + .mode_valid = atmel_hlcdc_panel_mode_valid,
    > + .best_encoder = atmel_hlcdc_panel_best_encoder,
    > +};
    > +
    > +static enum drm_connector_status
    > +atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_connector_to_atmel_hlcdc_panel(connector);
    > +
    > + if (!panel->panel) {
    > + panel->panel = of_drm_find_panel(panel->np);
    > + if (panel->panel)
    > + drm_panel_attach(panel->panel, &panel->connector);
    > + }
    > +
    > + if (panel->panel)
    > + return connector_status_connected;
    > +
    > + return connector_status_disconnected;
    > +}
    > +
    > +static void
    > +atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
    > +{
    > + struct atmel_hlcdc_panel *panel =
    > + drm_connector_to_atmel_hlcdc_panel(connector);
    > +
    > + if (panel->panel)
    > + drm_panel_detach(panel->panel);
    > +
    > + drm_sysfs_connector_remove(connector);
    > + drm_connector_cleanup(connector);
    > +
    > + pinctrl_put(panel->pinctrl);
    > +}
    > +
    > +static const struct drm_connector_funcs connector_funcs = {
    > + .dpms = drm_helper_connector_dpms,
    > + .detect = atmel_hlcdc_panel_connector_detect,
    > + .fill_modes = drm_helper_probe_single_connector_modes,
    > + .destroy = atmel_hlcdc_panel_connector_destroy,
    > +};
    > +
    > +int atmel_hlcdc_panel_create(struct drm_device *dev)
    > +{
    > + struct atmel_hlcdc_dc *dc = dev->dev_private;
    > + struct atmel_hlcdc_panel *panel;
    > + struct of_phandle_args out_args;
    > + u32 cfg;
    > + int ret;
    > +
    > + panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
    > + if (!panel)
    > + return -ENOMEM;
    > +
    > + ret = of_parse_phandle_with_fixed_args(dev->dev->of_node,
    > + "atmel,panel", 2, 0,
    > + &out_args);
    > + if (ret) {
    > + dev_err(dev->dev, "failed to retrieve panel info from DT\n");
    > + return ret;
    > + }
    > +
    > + switch (out_args.args[0]) {
    > + case ATMEL_HLCDC_CONNECTOR_RGB444:
    > + case ATMEL_HLCDC_CONNECTOR_RGB565:
    > + case ATMEL_HLCDC_CONNECTOR_RGB666:
    > + case ATMEL_HLCDC_CONNECTOR_RGB888:
    > + break;
    > + default:
    > + dev_err(dev->dev, "unknown RGB mode\n");
    > + return -EINVAL;
    > + }
    > +
    > + panel->np = out_args.np;
    > + panel->mode = out_args.args[0];
    > + panel->pinctrl = pinctrl_get_select(dev->dev,
    > + pin_state_names[panel->mode]);
    > + if (IS_ERR(panel->pinctrl)) {
    > + dev_err(dev->dev, "could not request pinctrl state\n");
    > + return PTR_ERR(panel->pinctrl);
    > + }
    > +
    > + cfg = out_args.args[1] & (ATMEL_HLCDC_HSPOL |
    > + ATMEL_HLCDC_VSPOL |
    > + ATMEL_HLCDC_VSPDLYS |
    > + ATMEL_HLCDC_VSPDLYE |
    > + ATMEL_HLCDC_DISPPOL |
    > + ATMEL_HLCDC_DISPDLY |
    > + ATMEL_HLCDC_VSPSU |
    > + ATMEL_HLCDC_VSPHO);
    > + cfg |= panel->mode << 8;
    > +
    > + regmap_write(dc->hlcdc->regmap,
    > + ATMEL_HLCDC_CFG(5),
    > + cfg);
    > +
    > + panel->dpms = DRM_MODE_DPMS_OFF;
    > +
    > + panel->hlcdc = dc->hlcdc;
    > +
    > + drm_connector_init(dev, &panel->connector, &connector_funcs,
    > + DRM_MODE_CONNECTOR_LVDS);
    > + drm_connector_helper_add(&panel->connector, &connector_helper_funcs);
    > + panel->connector.dpms = DRM_MODE_DPMS_OFF;
    > + panel->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
    > +
    > + drm_encoder_init(dev, &panel->encoder, &encoder_funcs,
    > + DRM_MODE_ENCODER_LVDS);
    > + drm_encoder_helper_add(&panel->encoder, &encoder_helper_funcs);
    > +
    > + drm_mode_connector_attach_encoder(&panel->connector, &panel->encoder);
    > + drm_sysfs_connector_add(&panel->connector);
    > +
    > + panel->encoder.possible_crtcs = 0x1;
    > +
    > + 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..f428b47
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
    > @@ -0,0 +1,658 @@
    > +/*
    > + * Copyright (C) 2014 Free Electrons
    > + * Copyright (C) 2014 Atmel
    > + *
    > + * Author: Boris BREZILLON <boris.brezillon@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_RGB888,
    > + DRM_FORMAT_ARGB1555,
    > + 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_RGB888,
    > + DRM_FORMAT_ARGB1555,
    > + 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_NV61,
    > + DRM_FORMAT_YUV422,
    > + DRM_FORMAT_NV21,
    > +};
    > +
    > +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_NV61:
    > + *mode = ATMEL_HLCDC_NV61_MODE;
    > + break;
    > + case DRM_FORMAT_YUV422:
    > + *mode = ATMEL_HLCDC_YUV422_MODE;
    > + break;
    > + case DRM_FORMAT_NV21:
    > + *mode = ATMEL_HLCDC_NV21_MODE;
    > + break;
    > + case DRM_FORMAT_YUV420:
    > + *mode = ATMEL_HLCDC_YUV420_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,
    > +};
    > +
    > +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
    > + struct drm_crtc *crtc,
    > + 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)
    > +{
    > + const struct atmel_hlcdc_layer_cfg_layout *layout =
    > + &plane->layer.desc->layout;
    > +
    > + if (!crtc_h || !crtc_w)
    > + return -EINVAL;
    > +
    > + if (!layout->pos && (crtc_x || crtc_y))
    > + return -EINVAL;
    > +
    > + if ((crtc_x + crtc_w) > crtc->mode.crtc_hdisplay ||
    > + (crtc_y + crtc_h) > crtc->mode.crtc_vdisplay)
    > + return -EINVAL;
    > +
    > + if (!layout->size &&
    > + (crtc->mode.crtc_hdisplay != crtc_w ||
    > + crtc->mode.crtc_vdisplay != crtc_h))
    > + return -EINVAL;
    > +
    > + if (plane->layer.desc->max_height &&
    > + crtc_h > plane->layer.desc->max_height)
    > + return -EINVAL;
    > +
    > + if (plane->layer.desc->max_width &&
    > + crtc_w > plane->layer.desc->max_width)
    > + return -EINVAL;
    > +
    > + if (!layout->memsize && (crtc_h != src_h || crtc_w != src_w))
    > + return -EINVAL;
    > +
    > + if (layout->size)
    > + atmel_hlcdc_layer_update_cfg(&plane->layer,
    > + layout->size,
    > + 0xffffffff,
    > + (crtc_w - 1) |
    > + ((crtc_h - 1) << 16));
    > +
    > + if (layout->memsize)
    > + atmel_hlcdc_layer_update_cfg(&plane->layer,
    > + layout->memsize,
    > + 0xffffffff,
    > + (src_w - 1) |
    > + ((src_h - 1) << 16));
    > +
    > + if (layout->pos)
    > + atmel_hlcdc_layer_update_cfg(&plane->layer,
    > + layout->pos,
    > + 0xffffffff,
    > + crtc_x | (crtc_y << 16));
    > +
    > + /* TODO: rework the rescaling part */
    > + if (crtc_w != src_w || crtc_h != src_h) {
    > + u32 factor_reg = 0;
    > +
    > + if (crtc_w != src_w) {
    > + int i;
    > + u32 factor;
    > + u32 *coeff_tab = heo_upscaling_xcoef;
    > + u32 max_memsize;
    > +
    > + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
    > + factor++;
    > + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
    > + if (max_memsize > src_w)
    > + factor--;
    > + factor_reg |= factor | 0x80000000;
    > + }
    > +
    > + if (crtc_h != src_h) {
    > + int i;
    > + u32 factor;
    > + u32 *coeff_tab = heo_upscaling_ycoef;
    > + u32 max_memsize;
    > +
    > + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
    > + factor++;
    > + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
    > + if (max_memsize > src_w)
    > + factor--;
    > + factor_reg |= (factor << 16) | 0x80000000;
    > + }
    > +
    > + atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
    > + factor_reg);
    > + }
    > +
    > + return 0;
    > +}
    > +
    > +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
    > + u32 format)
    > +{
    > + 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(format))
    > + cfg |= ATMEL_HLCDC_LAYER_LAEN;
    > + else
    > + cfg |= (plane->alpha << ATMEL_HLCDC_LAYER_GA_SHIFT) |
    > + 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 |
    > + ATMEL_HLCDC_LAYER_GA_MASK, cfg);
    > +}
    > +
    > +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
    > + u32 format)
    > +{
    > + u32 mode;
    > + int ret;
    > +
    > + ret = atmel_hlcdc_format_to_plane_mode(format, &mode);
    > + if (ret)
    > + return ret;
    > +
    > + atmel_hlcdc_layer_update_cfg(&plane->layer,
    > + ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
    > + 0xffffffff,
    > + mode);
    > +
    > + return 0;
    > +}
    > +
    > +int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
    > + u32 pixel_format,
    > + struct drm_gem_cma_object **gems,
    > + unsigned int *pitches,
    > + unsigned int *offsets,
    > + uint32_t src_x, uint32_t src_y,
    > + uint32_t src_w, uint32_t src_h)
    > +{
    > + struct atmel_hlcdc_layer *layer = &plane->layer;
    > + const struct atmel_hlcdc_layer_cfg_layout *layout =
    > + &layer->desc->layout;
    > + int bpp[ATMEL_HLCDC_MAX_PLANES];
    > + int nplanes;
    > + int ret = 0;
    > + int i;
    > +
    > + nplanes = drm_format_num_planes(pixel_format);
    > +
    > + for (i = 0; i < nplanes; i++) {
    > + bpp[i] = drm_format_plane_cpp(pixel_format, i);
    > + if (!bpp[i])
    > + return -EINVAL;
    > +
    > + if (!gems[i])
    > + return -EINVAL;
    > + }
    > +
    > + for (i = 0; i < nplanes; i++) {
    > + int offset;
    > +
    > + offset = offsets[i] + (src_y * pitches[i]) +
    > + (src_x * bpp[i]);
    > +
    > + ret = atmel_hlcdc_layer_update_set_gem(&plane->layer, i,
    > + gems[i], offset);
    > + if (ret)
    > + return ret;
    > +
    > + if (layout->xstride[i])
    > + atmel_hlcdc_layer_update_cfg(&plane->layer,
    > + layout->xstride[i],
    > + 0xffffffff,
    > + pitches[i] - (bpp[i] * src_w));
    > + }
    > +
    > + 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 drm_gem_cma_object *gems[ATMEL_HLCDC_MAX_PLANES];
    > + int nplanes;
    > + int ret = 0;
    > + int i;
    > +
    > + /* Subpixel positioning is not supported */
    > + if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
    > + DRM_DEBUG_KMS("Primary base does not support subpixel positioning\n");
    > + return -EINVAL;
    > + }
    > +
    > + src_h >>= 16;
    > + src_w >>= 16;
    > + src_x >>= 16;
    > + src_y >>= 16;
    > +
    > + nplanes = drm_format_num_planes(fb->pixel_format);
    > + if (nplanes > ATMEL_HLCDC_MAX_PLANES)
    > + return -EINVAL;
    > +
    > + for (i = 0; i < nplanes; i++)
    > + gems[i] = drm_fb_cma_get_gem_obj(fb, i);
    > +
    > + atmel_hlcdc_layer_update_start(&plane->layer);
    > + ret = atmel_hlcdc_plane_update_pos_and_size(plane, crtc,
    > + crtc_x, crtc_y,
    > + crtc_w, crtc_h,
    > + src_x, src_y,
    > + src_w, src_h);
    > + if (ret)
    > + goto out;
    > +
    > + atmel_hlcdc_plane_update_general_settings(plane, fb->pixel_format);
    > +
    > + ret = atmel_hlcdc_plane_update_format(plane, fb->pixel_format);
    > + if (ret)
    > + goto out;
    > +
    > +
    > + ret = atmel_hlcdc_plane_update_buffers(plane, fb->pixel_format,
    > + gems,
    > + fb->pitches, fb->offsets,
    > + src_x, src_y,
    > + src_w, src_h);
    > +
    > + if (ret)
    > + goto out;
    > +
    > + atmel_hlcdc_layer_update_commit(&plane->layer);
    > +
    > + drm_framebuffer_reference(fb);
    > + if (plane->base.fb)
    > + drm_framebuffer_unreference(plane->base.fb);
    > + plane->base.fb = fb;
    > +
    > + return 0;
    > +
    > +out:
    > + atmel_hlcdc_layer_update_rollback(&plane->layer);
    > +
    > + return ret;
    > +}
    > +
    > +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);
    > + plane->alpha = alpha;
    > + atmel_hlcdc_plane_update_general_settings(plane,
    > + plane->base.fb->pixel_format);
    > + atmel_hlcdc_layer_update_commit(&plane->layer);
    > +
    > + 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
    > + return -EINVAL;
    > +
    > + return 0;
    > +}
    > +
    > +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 *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);
    > +
    > + /* Set default property values*/
    > + plane->alpha = 0xff;
    > +
    > + 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);
    > +
    > + 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);
    > +
    > + 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]);
    > + 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;
    > + drm_object_attach_property(&plane->base.base,
    > + props->alpha, 255);
    > + break;
    > +
    > + case ATMEL_HLCDC_CURSOR_LAYER:
    > + if (planes->cursor)
    > + return ERR_PTR(-EINVAL);
    > + planes->cursor = plane;
    > + drm_object_attach_property(&plane->base.base,
    > + props->alpha, 255);
    > + break;
    > +
    > + default:
    > + break;
    > + }
    > + }
    > +
    > + return planes;
    > +}
    > diff --git a/drivers/gpu/drm/atmel_hlcdc/Kconfig b/drivers/gpu/drm/atmel_hlcdc/Kconfig
    > new file mode 100644
    > index 0000000..59c8eeb
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel_hlcdc/Kconfig
    > @@ -0,0 +1,11 @@
    > +config DRM_ATMEL_HLCDC
    > + tristate "DRM Support for ATMEL HLCDC Display Controller"
    > + depends on DRM && OF && ARM && COMMON_CLK
    > + select DRM_GEM_CMA_HELPER
    > + select DRM_KMS_HELPER
    > + select DRM_KMS_FB_HELPER
    > + select DRM_KMS_CMA_HELPER
    > + select DRM_PANEL
    > + 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..08de8d7
    > --- /dev/null
    > +++ b/drivers/gpu/drm/atmel_hlcdc/Makefile
    > @@ -0,0 +1,8 @@
    > +atmel_hlcdc-y := atmel_hlcdc_crtc.o \
    > + atmel_hlcdc_drv.o \
    > + atmel_hlcdc_layer.o \
    > + atmel_hlcdc_panel.o \
    > + atmel_hlcdc_plane.o \
    > + atmel_hlcdc_pwm.o
    > +
    > +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel_hlcdc.o



    --
    Boris Brezillon, Free Electrons
    Embedded Linux and Kernel engineering
    http://free-electrons.com


    \
     
     \ /
      Last update: 2014-07-03 16:21    [W:5.414 / U:0.236 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site