/* Copyright 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "console.h"
#include "dma.h"
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_DMA, outstr)
#define CPRINTS(format, args...) cprints(CC_DMA, format, ## args)

mec1322_dma_chan_t *dma_get_channel(enum dma_channel channel)
{
	mec1322_dma_regs_t *dma = MEC1322_DMA_REGS;

	return &dma->chan[channel];
}

void dma_disable(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);

	if (chan->ctrl & BIT(0))
		chan->ctrl &= ~BIT(0);

	if (chan->act == 1)
		chan->act = 0;
}

void dma_disable_all(void)
{
	int ch;
	mec1322_dma_regs_t *dma;

	for (ch = 0; ch < MEC1322_DMAC_COUNT; ch++) {
		mec1322_dma_chan_t *chan = dma_get_channel(ch);
		/* Abort any current transfer. */
		chan->ctrl |= BIT(25);
		/* Disable the channel. */
		chan->ctrl &= ~BIT(0);
		chan->act = 0;
	}

	/* Soft-reset the block. */
	dma = MEC1322_DMA_REGS;
	dma->ctrl |= 0x2;
}

/**
 * Prepare a channel for use and start it
 *
 * @param chan		Channel to read
 * @param count		Number of bytes to transfer
 * @param periph	Pointer to peripheral data register
 * @param memory	Pointer to memory address for receive/transmit
 * @param flags		DMA flags for the control register, normally:
 *				MEC1322_DMA_INC_MEM | MEC1322_DMA_TO_DEV for tx
 *				MEC1322_DMA_INC_MEM for rx
 */
static void prepare_channel(mec1322_dma_chan_t *chan, unsigned count,
		void *periph, void *memory, unsigned flags)
{
	int xfer_size = (flags >> 20) & 0x7;

	if (chan->ctrl & BIT(0))
		chan->ctrl &= ~BIT(0);

	chan->act |= 0x1;
	chan->dev = (uint32_t)periph;
	chan->mem_start = MEC1322_RAM_ALIAS((uint32_t)memory);
	chan->mem_end = MEC1322_RAM_ALIAS((uint32_t)memory) + xfer_size * count;
	chan->ctrl = flags;
}

void dma_go(mec1322_dma_chan_t *chan)
{
	/* Flush data in write buffer so that DMA can get the latest data */
	asm volatile("dsb;");

	/* Fire it up */
	chan->ctrl |= MEC1322_DMA_RUN;
}

void dma_prepare_tx(const struct dma_option *option, unsigned count,
		    const void *memory)
{
	mec1322_dma_chan_t *chan = dma_get_channel(option->channel);

	/*
	 * Cast away const for memory pointer; this is ok because we know
	 * we're preparing the channel for transmit.
	 */
	prepare_channel(chan, count, option->periph, (void *)memory,
			MEC1322_DMA_INC_MEM | MEC1322_DMA_TO_DEV |
			MEC1322_DMA_DEV(option->channel) | option->flags);
}

void dma_start_rx(const struct dma_option *option, unsigned count,
		  void *memory)
{
	mec1322_dma_chan_t *chan;

	chan = dma_get_channel(option->channel);

	prepare_channel(chan, count, option->periph, memory,
			MEC1322_DMA_INC_MEM | MEC1322_DMA_DEV(option->channel) |
			option->flags);
	dma_go(chan);
}

int dma_bytes_done(mec1322_dma_chan_t *chan, int orig_count)
{
	int xfer_size = (chan->ctrl >> 20) & 0x7;

	return orig_count - (chan->mem_end - chan->mem_start) / xfer_size;
}

bool dma_is_enabled(mec1322_dma_chan_t *chan)
{
	return (chan->ctrl & MEC1322_DMA_RUN);
}

void dma_init(void)
{
	mec1322_dma_regs_t *dma = MEC1322_DMA_REGS;
	dma->ctrl |= 0x1;
}

int dma_wait(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);
	timestamp_t deadline;

	if (chan->act == 0)
		return EC_SUCCESS;

	deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
	while (!(chan->int_status & 0x4)) {
		if (deadline.val <= get_time().val)
			return EC_ERROR_TIMEOUT;

		udelay(DMA_POLLING_INTERVAL_US);
	}
	return EC_SUCCESS;
}

void dma_clear_isr(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);

	chan->int_status |= 0x4;
}
