From: supriya karanth <[email protected]>
Handles:
1) Known transfer length
a) Non multiple of packet size
b) Multiple of packet size
2) Unknown transfer lengths
- Short packet indicates end of transfer
---------------> OUT Endpoint interrupt recieved
| |
| ____________________|_________________
| | |
| -> No ongoing transfer ->DMA Transfer ongoing and RXPKTRDY set?
| | ->Short Packet recieved
| request queued? ->PAUSE DMA transfer, Read Residue
| | ________________|____________
| | | |
| | residue!=0 residue=0
| | -> abort DMA ->Resume DMA
| | ->Update request->actual_len ->Wait for DMA
| | -> Clear DMA bits in CSR completion
| | -> Read Short Packet |
| |__________________________| |______
| | |
| call rxstate |
| ->RXPKTRDY set? Read RXCOUNT |
| _____________________|____________________ |
| | | |
| ->RXCOUNT == EP MAX packet Size ->RXCOUNT < EP MAX packet Size|
| ->Program DMA in Mode1 for length -> Program in PIO / Mode0 |
| which is multiple of packet -> Read Short packet from FIFO|
| size -> Update request->actual_len |
| | -> call musb_giveback |
| |________________________________________| |
|____________________________| |
| | |
| |____________________________________|
| |
| DMA completion interrupt recieved
| _______________________|_________________
| | |
| ->req->len = req->actual ->req->len < req->actual
| ->Clear DMA bits -> Short packet expected
| ->Call musb_giveback ->Clear DMA bits
| | ->wait for next OUT
| |_________________________________________|
|_______________________________|
Signed-off-by: Supriya Karanth <[email protected]>
Signed-off-by: Praveena NADAHALLY <[email protected]>
Acked-by: Linus Walleij <[email protected]>
---
drivers/usb/musb/musb_dma.h | 12 +++
drivers/usb/musb/musb_gadget.c | 125 +++++++++++++++++++++++++--
drivers/usb/musb/musb_gadget.h | 3 +
drivers/usb/musb/ux500_dma.c | 184 +++++++++++++++++++++++++++++++++++++++-
4 files changed, 312 insertions(+), 12 deletions(-)
diff --git a/drivers/usb/musb/musb_dma.h b/drivers/usb/musb/musb_dma.h
index 24d3921..bcad195 100644
--- a/drivers/usb/musb/musb_dma.h
+++ b/drivers/usb/musb/musb_dma.h
@@ -116,6 +116,7 @@ struct dma_controller;
* @max_len: the maximum number of bytes the channel can move in one
* transaction (typically representing many USB maximum-sized packets)
* @actual_len: how many bytes have been transferred
+ * @prog_len: how many bytes have been programmed for transfer
* @status: current channel status (updated e.g. on interrupt)
* @desired_mode: true if mode 1 is desired; false if mode 0 is desired
*
@@ -127,6 +128,7 @@ struct dma_channel {
/* FIXME not void* private_data, but a dma_controller * */
size_t max_len;
size_t actual_len;
+ size_t prog_len;
enum dma_channel_status status;
bool desired_mode;
};
@@ -155,6 +157,11 @@ dma_channel_status(struct dma_channel *c)
* @channel_release: call this to release a DMA channel
* @channel_abort: call this to abort a pending DMA transaction,
* returning it to FREE (but allocated) state
+ * @channel_pause: This function pauses the ongoing DMA transfer
+ * @channel_resume: This function resumes the ongoing DMA transfer
+ * @tx_status: Gets the residue of an ongoing DMA transfer
+ * @check_resiudue: checks if the residue of an ongoing DMA
+ * transfer is valid
*
* Controllers manage dma channels.
*/
@@ -169,6 +176,11 @@ struct dma_controller {
dma_addr_t dma_addr,
u32 length);
int (*channel_abort)(struct dma_channel *);
+ int (*channel_pause)(struct dma_channel *);
+ int (*channel_resume)(struct dma_channel *);
+ int (*tx_status)(struct dma_channel *);
+ int (*check_residue)(struct dma_channel *,
+ u32 residue);
int (*is_compatible)(struct dma_channel *channel,
u16 maxpacket,
void *buf, u32 length);
diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c
index b6b84da..b351f52 100644
--- a/drivers/usb/musb/musb_gadget.c
+++ b/drivers/usb/musb/musb_gadget.c
@@ -806,14 +806,10 @@ static void rxstate(struct musb *musb, struct
musb_request *req)
/* In case first packet is short */
if (fifo_count < musb_ep->packet_sz)
transfer_size = fifo_count;
- else if (request->short_not_ok)
- transfer_size = min(request->length -
- request->actual,
- channel->max_len);
else
transfer_size = min(request->length -
request->actual,
- (unsigned)fifo_count);
+ channel->max_len);
csr &= ~MUSB_RXCSR_DMAMODE;
csr |= (MUSB_RXCSR_DMAENAB |
@@ -821,13 +817,23 @@ static void rxstate(struct musb *musb, struct
musb_request *req)
musb_writew(epio, MUSB_RXCSR, csr);
- if (transfer_size <= musb_ep->packet_sz) {
+ if (transfer_size < musb_ep->packet_sz) {
musb_ep->dma->desired_mode = 0;
} else {
musb_ep->dma->desired_mode = 1;
/* Mode must be set after DMAENAB */
csr |= MUSB_RXCSR_DMAMODE;
musb_writew(epio, MUSB_RXCSR, csr);
+
+ /* Program the transfer length to be
+ * a multiple of packet size because
+ * short packets cant be transferred
+ * over mode1
+ */
+ transfer_size = transfer_size -
+ (transfer_size %
+ musb_ep->packet_sz);
+ musb_ep->dma->prog_len = transfer_size;
}
if (c->channel_program(channel,
@@ -835,9 +841,16 @@ static void rxstate(struct musb *musb, struct musb_request
*req)
channel->desired_mode,
request->dma
+ request->actual,
- transfer_size))
+ transfer_size)){
+
+ dev_dbg(musb->controller,
+ "%s OUT/RX DMA Size %d/%d
maxpacket %d\n",
+ musb_ep->end_point.name,
+ len, transfer_size,
+ musb_ep->packet_sz);
return;
+ }
}
#endif /* Mentor's DMA */
@@ -915,6 +928,10 @@ void musb_g_rx(struct musb *musb, u8 epnum)
void __iomem *epio = musb->endpoints[epnum].regs;
struct dma_channel *dma;
struct musb_hw_ep *hw_ep = &musb->endpoints[epnum];
+ u16 len;
+ u32 residue;
+ struct dma_controller *c = musb->dma_controller;
+ int status;
if (hw_ep->is_shared_fifo)
musb_ep = &hw_ep->ep_in;
@@ -924,8 +941,12 @@ void musb_g_rx(struct musb *musb, u8 epnum)
musb_ep_select(mbase, epnum);
req = next_request(musb_ep);
- if (!req)
+ if (!req) {
+ musb_ep->rx_pending = 1;
+ dev_dbg(musb->controller, "Packet recieved on %s but no request
queued\n",
+ musb_ep->end_point.name);
return;
+ }
request = &req->request;
@@ -960,7 +981,57 @@ void musb_g_rx(struct musb *musb, u8 epnum)
/* "should not happen"; likely RXPKTRDY pending for DMA */
dev_dbg(musb->controller, "%s busy, csr %04x\n",
musb_ep->end_point.name, csr);
+#ifndef CONFIG_USB_UX500_DMA
return;
+#else
+ /* For short_not_ok type transfers and mode0 transfers */
+ if (dma->desired_mode == 0 || request->short_not_ok)
+ return;
+
+ if (!(csr & MUSB_RXCSR_RXPKTRDY)) {
+ dev_dbg(musb->controller,
+ "%s: %s, DMA busy and Packet not ready\n",
+ __func__, musb_ep->end_point.name);
+ return;
+ }
+
+ /* For Mode1 we get here for the last short packet */
+ len = musb_readw(epio, MUSB_RXCOUNT);
+
+ /* We should get here only for a short packet.*/
+ if (len == musb_ep->packet_sz) {
+ dev_dbg(musb->controller,
+ "%s: %s, Packet not short RXCOUNT=%d\n",
+ __func__, musb_ep->end_point.name, len);
+ return;
+ }
+ /* Pause the channel to get the correct transfer residue.*/
+ status = c->channel_pause(musb_ep->dma);
+ residue = c->tx_status(musb_ep->dma);
+ status = c->check_residue(musb_ep->dma, residue);
+
+ if (status) {
+ /* Something's wrong */
+ status = c->channel_resume(musb_ep->dma);
+ return;
+ }
+ /* In cases when we don't know the transfer length the short
+ * packet indicates end of current transfer.
+ */
+ status = c->channel_abort(musb_ep->dma);
+ /* Update with the actual number of bytes transferred */
+ request->actual = musb_ep->dma->prog_len - residue;
+ /* Clear DMA bits in the CSR */
+ csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAENAB
+ | MUSB_RXCSR_DMAMODE);
+ musb_writew(epio, MUSB_RXCSR, csr);
+ /* Proceed to read the short packet */
+ rxstate(musb, req);
+ /* Don't program next transfer, it will tamper with the DMA
+ * busy condition. Wait for next OUT
+ */
+ return;
+#endif
}
if (dma && (csr & MUSB_RXCSR_DMAENAB)) {
@@ -988,6 +1059,26 @@ void musb_g_rx(struct musb *musb, u8 epnum)
musb_writew(epio, MUSB_RXCSR, csr);
}
+ /* We get here after DMA completion */
+ if ((dma->desired_mode == 1) && (!request->short_not_ok)) {
+ /* Incomplete? wait for next OUT packet */
+ if (request->actual < request->length) {
+ dev_dbg(musb->controller,
+ "%s: %s, Wait for next OUT\n",
+ __func__, musb_ep->end_point.name);
+ } else if (request->actual == request->length) {
+ dev_dbg(musb->controller,
+ "%s: %s, Transfer over mode1 done\n",
+ __func__, musb_ep->end_point.name);
+ musb_g_giveback(musb_ep, request, 0);
+ } else {
+ dev_dbg(musb->controller,
+ "%s: %s, Transfer length exceeded!!\n",
+ __func__, musb_ep->end_point.name);
+ }
+ return;
+ }
+
/* incomplete, and not short? wait for next IN packet */
if ((request->actual < request->length)
&& (musb_ep->dma->actual_len
@@ -1361,8 +1452,24 @@ static int musb_gadget_queue(struct usb_ep *ep, struct
usb_request *req,
list_add_tail(&request->list, &musb_ep->req_list);
/* it this is the head of the queue, start i/o ... */
- if (!musb_ep->busy && &request->list == musb_ep->req_list.next)
+ if (!musb_ep->busy && &request->list == musb_ep->req_list.next) {
+
+ /* In case of RX, if there is no packet pending to be read
+ * from fifo then wait for next interrupt
+ */
+ if (!request->tx) {
+ if (!musb_ep->rx_pending) {
+ dev_dbg(musb->controller, "No packet pending
for %s\n",
+ ep->name);
+ goto cleanup;
+ } else {
+ musb_ep->rx_pending = 0;
+ dev_dbg(musb->controller, "Read packet from
fifo %s\n",
+ ep->name);
+ }
+ }
musb_ep_restart(musb, request);
+ }
cleanup:
spin_unlock_irqrestore(&musb->lock, lockflags);
diff --git a/drivers/usb/musb/musb_gadget.h b/drivers/usb/musb/musb_gadget.h
index 66b7c5e..55eb686 100644
--- a/drivers/usb/musb/musb_gadget.h
+++ b/drivers/usb/musb/musb_gadget.h
@@ -90,6 +90,9 @@ struct musb_ep {
u8 busy;
u8 hb_mult;
+
+ /* true if packet is received in fifo and req_list is empty */
+ u8 rx_pending;
};
static inline struct musb_ep *to_musb_ep(struct usb_ep *ep)
diff --git a/drivers/usb/musb/ux500_dma.c b/drivers/usb/musb/ux500_dma.c
index f1059e7..6eddf14 100644
--- a/drivers/usb/musb/ux500_dma.c
+++ b/drivers/usb/musb/ux500_dma.c
@@ -106,7 +106,8 @@ static bool ux500_configure_channel(struct dma_channel
*channel,
direction = ux500_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
addr_width = (len & 0x3) ? DMA_SLAVE_BUSWIDTH_1_BYTE :
- DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ((dma_addr & 0x2) ? DMA_SLAVE_BUSWIDTH_2_BYTES :
+ DMA_SLAVE_BUSWIDTH_4_BYTES);
slave_conf.direction = direction;
slave_conf.src_addr = usb_fifo_addr;
@@ -134,6 +135,15 @@ static bool ux500_configure_channel(struct dma_channel
*channel,
return true;
}
+/**
+ * ux500_dma_controller_allocate() - allocates the DMA channels
+ * @c: pointer to DMA controller
+ * @hw_ep: pointer to endpoint
+ * @is_tx: transmit or receive direction
+ *
+ * This function allocates the DMA channel and initializes
+ * the channel
+*/
static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c,
struct musb_hw_ep *hw_ep, u8 is_tx)
{
@@ -173,6 +183,13 @@ static struct dma_channel
*ux500_dma_channel_allocate(struct dma_controller *c,
return &(ux500_channel->channel);
}
+/**
+ * ux500_dma_channel_release() - releases the DMA channel
+ * @channel: channel to be released
+ *
+ * This function releases the DMA channel
+ *
+*/
static void ux500_dma_channel_release(struct dma_channel *channel)
{
struct ux500_dma_channel *ux500_channel = channel->private_data;
@@ -192,13 +209,22 @@ static int ux500_dma_is_compatible(struct dma_channel
*channel,
{
if ((maxpacket & 0x3) ||
((int)buf & 0x3) ||
- (length < 512) ||
- (length & 0x3))
+ (length < 512))
return false;
else
return true;
}
+/**
+ * ux500_dma_channel_program() - Configures the channel and initiates transfer
+ * @channel: pointer to DMA channel
+ * @packet_sz: packet size
+ * @mode: mode
+ * @dma_addr: physical address of memory
+ * @len: length
+ *
+ * This function configures the channel and initiates the DMA transfer
+*/
static int ux500_dma_channel_program(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
@@ -220,6 +246,12 @@ static int ux500_dma_channel_program(struct dma_channel
*channel,
return ret;
}
+/**
+ * ux500_dma_channel_abort() - aborts the DMA transfer
+ * @channel: pointer to DMA channel.
+ *
+ * This function aborts the DMA transfer.
+*/
static int ux500_dma_channel_abort(struct dma_channel *channel)
{
struct ux500_dma_channel *ux500_channel = channel->private_data;
@@ -254,6 +286,124 @@ static int ux500_dma_channel_abort(struct dma_channel
*channel)
return 0;
}
+/**
+ * ux500_dma_channel_pause() - pauses the DMA transfer
+ * @channel: pointer to DMA channel.
+ *
+ * This function pauses the DMA transfer. This is needed to get
+ * the correct residue of an ongoing DMA transfer
+*/
+static int ux500_dma_channel_pause(struct dma_channel *channel)
+{
+ struct ux500_dma_channel *ux500_channel = channel->private_data;
+ struct ux500_dma_controller *controller = ux500_channel->controller;
+ struct musb *musb = controller->private_data;
+ int status;
+
+ status = ux500_channel->dma_chan->device->
+ device_control(ux500_channel->dma_chan,
+ DMA_PAUSE, 0);
+
+ dev_dbg(musb->controller,
+ "%s channel=%d, is_tx=%d, status=%d\n",
+ __func__, ux500_channel->ch_num, ux500_channel->is_tx
+ , status);
+ return status;
+}
+
+/**
+ * ux500_dma_channel_resume() - resumes the DMA transfer
+ * @channel: pointer to DMA channel.
+ *
+ * This function resumes a paused DMA transfer.
+*/
+static int ux500_dma_channel_resume(struct dma_channel *channel)
+{
+ struct ux500_dma_channel *ux500_channel = channel->private_data;
+ struct ux500_dma_controller *controller = ux500_channel->controller;
+ struct musb *musb = controller->private_data;
+ int status;
+
+ status = ux500_channel->dma_chan->device->
+ device_control(ux500_channel->dma_chan,
+ DMA_RESUME, 0);
+ dev_dbg(musb->controller,
+ "%s channel=%d, is_tx=%d, status=%d\n",
+ __func__, ux500_channel->ch_num, ux500_channel->is_tx
+ , status);
+ return status;
+}
+
+/**
+ * ux500_dma_tx_status() - Gets the residue of an ongoing DMA transfer
+ * @channel: pointer to DMA channel
+ *
+ * This function will get the number of bytes left to be transferred
+ * over the DMA
+ */
+static int ux500_dma_tx_status(struct dma_channel *channel)
+{
+ struct ux500_dma_channel *ux500_channel = channel->private_data;
+ struct ux500_dma_controller *controller = ux500_channel->controller;
+ struct musb *musb = controller->private_data;
+ struct dma_tx_state txstate;
+
+ ux500_channel->dma_chan->device->
+ device_tx_status(ux500_channel->dma_chan,
+ ux500_channel->cookie, &txstate);
+ dev_dbg(musb->controller,
+ "%s channel=%d, is_tx=%d, residue=%d\n",
+ __func__, ux500_channel->ch_num, ux500_channel->is_tx
+ , txstate.residue);
+ return txstate.residue;
+}
+
+/**
+ * ux500_dma_check_residue() - Checks if the DMA transfer residue is valid
+ * @channel: pointer to DMA channel
+ */
+static int ux500_dma_check_residue(struct dma_channel *channel, u32 residue)
+{
+ struct ux500_dma_channel *ux500_channel = channel->private_data;
+ struct ux500_dma_controller *controller = ux500_channel->controller;
+ struct musb *musb = controller->private_data;
+ int status;
+
+ dev_dbg(musb->controller,
+ "%s channel=%d, is_tx=%d, residue=%d\n",
+ __func__, ux500_channel->ch_num, ux500_channel->is_tx
+ , residue);
+
+ /* In cases where we know the transfer length and were expecting
+ * a DMA completion we could get into the DMA busy condition
+ * here if the next packet is short and the EP interrupt occurs
+ * before we recieve dma_completion interrupt for current transfer
+ * Wait for dma_completion. MUSB will interrupt us again for this
+ * short packet when we clear the DMA bits
+ */
+ if (!residue) {
+ dev_dbg(musb->controller,
+ "%s: Wait for DMA completion\n",
+ __func__);
+ status = -EINPROGRESS;
+ } else if (residue == ux500_channel->channel.prog_len) {
+ /* Nothing transferred over DMA? */
+ WARN_ON(1);
+ status = -EINVAL;
+ } else {
+ /* residue looks OK */
+ status = 0;
+ }
+
+ return status;
+}
+
+/**
+ * ux500_dma_controller_stop() - releases all the channels and frees the DMA
pipes
+ * @c: pointer to DMA controller
+ *
+ * This function frees all of the logical channels and frees the DMA pipes
+*/
static int ux500_dma_controller_stop(struct dma_controller *c)
{
struct ux500_dma_controller *controller = container_of(c,
@@ -285,6 +435,15 @@ static int ux500_dma_controller_stop(struct dma_controller
*c)
return 0;
}
+
+/**
+ * ux500_dma_controller_start() - creates the logical channels pool and
registers callbacks
+ * @c: pointer to DMA Controller
+ *
+ * This function requests the logical channels from the DMA driver and creates
+ * logical channels based on event lines and also registers the callbacks which
+ * are invoked after data transfer in the transmit or receive direction.
+*/
static int ux500_dma_controller_start(struct dma_controller *c)
{
struct ux500_dma_controller *controller = container_of(c,
@@ -356,6 +515,12 @@ static int ux500_dma_controller_start(struct
dma_controller *c)
return 0;
}
+/**
+ * dma_controller_destroy() - deallocates the DMA controller
+ * @c: pointer to dma controller.
+ *
+ * This function deallocates the DMA controller.
+*/
void dma_controller_destroy(struct dma_controller *c)
{
struct ux500_dma_controller *controller = container_of(c,
@@ -364,6 +529,15 @@ void dma_controller_destroy(struct dma_controller *c)
kfree(controller);
}
+/**
+ * dma_controller_create() - creates the dma controller and initializes
callbacks
+ *
+ * @musb: pointer to mentor core driver data instance|
+ * @base: base address of musb registers.
+ *
+ * This function creates the DMA controller and initializes the callbacks
+ * that are invoked from the Mentor IP core.
+*/
struct dma_controller *__devinit
dma_controller_create(struct musb *musb, void __iomem *base)
{
@@ -387,6 +561,10 @@ dma_controller_create(struct musb *musb, void __iomem
*base)
controller->controller.channel_release = ux500_dma_channel_release;
controller->controller.channel_program = ux500_dma_channel_program;
controller->controller.channel_abort = ux500_dma_channel_abort;
+ controller->controller.channel_pause = ux500_dma_channel_pause;
+ controller->controller.channel_resume = ux500_dma_channel_resume;
+ controller->controller.tx_status = ux500_dma_tx_status;
+ controller->controller.check_residue = ux500_dma_check_residue;
controller->controller.is_compatible = ux500_dma_is_compatible;
return &controller->controller;
--
1.7.1
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html