/*
 * 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);
}
