mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
845 lines
22 KiB
845 lines
22 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget |
|
* |
|
* epn.c - Generic endpoints management |
|
* |
|
* Copyright 2017 IBM Corporation |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/delay.h> |
|
#include <linux/ioport.h> |
|
#include <linux/slab.h> |
|
#include <linux/errno.h> |
|
#include <linux/list.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/proc_fs.h> |
|
#include <linux/prefetch.h> |
|
#include <linux/clk.h> |
|
#include <linux/usb/gadget.h> |
|
#include <linux/of.h> |
|
#include <linux/of_gpio.h> |
|
#include <linux/regmap.h> |
|
#include <linux/dma-mapping.h> |
|
|
|
#include "vhub.h" |
|
|
|
#define EXTRA_CHECKS |
|
|
|
#ifdef EXTRA_CHECKS |
|
#define CHECK(ep, expr, fmt...) \ |
|
do { \ |
|
if (!(expr)) EPDBG(ep, "CHECK:" fmt); \ |
|
} while(0) |
|
#else |
|
#define CHECK(ep, expr, fmt...) do { } while(0) |
|
#endif |
|
|
|
static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req) |
|
{ |
|
unsigned int act = req->req.actual; |
|
unsigned int len = req->req.length; |
|
unsigned int chunk; |
|
|
|
/* There should be no DMA ongoing */ |
|
WARN_ON(req->active); |
|
|
|
/* Calculate next chunk size */ |
|
chunk = len - act; |
|
if (chunk > ep->ep.maxpacket) |
|
chunk = ep->ep.maxpacket; |
|
else if ((chunk < ep->ep.maxpacket) || !req->req.zero) |
|
req->last_desc = 1; |
|
|
|
EPVDBG(ep, "kick req %p act=%d/%d chunk=%d last=%d\n", |
|
req, act, len, chunk, req->last_desc); |
|
|
|
/* If DMA unavailable, using staging EP buffer */ |
|
if (!req->req.dma) { |
|
|
|
/* For IN transfers, copy data over first */ |
|
if (ep->epn.is_in) { |
|
memcpy(ep->buf, req->req.buf + act, chunk); |
|
vhub_dma_workaround(ep->buf); |
|
} |
|
writel(ep->buf_dma, ep->epn.regs + AST_VHUB_EP_DESC_BASE); |
|
} else { |
|
if (ep->epn.is_in) |
|
vhub_dma_workaround(req->req.buf); |
|
writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE); |
|
} |
|
|
|
/* Start DMA */ |
|
req->active = true; |
|
writel(VHUB_EP_DMA_SET_TX_SIZE(chunk), |
|
ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
writel(VHUB_EP_DMA_SET_TX_SIZE(chunk) | VHUB_EP_DMA_SINGLE_KICK, |
|
ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
} |
|
|
|
static void ast_vhub_epn_handle_ack(struct ast_vhub_ep *ep) |
|
{ |
|
struct ast_vhub_req *req; |
|
unsigned int len; |
|
u32 stat; |
|
|
|
/* Read EP status */ |
|
stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
|
|
/* Grab current request if any */ |
|
req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); |
|
|
|
EPVDBG(ep, "ACK status=%08x is_in=%d, req=%p (active=%d)\n", |
|
stat, ep->epn.is_in, req, req ? req->active : 0); |
|
|
|
/* In absence of a request, bail out, must have been dequeued */ |
|
if (!req) |
|
return; |
|
|
|
/* |
|
* Request not active, move on to processing queue, active request |
|
* was probably dequeued |
|
*/ |
|
if (!req->active) |
|
goto next_chunk; |
|
|
|
/* Check if HW has moved on */ |
|
if (VHUB_EP_DMA_RPTR(stat) != 0) { |
|
EPDBG(ep, "DMA read pointer not 0 !\n"); |
|
return; |
|
} |
|
|
|
/* No current DMA ongoing */ |
|
req->active = false; |
|
|
|
/* Grab length out of HW */ |
|
len = VHUB_EP_DMA_TX_SIZE(stat); |
|
|
|
/* If not using DMA, copy data out if needed */ |
|
if (!req->req.dma && !ep->epn.is_in && len) |
|
memcpy(req->req.buf + req->req.actual, ep->buf, len); |
|
|
|
/* Adjust size */ |
|
req->req.actual += len; |
|
|
|
/* Check for short packet */ |
|
if (len < ep->ep.maxpacket) |
|
req->last_desc = 1; |
|
|
|
/* That's it ? complete the request and pick a new one */ |
|
if (req->last_desc >= 0) { |
|
ast_vhub_done(ep, req, 0); |
|
req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, |
|
queue); |
|
|
|
/* |
|
* Due to lock dropping inside "done" the next request could |
|
* already be active, so check for that and bail if needed. |
|
*/ |
|
if (!req || req->active) |
|
return; |
|
} |
|
|
|
next_chunk: |
|
ast_vhub_epn_kick(ep, req); |
|
} |
|
|
|
static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep) |
|
{ |
|
/* |
|
* d_next == d_last means descriptor list empty to HW, |
|
* thus we can only have AST_VHUB_DESCS_COUNT-1 descriptors |
|
* in the list |
|
*/ |
|
return (ep->epn.d_last + AST_VHUB_DESCS_COUNT - ep->epn.d_next - 1) & |
|
(AST_VHUB_DESCS_COUNT - 1); |
|
} |
|
|
|
static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, |
|
struct ast_vhub_req *req) |
|
{ |
|
struct ast_vhub_desc *desc = NULL; |
|
unsigned int act = req->act_count; |
|
unsigned int len = req->req.length; |
|
unsigned int chunk; |
|
|
|
/* Mark request active if not already */ |
|
req->active = true; |
|
|
|
/* If the request was already completely written, do nothing */ |
|
if (req->last_desc >= 0) |
|
return; |
|
|
|
EPVDBG(ep, "kick act=%d/%d chunk_max=%d free_descs=%d\n", |
|
act, len, ep->epn.chunk_max, ast_vhub_count_free_descs(ep)); |
|
|
|
/* While we can create descriptors */ |
|
while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) { |
|
unsigned int d_num; |
|
|
|
/* Grab next free descriptor */ |
|
d_num = ep->epn.d_next; |
|
desc = &ep->epn.descs[d_num]; |
|
ep->epn.d_next = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); |
|
|
|
/* Calculate next chunk size */ |
|
chunk = len - act; |
|
if (chunk <= ep->epn.chunk_max) { |
|
/* |
|
* Is this the last packet ? Because of having up to 8 |
|
* packets in a descriptor we can't just compare "chunk" |
|
* with ep.maxpacket. We have to see if it's a multiple |
|
* of it to know if we have to send a zero packet. |
|
* Sadly that involves a modulo which is a bit expensive |
|
* but probably still better than not doing it. |
|
*/ |
|
if (!chunk || !req->req.zero || (chunk % ep->ep.maxpacket) != 0) |
|
req->last_desc = d_num; |
|
} else { |
|
chunk = ep->epn.chunk_max; |
|
} |
|
|
|
EPVDBG(ep, " chunk: act=%d/%d chunk=%d last=%d desc=%d free=%d\n", |
|
act, len, chunk, req->last_desc, d_num, |
|
ast_vhub_count_free_descs(ep)); |
|
|
|
/* Populate descriptor */ |
|
desc->w0 = cpu_to_le32(req->req.dma + act); |
|
|
|
/* Interrupt if end of request or no more descriptors */ |
|
|
|
/* |
|
* TODO: Be smarter about it, if we don't have enough |
|
* descriptors request an interrupt before queue empty |
|
* or so in order to be able to populate more before |
|
* the HW runs out. This isn't a problem at the moment |
|
* as we use 256 descriptors and only put at most one |
|
* request in the ring. |
|
*/ |
|
desc->w1 = cpu_to_le32(VHUB_DSC1_IN_SET_LEN(chunk)); |
|
if (req->last_desc >= 0 || !ast_vhub_count_free_descs(ep)) |
|
desc->w1 |= cpu_to_le32(VHUB_DSC1_IN_INTERRUPT); |
|
|
|
/* Account packet */ |
|
req->act_count = act = act + chunk; |
|
} |
|
|
|
if (likely(desc)) |
|
vhub_dma_workaround(desc); |
|
|
|
/* Tell HW about new descriptors */ |
|
writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next), |
|
ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
|
|
EPVDBG(ep, "HW kicked, d_next=%d dstat=%08x\n", |
|
ep->epn.d_next, readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS)); |
|
} |
|
|
|
static void ast_vhub_epn_handle_ack_desc(struct ast_vhub_ep *ep) |
|
{ |
|
struct ast_vhub_req *req; |
|
unsigned int len, d_last; |
|
u32 stat, stat1; |
|
|
|
/* Read EP status, workaround HW race */ |
|
do { |
|
stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
stat1 = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
} while(stat != stat1); |
|
|
|
/* Extract RPTR */ |
|
d_last = VHUB_EP_DMA_RPTR(stat); |
|
|
|
/* Grab current request if any */ |
|
req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); |
|
|
|
EPVDBG(ep, "ACK status=%08x is_in=%d ep->d_last=%d..%d\n", |
|
stat, ep->epn.is_in, ep->epn.d_last, d_last); |
|
|
|
/* Check all completed descriptors */ |
|
while (ep->epn.d_last != d_last) { |
|
struct ast_vhub_desc *desc; |
|
unsigned int d_num; |
|
bool is_last_desc; |
|
|
|
/* Grab next completed descriptor */ |
|
d_num = ep->epn.d_last; |
|
desc = &ep->epn.descs[d_num]; |
|
ep->epn.d_last = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); |
|
|
|
/* Grab len out of descriptor */ |
|
len = VHUB_DSC1_IN_LEN(le32_to_cpu(desc->w1)); |
|
|
|
EPVDBG(ep, " desc %d len=%d req=%p (act=%d)\n", |
|
d_num, len, req, req ? req->active : 0); |
|
|
|
/* If no active request pending, move on */ |
|
if (!req || !req->active) |
|
continue; |
|
|
|
/* Adjust size */ |
|
req->req.actual += len; |
|
|
|
/* Is that the last chunk ? */ |
|
is_last_desc = req->last_desc == d_num; |
|
CHECK(ep, is_last_desc == (len < ep->ep.maxpacket || |
|
(req->req.actual >= req->req.length && |
|
!req->req.zero)), |
|
"Last packet discrepancy: last_desc=%d len=%d r.act=%d " |
|
"r.len=%d r.zero=%d mp=%d\n", |
|
is_last_desc, len, req->req.actual, req->req.length, |
|
req->req.zero, ep->ep.maxpacket); |
|
|
|
if (is_last_desc) { |
|
/* |
|
* Because we can only have one request at a time |
|
* in our descriptor list in this implementation, |
|
* d_last and ep->d_last should now be equal |
|
*/ |
|
CHECK(ep, d_last == ep->epn.d_last, |
|
"DMA read ptr mismatch %d vs %d\n", |
|
d_last, ep->epn.d_last); |
|
|
|
/* Note: done will drop and re-acquire the lock */ |
|
ast_vhub_done(ep, req, 0); |
|
req = list_first_entry_or_null(&ep->queue, |
|
struct ast_vhub_req, |
|
queue); |
|
break; |
|
} |
|
} |
|
|
|
/* More work ? */ |
|
if (req) |
|
ast_vhub_epn_kick_desc(ep, req); |
|
} |
|
|
|
void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep) |
|
{ |
|
if (ep->epn.desc_mode) |
|
ast_vhub_epn_handle_ack_desc(ep); |
|
else |
|
ast_vhub_epn_handle_ack(ep); |
|
} |
|
|
|
static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req, |
|
gfp_t gfp_flags) |
|
{ |
|
struct ast_vhub_req *req = to_ast_req(u_req); |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
struct ast_vhub *vhub = ep->vhub; |
|
unsigned long flags; |
|
bool empty; |
|
int rc; |
|
|
|
/* Paranoid checks */ |
|
if (!u_req || !u_req->complete || !u_req->buf) { |
|
dev_warn(&vhub->pdev->dev, "Bogus EPn request ! u_req=%p\n", u_req); |
|
if (u_req) { |
|
dev_warn(&vhub->pdev->dev, "complete=%p internal=%d\n", |
|
u_req->complete, req->internal); |
|
} |
|
return -EINVAL; |
|
} |
|
|
|
/* Endpoint enabled ? */ |
|
if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || |
|
!ep->dev->enabled) { |
|
EPDBG(ep, "Enqueuing request on wrong or disabled EP\n"); |
|
return -ESHUTDOWN; |
|
} |
|
|
|
/* Map request for DMA if possible. For now, the rule for DMA is |
|
* that: |
|
* |
|
* * For single stage mode (no descriptors): |
|
* |
|
* - The buffer is aligned to a 8 bytes boundary (HW requirement) |
|
* - For a OUT endpoint, the request size is a multiple of the EP |
|
* packet size (otherwise the controller will DMA past the end |
|
* of the buffer if the host is sending a too long packet). |
|
* |
|
* * For descriptor mode (tx only for now), always. |
|
* |
|
* We could relax the latter by making the decision to use the bounce |
|
* buffer based on the size of a given *segment* of the request rather |
|
* than the whole request. |
|
*/ |
|
if (ep->epn.desc_mode || |
|
((((unsigned long)u_req->buf & 7) == 0) && |
|
(ep->epn.is_in || !(u_req->length & (u_ep->maxpacket - 1))))) { |
|
rc = usb_gadget_map_request_by_dev(&vhub->pdev->dev, u_req, |
|
ep->epn.is_in); |
|
if (rc) { |
|
dev_warn(&vhub->pdev->dev, |
|
"Request mapping failure %d\n", rc); |
|
return rc; |
|
} |
|
} else |
|
u_req->dma = 0; |
|
|
|
EPVDBG(ep, "enqueue req @%p\n", req); |
|
EPVDBG(ep, " l=%d dma=0x%x zero=%d noshort=%d noirq=%d is_in=%d\n", |
|
u_req->length, (u32)u_req->dma, u_req->zero, |
|
u_req->short_not_ok, u_req->no_interrupt, |
|
ep->epn.is_in); |
|
|
|
/* Initialize request progress fields */ |
|
u_req->status = -EINPROGRESS; |
|
u_req->actual = 0; |
|
req->act_count = 0; |
|
req->active = false; |
|
req->last_desc = -1; |
|
spin_lock_irqsave(&vhub->lock, flags); |
|
empty = list_empty(&ep->queue); |
|
|
|
/* Add request to list and kick processing if empty */ |
|
list_add_tail(&req->queue, &ep->queue); |
|
if (empty) { |
|
if (ep->epn.desc_mode) |
|
ast_vhub_epn_kick_desc(ep, req); |
|
else |
|
ast_vhub_epn_kick(ep, req); |
|
} |
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void ast_vhub_stop_active_req(struct ast_vhub_ep *ep, |
|
bool restart_ep) |
|
{ |
|
u32 state, reg, loops; |
|
|
|
/* Stop DMA activity */ |
|
if (ep->epn.desc_mode) |
|
writel(VHUB_EP_DMA_CTRL_RESET, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
else |
|
writel(0, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
|
|
/* Wait for it to complete */ |
|
for (loops = 0; loops < 1000; loops++) { |
|
state = readl(ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
state = VHUB_EP_DMA_PROC_STATUS(state); |
|
if (state == EP_DMA_PROC_RX_IDLE || |
|
state == EP_DMA_PROC_TX_IDLE) |
|
break; |
|
udelay(1); |
|
} |
|
if (loops >= 1000) |
|
dev_warn(&ep->vhub->pdev->dev, "Timeout waiting for DMA\n"); |
|
|
|
/* If we don't have to restart the endpoint, that's it */ |
|
if (!restart_ep) |
|
return; |
|
|
|
/* Restart the endpoint */ |
|
if (ep->epn.desc_mode) { |
|
/* |
|
* Take out descriptors by resetting the DMA read |
|
* pointer to be equal to the CPU write pointer. |
|
* |
|
* Note: If we ever support creating descriptors for |
|
* requests that aren't the head of the queue, we |
|
* may have to do something more complex here, |
|
* especially if the request being taken out is |
|
* not the current head descriptors. |
|
*/ |
|
reg = VHUB_EP_DMA_SET_RPTR(ep->epn.d_next) | |
|
VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next); |
|
writel(reg, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
|
|
/* Then turn it back on */ |
|
writel(ep->epn.dma_conf, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
} else { |
|
/* Single mode: just turn it back on */ |
|
writel(ep->epn.dma_conf, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
} |
|
} |
|
|
|
static int ast_vhub_epn_dequeue(struct usb_ep* u_ep, struct usb_request *u_req) |
|
{ |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
struct ast_vhub *vhub = ep->vhub; |
|
struct ast_vhub_req *req; |
|
unsigned long flags; |
|
int rc = -EINVAL; |
|
|
|
spin_lock_irqsave(&vhub->lock, flags); |
|
|
|
/* Make sure it's actually queued on this endpoint */ |
|
list_for_each_entry (req, &ep->queue, queue) { |
|
if (&req->req == u_req) |
|
break; |
|
} |
|
|
|
if (&req->req == u_req) { |
|
EPVDBG(ep, "dequeue req @%p active=%d\n", |
|
req, req->active); |
|
if (req->active) |
|
ast_vhub_stop_active_req(ep, true); |
|
ast_vhub_done(ep, req, -ECONNRESET); |
|
rc = 0; |
|
} |
|
|
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
return rc; |
|
} |
|
|
|
void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep) |
|
{ |
|
u32 reg; |
|
|
|
if (WARN_ON(ep->d_idx == 0)) |
|
return; |
|
reg = readl(ep->epn.regs + AST_VHUB_EP_CONFIG); |
|
if (ep->epn.stalled || ep->epn.wedged) |
|
reg |= VHUB_EP_CFG_STALL_CTRL; |
|
else |
|
reg &= ~VHUB_EP_CFG_STALL_CTRL; |
|
writel(reg, ep->epn.regs + AST_VHUB_EP_CONFIG); |
|
|
|
if (!ep->epn.stalled && !ep->epn.wedged) |
|
writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), |
|
ep->vhub->regs + AST_VHUB_EP_TOGGLE); |
|
} |
|
|
|
static int ast_vhub_set_halt_and_wedge(struct usb_ep* u_ep, bool halt, |
|
bool wedge) |
|
{ |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
struct ast_vhub *vhub = ep->vhub; |
|
unsigned long flags; |
|
|
|
EPDBG(ep, "Set halt (%d) & wedge (%d)\n", halt, wedge); |
|
|
|
if (!u_ep || !u_ep->desc) |
|
return -EINVAL; |
|
if (ep->d_idx == 0) |
|
return 0; |
|
if (ep->epn.is_iso) |
|
return -EOPNOTSUPP; |
|
|
|
spin_lock_irqsave(&vhub->lock, flags); |
|
|
|
/* Fail with still-busy IN endpoints */ |
|
if (halt && ep->epn.is_in && !list_empty(&ep->queue)) { |
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
return -EAGAIN; |
|
} |
|
ep->epn.stalled = halt; |
|
ep->epn.wedged = wedge; |
|
ast_vhub_update_epn_stall(ep); |
|
|
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int ast_vhub_epn_set_halt(struct usb_ep *u_ep, int value) |
|
{ |
|
return ast_vhub_set_halt_and_wedge(u_ep, value != 0, false); |
|
} |
|
|
|
static int ast_vhub_epn_set_wedge(struct usb_ep *u_ep) |
|
{ |
|
return ast_vhub_set_halt_and_wedge(u_ep, true, true); |
|
} |
|
|
|
static int ast_vhub_epn_disable(struct usb_ep* u_ep) |
|
{ |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
struct ast_vhub *vhub = ep->vhub; |
|
unsigned long flags; |
|
u32 imask, ep_ier; |
|
|
|
EPDBG(ep, "Disabling !\n"); |
|
|
|
spin_lock_irqsave(&vhub->lock, flags); |
|
|
|
ep->epn.enabled = false; |
|
|
|
/* Stop active DMA if any */ |
|
ast_vhub_stop_active_req(ep, false); |
|
|
|
/* Disable endpoint */ |
|
writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); |
|
|
|
/* Disable ACK interrupt */ |
|
imask = VHUB_EP_IRQ(ep->epn.g_idx); |
|
ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); |
|
ep_ier &= ~imask; |
|
writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); |
|
writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); |
|
|
|
/* Nuke all pending requests */ |
|
ast_vhub_nuke(ep, -ESHUTDOWN); |
|
|
|
/* No more descriptor associated with request */ |
|
ep->ep.desc = NULL; |
|
|
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int ast_vhub_epn_enable(struct usb_ep* u_ep, |
|
const struct usb_endpoint_descriptor *desc) |
|
{ |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
struct ast_vhub_dev *dev; |
|
struct ast_vhub *vhub; |
|
u16 maxpacket, type; |
|
unsigned long flags; |
|
u32 ep_conf, ep_ier, imask; |
|
|
|
/* Check arguments */ |
|
if (!u_ep || !desc) |
|
return -EINVAL; |
|
|
|
maxpacket = usb_endpoint_maxp(desc); |
|
if (!ep->d_idx || !ep->dev || |
|
desc->bDescriptorType != USB_DT_ENDPOINT || |
|
maxpacket == 0 || maxpacket > ep->ep.maxpacket) { |
|
EPDBG(ep, "Invalid EP enable,d_idx=%d,dev=%p,type=%d,mp=%d/%d\n", |
|
ep->d_idx, ep->dev, desc->bDescriptorType, |
|
maxpacket, ep->ep.maxpacket); |
|
return -EINVAL; |
|
} |
|
if (ep->d_idx != usb_endpoint_num(desc)) { |
|
EPDBG(ep, "EP number mismatch !\n"); |
|
return -EINVAL; |
|
} |
|
|
|
if (ep->epn.enabled) { |
|
EPDBG(ep, "Already enabled\n"); |
|
return -EBUSY; |
|
} |
|
dev = ep->dev; |
|
vhub = ep->vhub; |
|
|
|
/* Check device state */ |
|
if (!dev->driver) { |
|
EPDBG(ep, "Bogus device state: driver=%p speed=%d\n", |
|
dev->driver, dev->gadget.speed); |
|
return -ESHUTDOWN; |
|
} |
|
|
|
/* Grab some info from the descriptor */ |
|
ep->epn.is_in = usb_endpoint_dir_in(desc); |
|
ep->ep.maxpacket = maxpacket; |
|
type = usb_endpoint_type(desc); |
|
ep->epn.d_next = ep->epn.d_last = 0; |
|
ep->epn.is_iso = false; |
|
ep->epn.stalled = false; |
|
ep->epn.wedged = false; |
|
|
|
EPDBG(ep, "Enabling [%s] %s num %d maxpacket=%d\n", |
|
ep->epn.is_in ? "in" : "out", usb_ep_type_string(type), |
|
usb_endpoint_num(desc), maxpacket); |
|
|
|
/* Can we use DMA descriptor mode ? */ |
|
ep->epn.desc_mode = ep->epn.descs && ep->epn.is_in; |
|
if (ep->epn.desc_mode) |
|
memset(ep->epn.descs, 0, 8 * AST_VHUB_DESCS_COUNT); |
|
|
|
/* |
|
* Large send function can send up to 8 packets from |
|
* one descriptor with a limit of 4095 bytes. |
|
*/ |
|
ep->epn.chunk_max = ep->ep.maxpacket; |
|
if (ep->epn.is_in) { |
|
ep->epn.chunk_max <<= 3; |
|
while (ep->epn.chunk_max > 4095) |
|
ep->epn.chunk_max -= ep->ep.maxpacket; |
|
} |
|
|
|
switch(type) { |
|
case USB_ENDPOINT_XFER_CONTROL: |
|
EPDBG(ep, "Only one control endpoint\n"); |
|
return -EINVAL; |
|
case USB_ENDPOINT_XFER_INT: |
|
ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_INT); |
|
break; |
|
case USB_ENDPOINT_XFER_BULK: |
|
ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_BULK); |
|
break; |
|
case USB_ENDPOINT_XFER_ISOC: |
|
ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_ISO); |
|
ep->epn.is_iso = true; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
/* Encode the rest of the EP config register */ |
|
if (maxpacket < 1024) |
|
ep_conf |= VHUB_EP_CFG_SET_MAX_PKT(maxpacket); |
|
if (!ep->epn.is_in) |
|
ep_conf |= VHUB_EP_CFG_DIR_OUT; |
|
ep_conf |= VHUB_EP_CFG_SET_EP_NUM(usb_endpoint_num(desc)); |
|
ep_conf |= VHUB_EP_CFG_ENABLE; |
|
ep_conf |= VHUB_EP_CFG_SET_DEV(dev->index + 1); |
|
EPVDBG(ep, "config=%08x\n", ep_conf); |
|
|
|
spin_lock_irqsave(&vhub->lock, flags); |
|
|
|
/* Disable HW and reset DMA */ |
|
writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); |
|
writel(VHUB_EP_DMA_CTRL_RESET, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
|
|
/* Configure and enable */ |
|
writel(ep_conf, ep->epn.regs + AST_VHUB_EP_CONFIG); |
|
|
|
if (ep->epn.desc_mode) { |
|
/* Clear DMA status, including the DMA read ptr */ |
|
writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
|
|
/* Set descriptor base */ |
|
writel(ep->epn.descs_dma, |
|
ep->epn.regs + AST_VHUB_EP_DESC_BASE); |
|
|
|
/* Set base DMA config value */ |
|
ep->epn.dma_conf = VHUB_EP_DMA_DESC_MODE; |
|
if (ep->epn.is_in) |
|
ep->epn.dma_conf |= VHUB_EP_DMA_IN_LONG_MODE; |
|
|
|
/* First reset and disable all operations */ |
|
writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
|
|
/* Enable descriptor mode */ |
|
writel(ep->epn.dma_conf, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
} else { |
|
/* Set base DMA config value */ |
|
ep->epn.dma_conf = VHUB_EP_DMA_SINGLE_STAGE; |
|
|
|
/* Reset and switch to single stage mode */ |
|
writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
writel(ep->epn.dma_conf, |
|
ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); |
|
writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); |
|
} |
|
|
|
/* Cleanup data toggle just in case */ |
|
writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), |
|
vhub->regs + AST_VHUB_EP_TOGGLE); |
|
|
|
/* Cleanup and enable ACK interrupt */ |
|
imask = VHUB_EP_IRQ(ep->epn.g_idx); |
|
writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); |
|
ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); |
|
ep_ier |= imask; |
|
writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); |
|
|
|
/* Woot, we are online ! */ |
|
ep->epn.enabled = true; |
|
|
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void ast_vhub_epn_dispose(struct usb_ep *u_ep) |
|
{ |
|
struct ast_vhub_ep *ep = to_ast_ep(u_ep); |
|
|
|
if (WARN_ON(!ep->dev || !ep->d_idx)) |
|
return; |
|
|
|
EPDBG(ep, "Releasing endpoint\n"); |
|
|
|
/* Take it out of the EP list */ |
|
list_del_init(&ep->ep.ep_list); |
|
|
|
/* Mark the address free in the device */ |
|
ep->dev->epns[ep->d_idx - 1] = NULL; |
|
|
|
/* Free name & DMA buffers */ |
|
kfree(ep->ep.name); |
|
ep->ep.name = NULL; |
|
dma_free_coherent(&ep->vhub->pdev->dev, |
|
AST_VHUB_EPn_MAX_PACKET + |
|
8 * AST_VHUB_DESCS_COUNT, |
|
ep->buf, ep->buf_dma); |
|
ep->buf = NULL; |
|
ep->epn.descs = NULL; |
|
|
|
/* Mark free */ |
|
ep->dev = NULL; |
|
} |
|
|
|
static const struct usb_ep_ops ast_vhub_epn_ops = { |
|
.enable = ast_vhub_epn_enable, |
|
.disable = ast_vhub_epn_disable, |
|
.dispose = ast_vhub_epn_dispose, |
|
.queue = ast_vhub_epn_queue, |
|
.dequeue = ast_vhub_epn_dequeue, |
|
.set_halt = ast_vhub_epn_set_halt, |
|
.set_wedge = ast_vhub_epn_set_wedge, |
|
.alloc_request = ast_vhub_alloc_request, |
|
.free_request = ast_vhub_free_request, |
|
}; |
|
|
|
struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) |
|
{ |
|
struct ast_vhub *vhub = d->vhub; |
|
struct ast_vhub_ep *ep; |
|
unsigned long flags; |
|
int i; |
|
|
|
/* Find a free one (no device) */ |
|
spin_lock_irqsave(&vhub->lock, flags); |
|
for (i = 0; i < vhub->max_epns; i++) |
|
if (vhub->epns[i].dev == NULL) |
|
break; |
|
if (i >= vhub->max_epns) { |
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
return NULL; |
|
} |
|
|
|
/* Set it up */ |
|
ep = &vhub->epns[i]; |
|
ep->dev = d; |
|
spin_unlock_irqrestore(&vhub->lock, flags); |
|
|
|
DDBG(d, "Allocating gen EP %d for addr %d\n", i, addr); |
|
INIT_LIST_HEAD(&ep->queue); |
|
ep->d_idx = addr; |
|
ep->vhub = vhub; |
|
ep->ep.ops = &ast_vhub_epn_ops; |
|
ep->ep.name = kasprintf(GFP_KERNEL, "ep%d", addr); |
|
d->epns[addr-1] = ep; |
|
ep->epn.g_idx = i; |
|
ep->epn.regs = vhub->regs + 0x200 + (i * 0x10); |
|
|
|
ep->buf = dma_alloc_coherent(&vhub->pdev->dev, |
|
AST_VHUB_EPn_MAX_PACKET + |
|
8 * AST_VHUB_DESCS_COUNT, |
|
&ep->buf_dma, GFP_KERNEL); |
|
if (!ep->buf) { |
|
kfree(ep->ep.name); |
|
ep->ep.name = NULL; |
|
return NULL; |
|
} |
|
ep->epn.descs = ep->buf + AST_VHUB_EPn_MAX_PACKET; |
|
ep->epn.descs_dma = ep->buf_dma + AST_VHUB_EPn_MAX_PACKET; |
|
|
|
usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EPn_MAX_PACKET); |
|
list_add_tail(&ep->ep.ep_list, &d->gadget.ep_list); |
|
ep->ep.caps.type_iso = true; |
|
ep->ep.caps.type_bulk = true; |
|
ep->ep.caps.type_int = true; |
|
ep->ep.caps.dir_in = true; |
|
ep->ep.caps.dir_out = true; |
|
|
|
return ep; |
|
}
|
|
|