diff --git a/bootloader/sansaconnect.c b/bootloader/sansaconnect.c index a87b23745f..c5dbca717d 100644 --- a/bootloader/sansaconnect.c +++ b/bootloader/sansaconnect.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id: $ * -* Copyright (C) 2011 by Tomasz Moń +* Copyright (C) 2011-2021 by Tomasz Moń * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -32,6 +32,7 @@ #include "uart-target.h" #include "power.h" #include "loader_strerror.h" +#include "usb.h" #define FLASH_BASE 0x00100000 #define PARAMETERS_FLASH_OFFSET 0x00010000 @@ -206,6 +207,8 @@ void main(void) printf("Rockbox boot loader"); printf("Version %s", rbversion); + usb_init(); + usb_start_monitoring(); clear_recoverzap(); @@ -215,6 +218,17 @@ void main(void) filesystem_init(); + if (usb_detect() == USB_INSERTED) + { + usb_enable(true); + while (usb_detect() == USB_INSERTED) + { + sleep(HZ); + storage_spin(); + } + usb_enable(false); + } + ret = disk_mount_all(); if (ret <= 0) error(EDISK, ret, true); @@ -269,8 +283,8 @@ void main(void) ret = kernel_entry(); printf("FAILED!"); } - + storage_sleepnow(); - + while(1); } 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.. */