| // 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; |
| } |