#if WITH_SRV_USB

#include <jbcore/debug.h>
#include <jbcore/reg.h>
#include <string.h>
#include <platform.h>
#include "stm32f10x_rcc.h"
#include "usb_lib.h"
#include "usb_regs.h"
#include <hw/usb.h>
#include <dev/usb/usbc.h>
#include <target/usbconfig.h>

static usb_callback usbcb;
__IO uint16_t SaveRState;
__IO uint16_t SaveTState;
static int pending_addr;

/* ep0 progress */
static bool ep0_tx_active = false;
static const uint8_t *ep0_tx_buf;
static size_t ep0_tx_pos;
static size_t ep0_tx_len;

static void set_device_address(uint8_t addr)
{
  int i;

  /* set address in every used endpoint */
  for (i = 0; i < EP_NUM; i++)
  {
    _SetEPAddress((uint8_t)i, (uint8_t)i);
  } /* for */
  _SetDADDR(addr | DADDR_EF); /* set device address */
}	

static void init_ep0(void)
{
	SetEPType(ENDP0, EP_CONTROL);
	SetEPTxStatus(ENDP0, EP_TX_NAK);
	SetEPRxAddr(ENDP0, ENDP0_RXADDR);
	SetEPTxAddr(ENDP0, ENDP0_TXADDR);
	Clear_Status_Out(ENDP0);
	SetEPRxCount(ENDP0, EP0_BUFLEN);
	SetEPRxValid(ENDP0);

	/* Initialize Endpoint 1 */
	SetEPType(ENDP1, EP_ISOCHRONOUS);
	SetEPDblBuffAddr(ENDP1, ENDP1_BUF0Addr, ENDP1_BUF1Addr);
	SetEPDblBuffCount(ENDP1, EP_DBUF_OUT, ISO_BUFLEN);
	ClearDTOG_RX(ENDP1);
	ClearDTOG_TX(ENDP1);
	ToggleDTOG_TX(ENDP1);
	SetEPRxStatus(ENDP1, EP_RX_VALID);
	SetEPTxStatus(ENDP1, EP_TX_DIS);
	SetEPRxValid(ENDP1);

	set_device_address(0);
}

void usbc_init(portid_t *port)
{
	int err;

	TRACE_ENTRY;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, DISABLE);
	_jk_thread_sleep(10);

	/* Select USBCLK source */
	RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
	_jk_thread_sleep(10);

	/* clock usb */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
	_jk_thread_sleep(10);

	_jk_irq_mask(USB_HP_CAN1_TX_IRQn);
	_jk_irq_mask(USB_LP_CAN1_RX0_IRQn);

	/* create a port to receive impulses on */
	*port = _jk_port_create();
	if (*port < 0)
		return;

	/* register for impulses on the usb irqs */
	irq_t irq = {
		.type = IRQ_HOOK_IMPULSE,
		.u.im = {
			.pid = _jk_get_current_process_id(),
			.port = *port,
			.priority_boost = HIGH_PRIORITY,
		},
	};

	memset(irq.u.im.data, 0, sizeof(irq.u.im.data));

	irq.u.im.data[0] = 0;
	err = _jk_irq_register(USB_HP_CAN1_TX_IRQn, &irq);
	if (err != NO_ERROR) {
		return; // XXX cleanup
	}

	irq.u.im.data[0] = 1;
	err = _jk_irq_register(USB_LP_CAN1_RX0_IRQn, &irq);
	if (err != NO_ERROR) {
		return; // XXX cleanup
	}

	TRACEF("resetting usb hardware\n");
	TRACEF("CNTR 0x%x\n", *CNTR);

	usbc_reset();

	TRACE_EXIT;
}

int usbc_set_callback(usb_callback cb)
{
	usbcb = cb;	

	return NO_ERROR;
}

void usbc_reset(void)
{
	/* reset usb hardware */
	*CNTR |= CNTR_FRES;
	_jk_thread_sleep(100);
	*CNTR = ~CNTR_PDWN;
	_jk_thread_sleep(100);
	*CNTR = 0; // unset reset

	/* clear pending interrupts */
	_SetISTR(0);

	SetBTABLE(BTABLE_ADDRESS);

	init_ep0();

	pending_addr = 0;
	set_device_address(0);
}

int usbc_set_active(bool enable)
{
	if (enable) {
		usbc_reset();

		/* unmask everything except SOF */
		*CNTR |= CNTR_CTRM | CNTR_DOVRM | CNTR_ERRM | CNTR_WKUPM | CNTR_SUSPM | CNTR_RESETM | CNTR_ESOFM;
		*DADDR |= DADDR_EF;
//		_jk_irq_unmask(USB_HP_CAN1_TX_IRQn); // XXX just use the LP interrupt for now
		_jk_irq_unmask(USB_LP_CAN1_RX0_IRQn);

		TARGET_SET_USB_ENABLE(true);
	} else {
		TARGET_SET_USB_ENABLE(false);

		_jk_irq_mask(USB_HP_CAN1_TX_IRQn);
		_jk_irq_mask(USB_LP_CAN1_RX0_IRQn);
		*DADDR &= ~DADDR_EF;
		*CNTR &= 0xff;
	}

	return NO_ERROR;
}

void usbc_irq_impulse(const char *impulse)
{
//	TRACEF("impulse %p: %d time %d\n", impulse, impulse[0], current_time());

	uint32_t istr = *ISTR;
//	TRACEF("CNTR 0x%x\n", *CNTR);
//	TRACEF("ISTR 0x%hx\n", istr);
//	TRACEF("DADDR 0x%hx\n", *DADDR);
//	TRACEF("ep 0x%x\n", _GetENDPOINT(0));
//	hexdump(0x40006000L, 16);

#if 0
	if ((istr & 0xff00) == 0)
		TRACEF("useless usb interrupt, %d\n", impulse[0]);
#endif

	if (istr & ISTR_CTR) {
		/* we get signal */
		LTRACEF("usb CTR\n");

		// XXX these are declared global in the STM code, move to local
	    SaveRState = _GetENDPOINT(ENDP0);
	    SaveTState = SaveRState & EPTX_STAT;
	    SaveRState &=  EPRX_STAT;	

//	    _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);

		uint ep = istr & ISTR_EP_ID;
		if (ep == 0 && (istr & ISTR_DIR)) {
			/* received data on ep0 */
			uint32_t epval = _GetENDPOINT(0);
			if (epval & EP_SETUP) {
				uint8_t setup_buf[64]; // XXX handle arbitrary sized setup packets

	//			TRACEF("count 0x%x\n", _GetEPRxCount(0));

				uint count = _GetEPRxCount(0);
				ALWAYS_ASSERT(count <= 64);

				PMAToUserBufferCopy(setup_buf, _GetEPRxAddr(0), count);

				_ClearEP_CTR_RX(ENDP0); /* SETUP bit kept frozen while CTR_RX = 1 */

				union usb_callback_args args;
				args.setup = (void *)setup_buf;
				if (usbcb)
					usbcb(CB_SETUP_MSG, &args);
			} else {
				LTRACEF("OUT on ep0, len %d\n", _GetEPRxCount(ENDP0));

				_ClearEP_CTR_RX(ENDP0);
			}

			_SetEPRxTxStatus(ENDP0, SaveRState, SaveTState);	

			LTRACEF("ep 0x%x\n", _GetENDPOINT(ENDP0));
		} else if (ep == 0 && !(istr & ISTR_DIR)) {
			/* in transfer on ep0 */
			LTRACEF("in transfer on ep %d, len %d\n", ep, _GetEPTxCount(ENDP0));
			LTRACEF("ep0 tx status: active %d, buf %p, pos %d, len %d\n", ep0_tx_active, ep0_tx_buf, ep0_tx_pos, ep0_tx_len);

			if (ep0_tx_active) {
				if (ep0_tx_pos == ep0_tx_len) {
					if ((ep0_tx_pos != 0) && (ep0_tx_pos % EP0_BUFLEN) == 0) {
						// will need to send zero length packet
						SetEPTxCount(ENDP0, 0);
						SaveTState = EP_TX_VALID;
					} else {
						// transfer is completed
						ep0_tx_active = false;
						SaveTState = EP_TX_STALL;
					}
				} else {
					// more data for the transfer
					size_t tosend = MIN(ep0_tx_len - ep0_tx_pos, EP0_BUFLEN);

					UserToPMABufferCopy(ep0_tx_buf + ep0_tx_pos, GetEPTxAddr(ENDP0), tosend);
					SetEPTxCount(ENDP0, tosend);

					ep0_tx_pos += tosend;
					SaveTState = EP_TX_VALID;
				}
			} else {
				TRACEF("in interrupt when not active?\n");
			}

			// see if we have a pending address change
			if (pending_addr > 0) {
				set_device_address(pending_addr);
				pending_addr = 0;
			}

			_ClearEP_CTR_TX(ENDP0);
			_SetEPRxTxStatus(ENDP0, SaveRState, SaveTState);	
		} else {
			if (ep == 1 && (istr & ISTR_DIR)) {
				// out isochronous transfer
				static uint32_t lastframe;
				uint32_t frame = _GetFNR() & FNR_FN;

				if (frame != ((lastframe + 1) & 0x7ff)) {
					TRACEF("lost frame, last %d, now %d\n", lastframe, frame);
				}

				static uint8_t isobuf[ISO_BUFLEN];
				size_t len;

				if (GetENDPOINT(ENDP1) & EP_DTOG_TX) {
					len = GetEPDblBuf0Count(ENDP1);
					PMAToUserBufferCopy(isobuf, ENDP1_BUF0Addr, len);
				} else {
					len = GetEPDblBuf1Count(ENDP1);
					PMAToUserBufferCopy(isobuf, ENDP1_BUF1Addr, len);
				}
#if 0
				static uint32_t count = 0;
				count++;
				if ((count % 1000) == 0)
					printf("iso count %d\n", count);
#endif

				lastframe = _GetFNR() & FNR_FN;
				_ClearEP_CTR_RX(ep);
			}
		}


//		TRACEF("ISTR 0x%hx\n", istr);
//		TRACEF("ep 1 0x%x\n", _GetENDPOINT(1));
//		hexdump(0x40006000L, 64);
	}

	if (istr & ISTR_RESET) {
//		TRACEF("usb RESET\n");
		_SetISTR((uint16_t)CLR_RESET);
		init_ep0();
		goto done;
	}

	if (istr & ISTR_DOVR) {
//		TRACEF("usb DOVR\n");
		_SetISTR((uint16_t)CLR_DOVR);
	}
	if (istr & ISTR_ERR) {
//		TRACEF("usb ERR\n");
		_SetISTR((uint16_t)CLR_ERR);
	}
	if (istr & ISTR_WKUP) {
//		TRACEF("usb WKUP\n");
		_SetISTR((uint16_t)CLR_WKUP);
		*CNTR &= ~CNTR_LPMODE;
		*CNTR &= ~CNTR_FSUSP;
//		init_ep0();
		goto done;
	}
	if (istr & ISTR_SUSP) {
//		TRACEF("usb SUSP\n");
		*CNTR |= CNTR_FSUSP;
		*CNTR |= CNTR_LPMODE;
		_SetISTR((uint16_t)CLR_SUSP);
	}
	if (istr & ISTR_SOF) {
//		TRACEF("usb SOF\n");
		_SetISTR((uint16_t)CLR_SOF);
	}
	if (istr & ISTR_ESOF) {
//		TRACEF("usb ESOF\n");
		_SetISTR((uint16_t)CLR_ESOF);
	}

done:
	if (impulse[0] == 0) {
		// HP interrupt
		_jk_irq_unmask(USB_HP_CAN1_TX_IRQn);
	} else {
		// LP interrupt
		_jk_irq_unmask(USB_LP_CAN1_RX0_IRQn);
	}
}

void usbc_ep0_ack(void)
{
	usbc_ep0_send(NULL, 0, 0);
}

void usbc_ep0_stall(void)
{
	LTRACEF("STALL\n");
	SaveTState = EP_TX_STALL;
}

void usb_copyin_buf(const void *buf, size_t len, size_t offset)
{
}

void usbc_ep0_send(const void *buf, size_t len, size_t maxlen)
{
	LTRACEF("buf %p, len %d, maxlen %d\n", buf, len, maxlen);

	if (ep0_tx_active) {
		TRACEF("error, tx already active\n");
		return;
	}

	size_t total_len = MIN(len, maxlen);
	size_t first_packet_len = MIN(EP0_BUFLEN, total_len);

	/* start the transfer */
	ep0_tx_buf = buf;
	ep0_tx_pos = first_packet_len;
	ep0_tx_len = total_len;
	ep0_tx_active = true;

	UserToPMABufferCopy(buf, GetEPTxAddr(ENDP0), first_packet_len);

	SetEPTxCount(ENDP0, first_packet_len);

	// XXX kind of grody, the way the STM code does it
	SaveTState = EP_TX_VALID;
	SaveRState = EP_RX_VALID;
}

void usbc_ep0_recv(void *buf, size_t len, ep_callback cb)
{
	PANIC_UNIMPLEMENTED;
}

bool usbc_is_highspeed(void)
{
	return false;
}

void usbc_set_address(uint8_t addr)
{
	pending_addr = addr;
}

#endif

