From: Boris BREZILLON <boris.brezillon@free-electrons.com>
To: Boris BREZILLON <boris.brezillon@free-electrons.com>
Cc: Thierry Reding <thierry.reding@gmail.com>,
Nicolas Ferre <nicolas.ferre@atmel.com>,
David Airlie <airlied@linux.ie>,
Samuel Ortiz <sameo@linux.intel.com>,
Lee Jones <lee.jones@linaro.org>,
Jean-Jacques Hiblot <jjhiblot@traphandler.com>,
Alexandre Belloni <alexandre.belloni@free-electrons.com>,
Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com>,
Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
devicetree@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
dri-devel@lists.freedesktop.org
Subject: Re: [PATCH v2 3/7] drm: add Atmel HLCDC Display Controller support
Date: Thu, 3 Jul 2014 15:34:40 +0200 [thread overview]
Message-ID: <20140703153440.4062b359@bbrezillon> (raw)
In-Reply-To: <1402329860-27520-4-git-send-email-boris.brezillon@free-electrons.com>
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
next prev parent reply other threads:[~2014-07-03 13:34 UTC|newest]
Thread overview: 65+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-06-09 16:04 [PATCH v2 0/7] drm: add support for Atmel HLCDC Display Controller Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 1/7] mfd: add atmel-hlcdc driver Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-15 8:53 ` Jean-Jacques Hiblot
2014-06-15 8:53 ` Jean-Jacques Hiblot
2014-06-15 14:48 ` Boris BREZILLON
2014-06-15 14:48 ` Boris BREZILLON
2014-06-16 7:46 ` Jean-Jacques Hiblot
2014-06-16 7:46 ` Jean-Jacques Hiblot
2014-06-16 7:46 ` Jean-Jacques Hiblot
2014-06-16 12:50 ` Lee Jones
2014-06-16 12:50 ` Lee Jones
2014-06-16 13:23 ` Boris BREZILLON
2014-06-16 13:23 ` Boris BREZILLON
2014-06-16 13:23 ` Boris BREZILLON
[not found] ` <539EEFD8.20703-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
2014-06-16 17:03 ` Lee Jones
2014-06-16 17:03 ` Lee Jones
2014-06-16 17:03 ` Lee Jones
2014-06-16 17:09 ` Boris BREZILLON
2014-06-16 17:09 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 2/7] pwm: add support for atmel-hlcdc-pwm device Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-13 11:43 ` Alexandre Belloni
2014-06-13 11:43 ` Alexandre Belloni
2014-06-13 12:17 ` Boris BREZILLON
2014-06-13 12:17 ` Boris BREZILLON
2014-06-15 9:11 ` Jean-Jacques Hiblot
2014-06-15 9:11 ` Jean-Jacques Hiblot
2014-06-15 14:55 ` Boris BREZILLON
2014-06-15 14:55 ` Boris BREZILLON
2014-07-04 13:31 ` Boris BREZILLON
2014-07-04 13:31 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 3/7] drm: add Atmel HLCDC Display Controller support Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-15 9:32 ` Jean-Jacques Hiblot
2014-06-15 15:00 ` Boris BREZILLON
2014-06-15 15:00 ` Boris BREZILLON
2014-06-17 9:06 ` Boris BREZILLON
2014-07-03 13:34 ` Boris BREZILLON [this message]
2014-06-09 16:04 ` [PATCH v2 4/7] ARM: at91/dt: split sama5d3 lcd pin definitions to match RGB mode configs Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-19 7:07 ` Bo Shen
2014-06-19 7:07 ` Bo Shen
2014-06-19 7:07 ` Bo Shen
2014-06-19 7:40 ` Boris BREZILLON
2014-06-19 7:40 ` Boris BREZILLON
2014-06-19 11:42 ` Boris BREZILLON
2014-06-19 11:42 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 5/7] ARM: at91/dt: define the HLCDC node available on sama5d3 SoCs Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 6/7] ARM: at91/dt: add LCD panel description to sama5d3xdm.dtsi Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` [PATCH v2 7/7] ARM: at91/dt: enable the LCD panel on sama5d3xek boards Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-09 16:04 ` Boris BREZILLON
2014-06-19 7:12 ` Bo Shen
2014-06-19 7:12 ` Bo Shen
2014-06-19 7:12 ` Bo Shen
2014-06-19 7:54 ` Boris BREZILLON
2014-06-19 7:54 ` Boris BREZILLON
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20140703153440.4062b359@bbrezillon \
--to=boris.brezillon@free-electrons.com \
--cc=airlied@linux.ie \
--cc=alexandre.belloni@free-electrons.com \
--cc=devicetree@vger.kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=jjhiblot@traphandler.com \
--cc=laurent.pinchart@ideasonboard.com \
--cc=lee.jones@linaro.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=nicolas.ferre@atmel.com \
--cc=plagnioj@jcrosoft.com \
--cc=sameo@linux.intel.com \
--cc=thierry.reding@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.