/* dwc controller pcd drivers  */
/*
 * (C) Copyright 2010 Amlogic, Inc
 *
 * Victor Wan, victor.wan@amlogic.com, 
 * 2010-03-24 @ Shanghai
 *
 */
#include "platform.h"
#include "usb_ch9.h"
#include "dwc_pcd.h"
#include "dwc_pcd_irq.h"

pcd_struct_t this_pcd;
dwc_ep_t g_dwc_eps[NUM_EP]; 
int dwc_core_init()
{

    int32_t         snpsid;
    
    snpsid = dwc_read_reg32(DWC_REG_GSNPSID);

    if ((snpsid & 0xFFFFF000) != 0x4F542000) {
        ERR("Bad value for SNPSID: 0x%08x\n", snpsid);
        return -1;
    }

    ERR("dwc core init is ok!\n");// show printf is ok.
    /*
     * Disable the global interrupt until all the interrupt
     * handlers are installed.
     */
    gahbcfg_data_t  ahbcfg = {.d32 = 0 };
    ahbcfg.b.glblintrmsk = 1;   /* Enable interrupts bit */
    dwc_modify_reg32(DWC_REG_GAHBCFG, ahbcfg.d32, 0);
    /*
     * Initialize the DWC_otg core.
     */
    dwc_otg_core_init();

    dwc_modify_reg32(DWC_REG_DCTL,0,2);// Disconnect data line
    dwc_otg_pcd_init();
    dwc_modify_reg32(DWC_REG_DCTL,2,0);// Connect data line

    /*
     * Enable the global interrupt after all the interrupt
     * handlers are installed.
     */
    dwc_otg_enable_global_interrupts();

    return 0;
}

int
dwc_otg_irq()
{
	dwc_common_irq();
	dwc_pcd_irq();
	return 0;
}
void dwc_otg_pullup(int is_on)
{
	if(is_on)
		dwc_modify_reg32(DWC_REG_DCTL,2,0);// connect data line
    else dwc_modify_reg32(DWC_REG_DCTL,0,2);// disconnect data line
}
static void dwc_otg_core_init() //Elvis Fool, add 'static'
{
    gahbcfg_data_t  ahbcfg = {.d32 = 0 };
    gusbcfg_data_t  usbcfg = {.d32 = 0 };
 //   gi2cctl_data_t  i2cctl = {.d32 = 0 };
//    hcfg_data_t     hcfg;
    dcfg_data_t     dcfg;
#ifndef USE_FULL_SPEED
	usbcfg.d32 = dwc_read_reg32(DWC_REG_GUSBCFG);

	usbcfg.b.ulpi_ext_vbus_drv = 0;
	usbcfg.b.term_sel_dl_pulse = 0;
	dwc_write_reg32(DWC_REG_GUSBCFG,usbcfg.d32);
#endif
	/*
	* Reset the Controller 
	*/
	dwc_otg_core_reset();

     	usbcfg.d32 = dwc_read_reg32(DWC_REG_GUSBCFG);
	usbcfg.b.srpcap = 0;
	usbcfg.b.hnpcap = 0;
#ifdef USE_FULL_SPEED
	DBG("Core work on Full Speed\n");
	usbcfg.b.physel = 1;  // Work at full speed
	dwc_write_reg32(DWC_REG_GUSBCFG, usbcfg.d32);
	
	dwc_otg_core_reset();	
/* for HOST
	hcfg.d32 = dwc_read_reg32(DWC_REG_ hcfg);
	hcfg.b.fslspclksel = val;
	dwc_write_reg32(&_core_if->host_if->host_global_regs->hcfg, hcfg.d32);
*/
// for Device
	dcfg.d32 = dwc_read_reg32(DWC_REG_DCFG);
	dcfg.b.devspd = 1;//Hi speed phy run at Full speed
	dwc_write_reg32(DWC_REG_DCFG, dcfg.d32);
#else
	DBG("Core work on High Speed\n");
	usbcfg.b.ulpi_utmi_sel = 1;
	usbcfg.b.phyif = 0; // 16 bit
	usbcfg.b.ddrsel = 0;
	dwc_write_reg32(DWC_REG_GUSBCFG, usbcfg.d32);

	dwc_otg_core_reset();
#endif
	ahbcfg.b.dmaenable = 0;
	dwc_write_reg32(DWC_REG_GAHBCFG, ahbcfg.d32);


    /*
     * Enable common interrupts 
     */
    dwc_otg_enable_common_interrupts();

    /*
     * Do device or host intialization based on mode during PCD and HCD
     * initialization 
     */
     if(dwc_read_reg32(DWC_REG_GINTSTS) & 0x1){
          DBG("Host Mode\n");
          return ;
    } else {
        DBG("Device Mode\n");
        dwc_otg_core_dev_init();
    }

}

/** 
 * This function initialized the PCD portion of the driver.
 *
 */
static int  dwc_otg_pcd_init()
{
        return 0;
}


/**
 * Do core a soft reset of the core.  Be careful with this because it
 * resets all the internal state machines of the core.
 */
static void dwc_otg_core_reset()		//Elvis Fool, add 'static'
{
    grstctl_t greset = {.d32 = 0 };
    int             count = 0;

    /*
     * Wait for AHB master IDLE state. 
     */
    do {
        udelay(10);
        greset.d32 = dwc_read_reg32(DWC_REG_GRSTCTL);
        if (++count > 100000) {
            //DBG("%s() HANG! AHB Idle GRSTCTL=%0x\n", dwc_otg_core_reset, greset.d32);
            return;
        }
    }
    while (greset.b.ahbidle == 0);

    /*
     * Core Soft Reset 
     */
    count = 0;
    greset.b.csftrst = 1;
    dwc_write_reg32(DWC_REG_GRSTCTL, greset.d32);
    do {
        greset.d32 = dwc_read_reg32(DWC_REG_GRSTCTL);
        if (++count > 1000000) {
            //DBG("%s() HANG! Soft Reset GRSTCTL=%0x\n", dwc_otg_core_reset, greset.d32);
            break;
        }
    }
    while (greset.b.csftrst == 1);

    /*
     * Wait for 3 PHY Clocks
     */
    wait_ms(10);
}

static void dwc_otg_enable_common_interrupts()
{
        gintmsk_data_t intr_mask = { 0};
        /* Clear any pending OTG Interrupts */
        dwc_write_reg32(DWC_REG_GOTGINT, 0xFFFFFFFF); 
        /* Clear any pending interrupts */
        dwc_write_reg32(DWC_REG_GINTSTS, 0xFFFFFFFF); 
        /* 
         * Enable the interrupts in the GINTMSK. 
         */
        intr_mask.b.modemismatch = 1;
        intr_mask.b.otgintr = 1;
        intr_mask.b.rxstsqlvl = 1;
        intr_mask.b.conidstschng = 1;
        intr_mask.b.wkupintr = 1;
        intr_mask.b.disconnect = 1;
        intr_mask.b.usbsuspend = 1;
        intr_mask.b.sessreqintr = 1;
        dwc_write_reg32(DWC_REG_GINTMSK, intr_mask.d32);
}

/** 
 * This function enables the Device mode interrupts.
 *
 * @param _core_if Programming view of DWC_otg controller
 */
static void dwc_otg_enable_device_interrupts()
{
        gintmsk_data_t intr_mask = {  0};

        /* Disable all interrupts. */
        dwc_write_reg32( DWC_REG_GINTMSK, 0);

        /* Clear any pending interrupts */
        dwc_write_reg32( DWC_REG_GINTSTS, 0xFFFFFFFF); 

        /* Enable the common interrupts */
        dwc_otg_enable_common_interrupts( );

        /* Enable interrupts */
        intr_mask.b.usbreset = 1;
        intr_mask.b.enumdone = 1;
        intr_mask.b.epmismatch = 1;
        intr_mask.b.inepintr = 1;
        intr_mask.b.outepintr = 1;
        intr_mask.b.erlysuspend = 1;

        dwc_modify_reg32( DWC_REG_GINTMSK, intr_mask.d32, intr_mask.d32);

}
/**
 * This function enables the controller's Global Interrupt in the AHB Config
 * register.
 *
 * @param[in] _core_if Programming view of DWC_otg controller.
 */
static void dwc_otg_enable_global_interrupts( )
{
        gahbcfg_data_t ahbcfg = { 0};
        ahbcfg.b.glblintrmsk = 1; /* Enable interrupts */
        dwc_modify_reg32(DWC_REG_GAHBCFG, 0, ahbcfg.d32);
}


/**
 * This function initializes the DWC_otg controller registers for
 * device mode.
 * 
 * @param _core_if Programming view of DWC_otg controller
 *
 */
static void dwc_otg_core_dev_init()
{
        dcfg_data_t dcfg = { 0};
        grstctl_t resetctl = { 0 };
        int i;
        fifosize_data_t nptxfifosize;

        /* Restart the Phy Clock */
        dwc_write_reg32(DWC_REG_PCGCCTL, 0);
        
        /* Device configuration register */
	dcfg.d32 = dwc_read_reg32(DWC_REG_DCFG);
#ifdef USE_FULL_SPEED
	dcfg.b.devspd = 1;//Hi speed phy run at Full speed
#else
	dcfg.b.devspd = 0;
#endif
	dcfg.b.perfrint = DWC_DCFG_FRAME_INTERVAL_80;
	dwc_write_reg32(DWC_REG_DCFG, dcfg.d32);
	
        /* Configure data FIFO sizes */

	/* Rx FIFO */
      dwc_write_reg32(DWC_REG_GRXFSIZ, 256 );
      //DBG("new grxfsiz=%08x\n",dwc_read_reg32(DWC_REG_GRXFSIZ));

	/* Non-periodic Tx FIFO */
      nptxfifosize.b.depth  = 256;
      nptxfifosize.b.startaddr = 256;
      dwc_write_reg32(DWC_REG_GNPTXFSIZ, nptxfifosize.d32 );
      //DBG("new gnptxfsiz=%08x\n",dwc_read_reg32(DWC_REG_GNPTXFSIZ));

        /* Flush the FIFOs */
      dwc_otg_flush_tx_fifo( 0x10); /* all Tx FIFOs */
      dwc_otg_flush_rx_fifo();

	/* Flush the Learning Queue. */
      resetctl.b.intknqflsh = 1;
      dwc_write_reg32( DWC_REG_GRSTCTL, resetctl.d32);

        /* Clear all pending Device Interrupts */
        dwc_write_reg32( DWC_REG_DIEPMSK, 0 );
        dwc_write_reg32( DWC_REG_DOEPMSK, 0 );
        dwc_write_reg32( DWC_REG_DAINT, 0xFFFFFFFF );
        dwc_write_reg32( DWC_REG_DAINTMSK, 0 );

        for (i=0; i < NUM_EP; i++) {
		depctl_data_t depctl;
		depctl.d32 = dwc_read_reg32(DWC_REG_IN_EP_REG(i));
		if (depctl.b.epena) {
			depctl.d32 = 0;
			depctl.b.epdis = 1;
			depctl.b.snak = 1;
		} else {
			depctl.d32 = 0;
		}
		dwc_write_reg32( DWC_REG_IN_EP_REG(i),depctl.d32);

		depctl.d32 = dwc_read_reg32(DWC_REG_OUT_EP_REG(i));
		if (depctl.b.epena) {
			depctl.d32 = 0;
			depctl.b.epdis = 1;
			depctl.b.snak = 1;
		} else {
			depctl.d32 = 0;
		}
		dwc_write_reg32( DWC_REG_OUT_EP_REG(i), depctl.d32);

		/* Device IN/OUT Endpoint Transfer Size */
		dwc_write_reg32(DWC_REG_IN_EP_TSIZE(i), 0);
		dwc_write_reg32(DWC_REG_OUT_EP_TSIZE(i), 0);
		/* Device IN/OUT Endpoint DMA Address Register */
		dwc_write_reg32( DWC_REG_IN_EP_DMA(i), 0);
		dwc_write_reg32( DWC_REG_OUT_EP_DMA(i), 0);
		/* Device IN/OUT Endpoint Interrupt Register */
		//dwc_write_reg32( DWC_REG_IN_EP_DMA(i), 0xFF);
		//dwc_write_reg32( DWC_REG_OUT_EP_INTR(i), 0xFF);
        }

        //dwc_otg_set_vbus_power(_core_if, 0); //Power off VBus
		
        dwc_otg_enable_device_interrupts(); 

	  //DBG("init gintmsk: 0x%x\n",dwc_read_reg32(DWC_REG_GINTMSK));
}
/**
 * Flush a Tx FIFO.
 *
 * @param _core_if Programming view of DWC_otg controller.
 * @param _num Tx FIFO to flush.
 */
static void dwc_otg_flush_tx_fifo( const int _num ) 	//Elvis Fool, add 'static'
{
        grstctl_t greset = { 0};
        int count = 0;


	 DBG("dwc_otg_flush_tx_fifo: %d\n",_num);
        greset.b.txfflsh = 1;
        greset.b.txfnum = _num;
        dwc_write_reg32(DWC_REG_GRSTCTL, greset.d32 );
        
        do {
                greset.d32 = dwc_read_reg32( DWC_REG_GRSTCTL);
                if (++count > 10000){
                       // ERR("%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n",
                       //           "dwc_otg_flush_tx_fifo", greset.d32,
                       //           dwc_read_reg32( DWC_REG_GNPTXSTS));
                        break;
                }

        } while (greset.b.txfflsh == 1);
        /* Wait for 3 PHY Clocks*/
        udelay(1);
}

/**
 * Flush Rx FIFO.
 *
 * @param _core_if Programming view of DWC_otg controller.
 */
static void dwc_otg_flush_rx_fifo( ) 
{
        grstctl_t greset = { 0};
        int count = 0;

	  DBG("dwc_otg_flush_rx_fifo\n");
        greset.b.rxfflsh = 1;
        dwc_write_reg32( DWC_REG_GRSTCTL, greset.d32 );
        
        do {
                greset.d32 = dwc_read_reg32( DWC_REG_GRSTCTL);
                if (++count > 10000){
                        //ERR("%s() HANG! GRSTCTL=%0x\n", "dwc_otg_flush_rx_fifo", 
                        //         greset.d32);
                        break;
                }
        } while (greset.b.rxfflsh == 1);        
        /* Wait for 3 PHY Clocks*/
        udelay(1);
}
/**
 * This function does the setup for a data transfer for EP0 and starts
 * the transfer.  For an IN transfer, the packets will be loaded into
 * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are
 * unloaded from the Rx FIFO in the ISR.
 *
 * @param _core_if Programming view of DWC_otg controller.
 * @param _ep The EP0 data.
 */
 #if 0
void dwc_otg_ep0_start_transfer(dwc_ep_t *_ep)
{
	depctl_data_t depctl;
	deptsiz0_data_t deptsiz;
        gintmsk_data_t intr_mask = { 0};

        _ep->total_len = _ep->xfer_len;

	//DBG("dwc_otg_ep0_start_transfer: xfer_len:%d\n",_ep->xfer_len);
	/* IN endpoint */
	if (_ep->is_in == 1) {
		gnptxsts_data_t tx_status = { 0};

		tx_status.d32 = dwc_read_reg32(DWC_REG_GNPTXSTS);
		if (tx_status.b.nptxqspcavail == 0){
			return;
		}

		depctl.d32 = dwc_read_reg32(DWC_REG_IN_EP_REG(0));
		deptsiz.d32 = dwc_read_reg32(DWC_REG_IN_EP_TSIZE(0));

		/* Zero Length Packet? */
		if (_ep->xfer_len == 0) {
			deptsiz.b.xfersize = 0;
			deptsiz.b.pktcnt = 1;
		} else {
			/* Program the transfer size and packet count
			*  as follows: xfersize = N * maxpacket +
			*  short_packet pktcnt = N + (short_packet
			*  exist ? 1 : 0)  
			*/
			if (_ep->xfer_len > _ep->maxpacket) {
				_ep->xfer_len = _ep->maxpacket;
				deptsiz.b.xfersize = _ep->maxpacket;
			}
			else {
				deptsiz.b.xfersize = _ep->xfer_len;
			}
			deptsiz.b.pktcnt = 1;

		}
		dwc_write_reg32(DWC_REG_IN_EP_TSIZE(0), deptsiz.d32);
             //DBG( "IN len=%d  xfersize=%d pktcnt=%d [%08x]\n",
              //              _ep->xfer_len,deptsiz.b.xfersize, deptsiz.b.pktcnt, deptsiz.d32);


		flush_cpu_cache();

		/* EP enable, IN data in FIFO */
		depctl.b.cnak = 1;
		depctl.b.epena = 1;
		dwc_write_reg32(DWC_REG_IN_EP_REG(0), depctl.d32);

		/** 
		 * Enable the Non-Periodic Tx FIFO empty interrupt, the
		 * data will be written into the fifo by the ISR.
		 */ 

		/* First clear it from GINTSTS */
		intr_mask.b.nptxfempty = 1;
		dwc_modify_reg32( DWC_REG_GINTSTS,intr_mask.d32, 0);
		dwc_modify_reg32( DWC_REG_GINTMSK,intr_mask.d32, intr_mask.d32);

                
	} else { /* OUT endpoint */

		depctl.d32 = dwc_read_reg32(DWC_REG_OUT_EP_REG(0));
		deptsiz.d32 = dwc_read_reg32(DWC_REG_OUT_EP_TSIZE(0));

		/* Program the transfer size and packet count as follows:
		*  xfersize = N * (maxpacket + 4 - (maxpacket % 4))
		*  pktcnt = N                                          */
#if 0
		if (_ep->xfer_len == 0) {
			/* Zero Length Packet */
			deptsiz.b.xfersize = _ep->maxpacket;
			deptsiz.b.pktcnt = 1;
		} else {
			deptsiz.b.pktcnt = 
			(_ep->xfer_len + (_ep->maxpacket - 1)) /_ep->maxpacket;
			deptsiz.b.xfersize = deptsiz.b.pktcnt * _ep->maxpacket;
		}
#else   // ep0 pktcnt is 1 bit

		deptsiz.b.xfersize = _ep->maxpacket;
		deptsiz.b.pktcnt = 1;
		udelay(1); // This is needed, don't know reason.
#endif
		

		dwc_write_reg32(DWC_REG_OUT_EP_TSIZE(0), deptsiz.d32);
             //DBG( "OUT len=%d  xfersize=%d pktcnt=%d\n",
             //               _ep->xfer_len,deptsiz.b.xfersize, deptsiz.b.pktcnt);


		/* EP enable */
		depctl.b.cnak = 1;
		depctl.b.epena = 1;
		dwc_write_reg32 (DWC_REG_OUT_EP_REG(0), depctl.d32);
	}
	
	flush_cpu_cache();	
}
 #endif
 
void dwc_otg_ep_start_transfer(dwc_ep_t *_ep)
{
	depctl_data_t depctl;
	deptsiz_data_t deptsiz;
	int epctl_reg,epctltsize_reg;
	int ep_num = _ep->num;
	int is_in = _ep->is_in;
	int ep_mps = _ep->maxpacket;

	//DBG("dwc_otg_ep_start_transfer: xfer_len:%d\n",_ep->xfer_len);

        _ep->total_len = _ep->xfer_len;

	if(is_in){
		epctl_reg = DWC_REG_IN_EP_REG(ep_num);
		epctltsize_reg = DWC_REG_IN_EP_TSIZE(ep_num);
	}else{
		epctl_reg = DWC_REG_OUT_EP_REG(ep_num);
		epctltsize_reg = DWC_REG_OUT_EP_TSIZE(ep_num);	
	}

	depctl.d32 = dwc_read_reg32(epctl_reg);
	deptsiz.d32 = dwc_read_reg32(epctltsize_reg);
	
	/* Zero Length Packet? */
	if (_ep->xfer_len == 0) {
		deptsiz.b.xfersize = is_in?0:ep_mps;
		deptsiz.b.pktcnt = 1;
	} else {
		deptsiz.b.pktcnt = 
		(_ep->xfer_len + (ep_mps - 1)) /ep_mps;
		if(is_in && _ep->xfer_len < ep_mps)
			deptsiz.b.xfersize = _ep->xfer_len;
		else
			deptsiz.b.xfersize = deptsiz.b.pktcnt * ep_mps;
	}

	/* Fill size and count */
	dwc_write_reg32(epctltsize_reg,deptsiz.d32);
	
	/* EP enable */
	depctl.b.cnak = 1;
	depctl.b.epena = 1;
	dwc_write_reg32 (epctl_reg, depctl.d32);
	
	/* IN endpoint */
	if (is_in) {
		gintmsk_data_t intr_mask = {0};
		//gnptxsts_data_t tx_status = { 0};

		//tx_status.d32 = dwc_read_reg32(DWC_REG_GNPTXSTS);
		//if (tx_status.b.nptxqspcavail == 0){
		//	return;
		//}

		/** 
		 * Enable the Non-Periodic Tx FIFO empty interrupt, the
		 * data will be written into the fifo by the ISR.
		 */ 

		/* First clear it from GINTSTS */
		intr_mask.b.nptxfempty = 1;
		dwc_modify_reg32( DWC_REG_GINTSTS,intr_mask.d32, 0);
		dwc_modify_reg32( DWC_REG_GINTMSK,intr_mask.d32, intr_mask.d32);
                
	} 

}
int dwc_otg_ep_req_start(pcd_struct_t *pcd,int ep_num)
{
	dwc_ep_t *ep = &g_dwc_eps[ep_num];

	ep->num = ep_num;
	//DBG("dwc_otg_ep_req_start: ep%d\n",ep_num);
        /* EP0 Transfer? */
	if (ep_num == 0) {
		//DBG("EP0 State: %d\n",pcd->ep0state);
		switch (pcd->ep0state) {
			case EP0_IN_DATA_PHASE:
				break;

			case EP0_OUT_DATA_PHASE:
				if (pcd->request_config) {
				        /* Work around for SetConfig cmd */
				        /* Complete STATUS PHASE */
				        ep->is_in = 1;
				        pcd->ep0state = EP0_STATUS;
				}
				else if(pcd->length == 0)
				{
				        /* Work around for MSC Reset cmd */
				        /* Complete STATUS PHASE */
				        ep->is_in = 1;
				        pcd->ep0state = EP0_STATUS;                                
				}
			break;

			default:
				return -1;
		}

		ep->start_xfer_buff = (uint8_t*)pcd->buf;
		ep->xfer_buff = (uint8_t*)pcd->buf;
		ep->xfer_len = pcd->length;
		ep->xfer_count = 0;
		ep->sent_zlp = 0;
		ep->total_len = ep->xfer_len;
		ep->maxpacket = 64;
		dwc_otg_ep_start_transfer( ep );

	} else {
		/* Setup and start the Transfer for Bulk */
		ep->start_xfer_buff = (uint8_t*)pcd->bulk_buf;
		ep->xfer_buff = (uint8_t*)pcd->bulk_buf;
		ep->xfer_len = pcd->bulk_len;
		ep->xfer_count = 0;
		ep->sent_zlp = 0;
		ep->total_len = ep->xfer_len;
		ep->maxpacket = BULK_EP_MPS;
		ep->is_in = (ep_num == BULK_IN_EP_NUM);
		dwc_otg_bulk_ep_activate( ep );
		dwc_otg_ep_start_transfer( ep );
	}

	return 0;

}

static void dwc_otg_bulk_ep_activate(dwc_ep_t *ep)
{
	depctl_data_t depctl = {0};
	daint_data_t daintmsk = {0};
	int epctl;
	int ep_num = ep->num;

	if(ep->is_in){
		epctl = DWC_REG_IN_EP_REG(ep_num);
		daintmsk.ep.in = 1<<BULK_IN_EP_NUM; //ep1:BULK_IN
	}
	else{
		epctl = DWC_REG_OUT_EP_REG(ep_num);
		daintmsk.ep.out = 1<<BULK_OUT_EP_NUM;//ep2:BULK_OUT
	}
	
	depctl.d32 = dwc_read_reg32(epctl);
	if (!depctl.b.usbactep) {
		depctl.b.mps = BULK_EP_MPS;
		depctl.b.eptype = 2;//BULK_STYLE
		depctl.b.setd0pid = 1;
		depctl.b.txfnum = 0;   //Non-Periodic TxFIFO
		depctl.b.usbactep = 1;

		dwc_write_reg32(epctl, depctl.d32);
	}

 	dwc_modify_reg32(DWC_REG_DAINTMSK, 0, daintmsk.d32);

	return;
}

void dwc_otg_power_off_phy(void)
{
	dwc_write_reg32(DWC_REG_PCGCCTL, 0xF);
}
