From 474293a12b6152041404378abd932ac495e5e18d Mon Sep 17 00:00:00 2001 From: Tomasz Moń Date: Sat, 5 Jun 2021 09:22:27 +0200 Subject: Sansa Connect: Initial TNETV105 driver port Port USB driver from Sansa Connect Linux kernel sources. The device successfully enumerates and responds to SCSI commands but actual disk access does not work. The SCSI response sent to host mentions that both internal storage and microsd card are not present. Change-Id: Ic6c07da12382c15c0b069f23a75f7df9765b7525 --- firmware/SOURCES | 2 + firmware/export/config.h | 6 +- firmware/export/config/sansaconnect.h | 11 +- .../arm/tms320dm320/sansa-connect/tnetv105_cppi.c | 1044 ++++++++++++++ .../arm/tms320dm320/sansa-connect/tnetv105_cppi.h | 144 ++ .../tms320dm320/sansa-connect/tnetv105_usb_drv.c | 1489 ++++++++++++++++++++ .../tms320dm320/sansa-connect/tnetv105_usb_drv.h | 335 +++++ .../tms320dm320/sansa-connect/usb-sansaconnect.c | 67 +- firmware/target/arm/tms320dm320/system-dm320.c | 7 + firmware/target/arm/tms320dm320/system-target.h | 1 + 10 files changed, 3049 insertions(+), 57 deletions(-) create mode 100644 firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c create mode 100644 firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.h create mode 100644 firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c create mode 100644 firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.h (limited to 'firmware') diff --git a/firmware/SOURCES b/firmware/SOURCES index 0d93439ff8..4c1fa7bf46 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -1264,6 +1264,8 @@ target/arm/tms320dm320/sansa-connect/lcd-sansaconnect.c target/arm/tms320dm320/sansa-connect/adc-sansaconnect.c target/arm/tms320dm320/sansa-connect/power-sansaconnect.c target/arm/tms320dm320/sansa-connect/powermgmt-sansaconnect.c +target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c +target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c target/arm/tms320dm320/sansa-connect/usb-sansaconnect.c target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c target/arm/tms320dm320/sansa-connect/backlight-sansaconnect.c diff --git a/firmware/export/config.h b/firmware/export/config.h index b758bef49d..0242045450 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -938,6 +938,9 @@ Lyre prototype 1 */ #define USB_DETECT_BY_REQUEST #elif CONFIG_USBOTG == USBOTG_RK27XX #define USB_DETECT_BY_REQUEST +#elif CONFIG_USBOTG == USBOTG_TNETV105 +#define USB_STATUS_BY_EVENT +#define USB_DETECT_BY_REQUEST #endif /* CONFIG_USB == */ #endif /* HAVE_USBSTACK */ @@ -1171,7 +1174,8 @@ Lyre prototype 1 */ (CONFIG_USBOTG == USBOTG_M66591) || \ (CONFIG_USBOTG == USBOTG_DESIGNWARE) || \ (CONFIG_USBOTG == USBOTG_AS3525) || \ - (CONFIG_USBOTG == USBOTG_RK27XX) + (CONFIG_USBOTG == USBOTG_RK27XX) || \ + (CONFIG_USBOTG == USBOTG_TNETV105) #define USB_HAS_BULK #define USB_HAS_INTERRUPT #elif defined(CPU_TCC780X) diff --git a/firmware/export/config/sansaconnect.h b/firmware/export/config/sansaconnect.h index 465a576664..5ae2be1b16 100644 --- a/firmware/export/config/sansaconnect.h +++ b/firmware/export/config/sansaconnect.h @@ -182,11 +182,18 @@ /* Offset ( in the firmware file's header ) to the real data */ #define FIRMWARE_OFFSET_FILE_DATA 8 -#if 0 +/* Hardware controlled charging */ +#define CONFIG_CHARGING CHARGING_SIMPLE + +#define CONFIG_USBOTG USBOTG_TNETV105 + #define HAVE_USBSTACK +#define HAVE_USB_POWER +#define HAVE_USB_CHARGING_ENABLE +#define HAVE_BOOTLOADER_USB_MODE #define USB_VENDOR_ID 0x0781 #define USB_PRODUCT_ID 0x7480 -#endif +#define USB_NUM_ENDPOINTS 5 #define INCLUDE_TIMEOUT_API diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c new file mode 100644 index 0000000000..93477bed9e --- /dev/null +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c @@ -0,0 +1,1044 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2021 by Tomasz Moń + * Copied with minor modifications from Sansa Connect Linux driver + * Copyright (c) 2005,2006 Zermatt Systems, Inc. + * Written by: Ben Bostwick + * + * 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 +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "panic.h" +#include "logf.h" +#include "tnetv105_usb_drv.h" +#include "tnetv105_cppi.h" + +/* This file is pretty much directly copied from the Sansa Connect + * Linux kernel source code. This is because the functionality is + * nicely separated from actual kernel specific code and CPPI seems + * complex (atleast without access to the datasheet). + * + * The only non cosmetic change was changing the dynamic allocations + * to static allocations. + * + * It seems that the only way to get interrupt on non-control endpoint + * acticity is to use the CPPI. This sounds like a plausible explanation + * for the fake DMA buffers mentioned in CPPI code. + */ + +/* Translate Linux consistent_sync() to Rockbox functions */ +#define DMA_TO_DEVICE commit_discard_dcache_range +#define DMA_FROM_DEVICE discard_dcache_range +#define consistent_sync(ptr, size, func) func(ptr, size) +/* Rockbox TMS320DM320 crt0.S maps everything to itself */ +#define __virt_to_phys(ptr) ptr +#define __phys_to_virt(ptr) ptr + +// CPPI functions +#define CB_SOF_BIT (1<<31) +#define CB_EOF_BIT (1<<30) +#define CB_OWNERSHIP_BIT (1<<29) +#define CB_EOQ_BIT (1<<28) +#define CB_ZLP_GARBAGE (1<<23) +#define CB_SIZE_MASK 0x0000ffff +#define CB_OFFSET_MASK 0xffff0000 +#define TEARDOWN_VAL 0xfffffffc + +#define MAX_BUF_SIZE 512 + +#define CPPI_DMA_RX_BUF_SIZE (MAX_BUF_SIZE * CPPI_RX_NUM_BUFS) + +static uint8_t *dma_recv_buf[CPPI_NUM_CHANNELS]; +static uint8_t ch0_rx_buf[CPPI_DMA_RX_BUF_SIZE]; +static uint8_t ch1_rx_buf[CPPI_DMA_RX_BUF_SIZE]; + +#if USB_CPPI_LOGGING +#define cppi_log_event0(msg) usb_log_event(msg, LOG_EVENT_D0, 0, 0, 0, 0) +#define cppi_log_event1(msg, data0) usb_log_event(msg, LOG_EVENT_D1, data0, 0, 0, 0) +#define cppi_log_event2(msg, data0, data1) usb_log_event(msg, LOG_EVENT_D2, data0, data1, 0, 0) +#define cppi_log_event3(msg, data0, data1, data2) usb_log_event(msg, LOG_EVENT_D3, data0, data1, data2, 0) +#define cppi_log_event4(msg, data0, data1, data2, data3) usb_log_event(msg, LOG_EVENT_D4, data0, data1, data2, data3) +#else +#define cppi_log_event0(x) +#define cppi_log_event1(x, y) +#define cppi_log_event2(x, y, z) +#define cppi_log_event3(x, y, z, w) +#define cppi_log_event4(x, y, z, w, u) +#endif + +/* + * This function processes transmit interrupts. It traverses the + * transmit buffer queue, detecting sent data buffers + * + * @return 0 if OK, non-zero otherwise. + */ +int tnetv_cppi_tx_int(struct cppi_info *cppi, int ch) +{ + cppi_tcb *CurrentTcb,*LastTcbProcessed; + uint32_t TxFrameStatus; + cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; + int bytes_sent = 0; + + cppi_log_event1("[cppi]TxInt ch", ch); + + CurrentTcb = pTxCtl->TxActQueueHead; + + if (CurrentTcb == 0) + { + cppi_log_event0("[cppi] tx int: no current tcb"); + return -1; + } + + // sync up the tcb from memory + consistent_sync(CurrentTcb, sizeof(*CurrentTcb), DMA_FROM_DEVICE); + + TxFrameStatus = CurrentTcb->mode; + LastTcbProcessed = NULL; + + cppi_log_event3("[cppi] int tcb status", (uint32_t) CurrentTcb, TxFrameStatus, CurrentTcb->Off_BLen); + + while(CurrentTcb && (TxFrameStatus & CB_OWNERSHIP_BIT) == 0) + { + cppi_log_event3("[cppi] tx int: tcb (mode) (len)", (uint32_t) CurrentTcb, CurrentTcb->mode, CurrentTcb->Off_BLen); + + // calculate the amount of bytes sent. + // don't count the fake ZLP byte + if (CurrentTcb->Off_BLen > 0x1) + { + bytes_sent += CurrentTcb->Off_BLen & 0xFFFF; + } + + if (CurrentTcb->mode & CB_EOQ_BIT) + { + if (CurrentTcb->Next) + { + cppi_log_event0(" [cppi] misqueue!"); + + // Misqueued packet + tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, TNETV_CPPI_TX_WORD_HDP), CurrentTcb->HNext); + } + else + { + cppi_log_event0("[cppi] eoq"); + + /* Tx End of Queue */ + pTxCtl->TxActive = 0; + } + } + + cppi_log_event1("[cppi]SendComplete: ", CurrentTcb->Off_BLen & 0xFFFF); + + // Write the completion pointer + tnetv_usb_reg_write(TNETV_DMA_TX_CMPL(ch), __dma_to_vlynq_phys(CurrentTcb->dma_handle)); + + + LastTcbProcessed = CurrentTcb; + CurrentTcb = CurrentTcb->Next; + + // clean up TCB fields + LastTcbProcessed->HNext = 0; + LastTcbProcessed->Next = 0; + LastTcbProcessed->BufPtr = 0; + LastTcbProcessed->Off_BLen = 0; + LastTcbProcessed->mode = 0; + LastTcbProcessed->Eop = 0; + + /* Push Tcb(s) back onto the list */ + if (pTxCtl->TcbPool) + { + LastTcbProcessed->Next = pTxCtl->TcbPool->Next; + pTxCtl->TcbPool->Next = LastTcbProcessed; + } + else + { + pTxCtl->TcbPool = LastTcbProcessed; + } + + consistent_sync(LastTcbProcessed, sizeof(*LastTcbProcessed), DMA_TO_DEVICE); + + // get the status of the next packet + if (CurrentTcb) + { + // sync up the tcb from memory + consistent_sync(CurrentTcb, sizeof(*CurrentTcb), DMA_FROM_DEVICE); + + TxFrameStatus = CurrentTcb->mode; + } + + + } + + pTxCtl->TxActQueueHead = CurrentTcb; + + if (!LastTcbProcessed) + { + cppi_log_event1(" [cppi]No Tx packets serviced on int! ch", ch); + return -1; + } + + return bytes_sent; +} + +int tnetv_cppi_flush_tx_queue(struct cppi_info *cppi, int ch) +{ + cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; + cppi_tcb *tcb, *next_tcb; + + tcb = pTxCtl->TxActQueueHead; + + cppi_log_event1("[cppi] flush TX ", (uint32_t) pTxCtl->TxActQueueHead); + + while (tcb) + { + tcb->mode = 0; + tcb->BufPtr = 0; + tcb->Off_BLen = 0; + tcb->Eop = 0; + tcb->HNext = 0; + + next_tcb = tcb->Next; + + tcb->Next = pTxCtl->TcbPool; + pTxCtl->TcbPool = tcb; + + tcb = next_tcb; + } + + pTxCtl->TxActQueueHead = 0; + pTxCtl->TxActQueueTail = 0; + pTxCtl->TxActive = 0; + + return 0; +} + + +/** + * @ingroup CPHAL_Functions + * This function transmits the data in FragList using available transmit + * buffer descriptors. More information on the use of the Mode parameter + * is available in the module-specific appendices. Note: The OS should + * not call Send() for a channel that has been requested to be torndown. + * + */ +int tnetv_cppi_send(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length, int send_zlp) +{ + cppi_txcntl *pTxCtl; + cppi_tcb *first_tcb; + cppi_tcb *tcb; + int queued_len; + dma_addr_t buf_to_send; + dma_addr_t buf_ptr; + int total_len = length; + int pktlen; + + pTxCtl = &cppi->tx_ctl[ch]; + + if (length == 0) + { + cppi_log_event0("[cppi] len = 0, nothing to send"); + return -1; + } + + // no send buffers.. try again later + if (!pTxCtl->TcbPool) + { + cppi_log_event0("[cppi] out of cppi buffers"); + return -1; + } + + // only send 1 packet at a time + if (pTxCtl->TxActQueueHead || pTxCtl->TxActive) + { + cppi_log_event0("[cppi] already sending!"); + return -1; + } + + buf_to_send = buf; + + // usb_requests can have a 32 bit length, but CPPI DMA fragments + // have a (64k - 1) limit. Split the usb_request up into fragments here. + first_tcb = pTxCtl->TcbPool; + tcb = first_tcb; + + cppi_log_event4("[cppi]cppi_send (buf) (len) (pool) (dma)", (uint32_t) buf_to_send, total_len, (uint32_t) first_tcb, first_tcb->dma_handle); + + queued_len = 0; + + do + { + buf_ptr = buf_to_send + queued_len; + tcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); + tcb->HNext = 0; + + // can't transfer more that 64k-1 bytes in 1 CPPI transfer + // need to queue up transfers if it's greater than that + pktlen = ((total_len - queued_len) > CPPI_MAX_FRAG) ? CPPI_MAX_FRAG : (total_len - queued_len); + tcb->Off_BLen = pktlen; + tcb->mode = (CB_OWNERSHIP_BIT | CB_SOF_BIT | CB_EOF_BIT | pktlen); + + queued_len += pktlen; + + if (queued_len < total_len) + { + tcb->HNext = __dma_to_vlynq_phys(((cppi_tcb *) tcb->Next)->dma_handle); + + // write out the buffer to memory + consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); + + cppi_log_event4("[cppi] q tcb", (uint32_t) tcb, ((uint32_t *) tcb)[0], ((uint32_t *) tcb)[1], ((uint32_t *) tcb)[2]); + cppi_log_event4("[cppi] ", ((uint32_t *) tcb)[3], ((uint32_t *) tcb)[4], ((uint32_t *) tcb)[5], ((uint32_t *) tcb)[6]); + + tcb = tcb->Next; + } + } while (queued_len < total_len); + + /* In the Tx Interrupt handler, we will need to know which TCB is EOP, + so we can save that information in the SOP */ + first_tcb->Eop = tcb; + + // set the secret ZLP bit if necessary, this will be a completely separate packet + if (send_zlp) + { +#if defined(AUTO_ZLP) && AUTO_ZLP + // add an extra buffer at the end to hold the ZLP + tcb->HNext = __dma_to_vlynq_phys(((cppi_tcb *) tcb->Next)->dma_handle); + + // write out the buffer to memory + consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); + + tcb = tcb->Next; + + /* In the Tx Interrupt handler, we will need to know which TCB is EOP, + so we can save that information in the SOP */ + first_tcb->Eop = tcb; +#endif + + buf_ptr = buf_to_send + queued_len; + tcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); // not used, but can't be zero + tcb->HNext = 0; + tcb->Off_BLen = 0x1; // device will send (((len - 1) / maxpacket) + 1) ZLPs + tcb->mode = (CB_SOF_BIT | CB_EOF_BIT | CB_OWNERSHIP_BIT | CB_ZLP_GARBAGE | 0x1); // send 1 ZLP + tcb->Eop = tcb; + + cppi_log_event0("[cppi] Send ZLP!"); + } + + pTxCtl->TcbPool = tcb->Next; + + tcb->Next = 0; + tcb->HNext = 0; + + // write out the buffer to memory + consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); + + cppi_log_event4("[cppi] q tcb", (uint32_t) tcb, ((uint32_t *) tcb)[0], ((uint32_t *) tcb)[1], ((uint32_t *) tcb)[2]); + cppi_log_event4("[cppi] ", ((uint32_t *) tcb)[3], ((uint32_t *) tcb)[4], ((uint32_t *) tcb)[5], ((uint32_t *) tcb)[6]); + + cppi_log_event4("[cppi] send queued (ptr) (len) (ftcb, ltcb)", (uint32_t) tcb->BufPtr, tcb->Off_BLen, (uint32_t) first_tcb, (uint32_t) tcb); + + /* put it on the queue */ + pTxCtl->TxActQueueHead = first_tcb; + pTxCtl->TxActQueueTail = tcb; + + cppi_log_event3("[cppi] setting state (head) (virt) (next)", (uint32_t) first_tcb, __dma_to_vlynq_phys(first_tcb->dma_handle), (uint32_t) first_tcb->HNext); + + /* write CPPI TX HDP - cache is cleaned above */ + tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, TNETV_CPPI_TX_WORD_HDP), __dma_to_vlynq_phys(first_tcb->dma_handle)); + + pTxCtl->TxActive = 1; + + return 0; +} + +/* + * This function allocates transmit buffer descriptors (internal CPHAL function). + * It creates a high priority transmit queue by default for a single Tx + * channel. If QoS is enabled for the given CPHAL device, this function + * will also allocate a low priority transmit queue. + * + * @return 0 OK, Non-Zero Not OK + */ +int tnetv_cppi_init_tcb(struct cppi_info *cppi, int ch) +{ + int i, num; + cppi_tcb *pTcb = 0; + char *AllTcb; + int tcbSize; + cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; + + num = pTxCtl->TxNumBuffers; + tcbSize = (sizeof(cppi_tcb) + 0xf) & ~0xf; + + cppi_log_event4("[cppi] init_tcb (ch) (num) (dma) (tcbsz)", ch, num, pTxCtl->tcb_start_dma_addr, tcbSize); + + if (pTxCtl->TxNumBuffers == 0) + { + return -1; + } + + /* if the memory has already been allocated, simply reuse it! */ + AllTcb = pTxCtl->TcbStart; + + // now reinitialize the TCB pool + pTxCtl->TcbPool = 0; + for (i = 0; i < num; i++) + { + pTcb = (cppi_tcb *)(AllTcb + (i * tcbSize)); + pTcb->dma_handle = pTxCtl->tcb_start_dma_addr + (i * tcbSize); + + pTcb->BufPtr = 0; + pTcb->mode = 0; + pTcb->HNext = 0; + pTcb->Off_BLen = 0; + pTcb->Eop = 0; + + pTcb->Next = (void *) pTxCtl->TcbPool; + + pTxCtl->TcbPool = pTcb; + } + + cppi_log_event2(" [cppi]TcbPool", (uint32_t) pTxCtl->TcbPool, pTxCtl->TcbPool->dma_handle); + +#if USB_CPPI_LOGGING + { + // BEN DEBUG + cppi_tcb *first_tcb = pTxCtl->TcbPool; + cppi_log_event4("[cppi] init tcb", (uint32_t) first_tcb, ((uint32_t *) first_tcb)[0], ((uint32_t *) first_tcb)[1], ((uint32_t *) first_tcb)[2]); + cppi_log_event4("[cppi] ", ((uint32_t *) first_tcb)[3], ((uint32_t *) first_tcb)[4], ((uint32_t *) first_tcb)[5], ((uint32_t *) first_tcb)[6]); + } +#endif + + return 0; +} + +// BEN DEBUG +void tnetv_cppi_dump_info(struct cppi_info *cppi) +{ + int ch; + cppi_rxcntl *pRxCtl; + cppi_txcntl *pTxCtl; + cppi_tcb *tcb; + cppi_rcb *rcb; + + logf("CPPI struct:\n"); + logf("Buf mem: %x Buf size: %d int: %x %x\n\n", (uint32_t) cppi->dma_mem, cppi->dma_size, tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS), tnetv_usb_reg_read(DM320_VLYNQ_INTST)); + + for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) + { + pRxCtl = &cppi->rx_ctl[ch]; + pTxCtl = &cppi->tx_ctl[ch]; + + logf("ch: %d\n", ch); + logf(" rx_numbufs: %d active %d free_buf_cnt %d\n", pRxCtl->RxNumBuffers, pRxCtl->RxActive, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); + logf(" q_cnt %d head %x tail %x\n", pRxCtl->RxActQueueCount, (uint32_t) pRxCtl->RxActQueueHead, (uint32_t) pRxCtl->RxActQueueTail); + logf(" fake_head: %x fake_tail: %x\n", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); + + rcb = (cppi_rcb *) pRxCtl->RcbStart; + do + { + if (!rcb) + break; + + logf(" Rcb: %x\n", (uint32_t) rcb); + logf(" HNext %x BufPtr %x Off_BLen %x mode %x\n", rcb->HNext, rcb->BufPtr, rcb->Off_BLen, rcb->mode); + logf(" Next %x Eop %x dma_handle %x fake_bytes %x\n", (uint32_t) rcb->Next, (uint32_t) rcb->Eop, rcb->dma_handle, rcb->fake_bytes); + rcb = rcb->Next; + + } while (rcb && rcb != (cppi_rcb *) pRxCtl->RcbStart); + + logf("\n"); + logf(" tx_numbufs: %d active %d\n", pTxCtl->TxNumBuffers, pTxCtl->TxActive); + logf(" q_cnt %d head %x tail %x\n", pTxCtl->TxActQueueCount, (uint32_t) pTxCtl->TxActQueueHead, (uint32_t) pTxCtl->TxActQueueTail); + + tcb = (cppi_tcb *) pTxCtl->TcbPool; + do + { + if (!tcb) + break; + + logf(" Tcb (pool): %x\n", (uint32_t) tcb); + logf(" HNext %x BufPtr %x Off_BLen %x mode %x\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode); + logf(" Next %x Eop %x dma_handle %x\n", (uint32_t) tcb->Next, (uint32_t) tcb->Eop, tcb->dma_handle); + tcb = tcb->Next; + + } while (tcb && tcb != (cppi_tcb *) pTxCtl->TcbPool); + + tcb = (cppi_tcb *) pTxCtl->TxActQueueHead; + do + { + if (!tcb) + break; + + logf(" Tcb (act): %x\n", (uint32_t) tcb); + logf(" HNext %x BufPtr %x Off_BLen %x mode %x\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode); + logf(" Next %x Eop %x dma_handle %x\n", (uint32_t) tcb->Next, (uint32_t) tcb->Eop, tcb->dma_handle); + tcb = tcb->Next; + + } while (tcb && tcb != (cppi_tcb *) pTxCtl->TxActQueueTail); + + } +} + +/** + * + * This function is called to indicate to the CPHAL that the upper layer + * software has finished processing the receive data (given to it by + * osReceive()). The CPHAL will then return the appropriate receive buffers + * and buffer descriptors to the available pool. + * + */ +int tnetv_cppi_rx_return(struct cppi_info *cppi, int ch, cppi_rcb *done_rcb) +{ + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + cppi_rcb *curRcb, *lastRcb, *endRcb; + int num_bufs = 0; + + if (!done_rcb) + return -1; + + //cppi_log_event3("[cppi] rx_return (last) (first) bufinq", (uint32_t) done_rcb, (uint32_t) done_rcb->Eop, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); + + curRcb = done_rcb; + endRcb = done_rcb->Eop; + do + { + curRcb->mode = CB_OWNERSHIP_BIT; + curRcb->Off_BLen = MAX_BUF_SIZE; + curRcb->Eop = 0; + + pRxCtl->RxActQueueCount++; + num_bufs++; + + lastRcb = curRcb; + curRcb = lastRcb->Next; + + consistent_sync(lastRcb, sizeof(*lastRcb), DMA_TO_DEVICE); + + } while (lastRcb != endRcb); + + cppi_log_event1("[cppi] rx_return done", num_bufs); + + // let the hardware know about the buffer(s) + tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), num_bufs); + + return 0; +} + +int tnetv_cppi_rx_int_recv(struct cppi_info *cppi, int ch, int *buf_size, void *buf, int maxpacket) +{ + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + cppi_rcb *CurrentRcb, *LastRcb = 0, *SopRcb; + uint8_t *cur_buf_data_addr; + int cur_buf_bytes; + int copy_buf_size = *buf_size; + int ret = -EAGAIN; + + *buf_size = 0; + + CurrentRcb = pRxCtl->RxFakeRcvHead; + if (!CurrentRcb) + { + cppi_log_event2("[cppi] rx_int recv: nothing in q", tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS), tnetv_usb_reg_read(DM320_VLYNQ_INTST)); + return -1; + } + + cppi_log_event1("[cppi] rx_int recv (ch)", ch); + cppi_log_event4(" [cppi] recv - Processing SOP descriptor fb hd tl", (uint32_t) CurrentRcb, CurrentRcb->fake_bytes, (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); + + SopRcb = CurrentRcb; + LastRcb = 0; + + do + { + // convert from vlynq phys to virt + cur_buf_data_addr = (uint8_t *) __vlynq_phys_to_dma(CurrentRcb->BufPtr); + cur_buf_data_addr = (uint8_t *) __phys_to_virt(cur_buf_data_addr); + cur_buf_bytes = (CurrentRcb->mode) & CB_SIZE_MASK; + + // make sure we don't overflow the buffer. + if (cur_buf_bytes > copy_buf_size) + { + ret = 0; + break; + } + + // BEN - packet can be ZLP + if (cur_buf_bytes) + { + consistent_sync(cur_buf_data_addr, MAX_BUF_SIZE, DMA_FROM_DEVICE); + + memcpy((buf + *buf_size), cur_buf_data_addr, cur_buf_bytes); + + copy_buf_size -= cur_buf_bytes; + *buf_size += cur_buf_bytes; + CurrentRcb->fake_bytes -= cur_buf_bytes; + } + else + { + CurrentRcb->fake_bytes = 0; + } + + cppi_log_event4(" [cppi] bytes totrcvd amtleft fake", cur_buf_bytes, *buf_size, copy_buf_size, CurrentRcb->fake_bytes); + + LastRcb = CurrentRcb; + CurrentRcb = LastRcb->Next; + + // sync out fake bytes info + consistent_sync(LastRcb, sizeof(*LastRcb), DMA_TO_DEVICE); + + // make sure each packet processed individually + if (cur_buf_bytes < maxpacket) + { + ret = 0; + break; + } + + } while (LastRcb != pRxCtl->RxFakeRcvTail && CurrentRcb->fake_bytes && copy_buf_size > 0); + + // make sure that the CurrentRcb isn't in the cache + consistent_sync(CurrentRcb, sizeof(*CurrentRcb), DMA_FROM_DEVICE); + + if (copy_buf_size == 0) + { + ret = 0; + } + + if (LastRcb) + { + SopRcb->Eop = LastRcb; + + cppi_log_event3(" [cppi] rcv end", *buf_size, (uint32_t) CurrentRcb, (uint32_t) SopRcb->Eop); + + if (LastRcb == pRxCtl->RxFakeRcvTail) + { + pRxCtl->RxFakeRcvHead = 0; + pRxCtl->RxFakeRcvTail = 0; + } + else + { + pRxCtl->RxFakeRcvHead = CurrentRcb; + } + + cppi_log_event1(" [cppi] st rx return", ch); + cppi_log_event2(" rcv fake hd tl", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); + + // all done, clean up the RCBs + tnetv_cppi_rx_return(cppi, ch, SopRcb); + } + + return ret; +} + +/* + * This function processes receive interrupts. It traverses the receive + * buffer queue, extracting the data and passing it to the upper layer software via + * osReceive(). It handles all error conditions and fragments without valid data by + * immediately returning the RCB's to the RCB pool. + */ +int tnetv_cppi_rx_int(struct cppi_info *cppi, int ch) +{ + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + cppi_rcb *CurrentRcb, *LastRcb = 0, *SopRcb; + uint32_t RxBufStatus,PacketsServiced; + int TotalFrags; + + cppi_log_event1("[cppi] rx_int (ch)", ch); + + CurrentRcb = pRxCtl->RxActQueueHead; + + if (!CurrentRcb) + { + cppi_log_event1("[cppi] rx_int no bufs!", (uint32_t) CurrentRcb); + return -1; + } + + // make sure that all of the buffers get an invalidated cache + consistent_sync(pRxCtl->RcbStart, sizeof(cppi_rcb) * CPPI_RX_NUM_BUFS, DMA_FROM_DEVICE); + + RxBufStatus = CurrentRcb->mode; + PacketsServiced = 0; + + cppi_log_event4("[cppi] currentrcb, mode numleft fake", (uint32_t) CurrentRcb, CurrentRcb->mode, pRxCtl->RxActQueueCount, CurrentRcb->fake_bytes); + cppi_log_event4("[cppi]", ((uint32_t *) CurrentRcb)[0], ((uint32_t *) CurrentRcb)[1], ((uint32_t *) CurrentRcb)[2], ((uint32_t *) CurrentRcb)[3]); + + while(((RxBufStatus & CB_OWNERSHIP_BIT) == 0) && (pRxCtl->RxActQueueCount > 0)) + { + cppi_log_event2(" [cppi]Processing SOP descriptor st", (uint32_t) CurrentRcb, RxBufStatus); + + SopRcb = CurrentRcb; + + TotalFrags = 0; + + do + { + TotalFrags++; + PacketsServiced++; + + // Write the completion pointer + tnetv_usb_reg_write(TNETV_DMA_RX_CMPL(ch), __dma_to_vlynq_phys(CurrentRcb->dma_handle)); + + CurrentRcb->fake_bytes = (CurrentRcb->mode) & 0xFFFF; + + // BEN - make sure this gets marked! + if (!CurrentRcb->fake_bytes || (CurrentRcb->mode & CB_ZLP_GARBAGE)) + { + CurrentRcb->mode &= 0xFFFF0000; + CurrentRcb->fake_bytes = 0x10000; + } + + cppi_log_event1(" fake_bytes:", CurrentRcb->fake_bytes); + + RxBufStatus = CurrentRcb->mode; + LastRcb = CurrentRcb; + CurrentRcb = LastRcb->Next; + + // sync the fake_bytes value back to mem + consistent_sync(LastRcb, sizeof(*LastRcb), DMA_TO_DEVICE); + + } while (((CurrentRcb->mode & CB_OWNERSHIP_BIT) == 0) && ((RxBufStatus & CB_EOF_BIT) == 0)); + + SopRcb->Eop = LastRcb; + + pRxCtl->RxActQueueHead = CurrentRcb; + pRxCtl->RxActQueueCount -= TotalFrags; + + if (LastRcb->mode & CB_EOQ_BIT) + { + if (CurrentRcb) + { + cppi_log_event1(" [cppi] rcv done q next", LastRcb->HNext); + tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, TNETV_CPPI_RX_WORD_HDP), LastRcb->HNext); + } + else + { + cppi_log_event0(" [cppi] rcv done"); + + pRxCtl->RxActive = 0; + } + } + + // BEN - add to the list of buffers we need to deal with + if (!pRxCtl->RxFakeRcvHead) + { + pRxCtl->RxFakeRcvHead = SopRcb; + pRxCtl->RxFakeRcvTail = SopRcb->Eop; + } + else + { + pRxCtl->RxFakeRcvTail = SopRcb->Eop; + } + + // make sure we have enough buffers + cppi_log_event1(" nextrcb", CurrentRcb->mode); + + if (CurrentRcb) + { + // continue the loop + RxBufStatus = CurrentRcb->mode; + } + + } /* while */ + + cppi_log_event2("[cppi] fake hd tl", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); + + // sync out all buffers before leaving + consistent_sync(pRxCtl->RcbStart, (CPPI_RX_NUM_BUFS * sizeof(cppi_rcb)), DMA_FROM_DEVICE); + + return PacketsServiced; +} + +static void tnetv_cppi_rx_queue_init(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length) +{ + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + cppi_rcb *rcb, *first_rcb; + unsigned int queued_len = 0; + int rcblen; + int num_frags = 0; + dma_addr_t buf_ptr; + + if (length == 0) + { + cppi_log_event0("[cppi] len = 0, nothing to recv"); + return; + } + + // usb_requests can have a 32 bit length, but CPPI DMA fragments + // have a 64k limit. Split the usb_request up into fragments here. + first_rcb = pRxCtl->RcbPool; + rcb = first_rcb; + + cppi_log_event2("[cppi] Rx queue add: head len", (uint32_t) first_rcb, length); + + while (queued_len < length) + { + buf_ptr = buf + queued_len; + rcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); + + rcb->HNext = 0; + rcb->mode = CB_OWNERSHIP_BIT; + + rcblen = ((length - queued_len) > MAX_BUF_SIZE) ? MAX_BUF_SIZE : (length - queued_len); + rcb->Off_BLen = rcblen; + + queued_len += rcblen; + if (queued_len < length) + { + rcb->HNext = __dma_to_vlynq_phys(((cppi_rcb *) (rcb->Next))->dma_handle); + rcb = rcb->Next; + } + + num_frags++; + } + + pRxCtl->RcbPool = rcb->Next; + rcb->Next = 0; + + cppi_log_event4("[cppi] Adding Rcb (dma) (paddr) (buf)", (uint32_t) rcb, rcb->dma_handle, __dma_to_vlynq_phys(rcb->dma_handle), (uint32_t) rcb->BufPtr); + cppi_log_event4("[cppi] Next HNext (len) of (total)", (uint32_t) rcb->Next, rcb->HNext, queued_len, length); + + pRxCtl->RxActQueueCount += num_frags; + + cppi_log_event4("[cppi] rx queued (ptr) (len) (ftcb, ltcb)", (uint32_t) rcb->BufPtr, rcb->Off_BLen, (uint32_t) first_rcb, (uint32_t) rcb); + cppi_log_event2(" [cppi] mode num_frags", rcb->mode, num_frags); + + pRxCtl->RxActQueueHead = first_rcb; + pRxCtl->RxActQueueTail = rcb; + + cppi_log_event2("[cppi] setting rx (head) (virt)", (uint32_t) first_rcb, __dma_to_vlynq_phys(first_rcb->dma_handle)); + cppi_log_event4("[cppi] ", ((uint32_t *) first_rcb)[0], ((uint32_t *) first_rcb)[1], ((uint32_t *) first_rcb)[2], ((uint32_t *) first_rcb)[3]); + + // make this into a circular buffer so we never get caught with + // no free buffers left + rcb->Next = pRxCtl->RxActQueueHead; + rcb->HNext = (uint32_t) (__dma_to_vlynq_phys(pRxCtl->RxActQueueHead->dma_handle)); +} + +int tnetv_cppi_rx_queue_add(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length) +{ + (void)buf; + (void)length; + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + unsigned int cur_bufs; + + cur_bufs = tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)); + + if (!pRxCtl->RxActive) + { + cppi_log_event0("[cppi] queue add - not active"); + + pRxCtl->RcbPool = (cppi_rcb *) pRxCtl->RcbStart; + + // add all the buffers to the active (circular) queue + tnetv_cppi_rx_queue_init(cppi, ch, (dma_addr_t) __virt_to_phys(dma_recv_buf[ch]), (MAX_BUF_SIZE * pRxCtl->RxNumBuffers)); + + /* write Rx Queue Head Descriptor Pointer */ + tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, TNETV_CPPI_RX_WORD_HDP), __dma_to_vlynq_phys(pRxCtl->RxActQueueHead->dma_handle)); + + pRxCtl->RxActive = 1; + + // sync out all buffers before starting + consistent_sync(pRxCtl->RcbStart, (CPPI_RX_NUM_BUFS * sizeof(cppi_rcb)), DMA_TO_DEVICE); + + // sync out temp rx buffer + consistent_sync(dma_recv_buf[ch], CPPI_DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + + if (cur_bufs < pRxCtl->RxActQueueCount) + { + // let the hardware know about the buffer(s) + tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), pRxCtl->RxActQueueCount - cur_bufs); + } + } + + cppi_log_event3("[cppi] rx add: (cur_bufs) (avail_bufs) (now)", cur_bufs, pRxCtl->RxActQueueCount, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); + + return 0; +} + +int tnetv_cppi_flush_rx_queue(struct cppi_info *cppi, int ch) +{ + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + cppi_rcb *rcb; + int num_bufs; + + cppi_log_event1("[cppi] flush RX ", (uint32_t) pRxCtl->RxActQueueHead); + + // flush out any pending receives + tnetv_cppi_rx_int(cppi, ch); + + // now discard all received data + rcb = pRxCtl->RxFakeRcvHead; + + if (rcb) + { + rcb->Eop = pRxCtl->RxFakeRcvTail; + + // clean up any unreceived RCBs + tnetv_cppi_rx_return(cppi, ch, rcb); + } + + pRxCtl->RxFakeRcvHead = 0; + pRxCtl->RxFakeRcvTail = 0; + + pRxCtl->RxActive = 0; + + // drain the HW free buffer count + num_bufs = tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)); + tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), -num_bufs); + + cppi_log_event2("[cppi] flush RX queue done (freed) act: ", num_bufs, (uint32_t) pRxCtl->RxActQueueCount); + + return 0; +} + + +/* + * This function allocates receive buffer descriptors (internal CPHAL function). + * After allocation, the function 'queues' (gives to the hardware) the newly + * created receive buffers to enable packet reception. + * + * @param ch Channel number. + * + * @return 0 OK, Non-Zero Not OK + */ +int tnetv_cppi_init_rcb(struct cppi_info *cppi, int ch) +{ + int i, num; + cppi_rcb *pRcb; + char *AllRcb; + int rcbSize; + cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; + + num = pRxCtl->RxNumBuffers; + rcbSize = (sizeof(cppi_rcb) + 0xf) & ~0xf; + + cppi_log_event2("[cppi] init_rcb ch num", ch, num); + + if (pRxCtl->RxNumBuffers == 0) + { + return -1; + } + + /* if the memory has already been allocated, simply reuse it! */ + AllRcb = pRxCtl->RcbStart; + + // now reinitialize the RCB pool + pRxCtl->RcbPool = 0; + for (i = (num - 1); i >= 0; i--) + { + pRcb = (cppi_rcb *)(AllRcb + (i * rcbSize)); + + pRcb->dma_handle = pRxCtl->rcb_start_dma_addr + (i * rcbSize); + + pRcb->BufPtr = 0; + pRcb->mode = 0; + pRcb->HNext = 0; + pRcb->Next = (void *) pRxCtl->RcbPool; + pRcb->Off_BLen = 0; + pRcb->Eop = 0; + pRcb->fake_bytes = 0; + + pRxCtl->RcbPool = pRcb; + } + + cppi_log_event2(" [cppi]RcbPool (dma)", (uint32_t) pRxCtl->RcbPool, pRxCtl->RcbPool->dma_handle); + + pRxCtl->RxActQueueCount = 0; + pRxCtl->RxActQueueHead = 0; + pRxCtl->RxActive = 0; + + pRxCtl->RxFakeRcvHead = 0; + pRxCtl->RxFakeRcvTail = 0; + + return 0; +} + +static uint8_t ch_buf_cnt[][2] = { + {CPPI_RX_NUM_BUFS, 2}, // ch0: bulk out/in + {CPPI_RX_NUM_BUFS, 2}, // ch1: bulk out/in + {0, 2}, // ch2: interrupt + {0, 2} // ch3: interrupt +}; + +void tnetv_cppi_init(struct cppi_info *cppi) +{ + int ch; + uint8_t *alloc_ptr; + int ch_mem_size[CPPI_NUM_CHANNELS]; + + // wipe cppi memory + memset(cppi, 0, sizeof(*cppi)); + + // find out how much memory we need to allocate + cppi->dma_size = 0; + for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) + { + ch_mem_size[ch] = (ch_buf_cnt[ch][0] * sizeof(cppi_rcb)) + (ch_buf_cnt[ch][1] * sizeof(cppi_tcb)); + cppi->dma_size += ch_mem_size[ch]; + } + + // allocate DMA-able memory + if (cppi->dma_size != CPPI_INFO_MEM_SIZE) + { + panicf("Invalid dma size expected %d got %d", cppi->dma_size, CPPI_INFO_MEM_SIZE); + } + cppi->dma_handle = (dma_addr_t) __virt_to_phys(cppi->dma_mem); + + memset(cppi->dma_mem, 0, cppi->dma_size); + + cppi_log_event2("[cppi] all CBs sz mem", cppi->dma_size, (uint32_t) cppi->dma_mem); + + // now set up the pointers + alloc_ptr = cppi->dma_mem; + for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) + { + cppi->rx_ctl[ch].RxNumBuffers = ch_buf_cnt[ch][0]; + cppi->rx_ctl[ch].RcbStart = alloc_ptr; + cppi->rx_ctl[ch].rcb_start_dma_addr = (dma_addr_t) __virt_to_phys(alloc_ptr); + alloc_ptr += (ch_buf_cnt[ch][0] * sizeof(cppi_rcb)); + + cppi->tx_ctl[ch].TxNumBuffers = ch_buf_cnt[ch][1]; + cppi->tx_ctl[ch].TcbStart = alloc_ptr; + cppi->tx_ctl[ch].tcb_start_dma_addr = (dma_addr_t) __virt_to_phys(alloc_ptr); + alloc_ptr += (ch_buf_cnt[ch][1] * sizeof(cppi_tcb)); + + cppi_log_event3("[cppi] alloc bufs: ch dmarcb dmatcb", ch, cppi->rx_ctl[ch].rcb_start_dma_addr, cppi->tx_ctl[ch].tcb_start_dma_addr); + + // set up receive buffer + if (ch_buf_cnt[ch][0]) + { + dma_recv_buf[ch] = (ch == 0) ? ch0_rx_buf : ((ch == 1) ? ch1_rx_buf : 0); + cppi_log_event3("[cppi] Alloc fake DMA buf ch", ch, (uint32_t) dma_recv_buf[ch], (uint32_t) __virt_to_phys(dma_recv_buf[ch])); + } + else + { + dma_recv_buf[ch] = 0; + } + } + +} + +void tnetv_cppi_cleanup(struct cppi_info *cppi) +{ + cppi_log_event0("wipe cppi mem"); + + // wipe cppi memory + memset(cppi, 0, sizeof(*cppi)); +} diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.h b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.h new file mode 100644 index 0000000000..9d0ac37cd0 --- /dev/null +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.h @@ -0,0 +1,144 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2021 by Tomasz Moń + * Copied with minor modifications from Sansa Connect Linux driver + * Copyright (c) 2005 Zermatt Systems, Inc. + * Written by: Ben Bostwick + * + * 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. + * + ****************************************************************************/ + +#ifndef TNETV105_CPPI_H +#define TNETV105_CPPI_H + +#include + +typedef uint32_t dma_addr_t; +#define USB_CPPI_LOGGING 0 +#define EAGAIN 11 /* Try again */ +#define CPPI_RX_NUM_BUFS 129 +#define CPPI_INFO_MEM_SIZE (2 * CPPI_RX_NUM_BUFS * sizeof(cppi_rcb) + 4 * 2 * sizeof(cppi_tcb)) + +#define CPPI_NUM_CHANNELS 4 +#define CPPI_MAX_FRAG 0xFE00 + +struct cppi_info; + +typedef struct +{ + uint32_t HNext; /*< Hardware's pointer to next buffer descriptor */ + uint32_t BufPtr; /*< Pointer to the data buffer */ + uint32_t Off_BLen; /*< Contains buffer offset and buffer length */ + uint32_t mode; /*< SOP, EOP, Ownership, EOQ, Teardown, Q Starv, Length */ + void *Next; + void *Eop; + dma_addr_t dma_handle; + uint32_t dummy; + +} cppi_tcb; + +typedef struct +{ + uint32_t HNext; /*< Hardware's pointer to next buffer descriptor */ + uint32_t BufPtr; /*< Pointer to the data buffer */ + uint32_t Off_BLen; /*< Contains buffer offset and buffer length */ + uint32_t mode; /*< SOP, EOP, Ownership, EOQ, Teardown Complete bits */ + void *Next; + void *Eop; + dma_addr_t dma_handle; + uint32_t fake_bytes; + +} cppi_rcb; + +typedef struct cppi_txcntl +{ + cppi_tcb *TcbPool; + cppi_tcb *TxActQueueHead; + cppi_tcb *TxActQueueTail; + uint32_t TxActQueueCount; + uint32_t TxActive; + cppi_tcb *LastTcbProcessed; + char *TcbStart; + dma_addr_t tcb_start_dma_addr; + int TxNumBuffers; + +#ifdef _CPHAL_STATS + uint32_t TxMisQCnt; + uint32_t TxEOQCnt; + uint32_t TxPacketsServiced; + uint32_t TxMaxServiced; + uint32_t NumTxInt; +#endif +} cppi_txcntl; + + +typedef struct cppi_rxcntl +{ + cppi_rcb *RcbPool; + cppi_rcb *RxActQueueHead; + cppi_rcb *RxActQueueTail; + uint32_t RxActQueueCount; + uint32_t RxActive; + char *RcbStart; + dma_addr_t rcb_start_dma_addr; + int RxNumBuffers; + + cppi_rcb *RxFakeRcvHead; + cppi_rcb *RxFakeRcvTail; + +#ifdef _CPHAL_STATS + uint32_t RxMisQCnt; + uint32_t RxEOQCnt; + uint32_t RxMaxServiced; + uint32_t RxPacketsServiced; + uint32_t NumRxInt; +#endif +} cppi_rxcntl; + +typedef struct cppi_info +{ + struct cppi_txcntl tx_ctl[CPPI_NUM_CHANNELS]; + struct cppi_rxcntl rx_ctl[CPPI_NUM_CHANNELS]; + + uint8_t dma_mem[CPPI_INFO_MEM_SIZE]; + int dma_size; + dma_addr_t dma_handle; + +} cppi_info; + +#define tnetv_cppi_rx_int_recv_check(cppi, ch) (((cppi)->rx_ctl[(ch)].RxFakeRcvHead) ? 1 : 0) + +int tnetv_cppi_init_tcb(struct cppi_info *cppi, int ch); +int tnetv_cppi_flush_tx_queue(struct cppi_info *cppi, int ch); +int tnetv_cppi_send(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length, int send_zlp); +int tnetv_cppi_tx_int(struct cppi_info *cppi, int ch); +void tnetv_cppi_free_tcb(struct cppi_info *cppi, int ch); + +int tnetv_cppi_init_rcb(struct cppi_info *cppi, int ch); +int tnetv_cppi_flush_rx_queue(struct cppi_info *cppi, int ch); +int tnetv_cppi_rx_return(struct cppi_info *cppi, int ch, cppi_rcb *done_rcb); +int tnetv_cppi_rx_queue_add(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length); +int tnetv_cppi_rx_int(struct cppi_info *cppi, int ch); +int tnetv_cppi_rx_int_recv(struct cppi_info *cppi, int ch, int *buf_size, void *buf, int maxpacket); +void tnetv_cppi_free_rcb(struct cppi_info *cppi, int ch); + +void tnetv_cppi_init(struct cppi_info *cppi); +void tnetv_cppi_cleanup(struct cppi_info *cppi); + +void tnetv_cppi_dump_info(struct cppi_info *cppi); + +#endif diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c new file mode 100644 index 0000000000..4fdf73cb50 --- /dev/null +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c @@ -0,0 +1,1489 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#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)); + + epCfg.f.in_dbl_buf = 1; + 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 = 1; + 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; + commit_discard_dcache_range(buf, size); + if ((buffer >= CONFIG_SDRAM_START) && (buffer <= CONFIG_SDRAM_START + SDRAM_SIZE)) + { + if (tnetv_cppi_send(&cppi, (epn - 1), buffer, size, 0)) + { + 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; + + if (epn == 0 || epn >= USB_NUM_ENDPOINTS) + { + return 0; + } + + flags = disable_irq_save(); + + /* set the maxpacket for this endpoint based on the current speed */ + ep_runtime[epn].max_packet_size = MAX_PACKET(epn, usb_drv_port_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 */ + tx_size = 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() 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 ? HZ/10 : 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_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, HZ/10, 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(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_ack(struct usb_ctrlrequest* req); + +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); + } +} diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.h b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.h new file mode 100644 index 0000000000..c31c9c6505 --- /dev/null +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.h @@ -0,0 +1,335 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2021 by Tomasz Moń + * Ported from Sansa Connect TNETV105 UDC Linux driver + * Copyright (c) 2005 Zermatt Systems, Inc. + * Written by: Ben Bostwick + * + * 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. + * + ****************************************************************************/ + +#ifndef TNETV105_USB_DRV_H +#define TNETV105_USB_DRV_H + +#include + +#define DM320_AHB_PADDR 0x00060000 +#define DM320_VLYNQ_PADDR 0x70000000 + +/* TNETV105 Memory Map */ +#define VLYNQ_BASE (0x70000000) + +#define TNETV_BASE (VLYNQ_BASE) +#define TNETV_V2USB_BASE (TNETV_BASE + 0x00000200) +#define TNETV_WDOG_BASE (TNETV_BASE + 0x00000280) +#define TNETV_USB_HOST_BASE (TNETV_BASE + 0x00010000) +#define TNETV_USB_DEVICE_BASE (TNETV_BASE + 0x00020000) + +#define TNETV_V2USB_REG(x) (TNETV_V2USB_BASE + (x)) + +#define TNETV_V2USB_RESET (TNETV_V2USB_REG(0x00)) +#define TNETV_V2USB_CLK_PERF (TNETV_V2USB_REG(0x04)) +#define TNETV_V2USB_CLK_MODE (TNETV_V2USB_REG(0x08)) +#define TNETV_V2USB_CLK_CFG (TNETV_V2USB_REG(0x0C)) +#define TNETV_V2USB_CLK_WKUP (TNETV_V2USB_REG(0x10)) +#define TNETV_V2USB_CLK_PWR (TNETV_V2USB_REG(0x14)) + +#define TNETV_V2USB_PID_VID (TNETV_V2USB_REG(0x28)) + +#define TNETV_V2USB_GPIO_DOUT (TNETV_V2USB_REG(0x40)) +#define TNETV_V2USB_GPIO_DIN (TNETV_V2USB_REG(0x44)) +#define TNETV_V2USB_GPIO_DIR (TNETV_V2USB_REG(0x48)) +#define TNETV_V2USB_GPIO_FS (TNETV_V2USB_REG(0x4C)) +#define TNETV_V2USB_GPIO_INTF (TNETV_V2USB_REG(0x50)) +#define TNETV_V2USB_GPIO_EOI (TNETV_V2USB_REG(0x54)) + +#define TNETV_USB_DEVICE_REG(x) (TNETV_USB_DEVICE_BASE + (x)) + +#define TNETV_USB_REV (TNETV_USB_DEVICE_REG(0x00)) +#define TNETV_USB_TX_CTL (TNETV_USB_DEVICE_REG(0x04)) +#define TNETV_USB_TX_TEARDOWN (TNETV_USB_DEVICE_REG(0x08)) +#define TNETV_USB_RX_CTL (TNETV_USB_DEVICE_REG(0x14)) +#define TNETV_USB_RX_TEARDOWN (TNETV_USB_DEVICE_REG(0x18)) +#define TNETV_USB_TX_ENDIAN_CTL (TNETV_USB_DEVICE_REG(0x40)) +#define TNETV_USB_RX_ENDIAN_CTL (TNETV_USB_DEVICE_REG(0x44)) + +#define TNETV_USB_RX_FREE_BUF_CNT(ch) (TNETV_USB_DEVICE_REG(0x140 + ((ch) * 4))) + +#define TNETV_USB_TX_INT_STATUS (TNETV_USB_DEVICE_REG(0x170)) +#define TNETV_USB_TX_INT_EN (TNETV_USB_DEVICE_REG(0x178)) +#define TNETV_USB_TX_INT_DIS (TNETV_USB_DEVICE_REG(0x17C)) +#define TNETV_USB_VBUS_INT (TNETV_USB_DEVICE_REG(0x180)) +#define TNETV_USB_VBUS_EOI (TNETV_USB_DEVICE_REG(0x184)) +#define TNETV_USB_RX_INT_STATUS (TNETV_USB_DEVICE_REG(0x190)) +#define TNETV_USB_RX_INT_EN (TNETV_USB_DEVICE_REG(0x198)) +#define TNETV_USB_RX_INT_DIS (TNETV_USB_DEVICE_REG(0x19C)) + +#define TNETV_USB_RESET_CMPL (TNETV_USB_DEVICE_REG(0x1A0)) +#define TNETV_CPPI_STATE (TNETV_USB_DEVICE_REG(0x1A4)) + +#define TNETV_USB_STATUS (TNETV_USB_DEVICE_REG(0x200)) +#define TNETV_USB_CTRL (TNETV_USB_DEVICE_REG(0x204)) +#define TNETV_USB_IF_STATUS (TNETV_USB_DEVICE_REG(0x210)) +#define TNETV_USB_IF_ERR (TNETV_USB_DEVICE_REG(0x214)) +#define TNETV_USB_IF_SM (TNETV_USB_DEVICE_REG(0x218)) + +#define TNETV_USB_EP0_CFG (TNETV_USB_DEVICE_REG(0x220)) +#define TNETV_USB_EP0_CNT (TNETV_USB_DEVICE_REG(0x224)) + +#define TNETV_USB_EPx_CFG(x) (TNETV_USB_DEVICE_REG(0x220 + (0x10 * (x)))) +#define TNETV_USB_EPx_IN_CNT(x) (TNETV_USB_DEVICE_REG(0x224 + (0x10 * (x)))) +#define TNETV_USB_EPx_OUT_CNT(x) (TNETV_USB_DEVICE_REG(0x228 + (0x10 * (x)))) +#define TNETV_USB_EPx_ADR(x) (TNETV_USB_DEVICE_REG(0x22C + (0x10 * (x)))) + +/* USB CPPI Config registers (0x300 - 0x30C) */ +#define TNETV_USB_RNDIS_MODE (TNETV_USB_DEVICE_REG(0x300)) +#define TNETV_USB_CELL_DMA_EN (TNETV_USB_DEVICE_REG(0x30C)) + +#define TNETV_USB_RAW_INT (TNETV_USB_DEVICE_REG(0x310)) +#define TNETV_USB_RAW_EOI (TNETV_USB_DEVICE_REG(0x314)) + +/* USB DMA setup RAM (0x800 - 0x8FF) */ +#define TNETV_DMA_BASE (TNETV_USB_DEVICE_BASE + 0x800) +#define TNETV_DMA_TX_STATE(ch, wd) ((uint32_t *) ((TNETV_DMA_BASE) + ((ch) * 0x40) + ((wd) * 4))) +#define TNETV_DMA_TX_CMPL(ch) ((TNETV_DMA_BASE) + ((ch) * 0x40) + 0x1C) + +#define TNETV_CPPI_TX_WORD_HDP 0 + +#define TNETV_DMA_RX_STATE(ch, wd) ((uint32_t *) ((TNETV_DMA_BASE) + ((ch) * 0x40) + 0x20 + ((wd) * 4))) +#define TNETV_DMA_RX_CMPL(ch) ((TNETV_DMA_BASE) + ((ch) * 0x40) + 0x3C) + +#define TNETV_CPPI_RX_WORD_HDP 1 + +#define TNETV_DMA_NUM_CHANNELS 3 + +#define TNETV_DMA_TX_NUM_WORDS 6 +#define TNETV_DMA_RX_NUM_WORDS 7 + + +/* USB Buffer RAM (0x1000 - 0x1A00) */ +#define TNETV_EP_DATA_ADDR(x) ((uint32_t *) ((TNETV_USB_DEVICE_BASE) + 0x1000 + (x))) + +#define TNETV_EP_DATA_SIZE (0xA00) + +#define TNETV_V2USB_RESET_DEV (1 << 0) + +#define TNETV_USB_CELL_DMA_EN_RX (1 << 0) +#define TNETV_USB_CELL_DMA_EN_TX (1 << 1) + +#define TNETV_V2USB_CLK_WKUP_VBUS (1 << 12) + +#define DM320_VLYNQ_INTPND_PHY ((DM320_AHB_PADDR) + 0x0314) + + +/* macro to convert from a linux pointer to a physical address + * to be sent over the VLYNQ bus. The dm320 vlynq rx registers are + * set up so the base address is the physical address of RAM + */ +#define __dma_to_vlynq_phys(addr) ((((uint32_t) (addr)) - 0x01000000)) +#define __vlynq_phys_to_dma(addr) ((((uint32_t) (addr)) + 0x01000000)) + +//---------------------------------------------------------------------- + +#define USB_FULL_SPEED_MAXPACKET 64 +#define USB_HIGH_SPEED_MAXPACKET 512 + +/* WORD offsets into the data memory */ +#define EP0_MAX_PACKET_SIZE 64 /* Control ep - 64 bytes */ +#define EP1_MAX_PACKET_SIZE 512 /* Bulk ep - 512 bytes */ +#define EP2_MAX_PACKET_SIZE 512 /* Bulk ep - 512 bytes */ +#define EP3_MAX_PACKET_SIZE 64 /* Int ep - 64 bytes */ +#define EP4_MAX_PACKET_SIZE 64 /* Int ep - 64 bytes */ + +/* BEN TODO: fix this crap */ +#define EP0_OUTPKT_ADDRESS 0 +#define EP0_INPKT_ADDRESS (EP0_MAX_PACKET_SIZE) +#define EP1_XBUFFER_ADDRESS (EP0_MAX_PACKET_SIZE << 1) +#define EP1_YBUFFER_ADDRESS (EP1_XBUFFER_ADDRESS + EP1_MAX_PACKET_SIZE) +#define EP2_XBUFFER_ADDRESS (EP1_XBUFFER_ADDRESS + (EP1_MAX_PACKET_SIZE << 1)) +#define EP2_YBUFFER_ADDRESS (EP2_XBUFFER_ADDRESS + EP2_MAX_PACKET_SIZE) +#define EP3_XBUFFER_ADDRESS (EP2_XBUFFER_ADDRESS + (EP2_MAX_PACKET_SIZE << 1)) +#define EP3_YBUFFER_ADDRESS (EP3_XBUFFER_ADDRESS + EP3_MAX_PACKET_SIZE) +#define EP4_XBUFFER_ADDRESS (EP3_XBUFFER_ADDRESS + (EP3_MAX_PACKET_SIZE << 1)) +#define EP4_YBUFFER_ADDRESS (EP4_XBUFFER_ADDRESS + EP4_MAX_PACKET_SIZE) +#define EP5_XBUFFER_ADDRESS (EP4_XBUFFER_ADDRESS + (EP4_MAX_PACKET_SIZE << 1)) +#define EP5_YBUFFER_ADDRESS (EP5_XBUFFER_ADDRESS + EP1_MAX_PACKET_SIZE) +#define EP6_XBUFFER_ADDRESS (EP5_XBUFFER_ADDRESS + (EP1_MAX_PACKET_SIZE << 1)) +#define EP6_YBUFFER_ADDRESS (EP6_XBUFFER_ADDRESS + EP2_MAX_PACKET_SIZE) +#define EP7_XBUFFER_ADDRESS (EP6_XBUFFER_ADDRESS + (EP2_MAX_PACKET_SIZE << 1)) +#define EP7_YBUFFER_ADDRESS (EP7_XBUFFER_ADDRESS + EP3_MAX_PACKET_SIZE) +#define EP8_XBUFFER_ADDRESS (EP7_XBUFFER_ADDRESS + (EP3_MAX_PACKET_SIZE << 1)) +#define EP8_YBUFFER_ADDRESS (EP8_XBUFFER_ADDRESS + EP4_MAX_PACKET_SIZE) + +#define SETUP_PKT_DATA_SIZE 8 + +#define EP0_BUF_SIZE_8 0 +#define EP0_BUF_SIZE_16 1 +#define EP0_BUF_SIZE_32 2 +#define EP0_BUF_SIZE_64 3 + +/* USB Status register */ +typedef struct { + uint32_t rsvd1 : 5; + uint32_t ep0_out_ack : 1; + uint32_t rsvd2 : 1; + uint32_t ep0_in_ack : 1; + uint32_t rsvd3 : 16; + uint32_t setup_ow : 1; + uint32_t setup : 1; + uint32_t vbus : 1; + uint32_t resume : 1; + uint32_t suspend : 1; + uint32_t reset : 1; + uint32_t sof : 1; + uint32_t any_int : 1; +} UsbStatusStruct; + +typedef union { + uint32_t val; + UsbStatusStruct f; +} UsbStatusType; + +/* USB Function control register */ +typedef struct { + uint32_t dir : 1; + uint32_t hs_test_mode : 3; + uint32_t rsvd1 : 1; + uint32_t wkup_en : 1; + uint32_t low_pwr_en : 1; + uint32_t connect : 1; + uint32_t rsvd2 : 4; + uint32_t ep0_in_int_en : 1; + uint32_t ep0_out_int_en : 1; + uint32_t err_cnt_en : 2; + uint32_t func_addr : 7; + uint32_t speed : 1; + uint32_t setupow_int_en : 1; + uint32_t setup_int_en : 1; + uint32_t vbus_int_en : 1; + uint32_t resume_int_en : 1; + uint32_t suspend_int_en : 1; + uint32_t reset_int_en : 1; + uint32_t sof_int_en : 1; + uint32_t rsvd3 : 1; +} UsbCtrlStruct; + +typedef union { + uint32_t val; + UsbCtrlStruct f; +} UsbCtrlType; + +/* Endpoint 0 Control Register */ +typedef struct { + uint32_t buf_size : 2; + uint32_t in_int_en : 1; + uint32_t in_stall : 1; + uint32_t dbl_buf : 1; + uint32_t in_toggle : 1; + uint32_t in_nak_int_en : 1; + uint32_t in_en : 1; + uint32_t res3 : 10; + uint32_t out_int_en : 1; + uint32_t out_stall : 1; + uint32_t res4 : 1; + uint32_t out_toggle : 1; + uint32_t out_nak_int_en : 1; + uint32_t out_en : 1; + uint32_t res6 : 8; +} UsbEp0CtrlStruct; + +typedef union { + uint32_t val; + UsbEp0CtrlStruct f; +} UsbEp0CtrlType; + +/* Endpoint 0 current packet size register */ +typedef struct { + uint32_t in_xbuf_cnt : 7; + uint32_t in_xbuf_nak : 1; + uint32_t in_ybuf_cnt : 7; + uint32_t in_ybuf_nak : 1; + uint32_t out_xbuf_cnt : 7; + uint32_t out_xbuf_nak : 1; + uint32_t out_ybuf_cnt : 7; + uint32_t out_ybuf_nak : 1; +} UsbEp0ByteCntStruct; + +typedef union { + uint32_t val; + UsbEp0ByteCntStruct f; +} UsbEp0ByteCntType; + +/* Endpoint n Configuration and Control register */ +typedef struct { + uint32_t res1 : 1; + uint32_t in_toggle_rst : 1; + uint32_t in_ack_int : 1; + uint32_t in_stall : 1; + uint32_t in_dbl_buf : 1; + uint32_t in_toggle : 1; + uint32_t in_nak_int : 1; + uint32_t in_en : 1; + uint32_t res2 : 1; + uint32_t out_toggle_rst : 1; + uint32_t out_ack_int : 1; + uint32_t out_stall : 1; + uint32_t out_dbl_buf : 1; + uint32_t out_toggle : 1; + uint32_t out_nak_int : 1; + uint32_t out_en : 1; + uint32_t in_buf_size : 8; + uint32_t out_buf_size : 8; +} UsbEpCfgCtrlStruct; + +typedef union { + uint32_t val; + UsbEpCfgCtrlStruct f; +} UsbEpCfgCtrlType; + +/* Endpoint n XY Buffer Start Address register */ +typedef struct { + uint8_t xBuffStartAddrIn; + uint8_t yBuffStartAddrIn; + uint8_t xBuffStartAddrOut; + uint8_t yBuffStartAddrOut; +} UsbEpStartAddrStruct; + +typedef union { + uint32_t val; + UsbEpStartAddrStruct f; +} UsbEpStartAddrType; + +/* Endpoint n Packet Control register */ +typedef struct { + uint32_t xBufPacketCount : 11; + uint32_t res1 : 4; + uint32_t xbuf_nak : 1; + uint32_t yBufPacketCount : 11; + uint32_t res2 : 4; + uint32_t ybuf_nak : 1; +} UsbEpByteCntStruct; + +typedef union { + uint32_t val; + UsbEpByteCntStruct f; +} UsbEpByteCntType; + +#define tnetv_usb_reg_read(x) (*((volatile uint32_t *) (x))) +#define tnetv_usb_reg_write(x, val) (*((volatile uint32_t *) (x)) = (uint32_t) (val)) + + +#endif diff --git a/firmware/target/arm/tms320dm320/sansa-connect/usb-sansaconnect.c b/firmware/target/arm/tms320dm320/sansa-connect/usb-sansaconnect.c index abe6622f0b..986efe374c 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/usb-sansaconnect.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/usb-sansaconnect.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id: $ * - * Copyright (C) 2011 by Tomasz Moń + * Copyright (C) 2011-2021 by Tomasz Moń * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,52 +22,12 @@ #include "config.h" #include "system.h" #include "kernel.h" -#include "usb.h" -#ifdef HAVE_USBSTACK -#include "usb_drv.h" #include "usb_core.h" -#endif - -static bool usb_is_connected = false; static int usb_detect_callback(struct timeout *tmo) { (void)tmo; - - if (IO_GIO_BITSET0 & (1 << 9)) - { - /* 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; - - /* Drive reset low */ - IO_GIO_BITCLR0 = (1 << 7); - - /* Disable VLYNQ clock */ - IO_CLK_MOD2 &= ~(1 << 13); - - usb_is_connected = false; - } - else - { - /* Enable M48XI crystal resonator */ - IO_CLK_LPCTL1 &= ~(0x01); - - /* Set GIO33 as CLKOUT1B */ - IO_GIO_FSEL3 |= 0x0003; - - /* Drive reset high */ - IO_GIO_BITSET0 = (1 << 7); - - /* Enable VLYNQ clock */ - IO_CLK_MOD2 |= (1 << 13); - - usb_is_connected = true; - } - + usb_status_event(usb_detect()); return 0; } @@ -82,20 +42,15 @@ void GIO9(void) timeout_register(&usb_oneshot, usb_detect_callback, HZ, 0); } -bool usb_drv_connected(void) -{ - return false; -} - int usb_detect(void) { - if (usb_is_connected == true) + if (IO_GIO_BITSET0 & (1 << 9)) { - return USB_INSERTED; + return USB_EXTRACTED; } else { - return USB_EXTRACTED; + return USB_INSERTED; } } @@ -127,14 +82,18 @@ void usb_init_device(void) /* Enable USB insert detection interrupt */ IO_INTC_EINT1 |= (1 << 14); - - /* Check if USB is connected */ - usb_detect_callback(NULL); } void usb_enable(bool on) { - (void)on; + if (on) + { + usb_core_init(); + } + else + { + usb_core_exit(); + } } void usb_attach(void) diff --git a/firmware/target/arm/tms320dm320/system-dm320.c b/firmware/target/arm/tms320dm320/system-dm320.c index 93cf3c51c4..935f3609a6 100644 --- a/firmware/target/arm/tms320dm320/system-dm320.c +++ b/firmware/target/arm/tms320dm320/system-dm320.c @@ -494,6 +494,13 @@ void udelay(int usec) { } } +void mdelay(int msec) +{ + int ms_per_tick = 1000 / HZ; + /* Round up to next full tick */ + sleep((msec + ms_per_tick - 1) / ms_per_tick); +} + #ifdef BOOTLOADER void system_prepare_fw_start(void) { diff --git a/firmware/target/arm/tms320dm320/system-target.h b/firmware/target/arm/tms320dm320/system-target.h index 59ae61f8df..1c46e909ed 100644 --- a/firmware/target/arm/tms320dm320/system-target.h +++ b/firmware/target/arm/tms320dm320/system-target.h @@ -30,6 +30,7 @@ #define CPUFREQ_MAX 175000000 void udelay(int usec); +void mdelay(int msec); #if defined(CREATIVE_ZVx) && defined(BOOTLOADER) /* hacky.. */ -- cgit v1.2.3