diff options
Diffstat (limited to 'drivers/usb/gadget/exynos_ss_udc.c')
-rw-r--r-- | drivers/usb/gadget/exynos_ss_udc.c | 3047 |
1 files changed, 3047 insertions, 0 deletions
diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c new file mode 100644 index 0000000..c8001e8 --- /dev/null +++ b/drivers/usb/gadget/exynos_ss_udc.c @@ -0,0 +1,3047 @@ +/* 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 <linux/usb/composite.h> +#include <linux/usb/exynos_usb3_drd.h> + +#include <linux/platform_data/exynos_usb3_drd.h> + +#include <asm/byteorder.h> + +#include "exynos_ss_udc.h" + +#define SAMSUNG_MUIC_OR_BATTERY_DRIVER_NOT_READY +#define USE_WAKE_LOCK + +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_ep0_restart(struct exynos_ss_udc *udc); +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 struct exynos_ss_udc *our_udc; + +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; +} + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +void exynos_ss_udc_cable_connect(struct exynos_ss_udc *udc, bool connect) +{ + static int last_connect; + + if (last_connect != connect) { + samsung_cable_check_status(connect); + last_connect = connect; + } +} +#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, connect) \ + exynos_ss_udc_cable_connect(udc, connect) +#else +#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, conect) +#endif + +/** + * 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_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 *udc_req; + + dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum); + + udc_req = kzalloc(sizeof(struct exynos_ss_udc_req), flags); + if (!udc_req) + return NULL; + + INIT_LIST_HEAD(&udc_req->queue); + + udc_req->req.dma = DMA_ADDR_INVALID; + return &udc_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 = {{0}, }; + int epnum = udc_ep->epnum; + 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 && epnum != 0 && 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 (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 (epnum == 0) { + if (value) + udc->ep0_state = EP0_STALL; + } else { + udc_ep->halted = !!value; + + if (!value) + 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 = {{0}, }; + 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: + if (udc->ep0_three_stage) + trb_type = CONTROL_STATUS_3; + else + trb_type = CONTROL_STATUS_2; + 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_process_set_config - process request SET_CONFIGURATION + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_config(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + int ret; + u16 config = le16_to_cpu(ctrl->wValue); + + dev_dbg(udc->dev, "%s\n", __func__); + + udc->ep0_state = EP0_STATUS_PHASE; + + switch (udc->state) { + case USB_STATE_ADDRESS: + ret = udc->driver->setup(&udc->gadget, ctrl); + if (!ret || ret == USB_GADGET_DELAYED_STATUS) { + ret = 1; + if (config) + udc->state = USB_STATE_CONFIGURED; + } + break; + + case USB_STATE_CONFIGURED: + ret = udc->driver->setup(&udc->gadget, ctrl); + if (!ret || ret == USB_GADGET_DELAYED_STATUS) { + ret = 1; + if (!config) + udc->state = USB_STATE_ADDRESS; + } + break; + + case USB_STATE_DEFAULT: + /* FALLTHROUGH */ + default: + ret = -EINVAL; + break; + } + + return 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__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + 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_set_isoch_delay - process request SET_ISOCH_DELAY + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_isoch_delay(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + u16 isoch_delay; + int ret = 1; + + if (ctrl->wIndex || ctrl->wLength) { + ret = -EINVAL; + goto err; + } + + switch (udc->state) { + case USB_STATE_DEFAULT: + /* FALLTHROUGH */ + case USB_STATE_ADDRESS: + /* FALLTHROUGH */ + case USB_STATE_CONFIGURED: + isoch_delay = le16_to_cpu(ctrl->wValue); + /* REVISIT don't know what to do with this value */ + break; + default: + ret = -EINVAL; + break; + } + +err: + return ret; +} + +/** + * exynos_ss_udc_set_test_mode - set TEST_MODE feature + * @udc: The device state. + * @wIndex: The request wIndex field. + */ +static int exynos_ss_udc_set_test_mode(struct exynos_ss_udc *udc, + u16 wIndex) +{ + u8 selector = wIndex >> 8; + char *mode; + u32 reg; + int ret = 0; + + switch (selector) { + case TEST_J: + mode = "TEST J"; + break; + case TEST_K: + mode = "TEST K"; + break; + case TEST_SE0_NAK: + mode = "TEST SE0 NAK"; + break; + case TEST_PACKET: + mode = "TEST PACKET"; + break; + case TEST_FORCE_EN: + mode = "TEST FORCE EN"; + break; + default: + mode = "unknown"; + ret = -EINVAL; + break; + } + + dev_info(udc->dev, "Test mode selector is %s\n", mode); + + if (ret == 0) { + reg = readl(udc->regs + EXYNOS_USB3_DCTL) & + ~EXYNOS_USB3_DCTL_TstCtl_MASK; + + reg |= EXYNOS_USB3_DCTL_TstCtl(selector); + + writel(reg, udc->regs + EXYNOS_USB3_DCTL); + } + + return ret; +} + +/** + * 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; + int ret; + bool restart; + u16 wValue; + u16 wIndex; + u8 recip; + + dev_dbg(udc->dev, "%s\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_U1_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); + break; + + case USB_DEVICE_U2_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + __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, wIndex); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, wIndex); + return -ENOENT; + } + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (!udc_ep->wedged) { + ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 0); + if (ret < 0) + return ret; + + /* 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; + int ret; + u16 wValue; + u16 wIndex; + u8 recip; + + dev_dbg(udc->dev, "%s\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_TEST_MODE: + if (wIndex & 0xff) + return -EINVAL; + + ret = exynos_ss_udc_set_test_mode(udc, wIndex); + if (ret < 0) + return ret; + break; + case USB_DEVICE_U1_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Enable U1 entry only for DWC3 revisions > 1.85a, + * since earlier revisions have a bug + */ + if (udc->release > 0x185a) + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); + break; + + case USB_DEVICE_U2_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Enable U2 entry only for DWC3 revisions > 1.85a, + * since earlier revisions have a bug + */ + if (udc->release > 0x185a) + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU2Ena); + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_INTERFACE: + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Currently, there is no functions supporting + * FUNCTION_SUSPEND feature. Moreover, if a function + * doesn't support the feature (true for all), composite + * driver returns error on 'setup' call. This causes + * Command Verifier test to fail. To fix it, will use + * dummy handler instead. + */ + + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, wIndex); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, wIndex); + return -ENOENT; + } + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + switch (wValue) { + case USB_ENDPOINT_HALT: + ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 1); + if (ret < 0) + return ret; + 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->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + 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: + if (udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* 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; + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + *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_set_address - process request SET_CONFIGURATION + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_address(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + int ret = 1; + u16 address = le16_to_cpu(ctrl->wValue); + + dev_dbg(udc->dev, "%s\n", __func__); + + switch (udc->state) { + case USB_STATE_DEFAULT: + if (address) + udc->state = USB_STATE_ADDRESS; + break; + + case USB_STATE_ADDRESS: + if (!address) + udc->state = USB_STATE_DEFAULT; + break; + + case USB_STATE_CONFIGURED: + /* FALLTHROUGH */ + default: + ret = -EINVAL; + break; + } + + if (ret == 1) { + __bic32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr_MASK); + __orr32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr(address)); + dev_dbg(udc->dev, "new address %d\n", address); + } + + return ret; +} + +/** + * 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_WAIT_NRDY; + } else + udc->ep0_three_stage = 1; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = exynos_ss_udc_process_set_address(udc, ctrl); + break; + + 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); + break; + + case USB_REQ_SET_FEATURE: + ret = exynos_ss_udc_process_set_feature(udc, ctrl); + break; + + case USB_REQ_SET_SEL: + ret = exynos_ss_udc_process_set_sel(udc); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = exynos_ss_udc_process_set_isoch_delay(udc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + ret = exynos_ss_udc_process_set_config(udc, ctrl); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && udc->driver) { + if (udc->ep0_three_stage == 0) + udc->ep0_state = EP0_STATUS_PHASE; + + 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) { + dev_dbg(udc->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + exynos_ss_udc_ep0_restart(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); + + /* 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_ep0_restart - stall and restart EP0 + * @udc: The device state. + * + * Stall EP0 and restart control transfer state machine. + */ +static void exynos_ss_udc_ep0_restart(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep *ep0 = &udc->eps[0]; + + exynos_ss_udc_ep_sethalt(&ep0->ep, 1); + exynos_ss_udc_kill_all_requests(udc, ep0, -ECONNRESET); + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); +} + +/** + * exynos_ss_udc_ep_cmd_complete - process event EP Command Complete + * @udc: The device state. + * @udc_ep: The endpoint this event is for. + * @event: The event being handled. + */ +static void exynos_ss_udc_ep_cmd_complete(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + struct exynos_ss_udc_ep_command *epcmd, *tepcmd; + struct exynos_ss_udc_req *udc_req; + int epnum; + int res; + bool restart; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out"); + + /* We use IOC _only_ for End Transfer command currently */ + + 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, + udc_ep->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); + } +} + +/** + * exynos_ss_udc_xfer_complete - complete transfer + * @udc: The device state. + * @udc_ep: The endpoint that has just completed. + * @event: The event being handled. + * + * Transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void exynos_ss_udc_xfer_complete(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + struct exynos_ss_udc_req *udc_req = udc_ep->req; + struct usb_request *req = &udc_req->req; + int size_left; + int result = 0; + + dev_dbg(udc->dev, "%s: ep%d%s, req %p\n", + __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out", req); + + if (!udc_req) { + dev_dbg(udc->dev, "XferCompl but no req\n"); + return; + } + + if (event & EXYNOS_USB3_DEPEVT_EventStatus_BUSERR) { + dev_err(udc->dev, "%s: Bus Error occured\n", __func__); + result = -ECONNRESET; + } + + if (udc_ep->trb->param2 & EXYNOS_USB3_TRB_HWO) + dev_err(udc->dev, "%s: HWO bit set\n", __func__); + + size_left = udc_ep->trb->param1 & EXYNOS_USB3_TRB_BUFSIZ_MASK; + + if (udc_ep->dir_in) { + /* Incomplete IN transfer */ + if (size_left) { + dev_err(udc->dev, "%s: BUFSIZ is not zero (%d)", + __func__, size_left); + /* REVISIT shall we -ECONNRESET here? */ + } + + udc_req->req.actual = udc_req->req.length - size_left; + } else { + int len; + + len = (req->length + udc_ep->ep.maxpacket - 1) & + ~(udc_ep->ep.maxpacket - 1); + udc_req->req.actual = len - size_left; + } + + 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: + 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; + } + } + + exynos_ss_udc_complete_request_lock(udc, udc_ep, udc_req, result); +} + +/** + * exynos_ss_udc_xfer_notready - process event Transfer Not Ready + * @udc: The device state. + * @udc_ep: The endpoint this event is for. + * @event: The event being handled. + */ +static void exynos_ss_udc_xfer_notready(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1; + int direction = index & 1; + u32 status = event & EXYNOS_USB3_DEPEVT_EventStatus_CTL_MASK; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum, + direction ? "in" : "out"); + + if (udc_ep->epnum == 0) { + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + /* + * Check if host is attempting to move data or start + * the status stage for a previous control transfer. + */ + if (status != + EXYNOS_USB3_DEPEVT_EventStatus_CTL_SETUP) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Setup phase\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + break; + + case EP0_DATA_PHASE: + /* + * Check if host is attempting to move data in the + * wrong direction. + */ + if (udc_ep->dir_in != direction && + status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Data phase\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + break; + + case EP0_WAIT_NRDY: + /* + * Check if host is attempting to start the data stage + * when data stage is not present or move more data + * than specified in the wLength field. + */ + if (status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Wait NotReady\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + udc_ep->dir_in = direction; + udc->ep0_state = EP0_STATUS_PHASE; + exynos_ss_udc_enqueue_status(udc); + break; + + case EP0_STATUS_PHASE: + /* FALLTHROUGH */ + default: + dev_dbg(udc->dev, "Unexpected XferNotReady\n"); + break; + } + } +} + +/** + * 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 = {{0}, }; + 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; + + 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; + + default: + dev_err(udc->dev, "Connection speed is unknown (%d)\n", speed); + return; + } + + /* Suspend the inactive Phy */ + if (udc->gadget.speed == USB_SPEED_SUPER) { + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + + /* Accept U1&U2 transition */ + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_AcceptU2Ena | + EXYNOS_USB3_DCTL_AcceptU1Ena); + } else { + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + } + + 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 = {{0}, }; + struct exynos_ss_udc_ep *ep; + int res; + int epnum; + + dev_dbg(udc->dev, "%s\n", __func__); +#ifdef CONFIG_USB_G_ANDROID + /* + * Android MTP should be configuration after disconnect uevet + * A reset USB device has the following characteristics: + * - Responds to the default USB address + * - Is not configured + * - Is not initially suspended + */ + if (udc->state == USB_STATE_CONFIGURED) + call_gadget(udc, disconnect); +#endif + /* Disable test mode */ + __bic32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_TstCtl_MASK); + + /* Enable PHYs */ + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + 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); + + udc->state = USB_STATE_DEFAULT; +} + +/** + * exynos_ss_udc_irq_ulstchng - process event USB Link State Change + * @udc: The device state. + * @event: The event being handled. + */ +static void exynos_ss_udc_irq_ulstchng(struct exynos_ss_udc *udc, u32 event) +{ + u32 link_state; + + link_state = event & EXYNOS_USB3_DEVT_EvtInfo_MASK; + + if (event & EXYNOS_USB3_DEVT_EvtInfo_SS) { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_U3) + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + else + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + } else { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend) + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + else + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + } + + /* Disconnect event detection for SMDK5250 EVT0 */ +#if defined(CONFIG_MACH_SMDK5250) + if (udc->release == 0x185a) { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend || + link_state == EXYNOS_USB3_DEVT_EvtInfo_SS_DIS) { + call_gadget(udc, disconnect); + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + dev_dbg(udc->dev, "Disconnection (0x%x, %s)\n", + link_state >> EXYNOS_USB3_DEVT_EvtInfo_SHIFT, + event & EXYNOS_USB3_DEVT_EvtInfo_SS ? + "SS" : "non-SS"); + } + } +#endif +} + +/** + * 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 epnum = get_usb_epnum(index); + struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum]; + + switch (event & EXYNOS_USB3_DEPEVT_EVENT_MASK) { + case EXYNOS_USB3_DEPEVT_EVENT_XferNotReady: + dev_dbg(udc->dev, "Xfer Not Ready\n"); + + exynos_ss_udc_xfer_notready(udc, udc_ep, event); + break; + + case EXYNOS_USB3_DEPEVT_EVENT_XferComplete: + dev_dbg(udc->dev, "Xfer Complete\n"); + + exynos_ss_udc_xfer_complete(udc, udc_ep, event); + + 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\n"); + + exynos_ss_udc_ep_cmd_complete(udc, udc_ep, event); + 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"); + exynos_ss_udc_irq_ulstchng(udc, event); + break; + + case EXYNOS_USB3_DEVT_EVENT_ConnectDone: +#if defined(USE_WAKE_LOCK) + wake_lock(&udc->usbd_wake_lock); +#endif + dev_dbg(udc->dev, "Connection Done"); + EXYNOS_SS_UDC_CABLE_CONNECT(udc, true); + exynos_ss_udc_irq_connectdone(udc); + break; + + case EXYNOS_USB3_DEVT_EVENT_USBRst: + dev_dbg(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); + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->state = USB_STATE_NOTATTACHED; + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + +#if defined(USE_WAKE_LOCK) + wake_lock_timeout(&udc->usbd_wake_lock, HZ * 5); +#endif + 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; + /* TODO: what if number of events more then buffer size? */ + + 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; + /* Do we need to read GEVENTCOUNT here and retry? */ + + 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; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + udc_ep->ep.maxpacket = epnum ? EP_SS_MPS : EP0_SS_MPS; +#else + udc_ep->ep.maxpacket = epnum ? EP_HS_MPS : EP0_HS_MPS; +#endif + 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_usb3_drd_pdata *pdata = 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 (pdata && pdata->phy_init) + pdata->phy_init(pdev, pdata->phy_type); + + __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_usb3_drd_pdata *pdata = 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 (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, pdata->phy_type); + + 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 = {{0}, }; + 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; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); +#else + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS); +#endif + 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; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); +#else + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS); +#endif + 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 = {{0}, }; + 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; + /* + * WORKAROUND: core revision 1.80a has a wrong release number + * in GSNPSID register + */ + if (udc->release == 0x131a) + udc->release = 0x180a; + dev_info(udc->dev, "Core ID Number: 0x%04x\n", reg >> 16); + dev_info(udc->dev, "Release Number: 0x%04x\n", udc->release); + + /* + * WORKAROUND: DWC3 revisions <1.90a have a bug + * when The device fails to connect at SuperSpeed + * and falls back to high-speed mode which causes + * the device to enter in a Connect/Disconnect loop + */ + if (udc->release < 0x190a) + __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) | +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + EXYNOS_USB3_DCFG_DevSpd(4), +#else + EXYNOS_USB3_DCFG_DevSpd(0), +#endif + 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); +} + +static int exynos_ss_udc_enable(struct exynos_ss_udc *udc) +{ + struct platform_device *pdev = to_platform_device(udc->dev); + + enable_irq(udc->irq); + clk_enable(udc->clk); + + exynos_ss_udc_phy_set(pdev); + exynos_ss_udc_corereset(udc); + exynos_ss_udc_init(udc); + + udc->eps[0].enabled = 1; + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); + + /* Start the device controller operation */ + exynos_ss_udc_run_stop(udc, 1); + + return 0; +} + +#ifdef CONFIG_USB_EXYNOS_SWITCH +#define EXYNOS_SS_UDC_ENABLE(udc) +#else +#define EXYNOS_SS_UDC_ENABLE(udc) exynos_ss_udc_enable(udc) +#endif + +static int exynos_ss_udc_disable(struct exynos_ss_udc *udc) +{ + struct platform_device *pdev = to_platform_device(udc->dev); + int ep; + + exynos_ss_udc_run_stop(udc, 0); + /* 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_phy_unset(pdev); + + clk_disable(udc->clk); + + disable_irq(udc->irq); + + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + + return 0; +} + +#ifdef CONFIG_USB_EXYNOS_SWITCH +#define EXYNOS_SS_UDC_DISABLE(udc) +#else +#define EXYNOS_SS_UDC_DISABLE(udc) exynos_ss_udc_disable(udc) +#endif + +/** + * exynos_ss_udc_vbus_session - software-controlled vbus active/in-active + * @gadget: The peripheral being vbus active/in-active. + * @is_active: The action to take (1 - vbus enable, 0 - vbus disable). + */ +static int exynos_ss_udc_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct exynos_ss_udc *udc = container_of(gadget, + struct exynos_ss_udc, gadget); + + if (!is_active) + exynos_ss_udc_disable(udc); + else + exynos_ss_udc_enable(udc); + + return 0; +} + +/** + * 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 = container_of(gadget, + struct exynos_ss_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 = { + .vbus_session = exynos_ss_udc_vbus_session, + .pullup = exynos_ss_udc_pullup, + .get_config_params = exynos_ss_udc_get_config_params, +}; + +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct exynos_ss_udc *udc = our_udc; + int ret; + + 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->speed < USB_SPEED_FULL) { + dev_err(udc->dev, "%s: bad speed\n", __func__); + return -EINVAL; + } + + if (!bind || !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; + udc->gadget.dev.dma_mask = udc->dev->dma_mask; + udc->gadget.speed = USB_SPEED_UNKNOWN; + + ret = device_add(&udc->gadget.dev); + if (ret) { + dev_err(udc->dev, "failed to register gadget device\n"); + goto err; + } + + ret = bind(&udc->gadget); + if (ret) { + dev_err(udc->dev, "failed bind %s\n", driver->driver.name); + + udc->gadget.dev.driver = NULL; + udc->driver = NULL; + goto err; + } + /* we must now enable ep0 ready for host detection and then + * set configuration. */ + EXYNOS_SS_UDC_ENABLE(udc); + /* report to the user, and return */ + dev_info(udc->dev, "bound driver %s\n", driver->driver.name); + return 0; + +err: + udc->driver = NULL; + udc->gadget.dev.driver = NULL; + return ret; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct exynos_ss_udc *udc = our_udc; + + if (!udc) + return -ENODEV; + + if (!driver || driver != udc->driver || !driver->unbind) + return -EINVAL; + + EXYNOS_SS_UDC_DISABLE(udc); + + udc->driver = NULL; + driver->unbind(&udc->gadget); + device_del(&udc->gadget.dev); + + dev_info(udc->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +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"); + return -ENOMEM; + } + + 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; + +#if defined(USE_WAKE_LOCK) + wake_lock_init(&udc->usbd_wake_lock, WAKE_LOCK_SUSPEND, + "usb device wake lock"); +#endif + ret = request_irq(udc->irq, + exynos_ss_udc_irq, + IRQF_SHARED, + dev_name(dev), + udc); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ\n"); + goto err_irq; + } + +#ifndef CONFIG_USB_XHCI_EXYNOS + disable_irq(udc->irq); +#endif + dev_info(dev, "regs %p, irq %d\n", udc->regs, udc->irq); + + udc->clk = clk_get(&pdev->dev, "usbdrd30"); + if (IS_ERR(udc->clk)) { + dev_err(dev, "cannot get UDC clock\n"); + ret = -EINVAL; + goto err_clk; + } + + device_initialize(&udc->gadget.dev); + + dev_set_name(&udc->gadget.dev, "gadget"); + + udc->gadget.is_dualspeed = 1; + 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; + + /* setup endpoint information */ + + INIT_LIST_HEAD(&udc->gadget.ep_list); + udc->gadget.ep0 = &udc->eps[0].ep; + + /* initialise the endpoints now the core has been initialised */ + 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); + + our_udc = udc; + return 0; + +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); + + 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); + if (res) + 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); + + if (udc->driver) { + call_gadget(udc, suspend); + EXYNOS_SS_UDC_DISABLE(udc); + } + + return 0; +} + +static int exynos_ss_udc_resume(struct platform_device *pdev) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + if (udc->driver) { + EXYNOS_SS_UDC_ENABLE(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, +}; + +static int __init exynos_ss_udc_modinit(void) +{ + return platform_driver_register(&exynos_ss_udc_driver); +} + +static void __exit exynos_ss_udc_modexit(void) +{ + platform_driver_unregister(&exynos_ss_udc_driver); +} + +module_init(exynos_ss_udc_modinit); +module_exit(exynos_ss_udc_modexit); + +MODULE_DESCRIPTION("EXYNOS SuperSpeed USB 3.0 Device Controller"); +MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>"); +MODULE_LICENSE("GPL"); |