/*
 * Blackfin MUSB HCD (Host Controller Driver) for u-boot
 *
 * Copyright (c) 2008-2009 Analog Devices Inc.
 *
 * Licensed under the GPL-2 or later.
 */

#include <common.h>

#include <usb.h>

#include <asm/blackfin.h>
#include <asm/clock.h>
#include <asm/mach-common/bits/usb.h>

#include "musb_core.h"

#ifndef CONFIG_USB_BLACKFIN_CLKIN
#define CONFIG_USB_BLACKFIN_CLKIN 24
#endif

/* MUSB platform configuration */
struct musb_config musb_cfg = {
	.regs       = (struct musb_regs *)USB_FADDR,
	.timeout    = 0x3FFFFFF,
	.musb_speed = 0,
};

/*
 * This function read or write data to endpoint fifo
 * Blackfin use DMA polling method to avoid buffer alignment issues
 *
 * ep		- Endpoint number
 * length	- Number of bytes to write to FIFO
 * fifo_data	- Pointer to data buffer to be read/write
 * is_write	- Flag for read or write
 */
void rw_fifo(u8 ep, u32 length, void *fifo_data, int is_write)
{
	struct bfin_musb_dma_regs *regs;
	u32 val = (u32)fifo_data;

	blackfin_dcache_flush_invalidate_range(fifo_data, fifo_data + length);

	regs = (void *)USB_DMA_INTERRUPT;
	regs += ep;

	/* Setup DMA address register */
	bfin_write16(&regs->addr_low, val);
	SSYNC();

	bfin_write16(&regs->addr_high, val >> 16);
	SSYNC();

	/* Setup DMA count register */
	bfin_write16(&regs->count_low, length);
	bfin_write16(&regs->count_high, 0);
	SSYNC();

	/* Enable the DMA */
	val = (ep << 4) | DMA_ENA | INT_ENA;
	if (is_write)
		val |= DIRECTION;
	bfin_write16(&regs->control, val);
	SSYNC();

	/* Wait for compelete */
	while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << ep)))
		continue;

	/* acknowledge dma interrupt */
	bfin_write_USB_DMA_INTERRUPT(1 << ep);
	SSYNC();

	/* Reset DMA */
	bfin_write16(&regs->control, 0);
	SSYNC();
}

void write_fifo(u8 ep, u32 length, void *fifo_data)
{
	rw_fifo(ep, length, fifo_data, 1);
}

void read_fifo(u8 ep, u32 length, void *fifo_data)
{
	rw_fifo(ep, length, fifo_data, 0);
}


/*
 * CPU and board-specific MUSB initializations.  Aliased function
 * signals caller to move on.
 */
static void __def_musb_init(void)
{
}
void board_musb_init(void) __attribute__((weak, alias("__def_musb_init")));

static void bfin_anomaly_init(void)
{
	u32 revid;

	if (!ANOMALY_05000346 && !ANOMALY_05000347)
		return;

	revid = bfin_revid();

#ifdef __ADSPBF54x__
	if (revid > 0)
		return;
#endif
#ifdef __ADSPBF52x__
	if (ANOMALY_BF526 && revid > 0)
		return;
	if (ANOMALY_BF527 && revid > 1)
		return;
#endif

	if (ANOMALY_05000346) {
		bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value);
		SSYNC();
	}

	if (ANOMALY_05000347) {
		bfin_write_USB_APHY_CNTRL(0x0);
		SSYNC();
	}
}

int musb_platform_init(void)
{
	/* board specific initialization */
	board_musb_init();

	bfin_anomaly_init();

	/* Configure PLL oscillator register */
	bfin_write_USB_PLLOSC_CTRL(0x3080 |
		((480 / CONFIG_USB_BLACKFIN_CLKIN) << 1));
	SSYNC();

	bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1);
	SSYNC();

	bfin_write_USB_EP_NI0_RXMAXP(64);
	SSYNC();

	bfin_write_USB_EP_NI0_TXMAXP(64);
	SSYNC();

	/* Route INTRUSB/INTR_RX/INTR_TX to USB_INT0*/
	bfin_write_USB_GLOBINTR(0x7);
	SSYNC();

	bfin_write_USB_GLOBAL_CTL(GLOBAL_ENA | EP1_TX_ENA | EP2_TX_ENA |
				EP3_TX_ENA | EP4_TX_ENA | EP5_TX_ENA |
				EP6_TX_ENA | EP7_TX_ENA | EP1_RX_ENA |
				EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA |
				EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA);
	SSYNC();

	return 0;
}

/*
 * This function performs Blackfin platform specific deinitialization for usb.
*/
void musb_platform_deinit(void)
{
}
