/* Copyright (c) 2013 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 "clock.h"
#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 CPRINTF(format, args...) cprintf(CC_DMA, format, ## args)

/* Callback data to use when IRQ fires */
static struct {
	void (*cb)(void *);	/* Callback function to call */
	void *cb_data;		/* Callback data for callback function */
} dma_irq[STM32_DMAC_COUNT];


/**
 * Return the IRQ for the DMA channel
 *
 * @param channel	Channel number
 * @return IRQ for the channel
 */
static int dma_get_irq(enum dma_channel channel)
{
#ifdef CHIP_FAMILY_STM32F0
	if (channel == STM32_DMAC_CH1)
		return STM32_IRQ_DMA_CHANNEL_1;

	return channel > STM32_DMAC_CH3 ?
		STM32_IRQ_DMA_CHANNEL_4_7 :
		STM32_IRQ_DMA_CHANNEL_2_3;
#else
	if (channel < STM32_DMAC_PER_CTLR)
		return STM32_IRQ_DMA_CHANNEL_1 + channel;
	else
		return STM32_IRQ_DMA2_CHANNEL1 +
			(channel - STM32_DMAC_PER_CTLR);
#endif
}

/*
 * Note, you must decrement the channel value by 1 from what is specified
 * in the datasheets, as they index from 1 and this indexes from 0!
 */
stm32_dma_chan_t *dma_get_channel(enum dma_channel channel)
{
	stm32_dma_regs_t *dma = STM32_DMA_REGS(channel);

	return &dma->chan[channel % STM32_DMAC_PER_CTLR];
}

#ifdef STM32_DMA_CSELR
void dma_select_channel(enum dma_channel channel, unsigned char stream)
{
	/* Local channel # starting from 0 on each DMA controller */
	const unsigned char ch = channel % STM32_DMAC_PER_CTLR;
	const unsigned char shift = STM32_DMA_PERIPHERALS_PER_CHANNEL;
	const unsigned char mask = (1 << shift) - 1;
	uint32_t val;

	ASSERT(ch < STM32_DMAC_PER_CTLR);
	ASSERT(stream <= mask);
	val = STM32_DMA_CSELR(channel) & ~(mask << ch * shift);
	STM32_DMA_CSELR(channel) = val | (stream << ch * shift);
}
#endif

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

	if (chan->ccr & STM32_DMA_CCR_EN)
		chan->ccr &= ~STM32_DMA_CCR_EN;
}

void dma_disable_all(void)
{
	int ch;

	for (ch = 0; ch < STM32_DMAC_COUNT; ch++) {
		stm32_dma_chan_t *chan = dma_get_channel(ch);
		chan->ccr &= ~STM32_DMA_CCR_EN;
	}
}

/**
 * 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:
 *				STM32_DMA_CCR_MINC | STM32_DMA_CCR_DIR for tx
 *				0 for rx
 */
static void prepare_channel(enum dma_channel channel, unsigned count,
		void *periph, void *memory, unsigned flags)
{
	stm32_dma_chan_t *chan = dma_get_channel(channel);
	uint32_t ccr = STM32_DMA_CCR_PL_VERY_HIGH;

	dma_disable(channel);
	dma_clear_isr(channel);

	/* Following the order in Doc ID 15965 Rev 5 p194 */
	chan->cpar = (uint32_t)periph;
	chan->cmar = (uint32_t)memory;
	chan->cndtr = count;
	chan->ccr = ccr;
	ccr |= flags;
	chan->ccr = ccr;
}

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

	/* Fire it up */
	chan->ccr |= STM32_DMA_CCR_EN;
}

void dma_prepare_tx(const struct dma_option *option, unsigned count,
		    const void *memory)
{
	/*
	 * Cast away const for memory pointer; this is ok because we know
	 * we're preparing the channel for transmit.
	 */
	prepare_channel(option->channel, count, option->periph, (void *)memory,
			STM32_DMA_CCR_MINC | STM32_DMA_CCR_DIR |
			option->flags);
}

void dma_start_rx(const struct dma_option *option, unsigned count,
		  void *memory)
{
	stm32_dma_chan_t *chan = dma_get_channel(option->channel);
	prepare_channel(option->channel, count, option->periph, memory,
			STM32_DMA_CCR_MINC | option->flags);
	dma_go(chan);
}

int dma_bytes_done(stm32_dma_chan_t *chan, int orig_count)
{
	if (!(chan->ccr & STM32_DMA_CCR_EN))
		return 0;
	return orig_count - chan->cndtr;
}

#ifdef CONFIG_DMA_HELP
void dma_dump(enum dma_channel channel)
{
	stm32_dma_regs_t *dma = STM32_DMA_REGS(channel);
	stm32_dma_chan_t *chan = dma_get_channel(channel);

	CPRINTF("ccr=%x, cndtr=%x, cpar=%x, cmar=%x\n", chan->ccr,
		chan->cndtr, chan->cpar, chan->cmar);
	CPRINTF("chan %d, isr=%x, ifcr=%x\n",
		channel,
		(dma->isr >> ((channel % STM32_DMAC_PER_CTLR) * 4)) & 0xf,
		(dma->ifcr >> ((channel % STM32_DMAC_PER_CTLR) * 4)) & 0xf);
}

void dma_check(enum dma_channel channel, char *buf)
{
	stm32_dma_chan_t *chan;
	int count;
	int i;

	chan = dma_get_channel(channel);
	count = chan->cndtr;
	CPRINTF("c=%d\n", count);
	udelay(100 * MSEC);
	CPRINTF("c=%d\n", chan->cndtr);
	for (i = 0; i < count; i++)
		CPRINTF("%02x ", buf[i]);
	udelay(100 * MSEC);
	CPRINTF("c=%d\n", chan->cndtr);
	for (i = 0; i < count; i++)
		CPRINTF("%02x ", buf[i]);
}

/* Run a check of memory-to-memory DMA */
void dma_test(enum dma_channel channel)
{
	stm32_dma_chan_t *chan = dma_get_channel(channel);
	uint32_t ctrl;
	char periph[16], memory[16];
	unsigned count = sizeof(periph);
	int i;

	memset(memory, '\0', sizeof(memory));
	for (i = 0; i < count; i++)
		periph[i] = 10 + i;

	/* Following the order in Doc ID 15965 Rev 5 p194 */
	chan->cpar = (uint32_t)periph;
	chan->cmar = (uint32_t)memory;
	chan->cndtr = count;
	ctrl = STM32_DMA_CCR_PL_MEDIUM;
	chan->ccr = ctrl;

	ctrl |= STM32_DMA_CCR_MINC; /* | STM32_DMA_CCR_DIR */;
	ctrl |= STM32_DMA_CCR_MEM2MEM;
	ctrl |= STM32_DMA_CCR_PINC;
/*	ctrl |= STM32_DMA_CCR_MSIZE_32_BIT; */
/*	ctrl |= STM32_DMA_CCR_PSIZE_32_BIT; */
	chan->ccr = ctrl;
	chan->ccr = ctrl | STM32_DMA_CCR_EN;

	for (i = 0; i < count; i++)
		CPRINTF("%d/%d ", periph[i], memory[i]);
	CPRINTF("\ncount=%d\n", chan->cndtr);
}
#endif /* CONFIG_DMA_HELP */

void dma_init(void)
{
#if defined(CHIP_FAMILY_STM32L4)
	STM32_RCC_AHB1ENR |= STM32_RCC_AHB1ENR_DMA1EN|STM32_RCC_AHB1ENR_DMA2EN;
#else
	STM32_RCC_AHBENR |= STM32_RCC_HB_DMA1;
#endif
#ifdef CHIP_FAMILY_STM32F3
	STM32_RCC_AHBENR |= STM32_RCC_HB_DMA2;
#endif
	/* Delay 1 AHB clock cycle after the clock is enabled */
	clock_wait_bus_cycles(BUS_AHB, 1);
}

int dma_wait(enum dma_channel channel)
{
	stm32_dma_regs_t *dma = STM32_DMA_REGS(channel);
	const uint32_t mask = STM32_DMA_ISR_TCIF(channel);
	timestamp_t deadline;

	deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
	while ((dma->isr & mask) != mask) {
		if (deadline.val <= get_time().val)
			return EC_ERROR_TIMEOUT;

		udelay(DMA_POLLING_INTERVAL_US);
	}
	return EC_SUCCESS;
}

static inline void _dma_wake_callback(void *cb_data)
{
	task_id_t id = (task_id_t)(int)cb_data;
	if (id != TASK_ID_INVALID)
		task_set_event(id, TASK_EVENT_DMA_TC, 0);
}

void dma_enable_tc_interrupt(enum dma_channel channel)
{
	dma_enable_tc_interrupt_callback(channel, _dma_wake_callback,
					 (void *)(int)task_get_current());
}

void dma_enable_tc_interrupt_callback(enum dma_channel channel,
				      void (*callback)(void *),
				      void *callback_data)
{
	stm32_dma_chan_t *chan = dma_get_channel(channel);

	dma_irq[channel].cb = callback;
	dma_irq[channel].cb_data = callback_data;

	chan->ccr |= STM32_DMA_CCR_TCIE;
	task_enable_irq(dma_get_irq(channel));
}

void dma_disable_tc_interrupt(enum dma_channel channel)
{
	stm32_dma_chan_t *chan = dma_get_channel(channel);

	chan->ccr &= ~STM32_DMA_CCR_TCIE;
	task_disable_irq(dma_get_irq(channel));

	dma_irq[channel].cb = NULL;
	dma_irq[channel].cb_data = NULL;
}

void dma_clear_isr(enum dma_channel channel)
{
	stm32_dma_regs_t *dma = STM32_DMA_REGS(channel);

	dma->ifcr |= STM32_DMA_ISR_ALL(channel);
}

#ifdef CONFIG_DMA_DEFAULT_HANDLERS
#ifdef CHIP_FAMILY_STM32F0
void dma_event_interrupt_channel_1(void)
{
	if (STM32_DMA1_REGS->isr & STM32_DMA_ISR_TCIF(STM32_DMAC_CH1)) {
		dma_clear_isr(STM32_DMAC_CH1);
		if (dma_irq[STM32_DMAC_CH1].cb != NULL)
			(*dma_irq[STM32_DMAC_CH1].cb)
				(dma_irq[STM32_DMAC_CH1].cb_data);
	}
}
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_1, dma_event_interrupt_channel_1, 1);

void dma_event_interrupt_channel_2_3(void)
{
	int i;

	for (i = STM32_DMAC_CH2; i <= STM32_DMAC_CH3; i++) {
		if (STM32_DMA1_REGS->isr & STM32_DMA_ISR_TCIF(i)) {
			dma_clear_isr(i);
			if (dma_irq[i].cb != NULL)
				(*dma_irq[i].cb)(dma_irq[i].cb_data);
		}
	}
}
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_2_3, dma_event_interrupt_channel_2_3, 1);

void dma_event_interrupt_channel_4_7(void)
{
	int i;

	for (i = STM32_DMAC_CH4; i <= STM32_DMAC_CH7; i++) {
		if (STM32_DMA1_REGS->isr & STM32_DMA_ISR_TCIF(i)) {
			dma_clear_isr(i);
			if (dma_irq[i].cb != NULL)
				(*dma_irq[i].cb)(dma_irq[i].cb_data);
		}
	}
}
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_4_7, dma_event_interrupt_channel_4_7, 1);

#else /* !CHIP_FAMILY_STM32F0 */

#define DECLARE_DMA_IRQ(x) \
	void CONCAT2(dma_event_interrupt_channel_, x)(void) \
	{ \
		dma_clear_isr(CONCAT2(STM32_DMAC_CH, x)); \
		if (dma_irq[CONCAT2(STM32_DMAC_CH, x)].cb != NULL) \
			(*dma_irq[CONCAT2(STM32_DMAC_CH, x)].cb) \
				(dma_irq[CONCAT2(STM32_DMAC_CH, x)].cb_data); \
	} \
	DECLARE_IRQ(CONCAT2(STM32_IRQ_DMA_CHANNEL_, x), \
		    CONCAT2(dma_event_interrupt_channel_, x), 1);

DECLARE_DMA_IRQ(1);
DECLARE_DMA_IRQ(2);
DECLARE_DMA_IRQ(3);
DECLARE_DMA_IRQ(4);
DECLARE_DMA_IRQ(5);
DECLARE_DMA_IRQ(6);
DECLARE_DMA_IRQ(7);
#ifdef CHIP_FAMILY_STM32F3
DECLARE_DMA_IRQ(9);
DECLARE_DMA_IRQ(10);
#endif

#endif /* CHIP_FAMILY_STM32F0 */
#endif /* CONFIG_DMA_DEFAULT_HANDLERS */
