blob: 43abe80006ac3e2f74f6eeb6653bb82272f41b99 [file] [log] [blame]
/*
* drivers/usb/gadget/aml_tiny_usbtool/dwc_pcd.c
*
* Copyright (C) 2015 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "platform.h"
#include "usb_ch9.h"
#include "dwc_pcd.h"
#include "dwc_pcd_irq.h"
#include <asm/arch/timer.h>
pcd_struct_t this_pcd;
dwc_ep_t g_dwc_eps[NUM_EP];
extern void _mdelay(unsigned long ms);
int dwc_core_init(void)
{
int32_t snpsid;
snpsid = dwc_read_reg32(DWC_REG_GSNPSID);
//printf("Value for SNPSID: 0x%08x\n", snpsid);
if ((snpsid & 0xFFFFF000) != 0x4F543000) {
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(void)
{
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(void) //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;
#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_data_t dcfg;
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(void)
{
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(void) //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
*/
_mdelay(10);
}
static void dwc_otg_enable_common_interrupts(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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);
}