| |
| /* |
| * drivers/usb/host/dwc_otg_hcd.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 <common.h> |
| #include <asm/io.h> |
| #include <asm/errno.h> |
| #include <asm/arch/usb.h> |
| #include <usb.h> |
| #include <malloc.h> |
| #include <linux/list.h> |
| #include <asm/cache.h> |
| |
| |
| #include "dwc_otg_regs.h" |
| #include "dwc_otg_hcd.h" |
| |
| #define kmalloc(x,y) malloc(x) |
| #define kfree(x) free(x) |
| |
| #define min_t(type,x,y) \ |
| ({ type __x = (x); type __y = (y); __x < __y ? __x : __y; }) |
| |
| #define flush_cpu_cache() do{ dcache_flush();}while(0)//flush_cache //do{_invalidate_dcache();AV_AHB_bus_invalidate();}while(0) //for porting |
| |
| /* ------------------------------------------------------- */ |
| #define get_unaligned_16(ptr) (((__u8 *)ptr)[0] | (((__u8 *)ptr)[1]<<8)) |
| #define get_unaligned_32(ptr) (((__u8 *)ptr)[0] | (((__u8 *)ptr)[1]<<8) | (((__u8 *)ptr)[2]<<16) | (((__u8 *)ptr)[3]<<24)) |
| //#define get_unaligned(ptr) (((__u8 *)ptr)[0] | (((__u8 *)ptr)[1]<<8) | (((__u8 *)ptr)[2]<<16) | (((__u8 *)ptr)[3]<<24)) |
| |
| static dwc_otg_device_t dwc_otg_dev; |
| static int dwc_otg_port_init(dwc_otg_core_if_t * _core_if); |
| static int is_insert = 0; |
| /* The bits respond the channel buy_state. */ |
| //static unsigned int chn_busy; |
| /* |
| void dwc_udelay(int dly) |
| { |
| int i; |
| for (i = 0;i<4000;i++) ; |
| } |
| */ |
| #define dwc_udelay(dly) udelay(dly) |
| /* |
| * Host module config |
| */ |
| static dwc_otg_core_params_t dwc_otg_module_params_host = { |
| DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE, /* otg_cap */ |
| 0, /* dma_enable */ |
| -1, /* dma_burst_size */ |
| DWC_SPEED_PARAM_HIGH, /* speed */ |
| 0, /* host_support_fs_ls_low_power */ |
| 0, /* host_ls_low_power_phy_clk */ |
| 1, /* enable_dynamic_fifo */ |
| -1, /* data_fifo_size */ |
| 0x100, /* dev_rx_fifo_size */ |
| 0x100, /* dev_nperio_tx_fifo_size */ |
| {-1, /* dev_perio_tx_fifo_size *//* 0 */ |
| -1, /* 1 */ |
| -1, /* 2 */ |
| -1, /* 3 */ |
| -1, /* 4 */ |
| -1, /* 5 */ |
| -1, /* 6 */ |
| -1, /* 7 */ |
| -1, /* 8 */ |
| -1, /* 9 */ |
| -1, /* 10 */ |
| -1, /* 11 */ |
| -1, /* 12 */ |
| -1, /* 13 */ |
| -1}, /* 14 */ |
| 320, /* host_rx_fifo_size */ |
| 256, /* host_nperio_tx_fifo_size */ |
| -1, /* host_perio_tx_fifo_size */ |
| 65535, /* max_transfer_size */ |
| 128, /* max_packet_count */ |
| 6, /* host_channels */ |
| 2, /* dev_endpoints */ |
| -1, /* phy_type */ |
| -1, /* phy_utmi_width */ |
| 0, /* phy_ulpi_ddr */ |
| 0, /* i2c_enable */ |
| }; |
| |
| /** |
| * This function disables the controller's Global Interrupt in the AHB Config |
| * register. |
| * |
| * @param[in] _core_if Programming view of DWC_otg controller. |
| */ |
| static void |
| dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * _core_if) |
| { |
| gahbcfg_data_t ahbcfg = {.d32 = 0 }; |
| ahbcfg.b.glblintrmsk = 0; /* disable interrupts */ |
| dwc_modify_reg32(&_core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); |
| } |
| /** |
| * This function enables the controller's Global Interrupt in the AHB Config |
| * register. |
| * |
| * @param[in] _core_if Programming view of DWC_otg controller. |
| */ |
| extern void |
| dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * _core_if) |
| { |
| gahbcfg_data_t ahbcfg = {.d32 = 0 }; |
| ahbcfg.b.glblintrmsk = 1; /* Enable interrupts */ |
| dwc_modify_reg32(&_core_if->core_global_regs->gahbcfg, 0, ahbcfg.d32); |
| } |
| |
| static dwc_otg_core_if_t * |
| dwc_otg_cil_init(const uint32_t * _reg_base_addr, dwc_otg_core_params_t * _core_params) |
| { |
| |
| dwc_otg_core_if_t *core_if = 0; |
| // dwc_otg_dev_if_t *dev_if = 0; |
| dwc_otg_host_if_t *host_if = 0; |
| uint8_t *reg_base = (uint8_t *) _reg_base_addr; |
| int i = 0; |
| |
| DWC_DEBUGPL(DBG_CILV, "%s(%p,%p)\n", __func__, _reg_base_addr, _core_params); |
| |
| core_if = (dwc_otg_core_if_t *) kmalloc(sizeof(dwc_otg_core_if_t), 0); |
| |
| if (core_if == 0) { |
| DWC_DEBUGPL(DBG_CIL, "Allocation of dwc_otg_core_if_t failed\n"); |
| return 0; |
| } |
| |
| memset(core_if, 0, sizeof(dwc_otg_core_if_t)); |
| |
| core_if->core_params = _core_params; |
| core_if->core_global_regs = (dwc_otg_core_global_regs_t *) reg_base; |
| |
| /* |
| * Allocate the Host Mode structures. |
| */ |
| host_if = (dwc_otg_host_if_t *) kmalloc(sizeof(dwc_otg_host_if_t), 0); |
| |
| if (host_if == 0) { |
| DWC_DEBUGPL(DBG_CIL, "Allocation of dwc_otg_host_if_t failed\n"); |
| // kfree(dev_if); |
| kfree(core_if); |
| return 0; |
| } |
| |
| host_if->host_global_regs = (dwc_otg_host_global_regs_t *) |
| (reg_base + DWC_OTG_HOST_GLOBAL_REG_OFFSET); |
| |
| host_if->hprt0 = (uint32_t *) (reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET); |
| |
| for (i = 0; i < MAX_EPS_CHANNELS; i++) { |
| host_if->hc_regs[i] = (dwc_otg_hc_regs_t *) |
| (reg_base + DWC_OTG_HOST_CHAN_REGS_OFFSET + (i * DWC_OTG_CHAN_REGS_OFFSET)); |
| DWC_DEBUGPL(DBG_CILV, "hc_reg[%d]->hcchar=%p\n", i, &host_if->hc_regs[i]->hcchar); |
| } |
| |
| host_if->num_host_channels = MAX_EPS_CHANNELS; |
| core_if->host_if = host_if; |
| |
| for (i = 0; i < MAX_EPS_CHANNELS; i++) { |
| core_if->data_fifo[i] = |
| (uint32_t *) (reg_base + DWC_OTG_DATA_FIFO_OFFSET + (i * DWC_OTG_DATA_FIFO_SIZE)); |
| DWC_DEBUGPL(DBG_CILV, "data_fifo[%d]=0x%08x\n", i, (unsigned) core_if->data_fifo[i]); |
| } |
| |
| core_if->pcgcctl = (uint32_t *) (reg_base + DWC_OTG_PCGCCTL_OFFSET); |
| |
| /* |
| * Store the contents of the hardware configuration registers here for |
| * easy access later. |
| */ |
| core_if->hwcfg1.d32 = dwc_read_reg32(&core_if->core_global_regs->ghwcfg1); |
| core_if->hwcfg2.d32 = dwc_read_reg32(&core_if->core_global_regs->ghwcfg2); |
| core_if->hwcfg3.d32 = dwc_read_reg32(&core_if->core_global_regs->ghwcfg3); |
| core_if->hwcfg4.d32 = dwc_read_reg32(&core_if->core_global_regs->ghwcfg4); |
| |
| DWC_DEBUGPL(DBG_CILV, "hwcfg1=%08x\n", core_if->hwcfg1.d32); |
| DWC_DEBUGPL(DBG_CILV, "hwcfg2=%08x\n", core_if->hwcfg2.d32); |
| DWC_DEBUGPL(DBG_CILV, "hwcfg3=%08x\n", core_if->hwcfg3.d32); |
| DWC_DEBUGPL(DBG_CILV, "hwcfg4=%08x\n", core_if->hwcfg4.d32); |
| |
| DWC_DEBUGPL(DBG_CILV, "op_mode=%0x\n", core_if->hwcfg2.b.op_mode); |
| DWC_DEBUGPL(DBG_CILV, "arch=%0x\n", core_if->hwcfg2.b.architecture); |
| DWC_DEBUGPL(DBG_CILV, "num_dev_ep=%d\n", core_if->hwcfg2.b.num_dev_ep); |
| DWC_DEBUGPL(DBG_CILV, "num_host_chan=%d\n", core_if->hwcfg2.b.num_host_chan); |
| DWC_DEBUGPL(DBG_CILV, "nonperio_tx_q_depth=0x%0x\n", core_if->hwcfg2.b.nonperio_tx_q_depth); |
| DWC_DEBUGPL(DBG_CILV, "host_perio_tx_q_depth=0x%0x\n", core_if->hwcfg2.b.host_perio_tx_q_depth); |
| DWC_DEBUGPL(DBG_CILV, "dev_token_q_depth=0x%0x\n", core_if->hwcfg2.b.dev_token_q_depth); |
| |
| DWC_DEBUGPL(DBG_CILV, "Total FIFO SZ=%d\n", core_if->hwcfg3.b.dfifo_depth); |
| DWC_DEBUGPL(DBG_CILV, "xfer_size_cntr_width=%0x\n", core_if->hwcfg3.b.xfer_size_cntr_width); |
| |
| /* |
| * Set the SRP sucess bit for FS-I2c |
| */ |
| core_if->srp_success = 0; |
| core_if->srp_timer_started = 0; |
| core_if->temp_buffer = kmalloc(DWC_OTG_MAX_TRANSFER_SIZE, 0); |
| if (!core_if->temp_buffer) { |
| kfree(core_if->host_if); |
| kfree(core_if); |
| return 0; |
| } |
| |
| return core_if; |
| |
| } |
| /** |
| * This function initializes the commmon interrupts, used in both |
| * device and host modes. |
| * |
| * @param[in] _core_if Programming view of the DWC_otg controller |
| * |
| */ |
| static void |
| dwc_otg_enable_common_interrupts(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| gintmsk_data_t intr_mask = {.d32 = 0 }; |
| |
| /* |
| * Clear any pending OTG Interrupts |
| */ |
| dwc_write_reg32(&global_regs->gotgint, 0xFFFFFFFF); |
| |
| /* |
| * Clear any pending interrupts |
| */ |
| dwc_write_reg32(&global_regs->gintsts, 0xFFFFFFFF); |
| |
| /* |
| * Enable the interrupts in the GINTMSK. |
| */ |
| intr_mask.b.modemismatch = 1; |
| intr_mask.b.otgintr = 1; |
| |
| if (!_core_if->dma_enable) { |
| 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(&global_regs->gintmsk, intr_mask.d32); |
| } |
| /** |
| * Do core a soft reset of the core. Be careful with this because it |
| * resets all the internal state machines of the core. |
| */ |
| void |
| dwc_otg_core_reset(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| volatile grstctl_t greset = {.d32 = 0 }; |
| int count = 0; |
| |
| DBG("%s\n", __func__); |
| /* |
| * Wait for AHB master IDLE state. |
| */ |
| do { |
| dwc_udelay(10); |
| greset.d32 = dwc_read_reg32(&global_regs->grstctl); |
| if (++count > 100000) { |
| DBG("%s() HANG! AHB Idle GRSTCTL=%0x\n", __func__, greset.d32); |
| return; |
| } |
| } |
| while (greset.b.ahbidle == 0); |
| |
| /* |
| * Core Soft Reset |
| */ |
| count = 0; |
| greset.b.csftrst = 1; |
| dwc_write_reg32(&global_regs->grstctl, greset.d32); |
| do { |
| dwc_udelay(10); |
| greset.d32 = dwc_read_reg32(&global_regs->grstctl); |
| if (++count > 1000000) { |
| DBG("%s() HANG! Soft Reset GRSTCTL=%0x\n", __func__, greset.d32); |
| break; |
| } |
| } |
| while (greset.b.csftrst == 1); |
| |
| /* |
| * Wait for 3 PHY Clocks |
| */ |
| wait_ms(100); |
| } |
| |
| /** |
| * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY |
| * type. |
| */ |
| static void |
| init_fslspclksel(dwc_otg_core_if_t * _core_if) |
| { |
| uint32_t val; |
| hcfg_data_t hcfg; |
| |
| if (_core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) { |
| /* |
| * Full speed PHY |
| */ |
| val = DWC_HCFG_48_MHZ; |
| } else { |
| /* |
| * High speed PHY running at full speed or high speed |
| */ |
| val = DWC_HCFG_30_60_MHZ; |
| } |
| |
| DWC_DEBUGPL(DBG_CIL, "Initializing HCFG.FSLSPClkSel to 0x%1x\n", val); |
| hcfg.d32 = dwc_read_reg32(&_core_if->host_if->host_global_regs->hcfg); |
| hcfg.b.fslspclksel = val; |
| dwc_write_reg32(&_core_if->host_if->host_global_regs->hcfg, hcfg.d32); |
| } |
| |
| /** |
| * Initializes the DevSpd field of the DCFG register depending on the PHY type |
| * and the enumeration speed of the device. |
| */ |
| static void |
| init_devspd(dwc_otg_core_if_t * _core_if) |
| { |
| uint32_t val; |
| dcfg_data_t dcfg; |
| |
| if (_core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) { |
| /* |
| * Full speed PHY |
| */ |
| val = 0x3; |
| } else if (_core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { |
| /* |
| * High speed PHY running at full speed |
| */ |
| val = 0x1; |
| } else { |
| /* |
| * High speed PHY running at high speed |
| */ |
| val = 0x0; |
| } |
| |
| DWC_DEBUGPL(DBG_CIL, "Initializing DCFG.DevSpd to 0x%1x\n", val); |
| dcfg.d32 = dwc_read_reg32(&_core_if->dev_if->dev_global_regs->dcfg); |
| dcfg.b.devspd = val; |
| dwc_write_reg32(&_core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); |
| } |
| |
| /** |
| * This function initializes the DWC_otg controller registers and |
| * prepares the core for device mode or host mode operation. |
| * |
| * @param _core_if Programming view of the DWC_otg controller |
| * |
| */ |
| |
| int |
| dwc_otg_core_init(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| // dwc_otg_dev_if_t *dev_if = _core_if->dev_if; |
| gahbcfg_data_t ahbcfg = {.d32 = 0 }; |
| gusbcfg_data_t usbcfg = {.d32 = 0 }; |
| gi2cctl_data_t i2cctl = {.d32 = 0 }; |
| |
| DWC_DEBUGPL(DBG_CILV, "dwc_otg_core_init(%p)\n", _core_if); |
| |
| /* |
| * Reset the Controller |
| */ |
| dwc_otg_core_reset(_core_if); |
| |
| _core_if->total_fifo_size = _core_if->hwcfg3.b.dfifo_depth; |
| _core_if->rx_fifo_size = dwc_read_reg32(&global_regs->grxfsiz); |
| _core_if->nperio_tx_fifo_size = dwc_read_reg32(&global_regs->gnptxfsiz) >> 16; |
| |
| DWC_DEBUGPL(DBG_CIL, "Total FIFO SZ=%d\n", _core_if->total_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "Rx FIFO SZ=%d\n", _core_if->rx_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO SZ=%d\n", _core_if->nperio_tx_fifo_size); |
| |
| /* |
| * This programming sequence needs to happen in FS mode before any other |
| * programming occurs |
| */ |
| if ((_core_if->core_params->speed == DWC_SPEED_PARAM_FULL) && |
| (_core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { |
| /* |
| * If FS mode with FS PHY |
| */ |
| |
| /* |
| * core_init() is now called on every switch so only call the |
| * following for the first time through. |
| */ |
| if (!_core_if->phy_init_done) { |
| _core_if->phy_init_done = 1; |
| DWC_DEBUGPL(DBG_CIL, "FS_PHY detected\n"); |
| usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); |
| usbcfg.b.physel = 1; |
| dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); |
| |
| /* |
| * Reset after a PHY select |
| */ |
| dwc_otg_core_reset(_core_if); |
| } |
| |
| /* |
| * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. Also do |
| * this on HNP Dev/Host mode switches (done in dev_init and |
| * host_init). |
| */ |
| if (dwc_otg_is_host_mode(_core_if)) { |
| init_fslspclksel(_core_if); |
| } else { |
| init_devspd(_core_if); |
| } |
| |
| if (_core_if->core_params->i2c_enable) { |
| DWC_DEBUGPL(DBG_CIL, "FS_PHY Enabling I2c\n"); |
| /* |
| * Program GUSBCFG.OtgUtmifsSel to I2C |
| */ |
| usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); |
| usbcfg.b.otgutmifssel = 1; |
| dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); |
| |
| /* |
| * Program GI2CCTL.I2CEn |
| */ |
| i2cctl.d32 = dwc_read_reg32(&global_regs->gi2cctl); |
| i2cctl.b.i2cdevaddr = 1; |
| i2cctl.b.i2cen = 0; |
| dwc_write_reg32(&global_regs->gi2cctl, i2cctl.d32); |
| i2cctl.b.i2cen = 1; |
| dwc_write_reg32(&global_regs->gi2cctl, i2cctl.d32); |
| } |
| |
| } |
| /* |
| * endif speed == DWC_SPEED_PARAM_FULL |
| */ |
| else { |
| /* |
| * High speed PHY. |
| */ |
| if (!_core_if->phy_init_done) { |
| _core_if->phy_init_done = 1; |
| /* |
| * HS PHY parameters. These parameters are preserved during soft |
| * reset so only program the first time. Do a soft reset |
| * immediately after setting phyif. |
| */ |
| usbcfg.b.ulpi_utmi_sel = _core_if->core_params->phy_type; |
| if (usbcfg.b.ulpi_utmi_sel == 1) { |
| /* |
| * ULPI interface |
| */ |
| usbcfg.b.phyif = 0; |
| usbcfg.b.ddrsel = _core_if->core_params->phy_ulpi_ddr; |
| } else { |
| /* |
| * UTMI+ interface |
| */ |
| if (_core_if->core_params->phy_utmi_width == 16) { |
| usbcfg.b.phyif = 1; |
| } else { |
| usbcfg.b.phyif = 0; |
| } |
| } |
| |
| dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); |
| |
| /* |
| * Reset after setting the PHY parameters |
| */ |
| dwc_otg_core_reset(_core_if); |
| } |
| } |
| |
| /* |
| * Program the GAHBCFG Register. |
| */ |
| switch (_core_if->hwcfg2.b.architecture) { |
| |
| case DWC_SLAVE_ONLY_ARCH: |
| DWC_DEBUGPL(DBG_CIL, "Slave Only Mode\n"); |
| ahbcfg.b.nptxfemplvl_txfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; |
| ahbcfg.b.ptxfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; |
| _core_if->dma_enable = 0; |
| break; |
| |
| case DWC_EXT_DMA_ARCH: |
| DWC_DEBUGPL(DBG_CIL, "External DMA Mode\n"); |
| ahbcfg.b.hburstlen = DWC_GAHBCFG_INT_DMA_BURST_INCR8; |
| _core_if->dma_enable = (_core_if->core_params->dma_enable != 0); |
| break; |
| |
| case DWC_INT_DMA_ARCH: |
| DWC_DEBUGPL(DBG_CIL, "Internal DMA Mode\n"); |
| // ahbcfg.b.hburstlen = |
| // _core_if->core_params->dma_burst_size;//config by out world |
| ahbcfg.b.hburstlen = DWC_GAHBCFG_INT_DMA_BURST_INCR8; // config by |
| // out world |
| _core_if->dma_enable = (_core_if->core_params->dma_enable != 0); |
| break; |
| |
| } |
| |
| ahbcfg.b.dmaenable = _core_if->dma_enable; |
| dwc_write_reg32(&global_regs->gahbcfg, ahbcfg.d32); |
| |
| // _core_if->en_multiple_tx_fifo = _core_if->hwcfg4.b.ded_fifo_en; |
| |
| |
| /* |
| * Program the GUSBCFG register. |
| */ |
| usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); |
| |
| switch (_core_if->hwcfg2.b.op_mode) { |
| case DWC_MODE_HNP_SRP_CAPABLE: |
| usbcfg.b.hnpcap = (_core_if->core_params->otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); |
| usbcfg.b.srpcap = (_core_if->core_params->otg_cap != DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); |
| break; |
| |
| case DWC_MODE_SRP_ONLY_CAPABLE: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = (_core_if->core_params->otg_cap != DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); |
| break; |
| |
| case DWC_MODE_NO_HNP_SRP_CAPABLE: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = 0; |
| break; |
| |
| case DWC_MODE_SRP_CAPABLE_DEVICE: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = (_core_if->core_params->otg_cap != DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); |
| break; |
| |
| case DWC_MODE_NO_SRP_CAPABLE_DEVICE: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = 0; |
| break; |
| |
| case DWC_MODE_SRP_CAPABLE_HOST: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = (_core_if->core_params->otg_cap != DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); |
| break; |
| |
| case DWC_MODE_NO_SRP_CAPABLE_HOST: |
| usbcfg.b.hnpcap = 0; |
| usbcfg.b.srpcap = 0; |
| break; |
| } |
| |
| dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); |
| |
| /* |
| * Enable common interrupts |
| */ |
| dwc_otg_enable_common_interrupts(_core_if); |
| |
| /* |
| * Do device or host intialization based on mode during PCD and HCD |
| * initialization |
| */ |
| if (dwc_otg_is_host_mode(_core_if)) { |
| DWC_DEBUGPL(DBG_ANY, "Host Mode\n"); |
| _core_if->op_state = A_HOST; |
| } else { |
| ERR("Device Mode\n"); |
| _core_if->op_state = B_PERIPHERAL; |
| #ifdef DWC_DEVICE_ONLY |
| dwc_otg_core_dev_init(_core_if); |
| #endif |
| return -1; |
| } |
| return 0; |
| |
| } |
| /** |
| * This function disables the Host Mode interrupts. |
| * |
| * @param _core_if Programming view of DWC_otg controller |
| */ |
| void |
| dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| |
| DWC_DEBUGPL(DBG_CILV, "%s()\n", __func__); |
| |
| dwc_write_reg32 (&global_regs->gintmsk, 0); |
| dwc_modify_reg32 (&global_regs->gahbcfg, 1, 0); |
| |
| } |
| |
| /** |
| * Halts the DWC_otg host mode operations in a clean manner. USB transfers are |
| * stopped. |
| */ |
| void |
| dwc_otg_hcd_stop(dwc_otg_core_if_t * core_if) |
| { |
| hprt0_data_t hprt0 = {.d32 = 0 }; |
| pcgcctl_data_t hcgcctl = {.d32 = 0}; |
| |
| if (core_if == 0) |
| return; |
| |
| DBG("DWC OTG HCD STOP\n"); |
| |
| /* |
| * Turn off all host-specific interrupts. |
| */ |
| dwc_otg_disable_host_interrupts(core_if); |
| |
| /* |
| * The root hub should be disconnected before this function is called. |
| * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) |
| * and the QH lists (via ..._hcd_endpoint_disable). |
| */ |
| |
| /* |
| * Turn off the vbus power |
| */ |
| DBG("PortPower off\n"); |
| hprt0.b.prtpwr = 0; |
| dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); |
| |
| hcgcctl.b.stoppclk = 1; |
| hcgcctl.b.gatehclk = 1; |
| hcgcctl.b.pwrclmp = 1; |
| //hcgcctl.b.rstpdwnmodule = 1; |
| //printf("core_if->pcgcctl : 0x%x, & = 0x%x\n",core_if->pcgcctl,&core_if->pcgcctl ); |
| |
| dwc_write_reg32(core_if->pcgcctl,hcgcctl.d32); |
| return; |
| } |
| |
| |
| /* |
| * --- API functions ------------------------------------------------------ |
| */ |
| int |
| submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int len, int interval) |
| { |
| DBG("dev=%p pipe=%#lx buf=%p size=%d int=%d", dev, pipe, buffer, len, interval); |
| |
| return -1; |
| } |
| static inline int |
| max_transfer_len(struct usb_device *dev, unsigned long pipe) |
| { |
| unsigned mpck = usb_maxpacket(dev, pipe); |
| |
| /* |
| * One PTD can transfer 1023 bytes but try to always transfer multiples |
| * of endpoint buffer size |
| */ |
| return 1023 / mpck * mpck; |
| } |
| |
| /** |
| * Clears the transfer state for a host channel. This function is normally |
| * called after a transfer is done and the host channel is being released. |
| * |
| * @param _core_if Programming view of DWC_otg controller. |
| * @param _hc Identifies the host channel to clean up. |
| */ |
| void |
| dwc_otg_hc_cleanup(dwc_otg_core_if_t * _core_if, int hc_num) |
| { |
| dwc_otg_hc_regs_t *hc_regs; |
| |
| /* |
| * Clear channel interrupt enables and any unhandled channel interrupt |
| * conditions. |
| */ |
| hc_regs = _core_if->host_if->hc_regs[hc_num]; |
| dwc_write_reg32(&hc_regs->hcintmsk, 0); |
| dwc_write_reg32(&hc_regs->hcint, 0xFFFFFFFF); |
| dwc_write_reg32(&hc_regs->hctsiz, 0); |
| dwc_write_reg32(&hc_regs->hcdma, 0); |
| dwc_write_reg32(&hc_regs->hcchar, 0); |
| |
| flush_cpu_cache(); |
| |
| } |
| |
| void dwc_otg_read_packet(dwc_otg_core_if_t * core_if, uint8_t * _dest, uint16_t _bytes,int hcnum) |
| { |
| int i; |
| int word_count = (_bytes + 3) / 4; |
| volatile uint32_t *fifo = core_if->data_fifo[hcnum]; |
| uint32_t *data_buff = (uint32_t *) _dest; |
| for (i = 0; i < word_count; i++, data_buff++) { |
| *data_buff = dwc_read_reg32(fifo); |
| } |
| return; |
| } |
| |
| static void dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_core_if_t * _core_if, int is_setup,int hcnum, void *buffer) |
| { |
| host_grxsts_data_t grxsts; |
| |
| grxsts.d32 = dwc_read_reg32(&_core_if->core_global_regs->grxstsp); |
| //printf("RxStsQ:ch%d,cnt=%d,pid=%d,PStatus=%d\n",grxsts.b.chnum,grxsts.b.bcnt,grxsts.b.dpid,grxsts.b.pktsts); |
| |
| switch (grxsts.b.pktsts) { |
| case DWC_GRXSTS_PKTSTS_IN: |
| /* Read the data into the host buffer. */ |
| if (grxsts.b.bcnt > 0) { |
| dwc_otg_read_packet(_core_if, buffer, grxsts.b.bcnt, hcnum); |
| /* Update the HC fields for the next packet received. */ |
| buffer += grxsts.b.bcnt; |
| } |
| case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: |
| case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR: |
| case DWC_GRXSTS_PKTSTS_CH_HALTED: |
| /* Handled in interrupt, just ignore data */ |
| break; |
| default: |
| ERR("RX_STS_Q Interrupt: Unknown status %d\n", |
| grxsts.b.pktsts); |
| break; |
| } |
| |
| return; |
| } |
| |
| void dwc_otg_hc_write_packet(dwc_otg_core_if_t * _core_if, void *buffer,int len, int chn_num) |
| { |
| uint32_t i; |
| uint32_t byte_count; |
| uint32_t dword_count; |
| |
| uint32_t *data_buff = (uint32_t *)buffer; |
| uint32_t *data_fifo = _core_if->data_fifo[chn_num];//just used channel0 |
| |
| byte_count = len ; |
| |
| dword_count = (byte_count + 3) / 4; |
| |
| if ((((unsigned long)data_buff) & 0x3) == 0) { |
| /* xfer_buff is DWORD aligned. */ |
| for (i = 0; i < dword_count; i++, data_buff++) { |
| dwc_write_reg32(data_fifo, *data_buff); |
| } |
| } else { |
| /* xfer_buff is not DWORD aligned. */ |
| |
| for (i = 0; i < dword_count; i++, data_buff++) { |
| dwc_write_reg32(data_fifo, get_unaligned(data_buff)); |
| } |
| } |
| //printf("dwc_otg_hc_write_packet finished %d %d chn%d\n", dword_count, len,chn_num); |
| } |
| |
| static void dwc_otg_hc_do_ping(dwc_otg_core_if_t *_core_if, int hc_num) |
| { |
| hcchar_data_t hcchar; |
| hctsiz_data_t hctsiz; |
| dwc_otg_hc_regs_t *hc_regs = _core_if->host_if->hc_regs[hc_num]; |
| |
| DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc_num); |
| |
| hctsiz.d32 = 0; |
| hctsiz.b.dopng = 1; |
| hctsiz.b.pktcnt = 1; |
| dwc_write_reg32(&hc_regs->hctsiz, hctsiz.d32); |
| |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| hcchar.b.chen = 1; |
| hcchar.b.chdis = 0; |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| } |
| |
| static int |
| dwc_otg_interrupt(dwc_otg_core_if_t * _core_if, int is_setup,int hcnum,void * buffer) |
| { |
| gintsts_data_t gintsts; |
| dwc_otg_hc_regs_t *hc_regs; |
| hctsiz_data_t hctsiz; |
| hcint_data_t hcint; |
| hcchar_data_t hcchar; |
| hcintmsk_data_t hcintmsk; |
| // dwc_otg_host_if_t *host_if ; |
| int handled; |
| dwc_otg_host_if_t *host_if = _core_if->host_if; |
| |
| do { |
| gintsts.d32 = dwc_read_reg32(&_core_if->core_global_regs->gintsts); |
| hc_regs = host_if->hc_regs[hcnum]; |
| hcint.d32 = dwc_read_reg32(&hc_regs->hcint); |
| handled = 0; |
| |
| if (gintsts.b.rxstsqlvl && (!_core_if->dma_enable)) { |
| dwc_otg_hcd_handle_rx_status_q_level_intr(_core_if,is_setup,hcnum,buffer); |
| DBG("rxstsqlvl interrupt!\n"); |
| _core_if->host_if->do_ping = 0; |
| handled = 1; |
| } |
| if (gintsts.b.hcintr) { |
| //DBG("dwc_otg_interrupt: hcintr interrupt!\n"); |
| if (hcint.b.xfercomp) { |
| hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); |
| dwc_otg_hc_cleanup(_core_if, hcnum); |
| if (is_setup) |
| return 8; |
| return (_core_if->transfer_size);// - hctsiz.b.xfersize); |
| } |
| if (hcint.b.ahberr) { |
| ERR("ahberr interrupt!\n"); |
| hcintmsk.d32 = 0; |
| hcintmsk.b.chhltd = 1; |
| dwc_write_reg32(&hc_regs->hcintmsk, hcintmsk.d32); |
| dwc_write_reg32(&hc_regs->hcint, ~hcintmsk.d32); |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| if (hcchar.b.chen == 0) |
| return -1; |
| else { |
| hcchar.b.chen = 1; |
| hcchar.b.chdis = 1; |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| } |
| |
| return -1; |
| |
| } |
| if (hcint.b.nyet) { |
| DBG("nyet interrupt!\n"); |
| _core_if->host_if->do_ping = 1; |
| //return -1; |
| |
| } |
| if (hcint.b.ack) { |
| DBG("ack interrupt!\n"); |
| _core_if->host_if->do_ping = 0; |
| //return -1; |
| |
| } |
| if (hcint.b.xacterr) { |
| ERR("xacterr interrupt!\n"); |
| return -1; |
| |
| } |
| if (hcint.b.bblerr) { |
| ERR("bblerr interrupt!\n"); |
| return -1; |
| } |
| if (hcint.b.datatglerr) { |
| ERR("datatglerr interrupt!\n"); |
| return -1; |
| } |
| |
| } |
| if (gintsts.b.portintr) { |
| ERR("hprt0:0x%08x\n",dwc_read_reg32(_core_if->host_if->hprt0)); |
| dwc_otg_port_init(_core_if); |
| return -2; |
| } |
| |
| }while(handled); |
| |
| if (gintsts.b.ptxfempty) { |
| //DBG("ptxfempty interrupt!\n"); |
| return -1; |
| } |
| return -1; |
| |
| } |
| |
| /* |
| * Do an USB transfer , dir = 0, out ; dir = 1 , in |
| */ |
| |
| static int |
| dwc_otg_submit_job(struct usb_device *dev, unsigned long pipe, int dir, void *buffer, int len, |
| int is_setup) |
| { |
| |
| dwc_otg_core_if_t *core_if = dwc_otg_dev.core_if; |
| dwc_otg_hc_regs_t *hc_regs; |
| hcchar_data_t hcchar; |
| hcsplt_data_t hcsplt; |
| hctsiz_data_t hctsiz; |
| int devnum = usb_pipedevice(pipe); |
| int epnum = usb_pipeendpoint(pipe); |
| // int dir_in = usb_pipein(pipe); |
| int toggle,chg_flag = 0; |
| static int hcnum = 0; |
| hcintmsk_data_t hcintmsk; |
| dwc_otg_host_if_t *host_if = core_if->host_if; |
| int stat, timeout; |
| int allchannel_int_mask; |
| hcint_data_t hcint; |
| dwc_otg_host_global_regs_t *host_global_regs = host_if->host_global_regs; |
| |
| // printf("dwc_otg_submit_job: dev: %d ,dev->parent: %x, dev->speed: %d\n", |
| // dev->devnum,dev->parent,dev->speed); |
| |
| // struct usb_device * hub1 = usb_get_dev_index(1); |
| |
| // if(hub1) |
| // printf("root_hub speed %d\n",hub1->speed); |
| // if(dev->parent != NULL) //not roothub |
| // if(dev->parent->speed ==2 && dev->speed != 2 ) |
| // return -EOPNOTSUPP; |
| |
| int type = usb_pipetype(pipe); |
| if (len >= 4096) { |
| ERR("Too big job!\n"); |
| dev->status = USB_ST_CRC_ERR; |
| return -E2BIG; |
| } |
| |
| if (type == PIPE_ISOCHRONOUS) { |
| ERR("isochronous transfers not supported"); |
| dev->status = USB_ST_CRC_ERR; |
| return -EOPNOTSUPP; |
| } |
| |
| //hcnum++; |
| if (hcnum > 6) hcnum = 0; |
| |
| hc_regs = host_if->hc_regs[hcnum]; |
| /* |
| if (!is_insert && (!core_if->dma_enable)) { |
| wait_ms(10); |
| if (dwc_otg_interrupt(core_if, is_setup,hcnum,buffer) == -2) |
| is_insert = 1; |
| else{ |
| DBG("wait insert failed"); |
| return -1; |
| } |
| |
| } */ |
| //chn_busy = chn_busy | 1<<hcnum ; |
| /* |
| * init hc char (corresponding devaddr and epnum) |
| */ |
| hcchar.d32 = 0; |
| hcchar.b.devaddr = devnum; |
| hcchar.b.epnum = epnum; |
| hcchar.b.epdir = dir; |
| hcchar.b.lspddev = 0; |
| |
| /* set hc interrupt mask */ |
| hcintmsk.d32 = dwc_read_reg32(&hc_regs->hcintmsk); |
| //hcintmsk.b.xfercompl = 1; |
| hcintmsk.d32 = 7; |
| dwc_write_reg32(&hc_regs->hcintmsk, hcintmsk.d32 ); |
| |
| /* clear int status*/ |
| hcint.d32 = dwc_read_reg32(&hc_regs->hcint); |
| |
| dwc_write_reg32(&hc_regs->hcint, hcint.d32 ); |
| |
| /* set all channel interrupt mask */ |
| allchannel_int_mask = dwc_read_reg32(&host_global_regs->haintmsk); |
| allchannel_int_mask |= 1; |
| dwc_write_reg32(&host_global_regs->haintmsk, allchannel_int_mask); |
| |
| switch (usb_pipetype(pipe)) { |
| case PIPE_CONTROL: |
| hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; |
| break; |
| case PIPE_BULK: |
| hcchar.b.eptype = DWC_OTG_EP_TYPE_BULK; |
| break; |
| case PIPE_INTERRUPT: |
| case PIPE_ISOCHRONOUS: |
| ERR("dwc_otg_submit_job: not support iso or interrupt transfer!\n"); |
| return -1; |
| } |
| |
| hcchar.b.mps = usb_maxpacket(dev, pipe); |
| |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| hcsplt.d32 = 0; |
| |
| dwc_write_reg32(&hc_regs->hcsplt, hcsplt.d32); |
| flush_cpu_cache(); |
| |
| |
| hctsiz.d32 = 0; |
| |
| hctsiz.b.pktcnt = (len + usb_maxpacket(dev, pipe) - 1)/ usb_maxpacket(dev, pipe); |
| if (hctsiz.b.pktcnt < 1) |
| hctsiz.b.pktcnt = 1; |
| if (hctsiz.b.pktcnt & 0x1) |
| chg_flag = 1; |
| toggle = usb_gettoggle(dev, epnum, dir); |
| DBG("packet is :%d, max packet size = %d,toggle %d\n", |
| hctsiz.b.pktcnt, usb_maxpacket(dev, pipe), toggle); |
| |
| switch (toggle) { |
| case 0: |
| hctsiz.b.pid = DWC_OTG_HC_PID_DATA0; |
| break; |
| case 1: |
| hctsiz.b.pid = DWC_OTG_HC_PID_DATA1; |
| break; |
| default: |
| ERR("error toggle!\n"); |
| return -EPROTO; |
| } |
| if (is_setup) |
| hctsiz.b.pid = DWC_OTG_HC_PID_SETUP; |
| |
| core_if->transfer_size = len; |
| hctsiz.b.xfersize = len; |
| |
| if (!is_setup && dir == 0 && core_if->host_if->do_ping) { |
| if (!core_if->dma_enable) { |
| dwc_otg_hc_do_ping(core_if, 0); |
| return -10;//It's ping state,pls do it again! |
| } |
| hctsiz.b.dopng = 1; |
| } |
| else |
| hctsiz.b.dopng = 0; |
| |
| DBG("ep%d packet is :%d, max packet size = %d = %d,b.len %d\n", hcchar.b.epnum,\ |
| hctsiz.b.pktcnt, usb_maxpacket(dev, pipe), hcchar.b.mps,hctsiz.b.xfersize); |
| |
| dwc_write_reg32(&hc_regs->hctsiz, hctsiz.d32); |
| |
| |
| dwc_write_reg32(&hc_regs->hcdma, (uint32_t) buffer); |
| flush_cpu_cache(); |
| |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| |
| hcchar.b.multicnt = 1; // for no split transfer set this bit 1 |
| hcchar.b.chen = 1; |
| hcchar.b.chdis = 0; |
| |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| |
| if (!core_if->dma_enable && !dir && len > 0) { |
| /* Load OUT packet into the appropriate Tx FIFO. */ |
| dwc_otg_hc_write_packet(core_if, buffer,len,hcnum); |
| } |
| else |
| ;//printf("dmaable:%d, %s,len=%d\n",core_if->dma_enable,dir?"IN":"OUT",len); |
| |
| if (usb_pipebulk(pipe)) |
| timeout = 1000; |
| else |
| timeout = 100; |
| /* |
| * Wait for it to complete |
| */ |
| while (1) { |
| udelay(20); |
| /* |
| * Check whether the controller is done |
| */ |
| stat = dwc_otg_interrupt(core_if, is_setup,hcnum,buffer); |
| |
| if (stat >= 0) { |
| if (chg_flag) |
| usb_dotoggle(dev, epnum, dir); |
| break; |
| } |
| |
| /* |
| * Check the timeout |
| */ |
| if (--timeout) |
| udelay(10); |
| else { |
| hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); |
| DBG("TIMEOUT-%s-%d-%d-%d\n",dir?"IN":"OUT",len,hctsiz.b.pktcnt,usb_pipetype(pipe)); |
| break; |
| } |
| } |
| return stat; |
| } |
| #define CTL_RETRY 5 |
| int |
| submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, |
| int len, struct devrequest *setup) |
| { |
| int epnum = usb_pipeendpoint(pipe); |
| int dir_in = usb_pipein(pipe); |
| int done; |
| int ret,retry; |
| //char *buffer; |
| // dwc_otg_core_if_t *core_if = dwc_otg_dev.core_if; |
| |
| /* |
| * Setup phase |
| */ |
| retry = 0; |
| SETUP_RETRY: |
| DBG("--- SETUP PHASE --------------------try: %d\n",retry); |
| DBG("dev=%ld ep=%ld buf=%x size=%d %s req:%d", |
| usb_pipedevice(pipe), usb_pipeendpoint(pipe), buffer, len, (dir_in?"dir_in":"dir_out"),setup->request); |
| |
| usb_settoggle(dev, epnum, 1, 0); |
| ret = dwc_otg_submit_job(dev, pipe, 0, setup, sizeof(struct devrequest), 1); |
| if (ret < 0) { |
| if (retry++ < CTL_RETRY) |
| goto SETUP_RETRY; |
| else{ is_insert = 0; |
| ERR("control setup phase error (ret = %d)", ret); |
| return -1; |
| } |
| } |
| |
| /* |
| * Data phase |
| */ |
| retry = 0; |
| DATA_RETRY: |
| DBG("--- DATA PHASE --------------------try: %d\n",retry); |
| |
| |
| done = 0; |
| usb_settoggle(dev, epnum, dir_in, 1); |
| if (done < len) { |
| |
| dwc_udelay(100); |
| |
| ret = dwc_otg_submit_job(dev, pipe, |
| dir_in, |
| (__u8 *) buffer, len, 0); |
| |
| if (ret < 0) { |
| if (retry++ < CTL_RETRY) |
| goto DATA_RETRY; |
| else{ is_insert = 0; |
| ERR("control data phase error (ret = %d)", ret); |
| return -1; |
| } |
| } |
| //if (!dir_in && ret < usb_maxpacket(dev, pipe)) /* short packet */ |
| // break; |
| |
| done += ret; |
| } |
| |
| /* |
| * Status phase |
| */ |
| retry = 0; |
| STATUS_RETRY: |
| DBG("--- STATUS PHASE -------------------try: %d\n",retry); |
| dwc_udelay(100); |
| |
| usb_settoggle(dev, epnum, !dir_in, 1); |
| ret = dwc_otg_submit_job(dev, pipe, !dir_in, NULL, 0, 0); |
| if (ret < 0) { |
| if (retry++ < CTL_RETRY) |
| goto STATUS_RETRY; |
| else{ is_insert = 0; |
| ERR("control status phase error (ret = %d)", ret); |
| return -1; |
| } |
| } |
| |
| dev->act_len = done; |
| DBG("return size = %d\n", done); |
| dev->status = 0; |
| |
| return done; |
| } |
| |
| |
| int |
| submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buf, int len) |
| { |
| int dir_out = usb_pipeout(pipe); |
| int max = max_transfer_len(dev, pipe); |
| // int epnum = usb_pipeendpoint(pipe); |
| |
| int done; |
| int ret; |
| // int toggle; |
| int retry; |
| char *buffer=NULL; |
| dwc_otg_core_if_t *core_if = dwc_otg_dev.core_if; |
| |
| DBG("--- BULK -----------------------------------------\n"); |
| DBG("dev=%ld ep=%ld buf=%x size=%d %s", |
| usb_pipedevice(pipe), usb_pipeendpoint(pipe), buf, len, (dir_out?"dir_out":"dir_in")); |
| if (len == 31) |
| DBG("scsi cmd from CBW: 0x%X\n",*(((unsigned char*)buf)+15)); |
| |
| done = 0; |
| if (dir_out) { |
| memcpy(core_if->temp_buffer, buf, len); |
| } |
| buffer = core_if->temp_buffer; |
| retry = 5; |
| while (done < len) { |
| RETRY: |
| dwc_udelay(100); |
| |
| ret = dwc_otg_submit_job(dev, pipe, |
| !dir_out, |
| (__u8 *) buffer + done, max > len - done ? len - done : max, 0); |
| if (ret < 0 ) { |
| if (retry--) { |
| DBG("error on bulk message, retry: %d",retry); |
| goto RETRY; |
| }else{ |
| is_insert = 0; |
| ERR("error on bulk message (ret = %d)", ret); |
| return -1; |
| } |
| } |
| |
| done += ret; |
| |
| //usb_dotoggle(dev, epnum, !dir_out); |
| |
| if (!dir_out && ret < max) /* short packet */ |
| break; |
| |
| } |
| DBG("return size = %d\n", done); |
| if (!dir_out) { |
| memcpy(buf, buffer, len); |
| } |
| dev->act_len = done; |
| dev->status = 0; |
| |
| return 0; |
| } |
| /** |
| * Flush a Tx FIFO. |
| * |
| * @param _core_if Programming view of DWC_otg controller. |
| * @param _num Tx FIFO to flush. |
| */ |
| extern void |
| dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * _core_if, const int _num) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| grstctl_t greset = { 0 }; |
| int count = 0; |
| |
| DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "Flush Tx FIFO %d\n", _num); |
| |
| greset.b.txfflsh = 1; |
| greset.b.txfnum = _num; |
| dwc_write_reg32(&global_regs->grstctl, greset.d32); |
| |
| do { |
| greset.d32 = dwc_read_reg32(&global_regs->grstctl); |
| if (++count > 10000) { |
| WARN("%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", |
| "dwc_otg_flush_tx_fifo", greset.d32, dwc_read_reg32(&global_regs->gnptxsts)); |
| break; |
| } |
| |
| } while (greset.b.txfflsh == 1); |
| /* |
| * Wait for 3 PHY Clocks |
| */ |
| wait_ms(1); |
| } |
| |
| /** |
| * Flush Rx FIFO. |
| * |
| * @param _core_if Programming view of DWC_otg controller. |
| */ |
| extern void |
| dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| volatile grstctl_t greset = { 0 }; |
| int count = 0; |
| |
| DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "%s\n", "dwc_otg_flush_rx_fifo"); |
| /* |
| * |
| */ |
| greset.b.rxfflsh = 1; |
| dwc_write_reg32(&global_regs->grstctl, greset.d32); |
| |
| do { |
| greset.d32 = dwc_read_reg32(&global_regs->grstctl); |
| if (++count > 10000) { |
| WARN("%s() HANG! GRSTCTL=%0x\n", "dwc_otg_flush_rx_fifo", greset.d32); |
| break; |
| } |
| } while (greset.b.rxfflsh == 1); |
| /* |
| * Wait for 3 PHY Clocks |
| */ |
| wait_ms(1); |
| } |
| static void |
| dwc_otg_set_vbus_power(dwc_otg_core_if_t * _core_if, int is_power_on) |
| { |
| if ( _core_if->set_vbus_power) |
| _core_if->set_vbus_power(is_power_on); |
| } |
| /** |
| * This function Reads HPRT0 in preparation to modify. It keeps the |
| * WC bits 0 so that if they are read as 1, they won't clear when you |
| * write it back |
| */ |
| static inline uint32_t |
| dwc_otg_read_hprt0(dwc_otg_core_if_t * _core_if) |
| { |
| hprt0_data_t hprt0; |
| hprt0.d32 = dwc_read_reg32(_core_if->host_if->hprt0); |
| hprt0.b.prtena = 0; |
| hprt0.b.prtconndet = 0; |
| hprt0.b.prtenchng = 0; |
| hprt0.b.prtovrcurrchng = 0; |
| return hprt0.d32; |
| } |
| /** |
| * This function enables the Host mode interrupts. |
| * |
| * @param _core_if Programming view of DWC_otg controller |
| */ |
| void |
| dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| gintmsk_data_t intr_mask = { 0 }; |
| |
| DWC_DEBUGPL(DBG_CIL, "%s()\n", "dwc_otg_enable_host_interrupts"); |
| |
| /* |
| * Disable all interrupts. |
| */ |
| dwc_write_reg32(&global_regs->gintmsk, 0); |
| |
| /* |
| * Clear any pending interrupts. |
| */ |
| dwc_write_reg32(&global_regs->gintsts, 0xFFFFFFFF); |
| |
| /* |
| * Enable the common interrupts |
| */ |
| dwc_otg_enable_common_interrupts(_core_if); |
| |
| /* |
| * Enable host mode interrupts without disturbing common |
| * interrupts. |
| */ |
| #ifndef NO_HOST_SOF |
| intr_mask.b.sofintr = 1; |
| #endif |
| intr_mask.b.portintr = 1;//6.1.1 |
| intr_mask.b.hcintr = 1; |
| |
| dwc_modify_reg32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); |
| } |
| |
| /** |
| * This function initializes the DWC_otg controller registers for |
| * host mode. |
| * |
| * This function flushes the Tx and Rx FIFOs and it flushes any entries in the |
| * request queues. Host channels are reset to ensure that they are ready for |
| * performing transfers. |
| * |
| * @param _core_if Programming view of DWC_otg controller |
| * |
| */ |
| void |
| dwc_otg_core_host_init(dwc_otg_core_if_t * _core_if) |
| { |
| dwc_otg_core_global_regs_t *global_regs = _core_if->core_global_regs; |
| dwc_otg_host_if_t *host_if = _core_if->host_if; |
| dwc_otg_core_params_t *params = _core_if->core_params; |
| hprt0_data_t hprt0 = { 0 }; |
| fifosize_data_t nptxfifosize; |
| fifosize_data_t ptxfifosize; |
| int i; |
| hcchar_data_t hcchar; |
| hcfg_data_t hcfg; |
| dwc_otg_hc_regs_t *hc_regs; |
| int num_channels; |
| gotgctl_data_t gotgctl = { 0 }; |
| |
| DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", "dwc_otg_core_host_init", _core_if); |
| |
| /* |
| * Restart the Phy Clock |
| */ |
| dwc_write_reg32(_core_if->pcgcctl, 0); |
| |
| /* |
| * Initialize Host Configuration Register |
| */ |
| init_fslspclksel(_core_if); |
| if (_core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { |
| hcfg.d32 = dwc_read_reg32(&host_if->host_global_regs->hcfg); |
| hcfg.b.fslssupp = 1; |
| dwc_write_reg32(&host_if->host_global_regs->hcfg, hcfg.d32); |
| } |
| |
| /* |
| * Configure data FIFO sizes 6.1.1.11----6.1.1.13 |
| */ |
| if (_core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { |
| DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n", _core_if->total_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n", params->host_rx_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n", params->host_nperio_tx_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "P Tx FIFO Size=%d\n", params->host_perio_tx_fifo_size); |
| |
| /* |
| * Rx FIFO |
| */ |
| DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n", dwc_read_reg32(&global_regs->grxfsiz)); |
| dwc_write_reg32(&global_regs->grxfsiz, params->host_rx_fifo_size); |
| DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n", dwc_read_reg32(&global_regs->grxfsiz)); |
| |
| /* |
| * Non-periodic Tx FIFO |
| */ |
| DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", dwc_read_reg32(&global_regs->gnptxfsiz)); |
| nptxfifosize.b.depth = params->host_nperio_tx_fifo_size; |
| nptxfifosize.b.startaddr = params->host_rx_fifo_size; |
| dwc_write_reg32(&global_regs->gnptxfsiz, nptxfifosize.d32); |
| DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", dwc_read_reg32(&global_regs->gnptxfsiz)); |
| |
| /* |
| * Periodic Tx FIFO |
| */ |
| DWC_DEBUGPL(DBG_CIL, "initial hptxfsiz=%08x\n", dwc_read_reg32(&global_regs->hptxfsiz)); |
| ptxfifosize.b.depth = params->host_perio_tx_fifo_size; |
| ptxfifosize.b.startaddr = nptxfifosize.b.startaddr + nptxfifosize.b.depth; |
| dwc_write_reg32(&global_regs->hptxfsiz, ptxfifosize.d32); |
| DWC_DEBUGPL(DBG_CIL, "new hptxfsiz=%08x\n", dwc_read_reg32(&global_regs->hptxfsiz)); |
| } |
| |
| /* |
| * Clear Host Set HNP Enable in the OTG Control Register |
| */ |
| gotgctl.b.hstsethnpen = 1; |
| dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0); |
| |
| /* |
| * Make sure the FIFOs are flushed. |
| */ |
| dwc_otg_flush_tx_fifo(_core_if, 0x10 /* all Tx FIFOs */ ); |
| dwc_otg_flush_rx_fifo(_core_if); |
| |
| /* |
| * Flush out any leftover queued requests. |
| */ |
| num_channels = _core_if->core_params->host_channels; |
| for (i = 0; i < num_channels; i++) { |
| hc_regs = _core_if->host_if->hc_regs[i]; |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| hcchar.b.chen = 0; |
| hcchar.b.chdis = 1; |
| hcchar.b.epdir = 0; |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| } |
| |
| /* |
| * Halt all channels to put them into a known state. |
| */ |
| for (i = 0; i < num_channels; i++) { |
| int count = 0; |
| hc_regs = _core_if->host_if->hc_regs[i]; |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| hcchar.b.chen = 1; |
| hcchar.b.chdis = 1; |
| hcchar.b.epdir = 0; |
| dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); |
| DWC_DEBUGPL(DBG_HCDV, "%s: Halt channel %d\n", "dwc_otg_core_host_init", i); |
| do { |
| hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); |
| if (++count > 1000) { |
| ERR("%s: Unable to clear halt on channel %d\n", "dwc_otg_core_host_init", i); |
| break; |
| } |
| } while (hcchar.b.chen); |
| } |
| |
| /* |
| * Turn on the vbus power. |
| */ |
| DBG("Init: Port Power? op_state=%d\n", _core_if->op_state); |
| if (_core_if->op_state == A_HOST) { |
| hprt0.d32 = dwc_otg_read_hprt0(_core_if); |
| DBG("Init: Power Port (%d)\n", hprt0.b.prtpwr); |
| if (hprt0.b.prtpwr == 0) { |
| hprt0.b.prtpwr = 1;//6.1.1.3 |
| dwc_write_reg32(host_if->hprt0, hprt0.d32); |
| /* |
| * pull gpio to switch power |
| */ |
| dwc_otg_set_vbus_power(_core_if, 1); |
| } |
| } else { |
| dwc_otg_set_vbus_power(_core_if, 0); // Power off VBus |
| } |
| |
| dwc_otg_enable_host_interrupts(_core_if); |
| } |
| |
| static int dwc_otg_hcd_enable = 0; |
| |
| /* wait at least 10ms for the reset process to complete 6.1.1.6 */ |
| static void |
| dwc_otg_reset_port(dwc_otg_core_if_t * _core_if) |
| { |
| hprt0_data_t hprt0; |
| hprt0.d32 = dwc_otg_read_hprt0(_core_if); |
| hprt0.b.prtrst = 1; |
| dwc_write_reg32(_core_if->host_if->hprt0, hprt0.d32); |
| |
| wait_ms(60); |
| |
| hprt0.b.prtrst = 0; |
| dwc_write_reg32(_core_if->host_if->hprt0, hprt0.d32); |
| wait_ms(20); |
| |
| } |
| static int |
| dwc_otg_port_init(dwc_otg_core_if_t * _core_if) |
| { |
| int retry = 10; |
| hprt0_data_t hprt0,hprt0_modify; |
| hprt0.d32 = 0; |
| /* |
| hprt0.d32 = dwc_otg_read_hprt0(_core_if); |
| |
| hprt0_data_t hprt0_modify; |
| |
| hprt0_modify.b.prtena = 0; |
| hprt0_modify.b.prtconndet = 0; |
| hprt0_modify.b.prtenchng = 0; |
| hprt0_modify.b.prtovrcurrchng = 0; |
| |
| wait_ms(30); |
| */ |
| next: |
| hprt0.d32 = dwc_read_reg32(_core_if->host_if->hprt0); |
| DBG("%s port data is 0x%X", __func__, hprt0.d32); |
| |
| hprt0_modify.d32 = dwc_read_reg32(_core_if->host_if->hprt0); |
| |
| if (hprt0.b.prtconndet) {//wait prtconndet interrupt |
| /* |
| * clear detect intr |
| */ |
| wait_ms(30); |
| dwc_write_reg32(_core_if->host_if->hprt0, hprt0_modify.d32); |
| wait_ms(30); |
| /* |
| * reset port 6.1.1.6 |
| */ |
| dwc_otg_reset_port(_core_if); |
| hprt0_modify.b.prtconndet = 1; |
| |
| }else{ |
| wait_ms(100); |
| if (retry--) |
| goto next; |
| INFO("No USB device found !"); |
| return 0; |
| } |
| /* |
| * Determine if the connected device is a high/full/low speed device |
| */ |
| hprt0.d32 = dwc_read_reg32(_core_if->host_if->hprt0); |
| |
| if (hprt0.b.prtenchng) {//6.1.1.8 |
| hprt0.d32 = dwc_read_reg32(_core_if->host_if->hprt0); |
| |
| hprt0_modify.b.prtena = 0; |
| hprt0_modify.b.prtconndet = 0; |
| hprt0_modify.b.prtenchng = 0; |
| hprt0_modify.b.prtovrcurrchng = 0; |
| hprt0_modify.b.prtenchng = 1; |
| dwc_write_reg32(_core_if->host_if->hprt0, hprt0_modify.d32); |
| |
| } |
| |
| // _core_if->host_if->do_ping = 1; // init value |
| _core_if->host_if->do_ping = 0; // init value |
| |
| if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED) {//6.1.1.9 |
| INFO("Lowspeed device found !\n"); |
| } else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) { |
| INFO("Fullspeed device found !\n"); |
| } else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) { |
| INFO("Highspeed device found !\n"); |
| } |
| |
| return 1; |
| } |
| /* |
| * --- Init functions ------------------------------------------------------ |
| */ |
| int |
| usb_lowlevel_init(int index,enum usb_init_type init, void **controller) |
| { |
| dwc_otg_device_t *dwc_otg_device = &dwc_otg_dev; |
| int retval = 0; |
| int32_t snpsid; |
| int i; |
| amlogic_usb_config_t * usb_config; |
| |
| printf("dwc_usb driver version: %s\n",DWC_DRIVER_VERSION); |
| |
| usb_config = board_usb_start(BOARD_USB_MODE_HOST,index); |
| |
| if (!usb_config || !usb_config->base_addr) { |
| ERR("Bad usb config or base addr! Need call board_usb_init() in board init\n"); |
| retval = -1; |
| goto fail; |
| } |
| |
| memset(dwc_otg_device, 0, sizeof(dwc_otg_device_t)); |
| |
| ERR("base addr: 0x%x",usb_config->base_addr); |
| dwc_otg_device->base = (void *)usb_config->base_addr; |
| |
| dwc_otg_device->index = index; |
| snpsid = dwc_read_reg32((uint32_t *) ((uint8_t *) dwc_otg_device->base + 0x40)); |
| |
| if ((snpsid & 0xFFFFF000) != 0x4F542000) { |
| ERR("Bad value for SNPSID: 0x%08x\n", snpsid); |
| retval = -1; |
| goto fail; |
| } |
| //dwc_otg_module_params_host.dma_enable = 1; |
| |
| dwc_otg_device->core_if = dwc_otg_cil_init(dwc_otg_device->base, &dwc_otg_module_params_host); |
| if (dwc_otg_device->core_if == 0) { |
| ERR("CIL initialization failed!\n"); |
| retval = -1; |
| goto fail; |
| } |
| dwc_otg_device->core_if->set_vbus_power = usb_config->set_vbus_power; |
| |
| /* |
| * Disable the global interrupt until all the interrupt |
| * handlers are installed. |
| */ |
| dwc_otg_disable_global_interrupts(dwc_otg_device->core_if); |
| /* |
| * Initialize the DWC_otg core. |
| */ |
| if (dwc_otg_core_init(dwc_otg_device->core_if)) |
| goto fail; |
| for (i = 0; i < 2; i++) { |
| dwc_otg_hc_cleanup(dwc_otg_device->core_if, i); |
| } |
| |
| dwc_otg_core_host_init(dwc_otg_device->core_if); |
| |
| /* |
| * Enable the global interrupt after all the interrupt |
| * handlers are installed. |
| */ |
| dwc_otg_enable_global_interrupts(dwc_otg_device->core_if); |
| |
| if (!dwc_otg_port_init(dwc_otg_device->core_if)) /* host initialization 6.1.1.3----6.1.1.9*/ |
| goto fail; |
| |
| dwc_otg_hcd_enable = 1; |
| dwc_otg_device->disabled = 0; |
| return 0; |
| fail: |
| return -1; |
| } |
| |
| int |
| usb_lowlevel_stop(int index) |
| { |
| dwc_otg_core_if_t *core_if = dwc_otg_dev.core_if; |
| |
| DBG("%s\n", __func__); |
| if (!dwc_otg_hcd_enable) |
| return 0; |
| #if 0 |
| if (!core_if) |
| return 0; |
| #endif |
| |
| dwc_otg_hcd_stop(core_if); |
| board_usb_stop(BOARD_USB_MODE_HOST,dwc_otg_dev.index); |
| #if 0 |
| if (core_if->temp_buffer) { |
| kfree(core_if->temp_buffer); |
| } |
| |
| if (core_if->host_if) { |
| kfree(core_if->host_if); |
| } |
| |
| if (core_if) { |
| kfree(core_if); |
| } |
| dwc_otg_dev.core_if = 0; |
| #endif |
| |
| return 0; |
| } |