Cc: Kukjin Kim <kgene.kim <at> samsung.com> Cc: Greg Kroah-Hartman <gregkh <at> suse.de> Cc: Felipe Balbi <balbi <at> ti.com> Adds Exynos USB3.0 device controller driver to support USB peripherals on Exynos5 chips. Signed-off-by: Anton Tikhomirov <av.tikhomirov@xxxxxxxxxxx> --- drivers/usb/gadget/Kconfig | 13 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/exynos_ss_udc.c | 2511 ++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/exynos_ss_udc.h | 342 +++++ 4 files changed, 2867 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/exynos_ss_udc.c create mode 100644 drivers/usb/gadget/exynos_ss_udc.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 7ecb68a..8d3bc6e 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -268,6 +268,19 @@ config USB_S3C_HSOTG The Samsung S3C64XX USB2.0 high-speed gadget controller integrated into the S3C64XX series SoC. +config USB_EXYNOS_SS_UDC + tristate "EXYNOS SuperSpeed USB 3.0 Device controller" + depends on EXYNOS_DEV_SS_UDC + select USB_GADGET_DUALSPEED + select USB_GADGET_SUPERSPEED + help + The Samsung Exynos SuperSpeed USB 3.0 device controller + integrated into the Exynos5 series SoC. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "exynos_ss_udc" and force all + gadget drivers to also be dynamically linked. + config USB_IMX tristate "Freescale i.MX1 USB Peripheral Controller" depends on ARCH_MXC diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index b7f6eef..092bc76 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX_PCI) += ci13xxx_pci.o obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o +obj-$(CONFIG_USB_EXYNOS_SS_UDC) += exynos_ss_udc.o obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o obj-$(CONFIG_USB_EG20T) += pch_udc.o obj-$(CONFIG_USB_MV_UDC) += mv_udc.o diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c new file mode 100644 index 0000000..aaad33c --- /dev/null +++ b/drivers/usb/gadget/exynos_ss_udc.c @@ -0,0 +1,2511 @@ +/* linux/drivers/usb/gadget/exynos_ss_udc.c + * + * Copyright 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS SuperSpeed USB 3.0 Device Controlle driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <asm/byteorder.h> + +#include <mach/map.h> + +#include <plat/regs-usb3-exynos-drd.h> +#include <plat/udc-ss.h> +#include <plat/usb-phy.h> + +#include "exynos_ss_udc.h" + +static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int result); +static void exynos_ss_udc_complete_setup(struct usb_ep *ep, + struct usb_request *req); +static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result); +static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + bool continuing); +static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep); +static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep); +static int exynos_ss_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int exynos_ss_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); + + +static int poll_bit_set(void __iomem *ptr, u32 val, int timeout) +{ + u32 reg; + + do { + reg = readl(ptr); + if (reg & val) + return 0; + + udelay(1); + } while (timeout-- > 0); + + return -ETIME; +} + +static int poll_bit_clear(void __iomem *ptr, u32 val, int timeout) +{ + u32 reg; + + do { + reg = readl(ptr); + if (!(reg & val)) + return 0; + + udelay(1); + } while (timeout-- > 0); + + return -ETIME; +} + +/** + * ep_from_windex - convert control wIndex value to endpoint + * @udc: The device state. + * @windex: The control request wIndex field (in host order). + * + * Convert the given wIndex into a pointer to an driver endpoint + * structure, or return NULL if it is not a valid endpoint. + */ +static struct exynos_ss_udc_ep *ep_from_windex(struct exynos_ss_udc *udc, + u32 windex) +{ + struct exynos_ss_udc_ep *ep = &udc->eps[windex & 0x7F]; + int dir = (windex & USB_DIR_IN) ? 1 : 0; + int idx = windex & 0x7F; + + if (windex >= 0x100) + return NULL; + + if (idx > EXYNOS_USB3_EPS) + return NULL; + + if (idx && ep->dir_in != dir) + return NULL; + + return ep; +} + +/** + * get_ep_head - return the first request on the endpoint + * @udc_ep: The endpoint to get request from. + * + * Get the first request on the endpoint. + */ +static struct exynos_ss_udc_req *get_ep_head(struct exynos_ss_udc_ep *udc_ep) +{ + if (list_empty(&udc_ep->queue)) + return NULL; + + return list_first_entry(&udc_ep->queue, + struct exynos_ss_udc_req, + queue); +} + +/** + * on_list - check request is on the given endpoint + * @ep: The endpoint to check. + * @test: The request to test if it is on the endpoint. + */ +static bool on_list(struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *test) +{ + struct exynos_ss_udc_req *udc_req, *treq; + + list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) { + if (udc_req == test) + return true; + } + + return false; +} + +/** + * exynos_ss_udc_map_dma - map the DMA memory being used for the request + * @udc: The device state. + * @udc_ep: The endpoint the request is on. + * @req: The request being processed. + * + * We've been asked to queue a request, so ensure that the memory buffer + * is correctly setup for DMA. If we've been passed an extant DMA address + * then ensure the buffer has been synced to memory. If our buffer has no + * DMA memory, then we map the memory and mark our request to allow us to + * cleanup on completion. + */ +static int exynos_ss_udc_map_dma(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct usb_request *req) +{ + enum dma_data_direction dir; + struct exynos_ss_udc_req *udc_req = our_req(req); + + dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* if the length is zero, ignore the DMA data */ + if (udc_req->req.length == 0) + return 0; + + if (req->dma == DMA_ADDR_INVALID) { + dma_addr_t dma; + + dma = dma_map_single(udc->dev, + req->buf, req->length, dir); + + if (unlikely(dma_mapping_error(udc->dev, dma))) + goto dma_error; + + udc_req->mapped = 1; + req->dma = dma; + } else + dma_sync_single_for_device(udc->dev, + req->dma, req->length, dir); + + return 0; + +dma_error: + dev_err(udc->dev, "%s: failed to map buffer %p, %d bytes\n", + __func__, req->buf, req->length); + + return -EIO; +} + +/** + * exynos_ss_udc_unmap_dma - unmap the DMA memory being used for the request + * @udc: The device state. + * @udc_ep: The endpoint for the request. + * @udc_req: The request being processed. + * + * This is the reverse of exynos_ss_udc_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. + */ +static void exynos_ss_udc_unmap_dma(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req) +{ + struct usb_request *req = &udc_req->req; + enum dma_data_direction dir; + + dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* ignore this if we're not moving any data */ + if (udc_req->req.length == 0) + return; + + if (udc_req->mapped) { + /* we mapped this, so unmap and remove the dma */ + + dma_unmap_single(udc->dev, req->dma, req->length, dir); + + req->dma = DMA_ADDR_INVALID; + udc_req->mapped = 0; + } +} + +/** + * exynos_ss_udc_issue_epcmd - issue physical endpoint-specific command + * @udc: The device state. + * @epcmd: The command to issue. + */ +static int exynos_ss_udc_issue_epcmd(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep_command *epcmd) +{ + int res; + u32 depcmd; + + /* If some of parameters are not in use, we will write it anyway + for simplification */ + writel(epcmd->param0, udc->regs + EXYNOS_USB3_DEPCMDPAR0(epcmd->ep)); + writel(epcmd->param1, udc->regs + EXYNOS_USB3_DEPCMDPAR1(epcmd->ep)); + writel(epcmd->param2, udc->regs + EXYNOS_USB3_DEPCMDPAR2(epcmd->ep)); + + depcmd = epcmd->cmdtyp | epcmd->cmdflags; + writel(depcmd, udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep)); + + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep), + EXYNOS_USB3_DEPCMDx_CmdAct, + 1000); + return res; +} + +/** + * exynos_ss_udc_run_stop - start/stop the device controller operation + * @udc: The device state. + * @is_on: The action to take (1 - start, 0 - stop). + */ +static void exynos_ss_udc_run_stop(struct exynos_ss_udc *udc, int is_on) +{ + int res; + + if (is_on) { + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_Run_Stop); + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DSTS, + EXYNOS_USB3_DSTS_DevCtrlHlt, + 1000); + } else { + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_Run_Stop); + res = poll_bit_set(udc->regs + EXYNOS_USB3_DSTS, + EXYNOS_USB3_DSTS_DevCtrlHlt, + 1000); + } + + if (res < 0) + dev_dbg(udc->dev, "Failed to %sconnect by software\n", + is_on ? "" : "dis"); +} + +/** + * exynos_ss_udc_pullup - software-controlled connect/disconnect to USB host + * @gadget: The peripheral being connected/disconnected. + * @is_on: The action to take (1 - connect, 0 - disconnect). + */ +static int exynos_ss_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct exynos_ss_udc *udc = our_udc(gadget); + + exynos_ss_udc_run_stop(udc, is_on); + + return 0; +} + +/** + * exynos_ss_udc_get_config_params - get UDC configuration + * @params: The controller parameters being returned to the caller. + */ +void exynos_ss_udc_get_config_params(struct usb_dcd_config_params *params) +{ + params->bU1devExitLat = EXYNOS_USB3_U1_DEV_EXIT_LAT; + params->bU2DevExitLat = cpu_to_le16(EXYNOS_USB3_U2_DEV_EXIT_LAT); +} + +static struct usb_gadget_ops exynos_ss_udc_gadget_ops = { + .pullup = exynos_ss_udc_pullup, + .get_config_params = exynos_ss_udc_get_config_params, + .udc_start = exynos_ss_udc_start, + .udc_stop = exynos_ss_udc_stop, +}; + +/** + * exynos_ss_udc_ep_enable - configure endpoint, making it usable + * @ep: The endpoint being configured. + * @desc: The descriptor for desired behavior. + */ +static int exynos_ss_udc_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + int dir_in; + int epnum; + + dev_dbg(udc->dev, + "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", + __func__, ep->name, desc->bEndpointAddress, desc->bmAttributes, + desc->wMaxPacketSize, desc->bInterval); + + /* not to be called for EP0 */ + WARN_ON(udc_ep->epnum == 0); + + if (udc_ep->enabled) + return -EINVAL; + + epnum = (desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + if (epnum != udc_ep->epnum) { + dev_err(udc->dev, "%s: EP number mismatch!\n", __func__); + return -EINVAL; + } + + dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + if (dir_in != udc_ep->dir_in) { + dev_err(udc->dev, "%s: EP direction mismatch!\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc_ep->lock, flags); + + /* update the endpoint state */ + udc_ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + udc_ep->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + switch (udc_ep->type) { + case USB_ENDPOINT_XFER_ISOC: + dev_err(udc->dev, "no current ISOC support\n"); + spin_unlock_irqrestore(&udc_ep->lock, flags); + return -EINVAL; + + case USB_ENDPOINT_XFER_BULK: + dev_dbg(udc->dev, "Bulk endpoint\n"); + break; + + case USB_ENDPOINT_XFER_INT: + dev_dbg(udc->dev, "Interrupt endpoint\n"); + break; + + case USB_ENDPOINT_XFER_CONTROL: + dev_dbg(udc->dev, "Control endpoint\n"); + break; + } + + exynos_ss_udc_ep_activate(udc, udc_ep); + udc_ep->enabled = 1; + udc_ep->halted = udc_ep->wedged = 0; + spin_unlock_irqrestore(&udc_ep->lock, flags); + + return 0; +} + +/** + * exynos_ss_udc_ep_disable - endpoint is no longer usable + * @ep: The endpoint being unconfigured. + */ +static int exynos_ss_udc_ep_disable(struct usb_ep *ep) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, + udc_ep->epnum, udc_ep->dir_in ? "in" : "out"); + + spin_lock_irqsave(&udc_ep->lock, flags); + exynos_ss_udc_ep_deactivate(udc, udc_ep); + udc_ep->enabled = 0; + spin_unlock_irqrestore(&udc_ep->lock, flags); + + /* terminate all requests with shutdown */ + exynos_ss_udc_kill_all_requests(udc, udc_ep, -ESHUTDOWN); + + return 0; +} + +/** + * exynos_ss_udc_ep_alloc_request - allocate a request object + * @ep: The endpoint to be used with the request. + * @flags: Allocation flags. + * + * Allocate a new USB request structure appropriate for the specified endpoint. + */ +static struct usb_request *exynos_ss_udc_ep_alloc_request(struct usb_ep *ep, + gfp_t flags) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_req *req; + + dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum); + + req = kzalloc(sizeof(struct exynos_ss_udc_req), flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + req->req.dma = DMA_ADDR_INVALID; + return &req->req; +} + +/** + * exynos_ss_udc_ep_free_request - free a request object + * @ep: The endpoint associated with the request. + * @req: The request being freed. + * + * Reverse the effect of exynos_ss_udc_ep_alloc_request(). + */ +static void exynos_ss_udc_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_req *udc_req = our_req(req); + + dev_dbg(udc->dev, "%s: ep%d, req %p\n", __func__, udc_ep->epnum, req); + + kfree(udc_req); +} + +/** + * exynos_ss_udc_ep_queue - queue (submit) an I/O request to an endpoint + * @ep: The endpoint associated with the request. + * @req: The request being submitted. + * @gfp_flags: Not used. + * + * Queue a request and start it if the first. + */ +static int exynos_ss_udc_ep_queue(struct usb_ep *ep, + struct usb_request *req, + gfp_t gfp_flags) +{ + struct exynos_ss_udc_req *udc_req = our_req(req); + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long irqflags; + bool first; + int ret; + + dev_dbg(udc->dev, "%s: ep%d%s (%p): %d@%p, noi=%d, zero=%d, snok=%d\n", + __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out", req, + req->length, req->buf, req->no_interrupt, + req->zero, req->short_not_ok); + + /* initialise status of the request */ + INIT_LIST_HEAD(&udc_req->queue); + + req->actual = 0; + req->status = -EINPROGRESS; + + /* Sync the buffers as necessary */ + if (req->buf == udc->ctrl_buff) + req->dma = udc->ctrl_buff_dma; + else if (req->buf == udc->ep0_buff) + req->dma = udc->ep0_buff_dma; + else { + ret = exynos_ss_udc_map_dma(udc, udc_ep, req); + if (ret) + return ret; + } + + spin_lock_irqsave(&udc_ep->lock, irqflags); + + first = list_empty(&udc_ep->queue); + list_add_tail(&udc_req->queue, &udc_ep->queue); + + if (first && !udc_ep->not_ready) + exynos_ss_udc_start_req(udc, udc_ep, udc_req, false); + + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return 0; +} + +/** + * exynos_ss_udc_ep_dequeue - dequeue an I/O request from an endpoint + * @ep: The endpoint associated with the request. + * @req: The request being canceled. + * + * Dequeue a request and call its completion routine. + */ +static int exynos_ss_udc_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct exynos_ss_udc_req *udc_req = our_req(req); + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d%s (%p)\n", __func__, + udc_ep->epnum, udc_ep->dir_in ? "in" : "out", req); + + spin_lock_irqsave(&udc_ep->lock, flags); + + if (!on_list(udc_ep, udc_req)) { + spin_unlock_irqrestore(&udc_ep->lock, flags); + return -EINVAL; + } + + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, -ECONNRESET); + spin_unlock_irqrestore(&udc_ep->lock, flags); + + return 0; +} + +/** + * exynos_ss_udc_ep_sethalt - set/clear the endpoint halt feature + * @ep: The endpoint being stalled/reset. + * @value: The action to take (1 - set stall, 0 - clear stall). + */ +static int exynos_ss_udc_ep_sethalt(struct usb_ep *ep, int value) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_ep_command epcmd; + int index = get_phys_epnum(udc_ep); + unsigned long irqflags; + int res; + + dev_dbg(udc->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + + spin_lock_irqsave(&udc_ep->lock, irqflags); + + if (value && udc_ep->dir_in && udc_ep->req) { + dev_dbg(udc->dev, "%s: transfer in progress!\n", __func__); + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + return -EAGAIN; + } + + if (udc_ep->epnum == 0) + /* Only OUT direction can be stalled */ + epcmd.ep = 0; + else + epcmd.ep = index; + + if (value) + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL; + else + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL; + + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to set/clear stall\n"); + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + return res; + } + + if (udc_ep->epnum == 0 && value) + udc->ep0_state = EP0_STALL; + + /* If everything is Ok, we mark endpoint as halted */ + if (value) + udc_ep->halted = 1; + else + udc_ep->halted = udc_ep->wedged = 0; + + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return 0; +} + +/** + * exynos_ss_udc_ep_setwedge - set the halt feature and ignore clear requests + * @ep: The endpoint being wedged. + */ +static int exynos_ss_udc_ep_setwedge(struct usb_ep *ep) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + unsigned long irqflags; + + spin_lock_irqsave(&udc_ep->lock, irqflags); + udc_ep->wedged = 1; + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return exynos_ss_udc_ep_sethalt(ep, 1); +} + +static struct usb_ep_ops exynos_ss_udc_ep_ops = { + .enable = exynos_ss_udc_ep_enable, + .disable = exynos_ss_udc_ep_disable, + .alloc_request = exynos_ss_udc_ep_alloc_request, + .free_request = exynos_ss_udc_ep_free_request, + .queue = exynos_ss_udc_ep_queue, + .dequeue = exynos_ss_udc_ep_dequeue, + .set_halt = exynos_ss_udc_ep_sethalt, + .set_wedge = exynos_ss_udc_ep_setwedge, +}; + +/** + * exynos_ss_udc_start_req - start a USB request from an endpoint's queue + * @udc: The device state. + * @udc_ep: The endpoint to process a request for. + * @udc_req: The request being started. + * @continuing: True if we are doing more for the current request. + * + * Start the given request running by setting the TRB appropriately, + * and issuing Start Transfer endpoint command. + */ +static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + bool continuing) +{ + struct exynos_ss_udc_ep_command epcmd; + struct usb_request *ureq = &udc_req->req; + enum trb_control trb_type = NORMAL; + int epnum = udc_ep->epnum; + int xfer_length; + int res; + + dev_dbg(udc->dev, "%s: ep%d%s, req %p\n", __func__, epnum, + udc_ep->dir_in ? "in" : "out", ureq); + + /* If endpoint is stalled, we will restart request later */ + if (udc_ep->halted) { + dev_dbg(udc->dev, "%s: ep%d is stalled\n", __func__, epnum); + return; + } + + udc_ep->req = udc_req; + + /* Get type of TRB */ + if (epnum == 0 && !continuing) + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + trb_type = CONTROL_SETUP; + break; + + case EP0_DATA_PHASE: + trb_type = CONTROL_DATA; + break; + + case EP0_STATUS_PHASE_2: + trb_type = CONTROL_STATUS_2; + break; + + case EP0_STATUS_PHASE_3: + trb_type = CONTROL_STATUS_3; + break; + default: + dev_warn(udc->dev, "%s: Erroneous EP0 state (%d)", + __func__, udc->ep0_state); + return; + break; + } + else + trb_type = NORMAL; + + /* Get transfer length */ + if (udc_ep->dir_in) + xfer_length = ureq->length; + else + xfer_length = (ureq->length + udc_ep->ep.maxpacket - 1) & + ~(udc_ep->ep.maxpacket - 1); + + /* Fill TRB */ + udc_ep->trb->buff_ptr_low = (u32) ureq->dma; + udc_ep->trb->buff_ptr_high = 0; + udc_ep->trb->param1 = EXYNOS_USB3_TRB_BUFSIZ(xfer_length); + udc_ep->trb->param2 = EXYNOS_USB3_TRB_IOC | + EXYNOS_USB3_TRB_LST | + EXYNOS_USB3_TRB_HWO | + EXYNOS_USB3_TRB_TRBCTL(trb_type); + + /* Start Transfer */ + epcmd.ep = get_phys_epnum(udc_ep); + epcmd.param0 = 0; + epcmd.param1 = udc_ep->trb_dma; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start transfer\n"); + + udc_ep->tri = (readl(udc->regs + EXYNOS_USB3_DEPCMD(epcmd.ep)) >> + EXYNOS_USB3_DEPCMDx_EventParam_SHIFT) & + EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT; +} + +/** + * exynos_ss_udc_enqueue_status - start a request for EP0 status stage + * @udc: The device state. + */ +static void exynos_ss_udc_enqueue_status(struct exynos_ss_udc *udc) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing status request\n", __func__); + + req->zero = 0; + req->length = 0; + req->buf = udc->ctrl_buff; + req->complete = NULL; + + if (!list_empty(&udc_req->queue)) { + dev_info(udc->dev, "%s already queued???\n", __func__); + return; + } + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) + dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret); +} + +/** + * exynos_ss_udc_enqueue_data - start a request for EP0 data stage + * @udc: The device state. + * @buff: The buffer used for data. + * @length: The length of data. + * @complete: The function called when request completes. + */ +static int exynos_ss_udc_enqueue_data(struct exynos_ss_udc *udc, + void *buff, int length, + void (*complete) (struct usb_ep *ep, + struct usb_request *req)) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing data request\n", __func__); + + req->zero = 0; + req->length = length; + + if (buff == NULL) + req->buf = udc->ep0_buff; + else + req->buf = buff; + + req->complete = complete; + + if (!list_empty(&udc_req->queue)) { + dev_info(udc->dev, "%s: already queued???\n", __func__); + return -EAGAIN; + } + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) { + dev_err(udc->dev, "%s: failed to enqueue data request (%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +/** + * exynos_ss_udc_enqueue_setup - start a request for EP0 setup stage + * @udc: The device state. + * + * Enqueue a request on EP0 if necessary to receive any SETUP packets + * from the host. + */ +static void exynos_ss_udc_enqueue_setup(struct exynos_ss_udc *udc) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing setup request\n", __func__); + + req->zero = 0; + req->length = EXYNOS_USB3_CTRL_BUFF_SIZE; + req->buf = udc->ctrl_buff; + req->complete = exynos_ss_udc_complete_setup; + + if (!list_empty(&udc_req->queue)) { + dev_dbg(udc->dev, "%s already queued???\n", __func__); + return; + } + + udc->eps[0].dir_in = 0; + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) + dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret); +} + +/** + * exynos_ss_udc_complete_set_sel - completion of SET_SEL request data stage + * @ep: The endpoint the request was on. + * @req: The request completed. + */ +static void exynos_ss_udc_complete_set_sel(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + u8 *sel = req->buf; + u32 param; + u32 dgcmd; + int res; + + /* Our device is U1/U2 enabled, so we will use U2PEL */ + param = sel[5] << 8 | sel[4]; + /* Documentation says "If the value is greater than 125us, then + * software must program a value of zero into this register */ + if (param > 125) + param = 0; + + dev_dbg(udc->dev, "%s: dgcmd_param = 0x%08x\n", __func__, param); + + dgcmd = EXYNOS_USB3_DGCMD_CmdAct | + EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams; + + writel(param, udc->regs + EXYNOS_USB3_DGCMDPAR); + writel(dgcmd, udc->regs + EXYNOS_USB3_DGCMD); + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DGCMD, + EXYNOS_USB3_DGCMD_CmdAct, + 1000); + if (res < 0) + dev_err(udc->dev, "Failed to set periodic parameters\n"); +} + +/** + * exynos_ss_udc_process_set_sel - process request SET_SEL + * @udc: The device state. + */ +static int exynos_ss_udc_process_set_sel(struct exynos_ss_udc *udc) +{ + int ret; + + dev_dbg(udc->dev, "%s\n", __func__); + + ret = exynos_ss_udc_enqueue_data(udc, udc->ep0_buff, + EXYNOS_USB3_EP0_BUFF_SIZE, + exynos_ss_udc_complete_set_sel); + if (ret < 0) { + dev_err(udc->dev, "%s: failed to become ready for SEL data\n", + __func__); + return ret; + } + + return 1; +} + +/** + * exynos_ss_udc_process_clr_feature - process request CLEAR_FEATURE + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_clr_feature(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep; + struct exynos_ss_udc_req *udc_req; + bool restart; + + dev_dbg(udc->dev, "%s\n", __func__); + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_U1_ENABLE: + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); + break; + + case USB_DEVICE_U2_ENABLE: + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU2Ena); + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, le16_to_cpu(ctrl->wIndex)); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, le16_to_cpu(ctrl->wIndex)); + return -ENOENT; + } + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_ENDPOINT_HALT: + if (!udc_ep->wedged) { + exynos_ss_udc_ep_sethalt(&udc_ep->ep, 0); + + /* If we have pending request, then start it */ + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, + udc_req, false); + } + } + break; + + default: + return -ENOENT; + } + + break; + + default: + return -ENOENT; + } + + return 1; +} + +/** + * exynos_ss_udc_process_set_feature - process request SET_FEATURE + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_feature(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep; + + dev_dbg(udc->dev, "%s\n", __func__); + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_U1_ENABLE: + /* Temporarily disabled because of HW bug */ +#if 0 + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); +#endif + break; + + case USB_DEVICE_U2_ENABLE: + /* Temporarily disabled because of HW bug */ +#if 0 + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU2Ena); +#endif + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, le16_to_cpu(ctrl->wIndex)); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, le16_to_cpu(ctrl->wIndex)); + return -ENOENT; + } + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_ENDPOINT_HALT: + exynos_ss_udc_ep_sethalt(&udc_ep->ep, 1); + break; + + default: + return -ENOENT; + } + + break; + + default: + return -ENOENT; + } + + return 1; +} + +/** + * exynos_ss_udc_process_get_status - process request GET_STATUS + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_get_status(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep0 = &udc->eps[0]; + struct exynos_ss_udc_ep *udc_ep; + u8 *reply = udc->ep0_buff; + u32 reg; + int ret; + + dev_dbg(udc->dev, "%s: USB_REQ_GET_STATUS\n", __func__); + + if (!udc_ep0->dir_in) { + dev_warn(udc->dev, "%s: direction out?\n", __func__); + return -EINVAL; + } + + if (le16_to_cpu(ctrl->wLength) != 2) + return -EINVAL; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + *reply = 1; + if (udc->gadget.speed == USB_SPEED_SUPER) { + reg = readl(udc->regs + EXYNOS_USB3_DCTL); + + if (reg & EXYNOS_USB3_DCTL_InitU1Ena) + *reply |= 1 << 2; + + if (reg & EXYNOS_USB3_DCTL_InitU2Ena) + *reply |= 1 << 3; + } + *(reply + 1) = 0; + break; + + case USB_RECIP_INTERFACE: + /* currently, the data result should be zero */ + *reply = 0; + *(reply + 1) = 0; + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, le16_to_cpu(ctrl->wIndex)); + if (!udc_ep) + return -ENOENT; + + *reply = udc_ep->halted ? 1 : 0; + *(reply + 1) = 0; + break; + + default: + return 0; + } + + ret = exynos_ss_udc_enqueue_data(udc, reply, 2, NULL); + if (ret) { + dev_err(udc->dev, "%s: failed to send reply\n", __func__); + return ret; + } + + return 1; +} + +/** + * exynos_ss_udc_process_control - process a control request + * @udc: The device state. + * @ctrl: The control request received. + * + * The controller has received the SETUP phase of a control request, and + * needs to work out what to do next (and whether to pass it on to the + * gadget driver). + */ +static void exynos_ss_udc_process_control(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *ep0 = &udc->eps[0]; + int ret = 0; + + dev_dbg(udc->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", + ctrl->bRequest, ctrl->bRequestType, + ctrl->wValue, ctrl->wLength); + + /* record the direction of the request, for later use when enquing + * packets onto EP0. */ + + ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; + dev_dbg(udc->dev, "ctrl: dir_in=%d\n", ep0->dir_in); + + /* if we've no data with this request, then the last part of the + * transaction is going to implicitly be IN. */ + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + udc->ep0_three_stage = 0; + udc->ep0_state = EP0_STATUS_PHASE_2; + } else + udc->ep0_three_stage = 1; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + __bic32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr_MASK); + __orr32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr(ctrl->wValue)); + + dev_info(udc->dev, "new address %d\n", ctrl->wValue); + + udc->ep0_state = EP0_WAIT_NRDY; + return; + + case USB_REQ_GET_STATUS: + ret = exynos_ss_udc_process_get_status(udc, ctrl); + break; + + case USB_REQ_CLEAR_FEATURE: + ret = exynos_ss_udc_process_clr_feature(udc, ctrl); + udc->ep0_state = EP0_WAIT_NRDY; + break; + + case USB_REQ_SET_FEATURE: + ret = exynos_ss_udc_process_set_feature(udc, ctrl); + udc->ep0_state = EP0_WAIT_NRDY; + break; + + case USB_REQ_SET_SEL: + ret = exynos_ss_udc_process_set_sel(udc); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && udc->driver) { + ret = udc->driver->setup(&udc->gadget, ctrl); + if (ret < 0) + dev_dbg(udc->dev, "driver->setup() ret %d\n", ret); + } + + /* the request is either unhandlable, or is not formatted correctly + * so respond with a STALL for the status stage to indicate failure. + */ + + if (ret < 0) { + struct exynos_ss_udc_ep_command epcmd; + int res; + + dev_dbg(udc->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + epcmd.ep = 0; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to set/clear stall\n"); + + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); + } +} + +/** + * exynos_ss_udc_complete_setup - completion of a setup transfer + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself submitted for + * EP0 setup packets. + */ +static void exynos_ss_udc_complete_setup(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + + if (req->status < 0) { + dev_dbg(udc->dev, "%s: failed %d\n", __func__, req->status); + return; + } + + exynos_ss_udc_process_control(udc, req->buf); +} + +/** + * exynos_ss_udc_kill_all_requests - remove all requests from the endpoint's queue + * @udc: The device state. + * @ep: The endpoint the requests may be on. + * @result: The result code to use. + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int result) +{ + struct exynos_ss_udc_req *udc_req, *treq; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum); + + spin_lock_irqsave(&udc_ep->lock, flags); + + list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) { + + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result); + } + + spin_unlock_irqrestore(&udc_ep->lock, flags); +} + +/** + * exynos_ss_udc_complete_request - complete a request given to us + * @udc: The device state. + * @udc_ep: The endpoint the request was on. + * @udc_req: The request being completed. + * @result: The result code (0 => Ok, otherwise errno) + * + * The given request has finished, so call the necessary completion + * if it has one and then look to see if we can start a new request + * on the endpoint. + * + * Note, expects the ep to already be locked as appropriate. + */ +static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result) +{ + bool restart; + + if (!udc_req) { + dev_dbg(udc->dev, "%s: nothing to complete\n", __func__); + return; + } + + dev_dbg(udc->dev, "complete: ep %p %s, req %p, %d => %p\n", + udc_ep, udc_ep->ep.name, udc_req, + result, udc_req->req.complete); + + /* only replace the status if we've not already set an error + * from a previous transaction */ + + if (udc_req->req.status == -EINPROGRESS) + udc_req->req.status = result; + + udc_ep->req = NULL; + udc_ep->tri = 0; + list_del_init(&udc_req->queue); + + if (udc_req->req.buf != udc->ctrl_buff && + udc_req->req.buf != udc->ep0_buff) + exynos_ss_udc_unmap_dma(udc, udc_ep, udc_req); + + if (udc_ep->epnum == 0) { + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + udc->ep0_state = EP0_DATA_PHASE; + break; + case EP0_DATA_PHASE: + udc->ep0_state = EP0_WAIT_NRDY; + break; + case EP0_STATUS_PHASE_2: + case EP0_STATUS_PHASE_3: + udc->ep0_state = EP0_SETUP_PHASE; + break; + default: + dev_err(udc->dev, "%s: Erroneous EP0 state (%d)", + __func__, udc->ep0_state); + /* Will try to repair from it */ + udc->ep0_state = EP0_SETUP_PHASE; + return; + break; + } + } + + /* call the complete request with the locks off, just in case the + * request tries to queue more work for this endpoint. */ + + if (udc_req->req.complete) { + spin_unlock(&udc_ep->lock); + udc_req->req.complete(&udc_ep->ep, &udc_req->req); + spin_lock(&udc_ep->lock); + } + + /* Look to see if there is anything else to do. Note, the completion + * of the previous request may have caused a new request to be started + * so be careful when doing this. */ + + if (!udc_ep->req && result >= 0) { + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, udc_req, false); + } + } +} + +/** + * exynos_ss_udc_complete_request_lock - complete a request given to us (locked) + * @udc: The device state. + * @udc_ep: The endpoint the request was on. + * @udc_req: The request being completed. + * @result: The result code (0 => Ok, otherwise errno). + * + * See exynos_ss_udc_complete_request(), but called with the endpoint's + * lock held. + */ +static void exynos_ss_udc_complete_request_lock(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result) +{ + unsigned long flags; + + spin_lock_irqsave(&udc_ep->lock, flags); + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result); + spin_unlock_irqrestore(&udc_ep->lock, flags); +} + +/** + * exynos_ss_udc_complete_in - complete IN transfer + * @udc: The device state. + * @udc_ep: The endpoint that has just completed. + * + * An IN transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void exynos_ss_udc_complete_in(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_req *udc_req = udc_ep->req; + struct usb_request *req = &udc_req->req; + int size_left; + + dev_dbg(udc->dev, "%s: ep%d, req %p\n", __func__, udc_ep->epnum, req); + + if (!udc_req) { + dev_dbg(udc->dev, "XferCompl but no req\n"); + return; + } + + if (udc_ep->trb->param2 & EXYNOS_USB3_TRB_HWO) { + dev_dbg(udc->dev, "%s: HWO bit set!\n", __func__); + return; + } + + size_left = udc_ep->trb->param1 & EXYNOS_USB3_TRB_BUFSIZ_MASK; + udc_req->req.actual = udc_req->req.length - size_left; + + if (size_left) + dev_dbg(udc->dev, "%s: BUFSIZ is not zero (%d)", + __func__, size_left); + + exynos_ss_udc_complete_request_lock(udc, udc_ep, udc_req, 0); +} + + +/** + * exynos_ss_udc_complete_out - complete OUT transfer + * @udc: The device state. + * @epnum: The endpoint that has just completed. + * + * An OUT transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void exynos_ss_udc_complete_out(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_req *udc_req = udc_ep->req; + struct usb_request *req = &udc_req->req; + int len, size_left; + + dev_dbg(udc->dev, "%s: ep%d, req %p\n", __func__, udc_ep->epnum, req); + + if (!udc_req) { + dev_dbg(udc->dev, "%s: no request active\n", __func__); + return; + } + + if (udc_ep->trb->param2 & EXYNOS_USB3_TRB_HWO) { + dev_dbg(udc->dev, "%s: HWO bit set!\n", __func__); + return; + } + + size_left = udc_ep->trb->param1 & EXYNOS_USB3_TRB_BUFSIZ_MASK; + len = (req->length + udc_ep->ep.maxpacket - 1) & + ~(udc_ep->ep.maxpacket - 1); + udc_req->req.actual = len - size_left; + + if (size_left) + dev_dbg(udc->dev, "%s: BUFSIZ is not zero (%d)", + __func__, size_left); + + exynos_ss_udc_complete_request_lock(udc, udc_ep, udc_req, 0); +} + +/** + * exynos_ss_udc_irq_connectdone - process event Connection Done + * @udc: The device state. + * + * Get the speed of connection and configure physical endpoints 0 & 1. + */ +static void exynos_ss_udc_irq_connectdone(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd; + u32 reg, speed; + int mps0, mps; + int i; + int res; + + dev_dbg(udc->dev, "%s\n", __func__); + + reg = readl(udc->regs + EXYNOS_USB3_DSTS); + speed = reg & EXYNOS_USB3_DSTS_ConnectSpd_MASK; + + /* Suspend the inactive Phy */ + if (speed == USB_SPEED_SUPER) + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + else + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + switch (speed) { + /* High-speed */ + case 0: + udc->gadget.speed = USB_SPEED_HIGH; + mps0 = EP0_HS_MPS; + mps = EP_HS_MPS; + break; + /* Full-speed */ + case 1: + case 3: + udc->gadget.speed = USB_SPEED_FULL; + mps0 = EP0_FS_MPS; + mps = EP_FS_MPS; + break; + /* Low-speed */ + case 2: + udc->gadget.speed = USB_SPEED_LOW; + mps0 = EP0_LS_MPS; + mps = EP_LS_MPS; + break; + /* SuperSpeed */ + case 4: + udc->gadget.speed = USB_SPEED_SUPER; + mps0 = EP0_SS_MPS; + mps = EP_SS_MPS; + break; + } + + udc->eps[0].ep.maxpacket = mps0; + for (i = 1; i < EXYNOS_USB3_EPS; i++) + udc->eps[i].ep.maxpacket = mps; + + epcmd.ep = 0; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(mps0); + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP0\n"); + + epcmd.ep = 1; + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP1\n"); +} + +/** + * exynos_ss_udc_irq_usbrst - process event USB Reset + * @udc: The device state. + */ +static void exynos_ss_udc_irq_usbrst(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd; + struct exynos_ss_udc_ep *ep; + int res; + int epnum; + + dev_dbg(udc->dev, "%s\n", __func__); + + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER; + + /* End transfer, kill all requests and clear STALL on the + non-EP0 endpoints */ + for (epnum = 1; epnum < EXYNOS_USB3_EPS; epnum++) { + + ep = &udc->eps[epnum]; + + epcmd.ep = get_phys_epnum(ep); + + if (ep->tri) { + epcmd.cmdflags = (ep->tri << + EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_HiPri_ForceRM | + EXYNOS_USB3_DEPCMDx_CmdIOC | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to end transfer\n"); + ep->not_ready = 1; + } + + ep->tri = 0; + } + + exynos_ss_udc_kill_all_requests(udc, ep, -ECONNRESET); + + if (ep->halted) + exynos_ss_udc_ep_sethalt(&ep->ep, 0); + } + + /* Set device address to 0 */ + __bic32(udc->regs + EXYNOS_USB3_DCFG, EXYNOS_USB3_DCFG_DevAddr_MASK); +} + +/** + * exynos_ss_udc_handle_depevt - handle endpoint-specific event + * @udc: The device state. + * @event: The event being handled. + */ +static void exynos_ss_udc_handle_depevt(struct exynos_ss_udc *udc, u32 event) +{ + int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1; + int dir_in = index & 1; + int epnum = get_usb_epnum(index); + struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum]; + struct exynos_ss_udc_ep_command *epcmd, *tepcmd; + struct exynos_ss_udc_req *udc_req; + bool restart; + int res; + + switch (event & EXYNOS_USB3_DEPEVT_EVENT_MASK) { + case EXYNOS_USB3_DEPEVT_EVENT_XferNotReady: + dev_dbg(udc->dev, "Xfer Not Ready: ep%d%s\n", + epnum, dir_in ? "in" : "out"); + if (epnum == 0 && udc->ep0_state == EP0_WAIT_NRDY) { + udc_ep->dir_in = dir_in; + + if (udc->ep0_three_stage) + udc->ep0_state = EP0_STATUS_PHASE_3; + else + udc->ep0_state = EP0_STATUS_PHASE_2; + + exynos_ss_udc_enqueue_status(udc); + } + break; + + case EXYNOS_USB3_DEPEVT_EVENT_XferComplete: + dev_dbg(udc->dev, "Xfer Complete: ep%d%s\n", + epnum, dir_in ? "in" : "out"); + if (dir_in) { + /* Handle "transfer complete" for IN EPs */ + exynos_ss_udc_complete_in(udc, udc_ep); + } else { + /* Handle "transfer complete" for OUT EPs */ + exynos_ss_udc_complete_out(udc, udc_ep); + } + + if (epnum == 0 && udc->ep0_state == EP0_SETUP_PHASE) + exynos_ss_udc_enqueue_setup(udc); + + break; + + case EXYNOS_USB3_DEPEVT_EVENT_EPCmdCmplt: + dev_dbg(udc->dev, "EP Cmd complete: ep%d%s\n", + epnum, dir_in ? "in" : "out"); + + udc_ep->not_ready = 0; + + /* Issue all pending commands for endpoint */ + list_for_each_entry_safe(epcmd, tepcmd, + &udc_ep->cmd_queue, queue) { + + dev_dbg(udc->dev, "Pending command %02xh for ep%d%s\n", + epcmd->cmdtyp, epnum, + dir_in ? "in" : "out"); + + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to issue command\n"); + + list_del_init(&epcmd->queue); + kfree(epcmd); + } + + /* If we have pending request, then start it */ + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, + udc_req, false); + } + + break; + } +} + +/** + * exynos_ss_udc_handle_devt - handle device-specific event + * @udc: The driver state. + * @event: The event being handled. + */ +static void exynos_ss_udc_handle_devt(struct exynos_ss_udc *udc, u32 event) +{ + switch (event & EXYNOS_USB3_DEVT_EVENT_MASK) { + case EXYNOS_USB3_DEVT_EVENT_ULStChng: + dev_dbg(udc->dev, "USB-Link State Change"); + break; + + case EXYNOS_USB3_DEVT_EVENT_ConnectDone: + dev_dbg(udc->dev, "Connection Done"); + exynos_ss_udc_irq_connectdone(udc); + break; + + case EXYNOS_USB3_DEVT_EVENT_USBRst: + dev_info(udc->dev, "USB Reset"); + exynos_ss_udc_irq_usbrst(udc); + break; + + case EXYNOS_USB3_DEVT_EVENT_DisconnEvt: + dev_info(udc->dev, "Disconnection Detected"); + call_gadget(udc, disconnect); + break; + + default: + break; + } +} + +static void exynos_ss_udc_handle_otgevt(struct exynos_ss_udc *udc, u32 event) +{} + +static void exynos_ss_udc_handle_gevt(struct exynos_ss_udc *udc, u32 event) +{} + +/** + * exynos_ss_udc_irq - handle device interrupt + * @irq: The IRQ number triggered. + * @pw: The pw value when registered the handler. + */ +static irqreturn_t exynos_ss_udc_irq(int irq, void *pw) +{ + struct exynos_ss_udc *udc = pw; + int indx = udc->event_indx; + int gevntcount; + u32 event; + u32 ecode1, ecode2; + + gevntcount = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)) & + EXYNOS_USB3_GEVNTCOUNTx_EVNTCount_MASK; + + dev_dbg(udc->dev, "INTERRUPT (%d)\n", gevntcount >> 2); + + while (gevntcount) { + event = udc->event_buff[indx++]; + + dev_dbg(udc->dev, "event[%x] = 0x%08x\n", + (unsigned int) &udc->event_buff[indx - 1], event); + + ecode1 = event & 0x01; + + if (ecode1 == 0) + /* Device Endpoint-Specific Event */ + exynos_ss_udc_handle_depevt(udc, event); + else { + ecode2 = (event & 0xfe) >> 1; + + switch (ecode2) { + /* Device-Specific Event */ + case 0x00: + exynos_ss_udc_handle_devt(udc, event); + break; + + /* OTG Event */ + case 0x01: + exynos_ss_udc_handle_otgevt(udc, event); + break; + + /* Other Core Event */ + case 0x03: + case 0x04: + exynos_ss_udc_handle_gevt(udc, event); + break; + + /* Unknown Event Type */ + default: + dev_info(udc->dev, "Unknown event type\n"); + break; + } + } + /* We processed 1 event (4 bytes) */ + writel(4, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + + if (indx > (EXYNOS_USB3_EVENT_BUFF_WSIZE - 1)) + indx = 0; + + gevntcount -= 4; + } + + udc->event_indx = indx; + + return IRQ_HANDLED; +} + +/** + * exynos_ss_udc_free_all_trb - free all allocated TRBs + * @udc: The device state. + */ +static void exynos_ss_udc_free_all_trb(struct exynos_ss_udc *udc) +{ + int epnum; + + for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) { + struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum]; + + if (udc_ep->trb_dma) + dma_free_coherent(NULL, + sizeof(struct exynos_ss_udc_trb), + udc_ep->trb, + udc_ep->trb_dma); + } +} + +/** + * exynos_ss_udc_initep - initialize a single endpoint + * @udc: The device state. + * @udc_ep: The endpoint being initialised. + * @epnum: The endpoint number. + * + * Initialise the given endpoint (as part of the probe and device state + * creation) to give to the gadget driver. Setup the endpoint name, any + * direction information and other state that may be required. + */ +static int __devinit exynos_ss_udc_initep(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int epnum) +{ + char *dir; + + if (epnum == 0) + dir = ""; + else if ((epnum % 2) == 0) { + dir = "out"; + } else { + dir = "in"; + udc_ep->dir_in = 1; + } + + udc_ep->epnum = epnum; + + snprintf(udc_ep->name, sizeof(udc_ep->name), "ep%d%s", epnum, dir); + + INIT_LIST_HEAD(&udc_ep->queue); + INIT_LIST_HEAD(&udc_ep->cmd_queue); + INIT_LIST_HEAD(&udc_ep->ep.ep_list); + + spin_lock_init(&udc_ep->lock); + + /* add to the list of endpoints known by the gadget driver */ + if (epnum) + list_add_tail(&udc_ep->ep.ep_list, &udc->gadget.ep_list); + + udc_ep->parent = udc; + udc_ep->ep.name = udc_ep->name; + udc_ep->ep.maxpacket = epnum ? EP_SS_MPS : EP0_SS_MPS; + udc_ep->ep.ops = &exynos_ss_udc_ep_ops; + udc_ep->trb = dma_alloc_coherent(NULL, + sizeof(struct exynos_ss_udc_trb), + &udc_ep->trb_dma, + GFP_KERNEL); + if (!udc_ep->trb) + return -ENOMEM; + + memset(udc_ep->trb, 0, sizeof(struct exynos_ss_udc_trb)); + + if (epnum == 0) { + /* allocate EP0 control request */ + udc->ctrl_req = exynos_ss_udc_ep_alloc_request(&udc->eps[0].ep, + GFP_KERNEL); + if (!udc->ctrl_req) + return -ENOMEM; + + udc->ep0_state = EP0_UNCONNECTED; + } + + return 0; +} + +/** + * exynos_ss_udc_phy_set - intitialize the controller PHY interface + * @pdev: The platform-level device instance. + */ +static void exynos_ss_udc_phy_set(struct platform_device *pdev) +{ + struct exynos_ss_udc_plat *plat = pdev->dev.platform_data; + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + /* The reset values: + * GUSB2PHYCFG(0) = 0x00002400 + * GUSB3PIPECTL(0) = 0x00260002 + */ + + __orr32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + + /* PHY initialization */ + if (plat && plat->phy_init) + plat->phy_init(pdev, S5P_USB_PHY_DRD); + + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + __bic32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + + + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM | + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim_MASK); + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim(9)); + + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); +} + +/** + * exynos_ss_udc_phy_unset - disable the controller PHY interface + * @pdev: The platform-level device instance. + */ +static void exynos_ss_udc_phy_unset(struct platform_device *pdev) +{ + struct exynos_ss_udc_plat *plat = pdev->dev.platform_data; + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM); + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + if (plat && plat->phy_exit) + plat->phy_exit(pdev, S5P_USB_PHY_DRD); + + dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); +} + +/** + * exynos_ss_udc_corereset - issue softreset to the core + * @udc: The device state. + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static int exynos_ss_udc_corereset(struct exynos_ss_udc *udc) +{ + int res; + + /* issue soft reset */ + __orr32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_CSftRst); + + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_CSftRst, + 1000); + if (res < 0) + dev_err(udc->dev, "Failed to get CSftRst asserted\n"); + + return res; +} + +/** + * exynos_ss_udc_ep0_activate - activate USB endpoint 0 + * @udc: The device state. + * + * Configure physical endpoints 0 & 1. + */ +static void exynos_ss_udc_ep0_activate(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd; + int res; + + /* Start New Configuration */ + epcmd.ep = 0; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start new configuration\n"); + + /* Configure Physical Endpoint 0 */ + epcmd.ep = 0; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP0\n"); + + /* Configure Physical Endpoint 1 */ + epcmd.ep = 1; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP1\n"); + + /* Configure Pysical Endpoint 0 Transfer Resource */ + epcmd.ep = 0; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, + "Failed to configure physical EP0 transfer resource\n"); + + /* Configure Pysical Endpoint 1 Transfer Resource */ + epcmd.ep = 1; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, + "Failed to configure physical EP1 transfer resource\n"); + + /* Enable Physical Endpoints 0 and 1 */ + writel(3, udc->regs + EXYNOS_USB3_DALEPENA); +} + +/** + * exynos_ss_udc_ep_activate - activate USB endpoint + * @udc: The device state. + * @udc_ep: The endpoint being activated. + * + * Configure physical endpoints > 1. + */ +static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_ep_command ep_command; + struct exynos_ss_udc_ep_command *epcmd = &ep_command; + int epnum = udc_ep->epnum; + int maxburst = udc_ep->ep.maxburst; + int res; + + if (!udc->eps_enabled) { + udc->eps_enabled = true; + + /* Start New Configuration */ + epcmd->ep = 0; + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG; + epcmd->cmdflags = + (2 << EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start new configuration\n"); + } + + if (udc_ep->not_ready) { + epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command), + GFP_ATOMIC); + if (!epcmd) { + /* Will try to issue command immediately */ + epcmd = &ep_command; + udc_ep->not_ready = 0; + } + } + + epcmd->ep = get_phys_epnum(udc_ep); + epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_EPType(udc_ep->type) | + EXYNOS_USB3_DEPCMDPAR0x_MPS(udc_ep->ep.maxpacket) | + EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(maxburst); + if (udc_ep->dir_in) + epcmd->param0 |= EXYNOS_USB3_DEPCMDPAR0x_FIFONum(epnum); + epcmd->param1 = EXYNOS_USB3_DEPCMDPAR1x_EpNum(epnum) | + (udc_ep->dir_in ? EXYNOS_USB3_DEPCMDPAR1x_EpDir : 0) | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + if (udc_ep->not_ready) + list_add_tail(&epcmd->queue, &udc_ep->cmd_queue); + else { + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP\n"); + } + + /* Configure Pysical Endpoint Transfer Resource */ + if (udc_ep->not_ready) { + epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command), + GFP_ATOMIC); + if (!epcmd) { + epcmd = &ep_command; + udc_ep->not_ready = 0; + } + } + + epcmd->ep = get_phys_epnum(udc_ep); + epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + if (udc_ep->not_ready) + list_add_tail(&epcmd->queue, &udc_ep->cmd_queue); + else { + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP " + "transfer resource\n"); + } + + /* Enable Physical Endpoint */ + __orr32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << epcmd->ep); +} + +/** + * exynos_ss_udc_ep_deactivate - deactivate USB endpoint + * @udc: The device state. + * @udc_ep: The endpoint being deactivated. + * + * End any active transfer and disable endpoint. + */ +static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_ep_command epcmd; + int index = get_phys_epnum(udc_ep); + + udc->eps_enabled = false; + + if (udc_ep->tri && !udc_ep->not_ready) { + int res; + + epcmd.ep = get_phys_epnum(udc_ep); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER; + epcmd.cmdflags = (udc_ep->tri << + EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_HiPri_ForceRM | + EXYNOS_USB3_DEPCMDx_CmdIOC | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to end transfer\n"); + udc_ep->not_ready = 1; + } + + udc_ep->tri = 0; + } + + __bic32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << index); +} + +/** + * exynos_ss_udc_init - initialize the controller + * @udc: The device state. + * + * Initialize the event buffer, enable events, activate USB EP0, + * and start the controller operation. + */ +static void exynos_ss_udc_init(struct exynos_ss_udc *udc) +{ + u32 reg; + + reg = readl(udc->regs + EXYNOS_USB3_GSNPSID); + udc->release = reg & 0xffff; + dev_info(udc->dev, "Core ID Number: 0x%04x\n", reg >> 16); + dev_info(udc->dev, "Release Number: 0x%04x\n", udc->release); + + __orr32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_U2RSTECN); + + writel(EXYNOS_USB3_GSBUSCFG0_INCR16BrstEna, + udc->regs + EXYNOS_USB3_GSBUSCFG0); + writel(EXYNOS_USB3_GSBUSCFG1_BREQLIMIT(3), + udc->regs + EXYNOS_USB3_GSBUSCFG1); + + /* Event buffer */ + udc->event_indx = 0; + writel(0, udc->regs + EXYNOS_USB3_GEVNTADR_63_32(0)); + writel(udc->event_buff_dma, + udc->regs + EXYNOS_USB3_GEVNTADR_31_0(0)); + /* Set Event Buffer size */ + writel(EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->regs + EXYNOS_USB3_GEVNTSIZ(0)); + + writel(EXYNOS_USB3_DCFG_NumP(1) | EXYNOS_USB3_DCFG_PerFrInt(2) | + EXYNOS_USB3_DCFG_DevSpd(4), + udc->regs + EXYNOS_USB3_DCFG); + + /* Flush any pending events */ + __orr32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0), + EXYNOS_USB3_GEVNTSIZx_EvntIntMask); + + reg = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + writel(reg, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + + __bic32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0), + EXYNOS_USB3_GEVNTSIZx_EvntIntMask); + + /* Enable events */ + writel(EXYNOS_USB3_DEVTEN_ULStCngEn | EXYNOS_USB3_DEVTEN_ConnectDoneEn | + EXYNOS_USB3_DEVTEN_USBRstEn | EXYNOS_USB3_DEVTEN_DisconnEvtEn, + udc->regs + EXYNOS_USB3_DEVTEN); + + exynos_ss_udc_ep0_activate(udc); + + /* Start the device controller operation */ + exynos_ss_udc_run_stop(udc, 1); +} + +static int exynos_ss_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct exynos_ss_udc *udc = our_udc(gadget); + + if (!udc) { + printk(KERN_ERR "%s: called with no device\n", __func__); + return -ENODEV; + } + + if (!driver) { + dev_err(udc->dev, "%s: no driver\n", __func__); + return -EINVAL; + } + + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->dev, "%s: bad speed\n", __func__); + return -EINVAL; + } + + if (!driver->setup) { + dev_err(udc->dev, "%s: missing entry points\n", __func__); + return -EINVAL; + } + + WARN_ON(udc->driver); + + driver->driver.bus = NULL; + udc->driver = driver; + udc->gadget.dev.driver = &driver->driver; + + exynos_ss_udc_corereset(udc); + + exynos_ss_udc_init(udc); + + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); + + dev_info(udc->dev, "bound driver %s\n", driver->driver.name); + return 0; +} + +static int exynos_ss_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct exynos_ss_udc *udc = our_udc(gadget); + int ep; + + if (!udc) + return -ENODEV; + + if (!driver || driver != udc->driver) + return -EINVAL; + + /* all endpoints should be shutdown */ + for (ep = 0; ep < EXYNOS_USB3_EPS; ep++) + exynos_ss_udc_ep_disable(&udc->eps[ep].ep); + + call_gadget(udc, disconnect); + + udc->driver = NULL; + udc->gadget.dev.driver = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + + exynos_ss_udc_run_stop(udc, 0); + dev_info(udc->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} + +static int __devinit exynos_ss_udc_probe(struct platform_device *pdev) +{ + struct exynos_ss_udc_plat *plat = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct exynos_ss_udc *udc; + struct resource *res; + int epnum; + int ret; + + if (!plat) { + dev_err(dev, "cannot get platform data\n"); + return -ENODEV; + } + + udc = kzalloc(sizeof(struct exynos_ss_udc), GFP_KERNEL); + if (!udc) { + dev_err(dev, "cannot get memory\n"); + ret = -ENOMEM; + goto err_mem; + } + + udc->dev = dev; + udc->plat = plat; + + udc->event_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_EVENT_BUFF_BSIZE, + &udc->event_buff_dma, + GFP_KERNEL); + if (!udc->event_buff) { + dev_err(dev, "cannot get memory for event buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->event_buff, 0, EXYNOS_USB3_EVENT_BUFF_BSIZE); + + udc->ctrl_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_CTRL_BUFF_SIZE, + &udc->ctrl_buff_dma, + GFP_KERNEL); + if (!udc->ctrl_buff) { + dev_err(dev, "cannot get memory for control buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->ctrl_buff, 0, EXYNOS_USB3_CTRL_BUFF_SIZE); + + udc->ep0_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_EP0_BUFF_SIZE, + &udc->ep0_buff_dma, + GFP_KERNEL); + if (!udc->ep0_buff) { + dev_err(dev, "cannot get memory for EP0 buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->ep0_buff, 0, EXYNOS_USB3_EP0_BUFF_SIZE); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "cannot find register resource 0\n"); + ret = -EINVAL; + goto err_mem; + } + + if (!request_mem_region(res->start, resource_size(res), + dev_name(dev))) { + dev_err(dev, "cannot reserve registers\n"); + ret = -ENOENT; + goto err_mem; + } + + udc->regs = ioremap(res->start, resource_size(res)); + if (!udc->regs) { + dev_err(dev, "cannot map registers\n"); + ret = -ENXIO; + goto err_remap; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "cannot find irq\n"); + goto err_irq; + } + + udc->irq = ret; + + ret = request_irq(ret, exynos_ss_udc_irq, 0, dev_name(dev), udc); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ\n"); + goto err_irq; + } + + dev_info(dev, "regs %p, irq %d\n", udc->regs, udc->irq); + + udc->clk = clk_get(&pdev->dev, "usbdev30"); + if (IS_ERR(udc->clk)) { + dev_err(dev, "cannot get UDC clock\n"); + ret = -EINVAL; + goto err_clk; + } + + dev_set_name(&udc->gadget.dev, "gadget"); + + udc->gadget.max_speed = USB_SPEED_SUPER; + udc->gadget.ops = &exynos_ss_udc_gadget_ops; + udc->gadget.name = dev_name(dev); + udc->gadget.dev.parent = dev; + udc->gadget.dev.dma_mask = dev->dma_mask; + udc->gadget.speed = USB_SPEED_UNKNOWN; + + /* setup endpoint information */ + + INIT_LIST_HEAD(&udc->gadget.ep_list); + udc->gadget.ep0 = &udc->eps[0].ep; + + /* initialise the endpoints */ + for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) { + ret = exynos_ss_udc_initep(udc, &udc->eps[epnum], epnum); + if (ret < 0) { + dev_err(dev, "cannot get memory for TRB\n"); + goto err_ep; + } + } + + platform_set_drvdata(pdev, udc); + + clk_enable(udc->clk); + exynos_ss_udc_phy_set(pdev); + + ret = device_register(&udc->gadget.dev); + if (ret) { + dev_err(udc->dev, "failed to register gadget device\n"); + goto err_add_device; + } + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret) { + dev_err(udc->dev, "failed to add gadget to udc list\n"); + goto err_add_udc; + } + + return 0; + +err_add_udc: + device_unregister(&udc->gadget.dev); +err_add_device: + exynos_ss_udc_phy_unset(pdev); + clk_disable(udc->clk); + platform_set_drvdata(pdev, NULL); +err_ep: + exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req); + exynos_ss_udc_free_all_trb(udc); + clk_put(udc->clk); +err_clk: + free_irq(udc->irq, udc); +err_irq: + iounmap(udc->regs); +err_remap: + release_mem_region(res->start, resource_size(res)); +err_mem: + if (udc->ep0_buff) + dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE, + udc->ep0_buff, udc->ep0_buff_dma); + if (udc->ctrl_buff) + dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE, + udc->ctrl_buff, udc->ctrl_buff_dma); + if (udc->event_buff) + dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->event_buff, udc->event_buff_dma); + kfree(udc); + + return ret; +} + +static int __devexit exynos_ss_udc_remove(struct platform_device *pdev) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + struct resource *res; + + usb_gadget_unregister_driver(udc->driver); + + usb_del_gadget_udc(&udc->gadget); + + exynos_ss_udc_phy_unset(pdev); + + clk_disable(udc->clk); + clk_put(udc->clk); + + free_irq(udc->irq, udc); + + iounmap(udc->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + device_unregister(&udc->gadget.dev); + + platform_set_drvdata(pdev, NULL); + + exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req); + exynos_ss_udc_free_all_trb(udc); + + dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE, + udc->ep0_buff, udc->ep0_buff_dma); + dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE, + udc->ctrl_buff, udc->ctrl_buff_dma); + dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->event_buff, udc->event_buff_dma); + kfree(udc); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos_ss_udc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + int ep; + + if (udc->driver) { + call_gadget(udc, suspend); + + /* all endpoints should be shutdown */ + for (ep = 0; ep < EXYNOS_USB3_EPS; ep++) + exynos_ss_udc_ep_disable(&udc->eps[ep].ep); + + call_gadget(udc, disconnect); + udc->gadget.speed = USB_SPEED_UNKNOWN; + } + + exynos_ss_udc_run_stop(udc, 0); + exynos_ss_udc_phy_unset(pdev); + + clk_disable(udc->clk); + + return 0; +} + +static int exynos_ss_udc_resume(struct platform_device *pdev) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + clk_enable(udc->clk); + + exynos_ss_udc_phy_set(pdev); + + if (udc->driver) { + /* we must now enable ep0 ready for host detection and then + * set configuration. */ + + exynos_ss_udc_corereset(udc); + + exynos_ss_udc_init(udc); + + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); + + call_gadget(udc, resume); + } + + return 0; +} +#else +#define exynos_ss_udc_suspend NULL +#define exynos_ss_udc_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver exynos_ss_udc_driver = { + .driver = { + .name = "exynos-ss-udc", + .owner = THIS_MODULE, + }, + .probe = exynos_ss_udc_probe, + .remove = __devexit_p(exynos_ss_udc_remove), + .suspend = exynos_ss_udc_suspend, + .resume = exynos_ss_udc_resume, +}; + +module_platform_driver(exynos_ss_udc_driver); + +MODULE_DESCRIPTION("EXYNOS SuperSpeed USB 3.0 Device Controller"); +MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@xxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/exynos_ss_udc.h b/drivers/usb/gadget/exynos_ss_udc.h new file mode 100644 index 0000000..a98a978 --- /dev/null +++ b/drivers/usb/gadget/exynos_ss_udc.h @@ -0,0 +1,342 @@ +/* linux/drivers/usb/gadget/exynos_ss_udc.h + * + * Copyright 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS SuperSpeed USB 3.0 Device Controlle driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __EXYNOS_SS_UDC_H__ +#define __EXYNOS_SS_UDC_H__ + +#define DMA_ADDR_INVALID (~((dma_addr_t)0)) + +/* Maximum packet size for different speeds */ +#define EP0_LS_MPS 8 +#define EP_LS_MPS 8 + +#define EP0_FS_MPS 64 +#define EP_FS_MPS 64 + +#define EP0_HS_MPS 64 +#define EP_HS_MPS 512 + +#define EP0_SS_MPS 512 +#define EP_SS_MPS 1024 + +#define EXYNOS_USB3_EPS 9 + +/* Has to be multiple of four */ +#define EXYNOS_USB3_EVENT_BUFF_WSIZE 256 +#define EXYNOS_USB3_EVENT_BUFF_BSIZE (EXYNOS_USB3_EVENT_BUFF_WSIZE << 2) + +#define EXYNOS_USB3_CTRL_BUFF_SIZE 8 +#define EXYNOS_USB3_EP0_BUFF_SIZE 512 + +#define EXYNOS_USB3_U1_DEV_EXIT_LAT 0 +#define EXYNOS_USB3_U2_DEV_EXIT_LAT 0x20 + + +/* Device register definitions */ + +#define EXYNOS_USB3_DCFG 0xC700 +#define EXYNOS_USB3_DCFG_NumP(_x) ((_x) << 17) +#define EXYNOS_USB3_DCFG_PerFrInt(_x) ((_x) << 10) +#define EXYNOS_USB3_DCFG_DevAddr_MASK (0x7f << 3) +#define EXYNOS_USB3_DCFG_DevAddr(_x) ((_x) << 3) +#define EXYNOS_USB3_DCFG_DevSpd(_x) ((_x) << 0) + +#define EXYNOS_USB3_DCTL 0xC704 +#define EXYNOS_USB3_DCTL_Run_Stop (1 << 31) +#define EXYNOS_USB3_DCTL_CSftRst (1 << 30) +#define EXYNOS_USB3_DCTL_InitU2Ena (1 << 12) +#define EXYNOS_USB3_DCTL_InitU1Ena (1 << 10) + +#define EXYNOS_USB3_DEVTEN 0xC708 +#define EXYNOS_USB3_DEVTEN_ULStCngEn (1 << 3) +#define EXYNOS_USB3_DEVTEN_ConnectDoneEn (1 << 2) +#define EXYNOS_USB3_DEVTEN_USBRstEn (1 << 1) +#define EXYNOS_USB3_DEVTEN_DisconnEvtEn (1 << 0) + +#define EXYNOS_USB3_DSTS 0xC70C +#define EXYNOS_USB3_DSTS_DevCtrlHlt (1 << 22) +#define EXYNOS_USB3_DSTS_ConnectSpd_MASK (0x7 << 0) + +#define EXYNOS_USB3_DGCMDPAR 0xC710 + +#define EXYNOS_USB3_DGCMD 0xC714 +#define EXYNOS_USB3_DGCMD_CmdAct (1 << 10) +/* Device generic commands */ +#define EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams (0x2 << 0) + +#define EXYNOS_USB3_DALEPENA 0xC720 + +#define EXYNOS_USB3_DEPCMDPAR2(_a) (0xC800 + ((_a) * 0x10)) + +#define EXYNOS_USB3_DEPCMDPAR1(_a) (0xC804 + ((_a) * 0x10)) +/* DEPCFG command parameter 1 */ +#define EXYNOS_USB3_DEPCMDPAR1x_EpNum(_x) ((_x) << 26) +#define EXYNOS_USB3_DEPCMDPAR1x_EpDir (1 << 25) +#define EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn (1 << 10) +#define EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn (1 << 8) + +#define EXYNOS_USB3_DEPCMDPAR0(_a) (0xC808 + ((_a) * 0x10)) +/* DEPCFG command parameter 0 */ +#define EXYNOS_USB3_DEPCMDPAR0x_MPS(_x) ((_x) << 3) +#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(_x) ((_x) << 22) +#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum(_x) ((_x) << 17) +#define EXYNOS_USB3_DEPCMDPAR0x_EPType(_x) ((_x) << 1) +/* DEPXFERCFG command parameter 0 */ +#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(_x) ((_x) << 0) + +#define EXYNOS_USB3_DEPCMD(_a) (0xC80C + ((_a) * 0x10)) +#define EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT 16 +#define EXYNOS_USB3_DEPCMDx_EventParam_SHIFT 16 +#define EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT (0x7f) +#define EXYNOS_USB3_DEPCMDx_HiPri_ForceRM (1 << 11) +#define EXYNOS_USB3_DEPCMDx_CmdAct (1 << 10) +#define EXYNOS_USB3_DEPCMDx_CmdIOC (1 << 8) +/* Physical Endpoint commands */ +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG (0x1 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG (0x2 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL (0x4 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL (0x5 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER (0x6 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER (0x8 << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG (0x9 << 0) + +/* Transfer Request Block */ +#define EXYNOS_USB3_TRB_BUFSIZ_MASK (0xffffff << 0) +#define EXYNOS_USB3_TRB_BUFSIZ(_x) ((_x) << 0) +#define EXYNOS_USB3_TRB_IOC (1 << 11) +#define EXYNOS_USB3_TRB_TRBCTL(_x) ((_x) << 4) +#define EXYNOS_USB3_TRB_LST (1 << 1) +#define EXYNOS_USB3_TRB_HWO (1 << 0) +/************************/ + +#define call_gadget(_udc, _entry) do { \ + if ((_udc)->gadget.speed != USB_SPEED_UNKNOWN && \ + (_udc)->driver && (_udc)->driver->_entry) \ + (_udc)->driver->_entry(&(_udc)->gadget); \ +} while (0) + +/** + * States of EP0 + */ +enum ctrl_ep_state { + EP0_UNCONNECTED, + EP0_SETUP_PHASE, + EP0_DATA_PHASE, + EP0_WAIT_NRDY, + EP0_STATUS_PHASE_2, + EP0_STATUS_PHASE_3, + EP0_STALL, +}; + +/** + * Types of TRB + */ +enum trb_control { + NORMAL = 1, + CONTROL_SETUP, + CONTROL_STATUS_2, + CONTROL_STATUS_3, + CONTROL_DATA, + ISOCHRONOUS_FIRST, + ISOCHRONOUS, + LINK_TRB, +}; + +/** + * struct exynos_ss_udc_trb - transfer request block (TRB) + * @buff_ptr_low: Buffer pointer low. + * @buff_ptr_high: Buffer pointer high. + * @param1: TRB parameter 1. + * @param2: TRB parameter 2. + */ +struct exynos_ss_udc_trb { + u32 buff_ptr_low; + u32 buff_ptr_high; + u32 param1; + u32 param2; +}; + +/** + * struct exynos_ss_udc_ep_command - endpoint command. + * @queue: The list of commands for the endpoint. + * @ep: physical endpoint number. + * @param0: Command parameter 0. + * @param1: Command parameter 1. + * @param2: Command parameter 2. + * @cmdtype: Command to issue. + * @cmdflags: Command flags. + */ +struct exynos_ss_udc_ep_command { + struct list_head queue; + + int ep; + u32 param0; + u32 param1; + u32 param2; + u32 cmdtyp; + u32 cmdflags; +}; + +/** + * struct exynos_ss_udc_req - data transfer request + * @req: The USB gadget request. + * @queue: The list of requests for the endpoint this is queued for. + * @mapped: DMA buffer for this request has been mapped via dma_map_single(). + */ +struct exynos_ss_udc_req { + struct usb_request req; + struct list_head queue; + unsigned char mapped; +}; + +/** + * struct exynos_ss_udc_ep - driver endpoint definition. + * @ep: The gadget layer representation of the endpoint. + * @queue: Queue of requests for this endpoint. + * @cmd_queue: Queue of commands for this endpoint. + * @parent: Reference back to the parent device structure. + * @req: The current request that the endpoint is processing. This is + * used to indicate an request has been loaded onto the endpoint + * and has yet to be completed (maybe due to data move, or simply + * awaiting an ack from the core all the data has been completed). + * @lock: State lock to protect contents of endpoint. + * @trb: Transfer Request Block. + * @trb_dma: Transfer Request Block DMA address. + * @tri: Transfer resource index. + * @epnum: The USB endpoint number. + * @type: The endpoint type. + * @dir_in: Set to true if this endpoint is of the IN direction, which + * means that it is sending data to the Host. + * @halted: Set if the endpoint has been halted. + * @enabled: Set to true if endpoint is enabled. + * @wedged: Set if the endpoint has been wedged. + * @not_ready: Set to true if a command for the endpoint hasn't completed + * during timeout interval. + * @name: The driver generated name for the endpoint. + * + * This is the driver's state for each registered enpoint, allowing it + * to keep track of transactions that need doing. Each endpoint has a + * lock to protect the state, to try and avoid using an overall lock + * for the host controller as much as possible. + */ +struct exynos_ss_udc_ep { + struct usb_ep ep; + struct list_head queue; + struct list_head cmd_queue; + struct exynos_ss_udc *parent; + struct exynos_ss_udc_req *req; + + spinlock_t lock; + + struct exynos_ss_udc_trb *trb; + dma_addr_t trb_dma; + u8 tri; + + unsigned char epnum; + unsigned int type; + unsigned int dir_in:1; + unsigned int halted:1; + unsigned int enabled:1; + unsigned int wedged:1; + unsigned int not_ready:1; + + char name[10]; +}; + +/** + * struct exynos_ss_udc - driver state. + * @dev: The parent device supplied to the probe function + * @driver: USB gadget driver + * @plat: The platform specific configuration data. + * @regs: The memory area mapped for accessing registers. + * @irq: The IRQ number we are using. + * @clk: The clock we are using. + * @release: The core release number. + * @event_buff: Event buffer. + * @event_buff_dma: Event buffer DMA address. + * @event_indx: Event buffer index. + * @eps_enabled: Set if new configuration for physical endpoints > 1 started. + * @ep0_state: State of EP0. + * @ep0_three_stage: Set if control transfer has three stages. + * @ep0_buff: Buffer for EP0 data. + * @ep0_buff_dma: EP0 data buffer DMA address. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_buff_dma: EP0 control request buffer DMA address. + * @ctrl_req: Request for EP0 control packets. + * @gadget: Represents USB slave device. + * @eps: The endpoints being supplied to the gadget framework + */ +struct exynos_ss_udc { + struct device *dev; + struct usb_gadget_driver *driver; + struct exynos_ss_udc_plat *plat; + + void __iomem *regs; + int irq; + struct clk *clk; + + u16 release; + + u32 *event_buff; + dma_addr_t event_buff_dma; + u32 event_indx; + + bool eps_enabled; + enum ctrl_ep_state ep0_state; + int ep0_three_stage; + + u8 *ep0_buff; + dma_addr_t ep0_buff_dma; + u8 *ctrl_buff; + dma_addr_t ctrl_buff_dma; + struct usb_request *ctrl_req; + + struct usb_gadget gadget; + struct exynos_ss_udc_ep eps[EXYNOS_USB3_EPS]; +}; + +/* conversion functions */ +static inline struct exynos_ss_udc *our_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct exynos_ss_udc, gadget); +} + +static inline struct exynos_ss_udc_req *our_req(struct usb_request *req) +{ + return container_of(req, struct exynos_ss_udc_req, req); +} + +static inline struct exynos_ss_udc_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct exynos_ss_udc_ep, ep); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) & ~val, ptr); +} + +static inline int get_phys_epnum(struct exynos_ss_udc_ep *udc_ep) +{ + return udc_ep->epnum * 2 + udc_ep->dir_in; +} + +static inline int get_usb_epnum(int index) +{ + return index >> 1; +} + +#endif /* __EXYNOS_SS_UDC_H__ */ -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html