Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / gpu / drm / atmel-hlcdc / atmel_hlcdc_layer.c
diff --git a/kernel/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c b/kernel/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
new file mode 100644 (file)
index 0000000..377e43c
--- /dev/null
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include "atmel_hlcdc_dc.h"
+
+static void
+atmel_hlcdc_layer_fb_flip_release(struct drm_flip_work *work, void *val)
+{
+       struct atmel_hlcdc_layer_fb_flip *flip = val;
+
+       if (flip->fb)
+               drm_framebuffer_unreference(flip->fb);
+       kfree(flip);
+}
+
+static void
+atmel_hlcdc_layer_fb_flip_destroy(struct atmel_hlcdc_layer_fb_flip *flip)
+{
+       if (flip->fb)
+               drm_framebuffer_unreference(flip->fb);
+       kfree(flip->task);
+       kfree(flip);
+}
+
+static void
+atmel_hlcdc_layer_fb_flip_release_queue(struct atmel_hlcdc_layer *layer,
+                                       struct atmel_hlcdc_layer_fb_flip *flip)
+{
+       int i;
+
+       if (!flip)
+               return;
+
+       for (i = 0; i < layer->max_planes; i++) {
+               if (!flip->dscrs[i])
+                       break;
+
+               flip->dscrs[i]->status = 0;
+               flip->dscrs[i] = NULL;
+       }
+
+       drm_flip_work_queue_task(&layer->gc, flip->task);
+       drm_flip_work_commit(&layer->gc, layer->wq);
+}
+
+static void atmel_hlcdc_layer_update_reset(struct atmel_hlcdc_layer *layer,
+                                          int id)
+{
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct atmel_hlcdc_layer_update_slot *slot;
+
+       if (id < 0 || id > 1)
+               return;
+
+       slot = &upd->slots[id];
+       bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
+       memset(slot->configs, 0,
+              sizeof(*slot->configs) * layer->desc->nconfigs);
+
+       if (slot->fb_flip) {
+               atmel_hlcdc_layer_fb_flip_release_queue(layer, slot->fb_flip);
+               slot->fb_flip = NULL;
+       }
+}
+
+static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       const struct atmel_hlcdc_layer_desc *desc = layer->desc;
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct regmap *regmap = layer->hlcdc->regmap;
+       struct atmel_hlcdc_layer_update_slot *slot;
+       struct atmel_hlcdc_layer_fb_flip *fb_flip;
+       struct atmel_hlcdc_dma_channel_dscr *dscr;
+       unsigned int cfg;
+       u32 action = 0;
+       int i = 0;
+
+       if (upd->pending < 0 || upd->pending > 1)
+               return;
+
+       slot = &upd->slots[upd->pending];
+
+       for_each_set_bit(cfg, slot->updated_configs, layer->desc->nconfigs) {
+               regmap_write(regmap,
+                            desc->regs_offset +
+                            ATMEL_HLCDC_LAYER_CFG(layer, cfg),
+                            slot->configs[cfg]);
+               action |= ATMEL_HLCDC_LAYER_UPDATE;
+       }
+
+       fb_flip = slot->fb_flip;
+
+       if (!fb_flip->fb)
+               goto apply;
+
+       if (dma->status == ATMEL_HLCDC_LAYER_DISABLED) {
+               for (i = 0; i < fb_flip->ngems; i++) {
+                       dscr = fb_flip->dscrs[i];
+                       dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
+                                    ATMEL_HLCDC_LAYER_DMA_IRQ |
+                                    ATMEL_HLCDC_LAYER_ADD_IRQ |
+                                    ATMEL_HLCDC_LAYER_DONE_IRQ;
+
+                       regmap_write(regmap,
+                                    desc->regs_offset +
+                                    ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
+                                    dscr->addr);
+                       regmap_write(regmap,
+                                    desc->regs_offset +
+                                    ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
+                                    dscr->ctrl);
+                       regmap_write(regmap,
+                                    desc->regs_offset +
+                                    ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
+                                    dscr->next);
+               }
+
+               action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
+               dma->status = ATMEL_HLCDC_LAYER_ENABLED;
+       } else {
+               for (i = 0; i < fb_flip->ngems; i++) {
+                       dscr =  fb_flip->dscrs[i];
+                       dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
+                                    ATMEL_HLCDC_LAYER_DMA_IRQ |
+                                    ATMEL_HLCDC_LAYER_DSCR_IRQ |
+                                    ATMEL_HLCDC_LAYER_DONE_IRQ;
+
+                       regmap_write(regmap,
+                                    desc->regs_offset +
+                                    ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
+                                    dscr->next);
+               }
+
+               action |= ATMEL_HLCDC_LAYER_A2Q;
+       }
+
+       /* Release unneeded descriptors */
+       for (i = fb_flip->ngems; i < layer->max_planes; i++) {
+               fb_flip->dscrs[i]->status = 0;
+               fb_flip->dscrs[i] = NULL;
+       }
+
+       dma->queue = fb_flip;
+       slot->fb_flip = NULL;
+
+apply:
+       if (action)
+               regmap_write(regmap,
+                            desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
+                            action);
+
+       atmel_hlcdc_layer_update_reset(layer, upd->pending);
+
+       upd->pending = -1;
+}
+
+void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       const struct atmel_hlcdc_layer_desc *desc = layer->desc;
+       struct regmap *regmap = layer->hlcdc->regmap;
+       struct atmel_hlcdc_layer_fb_flip *flip;
+       unsigned long flags;
+       unsigned int isr, imr;
+       unsigned int status;
+       unsigned int plane_status;
+       u32 flip_status;
+
+       int i;
+
+       regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IMR, &imr);
+       regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr);
+       status = imr & isr;
+       if (!status)
+               return;
+
+       spin_lock_irqsave(&layer->lock, flags);
+
+       flip = dma->queue ? dma->queue : dma->cur;
+
+       if (!flip) {
+               spin_unlock_irqrestore(&layer->lock, flags);
+               return;
+       }
+
+       /*
+        * Set LOADED and DONE flags: they'll be cleared if at least one
+        * memory plane is not LOADED or DONE.
+        */
+       flip_status = ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED |
+                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
+       for (i = 0; i < flip->ngems; i++) {
+               plane_status = (status >> (8 * i));
+
+               if (plane_status &
+                   (ATMEL_HLCDC_LAYER_ADD_IRQ |
+                    ATMEL_HLCDC_LAYER_DSCR_IRQ) &
+                   ~flip->dscrs[i]->ctrl) {
+                       flip->dscrs[i]->status |=
+                                       ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED;
+                       flip->dscrs[i]->ctrl |=
+                                       ATMEL_HLCDC_LAYER_ADD_IRQ |
+                                       ATMEL_HLCDC_LAYER_DSCR_IRQ;
+               }
+
+               if (plane_status &
+                   ATMEL_HLCDC_LAYER_DONE_IRQ &
+                   ~flip->dscrs[i]->ctrl) {
+                       flip->dscrs[i]->status |=
+                                       ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
+                       flip->dscrs[i]->ctrl |=
+                                       ATMEL_HLCDC_LAYER_DONE_IRQ;
+               }
+
+               if (plane_status & ATMEL_HLCDC_LAYER_OVR_IRQ)
+                       flip->dscrs[i]->status |=
+                                       ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN;
+
+               /*
+                * Clear LOADED and DONE flags if the memory plane is either
+                * not LOADED or not DONE.
+                */
+               if (!(flip->dscrs[i]->status &
+                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED))
+                       flip_status &= ~ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED;
+
+               if (!(flip->dscrs[i]->status &
+                     ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE))
+                       flip_status &= ~ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
+
+               /*
+                * An overrun on one memory plane impact the whole framebuffer
+                * transfer, hence we set the OVERRUN flag as soon as there's
+                * one memory plane reporting such an overrun.
+                */
+               flip_status |= flip->dscrs[i]->status &
+                              ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN;
+       }
+
+       /* Get changed bits */
+       flip_status ^= flip->status;
+       flip->status |= flip_status;
+
+       if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED) {
+               atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
+               dma->cur = dma->queue;
+               dma->queue = NULL;
+       }
+
+       if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE) {
+               atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
+               dma->cur = NULL;
+       }
+
+       if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN) {
+               regmap_write(regmap,
+                            desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
+                            ATMEL_HLCDC_LAYER_RST);
+               if (dma->queue)
+                       atmel_hlcdc_layer_fb_flip_release_queue(layer,
+                                                               dma->queue);
+
+               if (dma->cur)
+                       atmel_hlcdc_layer_fb_flip_release_queue(layer,
+                                                               dma->cur);
+
+               dma->cur = NULL;
+               dma->queue = NULL;
+       }
+
+       if (!dma->queue) {
+               atmel_hlcdc_layer_update_apply(layer);
+
+               if (!dma->cur)
+                       dma->status = ATMEL_HLCDC_LAYER_DISABLED;
+       }
+
+       spin_unlock_irqrestore(&layer->lock, flags);
+}
+
+void atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct regmap *regmap = layer->hlcdc->regmap;
+       const struct atmel_hlcdc_layer_desc *desc = layer->desc;
+       unsigned long flags;
+       unsigned int isr;
+
+       spin_lock_irqsave(&layer->lock, flags);
+
+       /* Disable the layer */
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
+                    ATMEL_HLCDC_LAYER_RST | ATMEL_HLCDC_LAYER_A2Q |
+                    ATMEL_HLCDC_LAYER_UPDATE);
+
+       /* Clear all pending interrupts */
+       regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr);
+
+       /* Discard current and queued framebuffer transfers. */
+       if (dma->cur) {
+               atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
+               dma->cur = NULL;
+       }
+
+       if (dma->queue) {
+               atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->queue);
+               dma->queue = NULL;
+       }
+
+       /*
+        * Then discard the pending update request (if any) to prevent
+        * DMA irq handler from restarting the DMA channel after it has
+        * been disabled.
+        */
+       if (upd->pending >= 0) {
+               atmel_hlcdc_layer_update_reset(layer, upd->pending);
+               upd->pending = -1;
+       }
+
+       dma->status = ATMEL_HLCDC_LAYER_DISABLED;
+
+       spin_unlock_irqrestore(&layer->lock, flags);
+}
+
+int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct regmap *regmap = layer->hlcdc->regmap;
+       struct atmel_hlcdc_layer_fb_flip *fb_flip;
+       struct atmel_hlcdc_layer_update_slot *slot;
+       unsigned long flags;
+       int i, j = 0;
+
+       fb_flip = kzalloc(sizeof(*fb_flip), GFP_KERNEL);
+       if (!fb_flip)
+               return -ENOMEM;
+
+       fb_flip->task = drm_flip_work_allocate_task(fb_flip, GFP_KERNEL);
+       if (!fb_flip->task) {
+               kfree(fb_flip);
+               return -ENOMEM;
+       }
+
+       spin_lock_irqsave(&layer->lock, flags);
+
+       upd->next = upd->pending ? 0 : 1;
+
+       slot = &upd->slots[upd->next];
+
+       for (i = 0; i < layer->max_planes * 4; i++) {
+               if (!dma->dscrs[i].status) {
+                       fb_flip->dscrs[j++] = &dma->dscrs[i];
+                       dma->dscrs[i].status =
+                               ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED;
+                       if (j == layer->max_planes)
+                               break;
+               }
+       }
+
+       if (j < layer->max_planes) {
+               for (i = 0; i < j; i++)
+                       fb_flip->dscrs[i]->status = 0;
+       }
+
+       if (j < layer->max_planes) {
+               spin_unlock_irqrestore(&layer->lock, flags);
+               atmel_hlcdc_layer_fb_flip_destroy(fb_flip);
+               return -EBUSY;
+       }
+
+       slot->fb_flip = fb_flip;
+
+       if (upd->pending >= 0) {
+               memcpy(slot->configs,
+                      upd->slots[upd->pending].configs,
+                      layer->desc->nconfigs * sizeof(u32));
+               memcpy(slot->updated_configs,
+                      upd->slots[upd->pending].updated_configs,
+                      DIV_ROUND_UP(layer->desc->nconfigs,
+                                   BITS_PER_BYTE * sizeof(unsigned long)) *
+                      sizeof(unsigned long));
+               slot->fb_flip->fb = upd->slots[upd->pending].fb_flip->fb;
+               if (upd->slots[upd->pending].fb_flip->fb) {
+                       slot->fb_flip->fb =
+                               upd->slots[upd->pending].fb_flip->fb;
+                       slot->fb_flip->ngems =
+                               upd->slots[upd->pending].fb_flip->ngems;
+                       drm_framebuffer_reference(slot->fb_flip->fb);
+               }
+       } else {
+               regmap_bulk_read(regmap,
+                                layer->desc->regs_offset +
+                                ATMEL_HLCDC_LAYER_CFG(layer, 0),
+                                upd->slots[upd->next].configs,
+                                layer->desc->nconfigs);
+       }
+
+       spin_unlock_irqrestore(&layer->lock, flags);
+
+       return 0;
+}
+
+void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+
+       atmel_hlcdc_layer_update_reset(layer, upd->next);
+       upd->next = -1;
+}
+
+void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer,
+                                    struct drm_framebuffer *fb,
+                                    unsigned int *offsets)
+{
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct atmel_hlcdc_layer_fb_flip *fb_flip;
+       struct atmel_hlcdc_layer_update_slot *slot;
+       struct atmel_hlcdc_dma_channel_dscr *dscr;
+       struct drm_framebuffer *old_fb;
+       int nplanes = 0;
+       int i;
+
+       if (upd->next < 0 || upd->next > 1)
+               return;
+
+       if (fb)
+               nplanes = drm_format_num_planes(fb->pixel_format);
+
+       if (nplanes > layer->max_planes)
+               return;
+
+       slot = &upd->slots[upd->next];
+
+       fb_flip = slot->fb_flip;
+       old_fb = slot->fb_flip->fb;
+
+       for (i = 0; i < nplanes; i++) {
+               struct drm_gem_cma_object *gem;
+
+               dscr = slot->fb_flip->dscrs[i];
+               gem = drm_fb_cma_get_gem_obj(fb, i);
+               dscr->addr = gem->paddr + offsets[i];
+       }
+
+       fb_flip->ngems = nplanes;
+       fb_flip->fb = fb;
+
+       if (fb)
+               drm_framebuffer_reference(fb);
+
+       if (old_fb)
+               drm_framebuffer_unreference(old_fb);
+}
+
+void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg,
+                                 u32 mask, u32 val)
+{
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct atmel_hlcdc_layer_update_slot *slot;
+
+       if (upd->next < 0 || upd->next > 1)
+               return;
+
+       if (cfg >= layer->desc->nconfigs)
+               return;
+
+       slot = &upd->slots[upd->next];
+       slot->configs[cfg] &= ~mask;
+       slot->configs[cfg] |= (val & mask);
+       set_bit(cfg, slot->updated_configs);
+}
+
+void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       struct atmel_hlcdc_layer_update_slot *slot;
+       unsigned long flags;
+
+       if (upd->next < 0  || upd->next > 1)
+               return;
+
+       slot = &upd->slots[upd->next];
+
+       spin_lock_irqsave(&layer->lock, flags);
+
+       /*
+        * Release pending update request and replace it by the new one.
+        */
+       if (upd->pending >= 0)
+               atmel_hlcdc_layer_update_reset(layer, upd->pending);
+
+       upd->pending = upd->next;
+       upd->next = -1;
+
+       if (!dma->queue)
+               atmel_hlcdc_layer_update_apply(layer);
+
+       spin_unlock_irqrestore(&layer->lock, flags);
+
+
+       upd->next = -1;
+}
+
+static int atmel_hlcdc_layer_dma_init(struct drm_device *dev,
+                                     struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       dma_addr_t dma_addr;
+       int i;
+
+       dma->dscrs = dma_alloc_coherent(dev->dev,
+                                       layer->max_planes * 4 *
+                                       sizeof(*dma->dscrs),
+                                       &dma_addr, GFP_KERNEL);
+       if (!dma->dscrs)
+               return -ENOMEM;
+
+       for (i = 0; i < layer->max_planes * 4; i++) {
+               struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
+
+               dscr->next = dma_addr + (i * sizeof(*dscr));
+       }
+
+       return 0;
+}
+
+static void atmel_hlcdc_layer_dma_cleanup(struct drm_device *dev,
+                                         struct atmel_hlcdc_layer *layer)
+{
+       struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+       int i;
+
+       for (i = 0; i < layer->max_planes * 4; i++) {
+               struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
+
+               dscr->status = 0;
+       }
+
+       dma_free_coherent(dev->dev, layer->max_planes * 4 *
+                         sizeof(*dma->dscrs), dma->dscrs,
+                         dma->dscrs[0].next);
+}
+
+static int atmel_hlcdc_layer_update_init(struct drm_device *dev,
+                               struct atmel_hlcdc_layer *layer,
+                               const struct atmel_hlcdc_layer_desc *desc)
+{
+       struct atmel_hlcdc_layer_update *upd = &layer->update;
+       int updated_size;
+       void *buffer;
+       int i;
+
+       updated_size = DIV_ROUND_UP(desc->nconfigs,
+                                   BITS_PER_BYTE *
+                                   sizeof(unsigned long));
+
+       buffer = devm_kzalloc(dev->dev,
+                             ((desc->nconfigs * sizeof(u32)) +
+                               (updated_size * sizeof(unsigned long))) * 2,
+                             GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       for (i = 0; i < 2; i++) {
+               upd->slots[i].updated_configs = buffer;
+               buffer += updated_size * sizeof(unsigned long);
+               upd->slots[i].configs = buffer;
+               buffer += desc->nconfigs * sizeof(u32);
+       }
+
+       upd->pending = -1;
+       upd->next = -1;
+
+       return 0;
+}
+
+int atmel_hlcdc_layer_init(struct drm_device *dev,
+                          struct atmel_hlcdc_layer *layer,
+                          const struct atmel_hlcdc_layer_desc *desc)
+{
+       struct atmel_hlcdc_dc *dc = dev->dev_private;
+       struct regmap *regmap = dc->hlcdc->regmap;
+       unsigned int tmp;
+       int ret;
+       int i;
+
+       layer->hlcdc = dc->hlcdc;
+       layer->wq = dc->wq;
+       layer->desc = desc;
+
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
+                    ATMEL_HLCDC_LAYER_RST);
+       for (i = 0; i < desc->formats->nformats; i++) {
+               int nplanes = drm_format_num_planes(desc->formats->formats[i]);
+
+               if (nplanes > layer->max_planes)
+                       layer->max_planes = nplanes;
+       }
+
+       spin_lock_init(&layer->lock);
+       drm_flip_work_init(&layer->gc, desc->name,
+                          atmel_hlcdc_layer_fb_flip_release);
+       ret = atmel_hlcdc_layer_dma_init(dev, layer);
+       if (ret)
+               return ret;
+
+       ret = atmel_hlcdc_layer_update_init(dev, layer, desc);
+       if (ret)
+               return ret;
+
+       /* Flush Status Register */
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
+                    0xffffffff);
+       regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR,
+                   &tmp);
+
+       tmp = 0;
+       for (i = 0; i < layer->max_planes; i++)
+               tmp |= (ATMEL_HLCDC_LAYER_DMA_IRQ |
+                       ATMEL_HLCDC_LAYER_DSCR_IRQ |
+                       ATMEL_HLCDC_LAYER_ADD_IRQ |
+                       ATMEL_HLCDC_LAYER_DONE_IRQ |
+                       ATMEL_HLCDC_LAYER_OVR_IRQ) << (8 * i);
+
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER, tmp);
+
+       return 0;
+}
+
+void atmel_hlcdc_layer_cleanup(struct drm_device *dev,
+                              struct atmel_hlcdc_layer *layer)
+{
+       const struct atmel_hlcdc_layer_desc *desc = layer->desc;
+       struct regmap *regmap = layer->hlcdc->regmap;
+
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
+                    0xffffffff);
+       regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
+                    ATMEL_HLCDC_LAYER_RST);
+
+       atmel_hlcdc_layer_dma_cleanup(dev, layer);
+       drm_flip_work_cleanup(&layer->gc);
+}