/**
 * \file
 *
 * \brief PDCA DMA Controller Driver
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
#include <assert.h>
#include <bitops.h>
#include <buffer.h>
#include <interrupt.h>
#include <io.h>
#include <irq_handler.h>
#include <status-codes.h>
#include <string.h>
#include <chip/memory-map.h>
#include <chip/irq-map.h>
#include <dmac/dma_controller.h>
#include <dmac/pdca.h>

#include "dmac_util.h"
#include "pdca_regs.h"

#ifdef CONFIG_PDCA_NR_CHANNELS
# if CONFIG_PDCA_NR_CHANNELS > CHIP_PDCA_NR_CHANNELS
#  error Too many PDCA channels requested
# endif
# define NR_CHANNELS	CONFIG_PDCA_NR_CHANNELS
#else
# define NR_CHANNELS	CHIP_PDCA_NR_CHANNELS
#endif

/**
 * \brief PDCA channel state flags
 */
enum pdca_chan_flag {
	PDCA_CHAN_ALLOCATED,	//!< In use by a client
	PDCA_CHAN_ENABLED,	//!< Ready to accept transfers
};

struct pdca_channel {
	struct slist		req_queue;
	struct buffer		*current_buf;
	struct buffer		*next_buf;
	void			*regs;
	unsigned int		rx_pid;
	unsigned int		tx_pid;
	unsigned long		flags;
	struct dmac_channel	dch;
};

static struct pdca_channel	pdca_channel[NR_CHANNELS];

/**
 * \internal
 * \brief Convert a generic dmac_channel struct to a private
 * pdca_channel struct
 */
static struct pdca_channel *pdca_channel_of(struct dmac_channel *ch)
{
	return container_of(ch, struct pdca_channel, dch);
}

static unsigned int pdca_chan_id(struct pdca_channel *chan)
{
	/* Quick'n'dirty channel identifier for debugging purposes */
	return ((unsigned int)chan->regs >> 6) & 0x1f;
}

static void pdca_chan_idle(struct pdca_channel *chan)
{
	pdca_chan_write_reg(chan, IDR, ~0UL);
	pdca_chan_write_reg(chan, CR, PDCA_CR_TDIS | PDCA_CR_ECLR);
}

static void pdca_chan_stop(struct pdca_channel *chan)
{
	pdca_chan_write_reg(chan, TCRR, 0);
	pdca_chan_write_reg(chan, TCR, 0);
	pdca_chan_idle(chan);
}

static void pdca_chan_req_done(struct pdca_channel *chan,
		struct dmac_request *req, int status)
{
	req->status = status;
	if (req->req_done)
		req->req_done(&chan->dch, req);
}

static void pdca_chan_error(struct pdca_channel *chan, struct buffer *buf)
{
	struct dmac_request	*req;

	dbg_printf("pdca ch%u error: MAR %08x TCR %u\n",
			pdca_chan_id(chan), pdca_chan_read_reg(chan, MAR),
			pdca_chan_read_reg(chan, TCR));

	/*
	 * All successful transfers have already been properly accounted
	 * for, so we just need to stop the queue and terminate the
	 * current request with an error status.
	 */
	pdca_chan_stop(chan);

	req = slist_pop_head(&chan->req_queue, struct dmac_request, node);
	pdca_chan_req_done(chan, req, -STATUS_BAD_ADDRESS);
}

static void pdca_chan_interrupt(struct pdca_channel *chan)
{
	struct dmac_request	*req;
	struct buffer		*buf;
	struct buffer		*buf_next;
	uint32_t		status;

	assert(!slist_is_empty(&chan->req_queue));

	status = pdca_chan_read_reg(chan, ISR);
	req = slist_peek_head(&chan->req_queue, struct dmac_request, node);
	buf = chan->current_buf;
	buf_next = chan->next_buf;

	/* First, check if there's any ongoing transfer that has completed */
	if (buf) {
		/*
		 * If two buffers are pending, and one of them has
		 * completed, the first one can't have caused an error
		 */
		if (buf_next && (status & PDCA_INT_RCZ)) {
			req->bytes_xfered += buf->len;
			buf = buf_next;
			buf_next = NULL;
		}

		/*
		 * Whatever buffer is "current" at this point must have
		 * caused the error, if any.
		 */
		if (status & PDCA_INT_TERR) {
			assert(!(status & PDCA_INT_TRC));
			pdca_chan_error(chan, buf);
			return;
		}

		/* If the controller is idle, all transfers are done */
		if (status & PDCA_INT_TRC) {
			req->bytes_xfered += buf->len;
			if (slist_node_is_last(&req->buf_list, &buf->node)) {
				/* The whole request is done */
				slist_pop_head_node(&chan->req_queue);
				pdca_chan_req_done(chan, req, STATUS_OK);
				req = slist_peek_head(&chan->req_queue,
						struct dmac_request, node);
				buf = NULL;
			} else {
				buf = slist_entry(buf->node.next,
						struct buffer, node);
			}
		}
	}

	if (slist_is_empty(&chan->req_queue)) {
		pdca_chan_idle(chan);
		goto out;
	}

	if (!buf) {
		assert(req->reg_width < 2);
		/*
		 * Starting a new request, so we must take care to
		 * initialize the Peripheral ID and register size
		 */
		switch (req->direction) {
		case DMA_FROM_DEVICE:
			assert(chan->rx_pid < PDCA_NR_PERIPH_IDS);
			pdca_chan_write_reg(chan, PSR,
					PDCA_PSR_PID(chan->rx_pid));
			break;
		case DMA_TO_DEVICE:
			assert(chan->tx_pid < PDCA_NR_PERIPH_IDS);
			pdca_chan_write_reg(chan, PSR,
					PDCA_PSR_PID(chan->tx_pid));
			break;
		default:
			unhandled_case(req->direction);
			break;
		}

		pdca_chan_write_reg(chan, MR, PDCA_MR_SIZE(req->reg_width));
		buf = slist_peek_head(&req->buf_list, struct buffer, node);
	}

	/* Must be room for at least one buffer since we're here... */
	assert(status & PDCA_INT_RCZ);

	if (status & PDCA_INT_TRC) {
		pdca_chan_write_reg(chan, MAR, buf->addr.phys);
		pdca_chan_write_reg(chan, TCR, buf->len >> req->reg_width);
	}

	if (!slist_node_is_last(&req->buf_list, &buf->node)) {
		buf_next = slist_entry(buf->node.next, struct buffer, node);
		pdca_chan_write_reg(chan, MARR, buf_next->addr.phys);
		pdca_chan_write_reg(chan, TCRR,
				buf_next->len >> req->reg_width);
		pdca_chan_write_reg(chan, IER, PDCA_INT_RCZ);
	} else {
		/*
		 * We're currently doing the last transfer, so
		 * we don't care about the reload registers.
		 */
		assert(!buf_next);
		pdca_chan_write_reg(chan, IDR, PDCA_INT_RCZ);
	}

out:
	chan->current_buf = buf;
	chan->next_buf = buf_next;
}

static void pdca_interrupt(void *data)
{
	unsigned int		chan_id;
	unsigned long		pending_mask;

	pending_mask = get_irq_group_requests(PDCA_IRQ);
	assert(pending_mask);

	while (pending_mask) {
		chan_id = __fls(pending_mask);
		clear_bit(chan_id, &pending_mask);
		pdca_chan_interrupt(&pdca_channel[chan_id]);
	}
}
DEFINE_IRQ_HANDLER(pdca, pdca_interrupt, 0);

static void pdca_chan_submit_req(struct dmac_channel *dch,
		struct dmac_request *req)
{
	struct pdca_channel	*chan = pdca_channel_of(dch);
	bool			queued = true;

	/* Sanity checks */
	assert(cpu_irq_is_enabled());
	assert(test_bit(PDCA_CHAN_ALLOCATED, &chan->flags));
	dmac_verify_req(req);

	/* Initialize request state */
	req->bytes_xfered = 0;
	req->status = -STATUS_IN_PROGRESS;

	/*
	 * Queue the request if possible. The interrupt handler will
	 * start the transfer as soon as possible.
	 */
	cpu_irq_disable();
	if (likely(test_bit(PDCA_CHAN_ENABLED, &chan->flags))) {
		slist_insert_tail(&chan->req_queue, &req->node);
		pdca_chan_write_reg(chan, CR, PDCA_CR_TEN);
		pdca_chan_write_reg(chan, IER, PDCA_INT_TRC | PDCA_INT_TERR);
	} else {
		queued = false;
	}
	cpu_irq_enable();

	/* If the channel is disabled, terminate the request immediately */
	if (!queued)
		pdca_chan_req_done(chan, req, -STATUS_IO_ERROR);
}

static void pdca_chan_reset(struct dmac_channel *dch)
{
	struct pdca_channel	*chan = pdca_channel_of(dch);
	struct dmac_request	*req;

	assert(cpu_irq_is_enabled());

	/* Disable and reset the channel */
	cpu_irq_disable();
	clear_bit(PDCA_CHAN_ENABLED, &chan->flags);
	pdca_chan_stop(chan);
	cpu_irq_enable();

	chan->current_buf = NULL;
	chan->next_buf = NULL;

	/* Terminate all requests */
	while (!slist_is_empty(&chan->req_queue)) {
		req = slist_pop_head(&chan->req_queue,
				struct dmac_request, node);
		pdca_chan_req_done(chan, req, -STATUS_IO_ERROR);
	}

	/* Allow queueing new requests */
	set_bit(PDCA_CHAN_ENABLED, &chan->flags);
}

static void pdca_chan_init(struct pdca_channel *chan, unsigned int index,
		enum dmac_periph_id rx_periph, enum dmac_periph_id tx_periph)
{
	build_assert(NR_CHANNELS < 32);
	assert(test_bit(PDCA_CHAN_ALLOCATED, &chan->flags));
	assert(index < NR_CHANNELS);
	assert(rx_periph != DMAC_PERIPH_NONE || tx_periph != DMAC_PERIPH_NONE);

	/* Start from a clean slate */
	chan->current_buf = NULL;
	chan->next_buf = NULL;

	/*
	 * Clear all flags except PDCA_CHAN_ALLOCATED, which must
	 * already be set at this point.
	 */
	chan->flags = 1 << PDCA_CHAN_ALLOCATED;

	/* Initialize private driver state */
	slist_init(&chan->req_queue);
	chan->regs = (void *)(PDCA_BASE + index * PDCA_CHAN_REGS_SIZE);
	chan->rx_pid = pdca_get_periph_id(rx_periph);
	chan->tx_pid = pdca_get_periph_id(tx_periph);

	/* Initialize public DMAC interface */
	chan->dch.submit_req = pdca_chan_submit_req;
	chan->dch.reset = pdca_chan_reset;
	chan->dch.max_buffer_size = 32768;

	/* Initialize hardware */
	pdca_chan_write_reg(chan, IDR, ~0UL);
	pdca_chan_write_reg(chan, CR, PDCA_CR_ECLR | PDCA_CR_TDIS);

	/* Allow clients to queue requests */
	set_bit(PDCA_CHAN_ENABLED, &chan->flags);
}

struct dmac_channel *pdca_alloc_channel(struct dma_controller *dmac,
		enum dmac_periph_id rx_periph, enum dmac_periph_id tx_periph,
		phys_addr_t rx_reg_addr, phys_addr_t tx_reg_addr)
{
	struct pdca_channel	*chan;
	unsigned int		i;

	/* Find any unused channel */
	for (i = 0; i < ARRAY_LEN(pdca_channel); i++) {
		chan = &pdca_channel[i];

		if (!atomic_test_and_set_bit(PDCA_CHAN_ALLOCATED,
					&chan->flags)) {
			/* Got it */
			pdca_chan_init(chan, i, rx_periph, tx_periph);
			break;
		}
		chan = NULL;
	}

	return chan ? &chan->dch : NULL;
}

void pdca_free_channel(struct dma_controller *dmac,
		struct dmac_channel *dch)
{
	struct pdca_channel	*chan = pdca_channel_of(dch);

	/* The queue must be empty, and there must be no ongoing transfers */
	assert(slist_is_empty(&chan->req_queue));
	assert(test_bit(PDCA_CHAN_ALLOCATED, &chan->flags));
	assert(test_bit(PDCA_CHAN_ENABLED, &chan->flags));
	assert(!(pdca_chan_read_reg(chan, SR) &  PDCA_SR_TEN));

	/* Disallow queueing new requests and mark the channel as free */
	atomic_clear_bit(PDCA_CHAN_ENABLED, &chan->flags);
	atomic_clear_bit(PDCA_CHAN_ALLOCATED, &chan->flags);
}

struct dma_controller pdca_controller = {
	.alloc_chan	= pdca_alloc_channel,
	.free_chan	= pdca_free_channel,
};

void pdca_init(void)
{
	setup_irq_handler(PDCA_IRQ, pdca, 0, &pdca_controller);
}
