ec164c389c
Change-Id: I6545d8985ab683c026f28f6a7c0e23b40d0a6506
1519 lines
41 KiB
C
1519 lines
41 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id: $
|
|
*
|
|
* Copyright (C) 2021 by Tomasz Moń
|
|
* Ported from Sansa Connect TNETV105 UDC Linux driver
|
|
* Copyright (c) 2005,2006 Zermatt Systems, Inc.
|
|
* Written by: Ben Bostwick
|
|
* Linux driver was modeled strongly after the pxa usb driver.
|
|
*
|
|
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "kernel.h"
|
|
#include "panic.h"
|
|
#include "logf.h"
|
|
#include "usb.h"
|
|
#include "usb_drv.h"
|
|
#include "usb_core.h"
|
|
#include <string.h>
|
|
#include "tnetv105_usb_drv.h"
|
|
#include "tnetv105_cppi.h"
|
|
|
|
#ifdef SANSA_CONNECT
|
|
#define SDRAM_SIZE 0x04000000
|
|
|
|
static void set_tnetv_reset(bool high)
|
|
{
|
|
if (high)
|
|
{
|
|
IO_GIO_BITSET0 = (1 << 7);
|
|
}
|
|
else
|
|
{
|
|
IO_GIO_BITCLR0 = (1 << 7);
|
|
}
|
|
}
|
|
|
|
static bool is_tnetv_reset_high(void)
|
|
{
|
|
return (IO_GIO_BITSET0 & (1 << 7)) ? true : false;
|
|
}
|
|
#endif
|
|
|
|
static bool setup_is_set_address;
|
|
|
|
static cppi_info cppi;
|
|
|
|
static struct ep_runtime_t
|
|
{
|
|
int max_packet_size;
|
|
bool in_allocated;
|
|
bool out_allocated;
|
|
uint8_t *rx_buf; /* OUT */
|
|
int rx_remaining;
|
|
int rx_size;
|
|
uint8_t *tx_buf; /* IN */
|
|
int tx_remaining;
|
|
int tx_size;
|
|
volatile bool block; /* flag indicating that transfer is blocking */
|
|
struct semaphore complete; /* semaphore for blocking transfers */
|
|
}
|
|
ep_runtime[USB_NUM_ENDPOINTS];
|
|
|
|
static const struct
|
|
{
|
|
int type;
|
|
int hs_max_packet_size;
|
|
/* Not sure what xyoff[1] is for. Presumably it is double buffer, but how
|
|
* the double buffering works is not so clear from the Sansa Connect Linux
|
|
* kernel patch. As TNETV105 datasheet is not available, the values are
|
|
* simply taken from the Linux patch as potential constraints are unknown.
|
|
*
|
|
* Linux kernel has 9 endpoints:
|
|
* * 0: ep0
|
|
* * 1: ep1in-bulk
|
|
* * 2: ep2out-bulk
|
|
* * 3: ep3in-int
|
|
* * 4: ep4in-int
|
|
* * 5: ep1out-bulk
|
|
* * 6: ep2in-bulk
|
|
* * 7: ep3out-int
|
|
* * 8: ep4out-int
|
|
*/
|
|
uint16_t xyoff_in[2];
|
|
uint16_t xyoff_out[2];
|
|
}
|
|
ep_const_data[USB_NUM_ENDPOINTS] =
|
|
{
|
|
{
|
|
.type = USB_ENDPOINT_XFER_CONTROL,
|
|
.hs_max_packet_size = EP0_MAX_PACKET_SIZE,
|
|
/* Do not set xyoff as it likely does not apply here.
|
|
* Linux simply hardcodes the offsets when needed.
|
|
*/
|
|
},
|
|
{
|
|
.type = USB_ENDPOINT_XFER_BULK,
|
|
.hs_max_packet_size = EP1_MAX_PACKET_SIZE,
|
|
.xyoff_in = {EP1_XBUFFER_ADDRESS, EP1_YBUFFER_ADDRESS},
|
|
.xyoff_out = {EP5_XBUFFER_ADDRESS, EP5_YBUFFER_ADDRESS},
|
|
},
|
|
{
|
|
.type = USB_ENDPOINT_XFER_BULK,
|
|
.hs_max_packet_size = EP2_MAX_PACKET_SIZE,
|
|
.xyoff_in = {EP6_XBUFFER_ADDRESS, EP6_YBUFFER_ADDRESS},
|
|
.xyoff_out = {EP2_XBUFFER_ADDRESS, EP2_YBUFFER_ADDRESS},
|
|
},
|
|
{
|
|
.type = USB_ENDPOINT_XFER_INT,
|
|
.hs_max_packet_size = EP3_MAX_PACKET_SIZE,
|
|
.xyoff_in = {EP3_XBUFFER_ADDRESS, EP3_YBUFFER_ADDRESS},
|
|
.xyoff_out = {EP7_XBUFFER_ADDRESS, EP7_YBUFFER_ADDRESS},
|
|
},
|
|
{
|
|
.type = USB_ENDPOINT_XFER_INT,
|
|
.hs_max_packet_size = EP4_MAX_PACKET_SIZE,
|
|
.xyoff_in = {EP4_XBUFFER_ADDRESS, EP4_YBUFFER_ADDRESS},
|
|
.xyoff_out = {EP8_XBUFFER_ADDRESS, EP8_YBUFFER_ADDRESS},
|
|
},
|
|
};
|
|
|
|
#define VLYNQ_CTL_RESET_MASK 0x0001
|
|
#define VLYNQ_CTL_CLKDIR_MASK 0x8000
|
|
#define VLYNQ_STS_LINK_MASK 0x0001
|
|
|
|
#define DM320_VLYNQ_CTRL_RESET (1 << 0)
|
|
#define DM320_VLYNQ_CTRL_LOOP (1 << 1)
|
|
#define DM320_VLYNQ_CTRL_ADR_OPT (1 << 2)
|
|
#define DM320_VLYNQ_CTRL_INT_CFG (1 << 7)
|
|
#define DM320_VLYNQ_CTRL_INT_VEC_MASK (0x00001F00)
|
|
#define DM320_VLYNQ_CTRL_INT_EN (1 << 13)
|
|
#define DM320_VLYNQ_CTRL_INT_LOC (1 << 14)
|
|
#define DM320_VLYNQ_CTRL_CLKDIR (1 << 15)
|
|
#define DM320_VLYNQ_CTRL_CLKDIV_MASK (0x00070000)
|
|
#define DM320_VLYNQ_CTRL_PWR_MAN (1 << 31)
|
|
|
|
#define DM320_VLYNQ_STAT_LINK (1 << 0)
|
|
#define DM320_VLYNQ_STAT_MST_PEND (1 << 1)
|
|
#define DM320_VLYNQ_STAT_SLV_PEND (1 << 2)
|
|
#define DM320_VLYNQ_STAT_F0_NE (1 << 3)
|
|
#define DM320_VLYNQ_STAT_F1_NE (1 << 4)
|
|
#define DM320_VLYNQ_STAT_F2_NE (1 << 5)
|
|
#define DM320_VLYNQ_STAT_F3_NE (1 << 6)
|
|
#define DM320_VLYNQ_STAT_LOC_ERR (1 << 7)
|
|
#define DM320_VLYNQ_STAT_REM_ERR (1 << 8)
|
|
#define DM320_VLYNQ_STAT_FC_OUT (1 << 9)
|
|
#define DM320_VLYNQ_STAT_FC_IN (1 << 10)
|
|
|
|
#define MAX_PACKET(epn, speed) ((((speed) == USB_SPEED_HIGH) && (((epn) == 1) || ((epn) == 2))) ? USB_HIGH_SPEED_MAXPACKET : USB_FULL_SPEED_MAXPACKET)
|
|
|
|
#define VLYNQ_INTR_USB20 (1 << 0)
|
|
#define VLYNQ_INTR_CPPI (1 << 1)
|
|
|
|
static inline void set_vlynq_clock(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
IO_CLK_MOD2 |= (1 << 13);
|
|
}
|
|
else
|
|
{
|
|
IO_CLK_MOD2 &= ~(1 << 13);
|
|
}
|
|
}
|
|
|
|
static inline void set_vlynq_irq(bool enabled)
|
|
{
|
|
if (enabled)
|
|
{
|
|
/* Enable VLYNQ interrupt */
|
|
IO_INTC_EINT1 |= (1 << 0);
|
|
}
|
|
else
|
|
{
|
|
IO_INTC_EINT1 &= ~(1 << 0);
|
|
}
|
|
}
|
|
|
|
static int tnetv_hw_reset(void)
|
|
{
|
|
int timeout;
|
|
|
|
/* hold down the reset pin on the USB chip */
|
|
set_tnetv_reset(false);
|
|
|
|
/* Turn on VLYNQ clock. */
|
|
set_vlynq_clock(true);
|
|
|
|
/* now reset the VLYNQ module */
|
|
VL_CTRL |= (VLYNQ_CTL_CLKDIR_MASK | DM320_VLYNQ_CTRL_PWR_MAN);
|
|
VL_CTRL |= VLYNQ_CTL_RESET_MASK;
|
|
|
|
mdelay(10);
|
|
|
|
/* pull up the reset pin */
|
|
set_tnetv_reset(true);
|
|
|
|
/* take the VLYNQ out of reset */
|
|
VL_CTRL &= ~VLYNQ_CTL_RESET_MASK;
|
|
|
|
timeout = 0;
|
|
while (!(VL_STAT & VLYNQ_STS_LINK_MASK) && timeout++ < 50);
|
|
{
|
|
mdelay(40);
|
|
}
|
|
|
|
if (!(VL_STAT & VLYNQ_STS_LINK_MASK))
|
|
{
|
|
logf("ERROR: VLYNQ not initialized!\n");
|
|
return -1;
|
|
}
|
|
|
|
/* set up vlynq local map */
|
|
VL_TXMAP = DM320_VLYNQ_PADDR;
|
|
VL_RXMAPOF1 = CONFIG_SDRAM_START;
|
|
VL_RXMAPSZ1 = SDRAM_SIZE;
|
|
|
|
/* set up vlynq remote map for tnetv105 */
|
|
VL_TXMAP_R = 0x00000000;
|
|
VL_RXMAPOF1_R = 0x0C000000;
|
|
VL_RXMAPSZ1_R = 0x00030000;
|
|
|
|
/* clear TNETV gpio state */
|
|
tnetv_usb_reg_write(TNETV_V2USB_GPIO_FS, 0);
|
|
|
|
/* set USB_CHARGE_EN pin (gpio 1) - output, disable pullup */
|
|
tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, 0);
|
|
tnetv_usb_reg_write(TNETV_V2USB_GPIO_DIR, 0xffff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tnetv_xcvr_on(void)
|
|
{
|
|
return tnetv_hw_reset();
|
|
}
|
|
|
|
static void tnetv_xcvr_off(void)
|
|
{
|
|
/* turn off vlynq module clock */
|
|
set_vlynq_clock(false);
|
|
|
|
/* hold down the reset pin on the USB chip */
|
|
set_tnetv_reset(false);
|
|
}
|
|
|
|
/* Copy data from the usb data memory. The memory reads should be done 32 bits at a time.
|
|
* We do not assume that the dst data is aligned.
|
|
*/
|
|
static void tnetv_copy_from_data_mem(void *dst, const volatile uint32_t *sp, int size)
|
|
{
|
|
uint8_t *dp = (uint8_t *) dst;
|
|
uint32_t value;
|
|
|
|
while (size >= 4)
|
|
{
|
|
value = *sp++;
|
|
dp[0] = value;
|
|
dp[1] = value >> 8;
|
|
dp[2] = value >> 16;
|
|
dp[3] = value >> 24;
|
|
dp += 4;
|
|
size -= 4;
|
|
}
|
|
|
|
if (size)
|
|
{
|
|
value = sp[0];
|
|
switch (size)
|
|
{
|
|
case 3:
|
|
dp[2] = value >> 16;
|
|
case 2:
|
|
dp[1] = value >> 8;
|
|
case 1:
|
|
dp[0] = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Copy data into the usb data memory. The memory writes must be done 32 bits at a time.
|
|
* We do not assume that the src data is aligned.
|
|
*/
|
|
static void tnetv_copy_to_data_mem(volatile uint32_t *dp, const void *src, int size)
|
|
{
|
|
const uint8_t *sp = (const uint8_t *) src;
|
|
uint32_t value;
|
|
|
|
while (size >= 4)
|
|
{
|
|
value = sp[0] | (sp[1] << 8) | (sp[2] << 16) | (sp[3] << 24);
|
|
*dp++ = value;
|
|
sp += 4;
|
|
size -= 4;
|
|
}
|
|
|
|
switch (size)
|
|
{
|
|
case 3:
|
|
value = sp[0] | (sp[1] << 8) | (sp[2] << 16);
|
|
*dp = value;
|
|
break;
|
|
case 2:
|
|
value = sp[0] | (sp[1] << 8);
|
|
*dp = value;
|
|
break;
|
|
case 1:
|
|
value = sp[0];
|
|
*dp = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void tnetv_init_endpoints(void)
|
|
{
|
|
UsbEp0CtrlType ep0Cfg;
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
UsbEpCfgCtrlType epCfg;
|
|
UsbEpStartAddrType epStartAddr;
|
|
int ch, wd, epn;
|
|
|
|
ep0Cnt.val = 0;
|
|
ep0Cnt.f.out_ybuf_nak = 1;
|
|
ep0Cnt.f.out_xbuf_nak = 1;
|
|
ep0Cnt.f.in_ybuf_nak = 1;
|
|
ep0Cnt.f.in_xbuf_nak = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
|
|
/* Setup endpoint zero */
|
|
ep0Cfg.val = 0;
|
|
ep0Cfg.f.buf_size = EP0_BUF_SIZE_64; /* must be 64 bytes for USB 2.0 */
|
|
ep0Cfg.f.dbl_buf = 0;
|
|
ep0Cfg.f.in_en = 1;
|
|
ep0Cfg.f.in_int_en = 1;
|
|
ep0Cfg.f.out_en = 1;
|
|
ep0Cfg.f.out_int_en = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Cfg.val);
|
|
|
|
/* disable cell dma */
|
|
tnetv_usb_reg_write(TNETV_USB_CELL_DMA_EN, 0);
|
|
|
|
/* turn off dma engines */
|
|
tnetv_usb_reg_write(TNETV_USB_TX_CTL, 0);
|
|
tnetv_usb_reg_write(TNETV_USB_RX_CTL, 0);
|
|
|
|
/* clear out DMA registers */
|
|
for (ch = 0; ch < TNETV_DMA_NUM_CHANNELS; ch++)
|
|
{
|
|
for (wd = 0; wd < TNETV_DMA_TX_NUM_WORDS; wd++)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, wd), 0);
|
|
}
|
|
|
|
for (wd = 0; wd < TNETV_DMA_RX_NUM_WORDS; wd++)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, wd), 0);
|
|
}
|
|
|
|
/* flush the free buf count */
|
|
while (tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)) != 0)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), 0xFFFF);
|
|
}
|
|
}
|
|
|
|
for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_ADR(epn),0);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), 0);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000);
|
|
}
|
|
|
|
/* Setup the other endpoints */
|
|
for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++)
|
|
{
|
|
epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn));
|
|
epStartAddr.val = tnetv_usb_reg_read(TNETV_USB_EPx_ADR(epn));
|
|
|
|
/* Linux kernel enables dbl buf for both IN and OUT.
|
|
* For IN this is problematic when tnetv_cppi_send() is called
|
|
* to send single ZLP, it will actually send two ZLPs.
|
|
* Disable the dbl buf here as datasheet is not available and
|
|
* this results in working mass storage on Windows 10.
|
|
*/
|
|
epCfg.f.in_dbl_buf = 0;
|
|
epCfg.f.in_toggle_rst = 1;
|
|
epCfg.f.in_ack_int = 0;
|
|
epCfg.f.in_stall = 0;
|
|
epCfg.f.in_nak_int = 0;
|
|
epCfg.f.out_dbl_buf = 0;
|
|
epCfg.f.out_toggle_rst = 1;
|
|
epCfg.f.out_ack_int = 0;
|
|
epCfg.f.out_stall = 0;
|
|
epCfg.f.out_nak_int = 0;
|
|
|
|
/* buf_size is specified "in increments of 8 bytes" */
|
|
epCfg.f.in_buf_size = ep_const_data[epn].hs_max_packet_size >> 3;
|
|
epCfg.f.out_buf_size = ep_const_data[epn].hs_max_packet_size >> 3;
|
|
|
|
epStartAddr.f.xBuffStartAddrIn = ep_const_data[epn].xyoff_in[0] >> 4;
|
|
epStartAddr.f.yBuffStartAddrIn = ep_const_data[epn].xyoff_in[1] >> 4;
|
|
epStartAddr.f.xBuffStartAddrOut = ep_const_data[epn].xyoff_out[0] >> 4;
|
|
epStartAddr.f.yBuffStartAddrOut = ep_const_data[epn].xyoff_out[1] >> 4;
|
|
|
|
/* allocate memory for DMA */
|
|
tnetv_cppi_init_rcb(&cppi, (epn - 1));
|
|
/* set up DMA queue */
|
|
tnetv_cppi_init_tcb(&cppi, (epn - 1));
|
|
|
|
/* now write out the config to the TNETV (write enable bits last) */
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_ADR(epn), epStartAddr.val);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000);
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000);
|
|
}
|
|
|
|
/* turn on dma engines */
|
|
tnetv_usb_reg_write(TNETV_USB_TX_CTL, 1);
|
|
tnetv_usb_reg_write(TNETV_USB_RX_CTL, 1);
|
|
|
|
/* enable cell dma */
|
|
tnetv_usb_reg_write(TNETV_USB_CELL_DMA_EN, (TNETV_USB_CELL_DMA_EN_RX | TNETV_USB_CELL_DMA_EN_TX));
|
|
}
|
|
|
|
static void tnetv_udc_enable_interrupts(void)
|
|
{
|
|
UsbCtrlType usb_ctl;
|
|
uint8_t tx_int_en, rx_int_en;
|
|
int ep, chan;
|
|
|
|
/* set up the system interrupts */
|
|
usb_ctl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usb_ctl.f.vbus_int_en = 1;
|
|
usb_ctl.f.reset_int_en = 1;
|
|
usb_ctl.f.suspend_int_en = 1;
|
|
usb_ctl.f.resume_int_en = 1;
|
|
usb_ctl.f.ep0_in_int_en = 1;
|
|
usb_ctl.f.ep0_out_int_en = 1;
|
|
usb_ctl.f.setup_int_en = 1;
|
|
usb_ctl.f.setupow_int_en = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usb_ctl.val);
|
|
|
|
/* Enable the DMA endpoint interrupts */
|
|
tx_int_en = 0;
|
|
rx_int_en = 0;
|
|
|
|
for (ep = 1; ep < USB_NUM_ENDPOINTS; ep++)
|
|
{
|
|
chan = ep - 1;
|
|
rx_int_en |= (1 << chan); /* OUT */
|
|
tx_int_en |= (1 << chan); /* IN */
|
|
}
|
|
|
|
/* enable rx interrupts */
|
|
tnetv_usb_reg_write(TNETV_USB_RX_INT_EN, rx_int_en);
|
|
/* enable tx interrupts */
|
|
tnetv_usb_reg_write(TNETV_USB_TX_INT_EN, tx_int_en);
|
|
|
|
set_vlynq_irq(true);
|
|
}
|
|
|
|
static void tnetv_udc_disable_interrupts(void)
|
|
{
|
|
UsbCtrlType usb_ctl;
|
|
|
|
/* disable interrupts from linux */
|
|
set_vlynq_irq(false);
|
|
|
|
/* Disable Endpoint Interrupts */
|
|
tnetv_usb_reg_write(TNETV_USB_RX_INT_DIS, 0x3);
|
|
tnetv_usb_reg_write(TNETV_USB_TX_INT_DIS, 0x3);
|
|
|
|
/* Disable USB system interrupts */
|
|
usb_ctl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usb_ctl.f.vbus_int_en = 0;
|
|
usb_ctl.f.reset_int_en = 0;
|
|
usb_ctl.f.suspend_int_en = 0;
|
|
usb_ctl.f.resume_int_en = 0;
|
|
usb_ctl.f.ep0_in_int_en = 0;
|
|
usb_ctl.f.ep0_out_int_en = 0;
|
|
usb_ctl.f.setup_int_en = 0;
|
|
usb_ctl.f.setupow_int_en = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usb_ctl.val);
|
|
}
|
|
|
|
static void tnetv_ep_halt(int epn, bool in)
|
|
{
|
|
if (in)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000);
|
|
}
|
|
else
|
|
{
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000);
|
|
}
|
|
}
|
|
|
|
/* Reset the TNETV usb2.0 controller and configure it to run in function mode */
|
|
static void tnetv_usb_reset(void)
|
|
{
|
|
uint32_t timeout = 0;
|
|
int wd;
|
|
int ch;
|
|
|
|
/* configure function clock */
|
|
tnetv_usb_reg_write(TNETV_V2USB_CLK_CFG, 0x80);
|
|
|
|
/* Reset the USB 2.0 function module */
|
|
tnetv_usb_reg_write(TNETV_V2USB_RESET, 0x01);
|
|
|
|
/* now poll the module ready register until the 2.0 controller finishes resetting */
|
|
while (!(tnetv_usb_reg_read(TNETV_USB_RESET_CMPL) & 0x1) && (timeout < 1000000))
|
|
{
|
|
timeout++;
|
|
}
|
|
|
|
if (!(tnetv_usb_reg_read(TNETV_USB_RESET_CMPL) & 0x1))
|
|
{
|
|
logf("tnetv105_udc: VLYNQ USB module reset failed!\n");
|
|
return;
|
|
}
|
|
|
|
/* turn off external clock */
|
|
tnetv_usb_reg_write(TNETV_V2USB_CLK_PERF, 0);
|
|
|
|
/* clear out USB data memory */
|
|
for (wd = 0; wd < TNETV_EP_DATA_SIZE; wd += 4)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_EP_DATA_ADDR(wd), 0);
|
|
}
|
|
|
|
/* clear out DMA memory */
|
|
for (ch = 0; ch < TNETV_DMA_NUM_CHANNELS; ch++)
|
|
{
|
|
for (wd = 0; wd < TNETV_DMA_TX_NUM_WORDS; wd++)
|
|
{
|
|
tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, wd), 0);
|
|
}
|
|
|
|
for (wd = 0; wd < TNETV_DMA_RX_NUM_WORDS; wd++)
|
|
{
|
|
|
|
tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, wd), 0);
|
|
}
|
|
}
|
|
|
|
/* point VLYNQ interrupts at the pending register */
|
|
VL_INTPTR = DM320_VLYNQ_INTPND_PHY;
|
|
|
|
/* point VLYNQ remote interrupts at the pending register */
|
|
VL_INTPTR_R = 0;
|
|
|
|
/* clear out interrupt register */
|
|
VL_INTST |= 0xFFFFFFFF;
|
|
|
|
/* enable interrupts on remote device */
|
|
VL_CTRL_R |= (DM320_VLYNQ_CTRL_INT_EN);
|
|
VL_INTVEC30_R = 0x8180;
|
|
|
|
/* enable VLYNQ interrupts & set interrupts to trigger VLYNQ int */
|
|
VL_CTRL |= (DM320_VLYNQ_CTRL_INT_LOC | DM320_VLYNQ_CTRL_INT_CFG);
|
|
}
|
|
|
|
static int tnetv_ep_start_xmit(int epn, void *buf, int size)
|
|
{
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
|
|
if (epn == 0)
|
|
{
|
|
/* Write the Control Data packet to the EP0 IN memory area */
|
|
tnetv_copy_to_data_mem(TNETV_EP_DATA_ADDR(EP0_INPKT_ADDRESS), buf, size);
|
|
|
|
/* start xmitting */
|
|
ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT);
|
|
ep0Cnt.f.in_xbuf_cnt = size;
|
|
ep0Cnt.f.in_xbuf_nak = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
}
|
|
else
|
|
{
|
|
dma_addr_t buffer = (dma_addr_t)buf;
|
|
int send_zlp = 0;
|
|
if (size == 0)
|
|
{
|
|
/* Any address in SDRAM will do, contents do not matter */
|
|
buffer = CONFIG_SDRAM_START;
|
|
size = 1;
|
|
send_zlp = 1;
|
|
}
|
|
else
|
|
{
|
|
commit_discard_dcache_range(buf, size);
|
|
}
|
|
if ((buffer >= CONFIG_SDRAM_START) && (buffer + size < CONFIG_SDRAM_START + SDRAM_SIZE))
|
|
{
|
|
if (tnetv_cppi_send(&cppi, (epn - 1), buffer, size, send_zlp))
|
|
{
|
|
panicf("tnetv_cppi_send() failed");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
panicf("USB xmit buf outside SDRAM %p", buf);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tnetv_gadget_req_nuke(int epn, bool in)
|
|
{
|
|
struct ep_runtime_t *ep = &ep_runtime[epn];
|
|
uint32_t old_rx_int = 0;
|
|
uint32_t old_tx_int = 0;
|
|
int ch;
|
|
int flags;
|
|
|
|
/* don't nuke control ep */
|
|
if (epn == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
flags = disable_irq_save();
|
|
|
|
/* save and disable interrupts before nuking request */
|
|
old_rx_int = tnetv_usb_reg_read(TNETV_USB_RX_INT_EN);
|
|
old_tx_int = tnetv_usb_reg_read(TNETV_USB_TX_INT_EN);
|
|
tnetv_usb_reg_write(TNETV_USB_RX_INT_DIS, 0x3);
|
|
tnetv_usb_reg_write(TNETV_USB_TX_INT_DIS, 0x3);
|
|
|
|
ch = epn - 1;
|
|
|
|
if (in)
|
|
{
|
|
tnetv_cppi_flush_tx_queue(&cppi, ch);
|
|
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000);
|
|
if (ep->tx_remaining > 0)
|
|
{
|
|
usb_core_transfer_complete(epn, USB_DIR_IN, -1, 0);
|
|
}
|
|
ep->tx_buf = NULL;
|
|
ep->tx_remaining = 0;
|
|
ep->tx_size = 0;
|
|
|
|
if (ep->block)
|
|
{
|
|
semaphore_release(&ep->complete);
|
|
ep->block = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tnetv_cppi_flush_rx_queue(&cppi, ch);
|
|
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000);
|
|
if (ep->rx_remaining > 0)
|
|
{
|
|
usb_core_transfer_complete(epn, USB_DIR_OUT, -1, 0);
|
|
}
|
|
ep->rx_buf = NULL;
|
|
ep->rx_remaining = 0;
|
|
ep->rx_size = 0;
|
|
}
|
|
|
|
/* reenable any interrupts */
|
|
tnetv_usb_reg_write(TNETV_USB_RX_INT_EN, old_rx_int);
|
|
tnetv_usb_reg_write(TNETV_USB_TX_INT_EN, old_tx_int);
|
|
|
|
restore_irq(flags);
|
|
}
|
|
|
|
static int tnetv_gadget_ep_enable(int epn, bool in)
|
|
{
|
|
UsbEpCfgCtrlType epCfg;
|
|
int flags;
|
|
enum usb_device_speed speed;
|
|
|
|
if (epn == 0 || epn >= USB_NUM_ENDPOINTS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
flags = disable_irq_save();
|
|
|
|
/* set the maxpacket for this endpoint based on the current speed */
|
|
speed = usb_drv_port_speed() ? USB_SPEED_HIGH : USB_SPEED_FULL;
|
|
ep_runtime[epn].max_packet_size = MAX_PACKET(epn, speed);
|
|
|
|
/* Enable the endpoint */
|
|
epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn));
|
|
if (in)
|
|
{
|
|
epCfg.f.in_en = 1;
|
|
epCfg.f.in_stall = 0;
|
|
epCfg.f.in_toggle_rst = 1;
|
|
epCfg.f.in_buf_size = ep_runtime[epn].max_packet_size >> 3;
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000);
|
|
}
|
|
else
|
|
{
|
|
epCfg.f.out_en = 1;
|
|
epCfg.f.out_stall = 0;
|
|
epCfg.f.out_toggle_rst = 1;
|
|
epCfg.f.out_buf_size = ep_runtime[epn].max_packet_size >> 3;
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000);
|
|
}
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val);
|
|
|
|
restore_irq(flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tnetv_gadget_ep_disable(int epn, bool in)
|
|
{
|
|
UsbEpCfgCtrlType epCfg;
|
|
int flags;
|
|
|
|
if (epn == 0 || epn >= USB_NUM_ENDPOINTS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
flags = disable_irq_save();
|
|
|
|
/* Disable the endpoint */
|
|
epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn));
|
|
if (in)
|
|
{
|
|
epCfg.f.in_en = 0;
|
|
}
|
|
else
|
|
{
|
|
epCfg.f.out_en = 0;
|
|
}
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val);
|
|
|
|
/* Turn off the endpoint and unready it */
|
|
tnetv_ep_halt(epn, in);
|
|
|
|
restore_irq(flags);
|
|
|
|
/* Clear out all the pending requests */
|
|
tnetv_gadget_req_nuke(epn, in);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TNETV udc goo
|
|
* Power up and enable the udc. This includes resetting the hardware, turn on the appropriate clocks
|
|
* and initializing things so that the first setup packet can be received.
|
|
*/
|
|
static void tnetv_udc_enable(void)
|
|
{
|
|
/* Enable M48XI crystal resonator */
|
|
IO_CLK_LPCTL1 &= ~(0x01);
|
|
|
|
/* Set GIO33 as CLKOUT1B */
|
|
IO_GIO_FSEL3 |= 0x0003;
|
|
|
|
if (tnetv_xcvr_on())
|
|
return;
|
|
|
|
tnetv_usb_reset();
|
|
|
|
/* BEN - RNDIS mode is assuming zlps after packets that are multiples of buffer endpoints
|
|
* zlps are not required by the spec and many controllers don't send them.
|
|
* set DMA to RNDIS mode (packet concatenation, less interrupts)
|
|
* tnetv_usb_reg_write(TNETV_USB_RNDIS_MODE, 0xFF);
|
|
*/
|
|
tnetv_usb_reg_write(TNETV_USB_RNDIS_MODE, 0);
|
|
|
|
tnetv_init_endpoints();
|
|
|
|
tnetv_udc_enable_interrupts();
|
|
}
|
|
|
|
static void tnetv_udc_disable(void)
|
|
{
|
|
tnetv_udc_disable_interrupts();
|
|
|
|
tnetv_hw_reset();
|
|
|
|
tnetv_xcvr_off();
|
|
|
|
/* Set GIO33 as normal output, drive it low */
|
|
IO_GIO_FSEL3 &= ~(0x0003);
|
|
IO_GIO_BITCLR2 = (1 << 1);
|
|
|
|
/* Disable M48XI crystal resonator */
|
|
IO_CLK_LPCTL1 |= 0x01;
|
|
}
|
|
|
|
static void tnetv_udc_handle_reset(void)
|
|
{
|
|
UsbCtrlType usbCtrl;
|
|
|
|
/* disable USB interrupts */
|
|
tnetv_udc_disable_interrupts();
|
|
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usbCtrl.f.func_addr = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val);
|
|
|
|
/* Reset endpoints */
|
|
tnetv_init_endpoints();
|
|
|
|
/* Re-enable interrupts */
|
|
tnetv_udc_enable_interrupts();
|
|
}
|
|
|
|
static void ep_write(int epn)
|
|
{
|
|
struct ep_runtime_t *ep = &ep_runtime[epn];
|
|
int tx_size;
|
|
if (epn == 0)
|
|
{
|
|
tx_size = MIN(ep->max_packet_size, ep->tx_remaining);
|
|
}
|
|
else
|
|
{
|
|
/* DMA takes care of splitting the buffer into packets,
|
|
* but only up to CPPI_MAX_FRAG. After the data is sent
|
|
* a single interrupt is generated. There appears to be
|
|
* splitting code in the tnetv_cppi_send() function but
|
|
* it is somewhat suspicious (it doesn't seem like it
|
|
* will work with requests larger than 2*CPPI_MAX_FRAG).
|
|
* Also, if tnetv_cppi_send() does the splitting, we will
|
|
* get an interrupt after CPPI_MAX_FRAG but before the
|
|
* full request is sent.
|
|
*
|
|
* CPPI_MAX_FRAG is multiple of both 64 and 512 so we
|
|
* don't have to worry about this split prematurely ending
|
|
* the transfer.
|
|
*/
|
|
tx_size = MIN(CPPI_MAX_FRAG, ep->tx_remaining);
|
|
}
|
|
tnetv_ep_start_xmit(epn, ep->tx_buf, tx_size);
|
|
ep->tx_remaining -= tx_size;
|
|
ep->tx_buf += tx_size;
|
|
}
|
|
|
|
static void in_interrupt(int epn)
|
|
{
|
|
struct ep_runtime_t *ep = &ep_runtime[epn];
|
|
|
|
if (ep->tx_remaining <= 0)
|
|
{
|
|
usb_core_transfer_complete(epn, USB_DIR_IN, 0, ep->tx_size);
|
|
/* release semaphore for blocking transfer */
|
|
if (ep->block)
|
|
{
|
|
semaphore_release(&ep->complete);
|
|
ep->tx_buf = NULL;
|
|
ep->tx_size = 0;
|
|
ep->tx_remaining = 0;
|
|
ep->block = false;
|
|
}
|
|
}
|
|
else if (ep->tx_buf)
|
|
{
|
|
ep_write(epn);
|
|
}
|
|
}
|
|
|
|
static void ep_read(int epn)
|
|
{
|
|
if (epn == 0)
|
|
{
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT);
|
|
ep0Cnt.f.out_xbuf_nak = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
}
|
|
else
|
|
{
|
|
struct ep_runtime_t *ep = &ep_runtime[epn];
|
|
tnetv_cppi_rx_queue_add(&cppi, (epn - 1), 0, ep->rx_remaining);
|
|
}
|
|
}
|
|
|
|
static void out_interrupt(int epn)
|
|
{
|
|
struct ep_runtime_t *ep = &ep_runtime[epn];
|
|
int is_short;
|
|
int rcv_len;
|
|
|
|
if (epn == 0)
|
|
{
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
|
|
/* get the length of the received data */
|
|
ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT);
|
|
rcv_len = ep0Cnt.f.out_xbuf_cnt;
|
|
|
|
if (rcv_len > ep->rx_remaining)
|
|
{
|
|
rcv_len = ep->rx_remaining;
|
|
}
|
|
|
|
tnetv_copy_from_data_mem(ep->rx_buf, TNETV_EP_DATA_ADDR(EP0_OUTPKT_ADDRESS), rcv_len);
|
|
ep->rx_buf += rcv_len;
|
|
ep->rx_remaining -= rcv_len;
|
|
|
|
/* See if we are done */
|
|
is_short = rcv_len && (rcv_len < ep->max_packet_size);
|
|
if (is_short || (ep->rx_remaining == 0))
|
|
{
|
|
usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining);
|
|
ep->rx_remaining = 0;
|
|
ep->rx_size = 0;
|
|
ep->rx_buf = 0;
|
|
return;
|
|
}
|
|
|
|
/* make sure nak is cleared only if we expect more data */
|
|
ep0Cnt.f.out_xbuf_nak = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
ep_read(epn);
|
|
}
|
|
else if (ep->rx_remaining > 0)
|
|
{
|
|
int ret, bytes_rcvd;
|
|
|
|
/* copy the data from the DMA buffers */
|
|
bytes_rcvd = ep->rx_remaining;
|
|
ret = tnetv_cppi_rx_int_recv(&cppi, (epn - 1), &bytes_rcvd, ep->rx_buf, ep->max_packet_size);
|
|
if (ret == 0 || ret == -EAGAIN)
|
|
{
|
|
ep->rx_buf += bytes_rcvd;
|
|
ep->rx_remaining -= bytes_rcvd;
|
|
}
|
|
|
|
/* complete the request if we got a short packet or an error
|
|
* make sure we don't complete a request with zero bytes.
|
|
*/
|
|
if ((ret == 0) && (ep->rx_remaining != ep->rx_size))
|
|
{
|
|
usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining);
|
|
ep->rx_remaining = 0;
|
|
ep->rx_size = 0;
|
|
ep->rx_buf = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool tnetv_handle_cppi(void)
|
|
{
|
|
int ret;
|
|
int ch;
|
|
uint32_t tx_intstatus;
|
|
uint32_t rx_intstatus;
|
|
uint32_t status;
|
|
int rcv_sched = 0;
|
|
|
|
rx_intstatus = tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS);
|
|
tx_intstatus = tnetv_usb_reg_read(TNETV_USB_TX_INT_STATUS);
|
|
|
|
/* handle any transmit interrupts */
|
|
status = tx_intstatus;
|
|
for (ch = 0; ch < CPPI_NUM_CHANNELS && status; ch++)
|
|
{
|
|
if (status & 0x1)
|
|
{
|
|
ret = tnetv_cppi_tx_int(&cppi, ch);
|
|
if (ret >= 0)
|
|
{
|
|
in_interrupt(ch + 1);
|
|
}
|
|
}
|
|
|
|
status = status >> 1;
|
|
}
|
|
|
|
rcv_sched = 0;
|
|
status = rx_intstatus;
|
|
for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++)
|
|
{
|
|
if (status & 0x1 || tnetv_cppi_rx_int_recv_check(&cppi, ch))
|
|
{
|
|
ret = tnetv_cppi_rx_int(&cppi, ch);
|
|
if (ret < 0)
|
|
{
|
|
/* only an error if interrupt bit is set */
|
|
logf("CPPI Rx: failed to ACK int!\n");
|
|
}
|
|
else
|
|
{
|
|
if (tnetv_cppi_rx_int_recv_check(&cppi, ch))
|
|
{
|
|
out_interrupt(ch + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tnetv_cppi_rx_int_recv_check(&cppi, ch))
|
|
{
|
|
rcv_sched = 1;
|
|
}
|
|
|
|
status = status >> 1;
|
|
}
|
|
|
|
rx_intstatus = tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS);
|
|
tx_intstatus = tnetv_usb_reg_read(TNETV_USB_TX_INT_STATUS);
|
|
|
|
if (rx_intstatus || tx_intstatus || rcv_sched)
|
|
{
|
|
/* Request calling again after short delay
|
|
* Needed when for example when OUT endpoint has pending data
|
|
* but the USB task did not call usb_drv_recv_nonblocking() yet.
|
|
*/
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int cppi_timeout_cb(struct timeout *tmo)
|
|
{
|
|
(void)tmo;
|
|
int flags = disable_irq_save();
|
|
bool requeue = tnetv_handle_cppi();
|
|
restore_irq(flags);
|
|
return requeue ? 1 : 0;
|
|
}
|
|
|
|
void VLYNQ(void) __attribute__ ((section(".icode")));
|
|
void VLYNQ(void)
|
|
{
|
|
UsbStatusType sysIntrStatus;
|
|
UsbStatusType sysIntClear;
|
|
UsbCtrlType usbCtrl;
|
|
volatile uint32_t *reg;
|
|
uint32_t vlynq_intr;
|
|
|
|
/* Clear interrupt */
|
|
IO_INTC_IRQ1 = (1 << 0);
|
|
|
|
/* clear out VLYNQ interrupt register */
|
|
vlynq_intr = VL_INTST;
|
|
|
|
if (vlynq_intr & VLYNQ_INTR_USB20)
|
|
{
|
|
VL_INTST = VLYNQ_INTR_USB20;
|
|
|
|
/* Examine system interrupt status */
|
|
sysIntrStatus.val = tnetv_usb_reg_read(TNETV_USB_STATUS);
|
|
|
|
if (sysIntrStatus.f.reset)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.reset = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
|
|
tnetv_udc_handle_reset();
|
|
usb_core_bus_reset();
|
|
}
|
|
|
|
if (sysIntrStatus.f.suspend)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.suspend = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
}
|
|
|
|
if (sysIntrStatus.f.resume)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.resume = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
}
|
|
|
|
if (sysIntrStatus.f.vbus)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.vbus = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
|
|
if (*((uint32_t *) TNETV_USB_IF_STATUS) & 0x40)
|
|
{
|
|
/* write out connect bit */
|
|
reg = (volatile uint32_t *) TNETV_USB_CTRL;
|
|
*reg |= 0x80;
|
|
|
|
/* write to wakeup bit in clock config */
|
|
reg = (volatile uint32_t *) TNETV_V2USB_CLK_WKUP;
|
|
*reg |= TNETV_V2USB_CLK_WKUP_VBUS;
|
|
}
|
|
else
|
|
{
|
|
/* clear out connect bit */
|
|
reg = (volatile uint32_t *) TNETV_USB_CTRL;
|
|
*reg &= ~0x80;
|
|
}
|
|
}
|
|
|
|
if (sysIntrStatus.f.setup_ow)
|
|
{
|
|
sysIntrStatus.f.setup_ow = 0;
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.setup_ow = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
}
|
|
if (sysIntrStatus.f.setup)
|
|
{
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
static struct usb_ctrlrequest setup;
|
|
|
|
sysIntrStatus.f.setup = 0;
|
|
|
|
/* Copy setup packet into buffer */
|
|
tnetv_copy_from_data_mem(&setup, TNETV_EP_DATA_ADDR(EP0_OUTPKT_ADDRESS), sizeof(setup));
|
|
|
|
/* Determine next stage of the control message */
|
|
if (setup.bRequestType & USB_DIR_IN)
|
|
{
|
|
/* This is a control-read. Switch directions to send the response.
|
|
* set the dir bit before clearing the interrupt
|
|
*/
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usbCtrl.f.dir = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val);
|
|
}
|
|
else
|
|
{
|
|
/* This is a control-write. Remain using USB_DIR_OUT to receive the rest of the data.
|
|
* set the NAK bits according to supplement doc
|
|
*/
|
|
ep0Cnt.val = 0;
|
|
ep0Cnt.f.in_xbuf_nak = 1;
|
|
ep0Cnt.f.out_xbuf_nak = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
|
|
/* clear the dir bit before clearing the interrupt */
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usbCtrl.f.dir = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val);
|
|
}
|
|
|
|
/* Clear interrupt */
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.setup = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
|
|
if (((setup.bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) &&
|
|
(setup.bRequest == USB_REQ_SET_ADDRESS))
|
|
{
|
|
/* Rockbox USB core works according to USB specification, i.e.
|
|
* it first acknowledges the control transfer and then sets
|
|
* the address. However, Linux TNETV105 driver first sets the
|
|
* address and then acknowledges the transfer. At first,
|
|
* it seemed that Linux driver was wrong, but it seems that
|
|
* TNETV105 simply requires such order. It might be documented
|
|
* in the datasheet and thus there is no comment in the Linux
|
|
* driver about this.
|
|
*/
|
|
setup_is_set_address = true;
|
|
}
|
|
else
|
|
{
|
|
setup_is_set_address = false;
|
|
}
|
|
|
|
/* Process control packet */
|
|
usb_core_legacy_control_request(&setup);
|
|
}
|
|
|
|
if (sysIntrStatus.f.ep0_in_ack)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.ep0_in_ack = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
|
|
in_interrupt(0);
|
|
}
|
|
|
|
if (sysIntrStatus.f.ep0_out_ack)
|
|
{
|
|
sysIntClear.val = 0;
|
|
sysIntClear.f.ep0_out_ack = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val);
|
|
|
|
out_interrupt(0);
|
|
}
|
|
}
|
|
|
|
if (vlynq_intr & VLYNQ_INTR_CPPI)
|
|
{
|
|
static struct timeout cppi_timeout;
|
|
|
|
VL_INTST = VLYNQ_INTR_CPPI;
|
|
|
|
if (tnetv_handle_cppi())
|
|
{
|
|
timeout_register(&cppi_timeout, cppi_timeout_cb, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void usb_charging_maxcurrent_change(int maxcurrent)
|
|
{
|
|
uint32_t wreg;
|
|
|
|
if (!is_tnetv_reset_high())
|
|
{
|
|
/* TNETV105 is in reset, it is not getting more than 100 mA */
|
|
return;
|
|
}
|
|
|
|
wreg = tnetv_usb_reg_read(TNETV_V2USB_GPIO_DOUT);
|
|
if (maxcurrent < 500)
|
|
{
|
|
/* set tnetv into low power mode */
|
|
tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, (wreg & ~0x2));
|
|
}
|
|
else
|
|
{
|
|
/* set tnetv into high power mode */
|
|
tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, (wreg | 0x2));
|
|
}
|
|
}
|
|
|
|
void usb_drv_init(void)
|
|
{
|
|
int epn;
|
|
memset(ep_runtime, 0, sizeof(ep_runtime));
|
|
ep_runtime[0].max_packet_size = EP0_MAX_PACKET_SIZE;
|
|
ep_runtime[0].in_allocated = true;
|
|
ep_runtime[0].out_allocated = true;
|
|
for (epn = 0; epn < USB_NUM_ENDPOINTS; epn++)
|
|
{
|
|
semaphore_init(&ep_runtime[epn].complete, 1, 0);
|
|
}
|
|
tnetv_cppi_init(&cppi);
|
|
tnetv_udc_enable();
|
|
}
|
|
|
|
void usb_drv_exit(void)
|
|
{
|
|
tnetv_udc_disable();
|
|
tnetv_cppi_cleanup(&cppi);
|
|
}
|
|
|
|
void usb_drv_stall(int endpoint, bool stall, bool in)
|
|
{
|
|
int epn = EP_NUM(endpoint);
|
|
|
|
if (epn == 0)
|
|
{
|
|
UsbEp0CtrlType ep0Ctrl;
|
|
ep0Ctrl.val = tnetv_usb_reg_read(TNETV_USB_EP0_CFG);
|
|
if (in)
|
|
{
|
|
ep0Ctrl.f.in_stall = stall ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
ep0Ctrl.f.out_stall = stall ? 1 : 0;
|
|
}
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Ctrl.val);
|
|
}
|
|
else
|
|
{
|
|
UsbEpCfgCtrlType epCfg;
|
|
epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn));
|
|
if (in)
|
|
{
|
|
epCfg.f.in_stall = stall ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
epCfg.f.out_stall = stall ? 1 : 0;
|
|
}
|
|
tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val);
|
|
}
|
|
}
|
|
|
|
bool usb_drv_stalled(int endpoint, bool in)
|
|
{
|
|
int epn = EP_NUM(endpoint);
|
|
if (epn == 0)
|
|
{
|
|
UsbEp0CtrlType ep0Ctrl;
|
|
ep0Ctrl.val = tnetv_usb_reg_read(TNETV_USB_EP0_CFG);
|
|
if (in)
|
|
{
|
|
return ep0Ctrl.f.in_stall;
|
|
}
|
|
else
|
|
{
|
|
return ep0Ctrl.f.out_stall;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UsbEpCfgCtrlType epCfg;
|
|
epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn));
|
|
if (in)
|
|
{
|
|
return epCfg.f.in_stall;
|
|
}
|
|
else
|
|
{
|
|
return epCfg.f.out_stall;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int _usb_drv_send(int endpoint, void *ptr, int length, bool block)
|
|
{
|
|
int epn = EP_NUM(endpoint);
|
|
struct ep_runtime_t *ep;
|
|
int flags;
|
|
|
|
ep = &ep_runtime[epn];
|
|
|
|
flags = disable_irq_save();
|
|
ep->tx_buf = ptr;
|
|
ep->tx_remaining = ep->tx_size = length;
|
|
ep->block = block;
|
|
ep_write(epn);
|
|
restore_irq(flags);
|
|
|
|
/* wait for transfer to end */
|
|
if (block)
|
|
{
|
|
semaphore_wait(&ep->complete, TIMEOUT_BLOCK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int usb_drv_send(int endpoint, void* ptr, int length)
|
|
{
|
|
if ((EP_NUM(endpoint) == 0) && (length == 0))
|
|
{
|
|
if (setup_is_set_address)
|
|
{
|
|
/* usb_drv_set_address() will call us later */
|
|
return 0;
|
|
}
|
|
/* HACK: Do not wait for status stage ZLP
|
|
* This seems to be the only way to get through SET ADDRESS
|
|
* and retain ability to receive SETUP packets.
|
|
*/
|
|
return _usb_drv_send(endpoint, ptr, length, false);
|
|
}
|
|
return _usb_drv_send(endpoint, ptr, length, false);
|
|
}
|
|
|
|
int usb_drv_send_nonblocking(int endpoint, void* ptr, int length)
|
|
{
|
|
return _usb_drv_send(endpoint, ptr, length, false);
|
|
}
|
|
|
|
int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length)
|
|
{
|
|
int epn = EP_NUM(endpoint);
|
|
struct ep_runtime_t *ep;
|
|
int flags;
|
|
|
|
ep = &ep_runtime[epn];
|
|
|
|
flags = disable_irq_save();
|
|
ep->rx_buf = ptr;
|
|
ep->rx_remaining = ep->rx_size = length;
|
|
ep_read(epn);
|
|
restore_irq(flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usb_drv_set_address(int address)
|
|
{
|
|
UsbCtrlType usbCtrl;
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usbCtrl.f.func_addr = address;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val);
|
|
|
|
/* This seems to be the only working order */
|
|
setup_is_set_address = false;
|
|
usb_drv_send(EP_CONTROL, NULL, 0);
|
|
usb_drv_cancel_all_transfers();
|
|
}
|
|
|
|
/* return port speed FS=0, HS=1 */
|
|
int usb_drv_port_speed(void)
|
|
{
|
|
UsbCtrlType usbCtrl;
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
return usbCtrl.f.speed ? 1 : 0;
|
|
}
|
|
|
|
void usb_drv_cancel_all_transfers(void)
|
|
{
|
|
int epn;
|
|
if (setup_is_set_address)
|
|
{
|
|
return;
|
|
}
|
|
for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++)
|
|
{
|
|
tnetv_gadget_req_nuke(epn, false);
|
|
tnetv_gadget_req_nuke(epn, true);
|
|
}
|
|
}
|
|
|
|
static const uint8_t TestPacket[] =
|
|
{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
|
|
0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xDF,
|
|
0xEF, 0xF7, 0xFB, 0xFD, 0xFC, 0x7E, 0xBF, 0xDF,
|
|
0xEF, 0xF7, 0xFB, 0xFD, 0x7E
|
|
};
|
|
|
|
void usb_drv_set_test_mode(int mode)
|
|
{
|
|
UsbCtrlType usbCtrl;
|
|
if (mode == 4)
|
|
{
|
|
volatile uint32_t *reg;
|
|
UsbEp0ByteCntType ep0Cnt;
|
|
UsbEp0CtrlType ep0Cfg;
|
|
uint8_t *addr;
|
|
size_t i;
|
|
|
|
/* set up the xnak for ep0 */
|
|
reg = (volatile uint32_t *) TNETV_USB_EP0_CNT;
|
|
*reg &= ~0xFF;
|
|
|
|
/* Setup endpoint zero */
|
|
ep0Cfg.val = 0;
|
|
ep0Cfg.f.buf_size = EP0_BUF_SIZE_64; /* must be 64 bytes for USB 2.0 */
|
|
ep0Cfg.f.dbl_buf = 0;
|
|
ep0Cfg.f.in_en = 1;
|
|
ep0Cfg.f.in_int_en = 0;
|
|
ep0Cfg.f.out_en = 0;
|
|
ep0Cfg.f.out_int_en = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Cfg.val);
|
|
|
|
addr = (uint8_t *) TNETV_EP_DATA_ADDR(EP0_INPKT_ADDRESS);
|
|
for (i = 0; i < sizeof(TestPacket); i++)
|
|
{
|
|
*addr++ = TestPacket[i];
|
|
}
|
|
|
|
/* start xmitting (only 53 bytes are used) */
|
|
ep0Cnt.val = 0;
|
|
ep0Cnt.f.in_xbuf_cnt = 53;
|
|
ep0Cnt.f.in_xbuf_nak = 0;
|
|
tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val);
|
|
}
|
|
|
|
/* write the config */
|
|
usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL);
|
|
usbCtrl.f.hs_test_mode = mode;
|
|
usbCtrl.f.dir = 1;
|
|
tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val);
|
|
}
|
|
|
|
int usb_drv_request_endpoint(int type, int dir)
|
|
{
|
|
int epn;
|
|
for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++)
|
|
{
|
|
if (type == ep_const_data[epn].type)
|
|
{
|
|
if ((dir == USB_DIR_IN) && (!ep_runtime[epn].in_allocated))
|
|
{
|
|
ep_runtime[epn].in_allocated = true;
|
|
tnetv_gadget_ep_enable(epn, true);
|
|
return epn | USB_DIR_IN;
|
|
}
|
|
if ((dir == USB_DIR_OUT) && (!ep_runtime[epn].out_allocated))
|
|
{
|
|
ep_runtime[epn].out_allocated = true;
|
|
tnetv_gadget_ep_enable(epn, false);
|
|
return epn | USB_DIR_OUT;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void usb_drv_release_endpoint(int ep)
|
|
{
|
|
int epn = EP_NUM(ep);
|
|
if (EP_DIR(ep) == DIR_IN)
|
|
{
|
|
ep_runtime[epn].in_allocated = false;
|
|
tnetv_gadget_ep_disable(epn, true);
|
|
}
|
|
else
|
|
{
|
|
ep_runtime[epn].out_allocated = false;
|
|
tnetv_gadget_ep_disable(epn, false);
|
|
}
|
|
}
|