rockbox/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_cppi.c

1048 lines
33 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* 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 <string.h>
#include "config.h"
#include "system.h"
#include "kernel.h"
#include "panic.h"
#include "tnetv105_usb_drv.h"
#include "tnetv105_cppi.h"
#if USB_CPPI_LOGGING
#define LOGF_ENABLE
#endif
#include "logf.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) logf(msg)
#define cppi_log_event1(msg, data0) logf(msg " %lx", (uint32_t)data0)
#define cppi_log_event2(msg, data0, data1) logf(msg " %lx %lx", (uint32_t)data0, (uint32_t)data1)
#define cppi_log_event3(msg, data0, data1, data2) logf(msg " %lx %lx %lx", (uint32_t)data0, (uint32_t)data1, (uint32_t)data2)
#define cppi_log_event4(msg, data0, data1, data2, data3) logf(msg " %lx %lx %lx %lx", (uint32_t)data0, (uint32_t)data1, (uint32_t)data2, (uint32_t)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: %lx Buf size: %d int: %lx %lx\n\n", (uint32_t) cppi->dma_mem, cppi->dma_size, tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS), tnetv_usb_reg_read(VL_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 %ld free_buf_cnt %ld\n", pRxCtl->RxNumBuffers, pRxCtl->RxActive, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)));
logf(" q_cnt %ld head %lx tail %lx\n", pRxCtl->RxActQueueCount, (uint32_t) pRxCtl->RxActQueueHead, (uint32_t) pRxCtl->RxActQueueTail);
logf(" fake_head: %lx fake_tail: %lx\n", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail);
rcb = (cppi_rcb *) pRxCtl->RcbStart;
do
{
if (!rcb)
break;
logf(" Rcb: %lx\n", (uint32_t) rcb);
logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", rcb->HNext, rcb->BufPtr, rcb->Off_BLen, rcb->mode);
logf(" Next %lx Eop %lx dma_handle %lx fake_bytes %lx\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 %ld\n", pTxCtl->TxNumBuffers, pTxCtl->TxActive);
logf(" q_cnt %ld head %lx tail %lx\n", pTxCtl->TxActQueueCount, (uint32_t) pTxCtl->TxActQueueHead, (uint32_t) pTxCtl->TxActQueueTail);
tcb = (cppi_tcb *) pTxCtl->TcbPool;
do
{
if (!tcb)
break;
logf(" Tcb (pool): %lx\n", (uint32_t) tcb);
logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode);
logf(" Next %lx Eop %lx dma_handle %lx\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): %lx\n", (uint32_t) tcb);
logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode);
logf(" Next %lx Eop %lx dma_handle %lx\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(VL_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));
}