On 06/09/2014 06:04 PM, Boris BREZILLON 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@xxxxxxxxxxxxxxxxxx> > --- > .../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@xxxxxxxxxxxxxxx> > + * Author: Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx> > + * > + * 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)); Acording to the datasheet, it's vm.vback_porch instead of (vm.vback_porch -1). > + > + 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@xxxxxxxxxxxxxxx> > + * Author: Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxxx>"); > +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx>"); > +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@xxxxxxxxxxxxxxx> > + * Author: Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxxx> > + * Author: Boris BREZILLON <boris.brezillon@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxxxxxx> > + * > + * 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 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html