diff options
Diffstat (limited to 'drivers/usb/cdns3/gadget.c')
-rw-r--r-- | drivers/usb/cdns3/gadget.c | 2526 |
1 files changed, 2526 insertions, 0 deletions
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c new file mode 100644 index 000000000000..b3d81aabe9c3 --- /dev/null +++ b/drivers/usb/cdns3/gadget.c @@ -0,0 +1,2526 @@ +/** + * gadget.c - Cadence USB3 Device Core file + * + * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com + * Copyright 2017 NXP + * + * Authors: Pawel Jez <pjez@cadence.com>, + * Konrad Kociolek <konrad@cadence.com> + * Peter Chen <peter.chen@nxp.com> + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> +#include <linux/usb/composite.h> +#include <linux/of_platform.h> +#include <linux/usb/gadget.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/byteorder/generic.h> +#include <linux/ctype.h> + +#include "core.h" +#include "gadget-export.h" +#include "gadget.h" +#include "io.h" + +/*-------------------------------------------------------------------------*/ +/* Function declarations */ + +static void select_ep(struct usb_ss_dev *usb_ss, u32 ep); +static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_ep0_config(struct usb_ss_dev *usb_ss); +static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss); +static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss, + dma_addr_t dma_addr, unsigned int length, int erdy); +static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep); +static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req, int set); +static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss); +static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss, + int dir); +static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss, + u32 usb_ists); +static int usb_ss_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); +static int usb_ss_gadget_ep0_disable(struct usb_ep *ep); +static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value); +static int usb_ss_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags); +static int usb_ss_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); +static int usb_ss_gadget_ep_disable(struct usb_ep *ep); +static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void usb_ss_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +static int usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags); +static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request); +static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value); +static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep); +static int usb_ss_gadget_get_frame(struct usb_gadget *gadget); +static int usb_ss_gadget_wakeup(struct usb_gadget *gadget); +static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered); +static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on); +static int usb_ss_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget); +static int usb_ss_init_ep(struct usb_ss_dev *usb_ss); +static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss); +static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss); +static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss); +static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable); +static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss); + +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static u32 gadget_readl(struct usb_ss_dev *usb_ss, uint32_t __iomem *reg) +{ + return cdns_readl(reg); +} + +static void gadget_writel(struct usb_ss_dev *usb_ss, + uint32_t __iomem *reg, u32 value) +{ + cdns_writel(reg, value); +} + +/** + * next_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +static struct usb_request *next_request(struct list_head *list) +{ + if (list_empty(list)) + return NULL; + return list_first_entry(list, struct usb_request, list); +} + +/** + * wait_reg_bit - Read reg and compare until equal to specific value + * @reg: the register address to read + * @value: the value to compare + * @wait_value: 0 or 1 + * @timeout_ms: timeout value in milliseconds, must be larger than 1 + * + * Returns -ETIMEDOUT if timeout occurs + */ +static int wait_reg_bit(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int wait_value, int timeout_ms) +{ + u32 temp; + + WARN_ON(timeout_ms <= 0); + timeout_ms *= 100; + temp = cdns_readl(reg); + while (timeout_ms-- > 0) { + if (!!(temp & value) == wait_value) + return 0; + temp = cdns_readl(reg); + udelay(10); + } + + dev_err(&usb_ss->dev, "wait register timeout %s\n", __func__); + return -ETIMEDOUT; +} + +static int wait_reg_bit_set(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int timeout_ms) +{ + return wait_reg_bit(usb_ss, reg, value, 1, timeout_ms); +} + +static int wait_reg_bit_clear(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int timeout_ms) +{ + return wait_reg_bit(usb_ss, reg, value, 0, timeout_ms); +} + +/** + * select_ep - selects endpoint + * @usb_ss: extended gadget object + * @ep: endpoint address + */ +static void select_ep(struct usb_ss_dev *usb_ss, u32 ep) +{ + if (!usb_ss || !usb_ss->regs) { + dev_err(&usb_ss->dev, "Failed to select endpoint!\n"); + return; + } + + gadget_writel(usb_ss, &usb_ss->regs->ep_sel, ep); +} + +/** + * usb_ss_allocate_trb_pool - Allocates TRB's pool for selected endpoint + * @usb_ss_ep: extended endpoint object + * + * Function will return 0 on success or -ENOMEM on allocation error + */ +static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + if (!usb_ss_ep->trb_pool) { + usb_ss_ep->trb_pool = dma_zalloc_coherent(usb_ss->sysdev, + sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM, + &usb_ss_ep->trb_pool_dma, GFP_DMA); + if (!usb_ss_ep->trb_pool) + return -ENOMEM; + } + + if (!usb_ss_ep->cpu_addr) { + usb_ss_ep->cpu_addr = dma_alloc_coherent(usb_ss->sysdev, + CDNS3_UNALIGNED_BUF_SIZE, + &usb_ss_ep->dma_addr, GFP_DMA); + if (!usb_ss_ep->cpu_addr) + return -ENOMEM; + } + + return 0; +} + +/** + * cdns_data_flush - do flush data at onchip buffer + * @usb_ss_ep: extended endpoint object + * + * Endpoint must be selected before call to this function + * + * Returns zero on success or negative value on failure + */ +static int cdns_data_flush(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK); + /* wait for DFLUSH cleared */ + return wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK, 100); +} + +/** + * cdns_ep_stall_flush - Stalls and flushes selected endpoint + * @usb_ss_ep: extended endpoint object + * + * Endpoint must be selected before call to this function + */ +static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK | EP_CMD__ERDY__MASK | + EP_CMD__SSTALL__MASK); + + /* wait for DFLUSH cleared */ + wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK, 100); + usb_ss_ep->stalled_flag = 1; +} + +/** + * cdns_ep0_config - Configures default endpoint + * @usb_ss: extended gadget object + * + * Functions sets parameters: maximal packet size and enables interrupts + */ +static void cdns_ep0_config(struct usb_ss_dev *usb_ss) +{ + u32 reg, max_packet_size = 0; + + switch (usb_ss->gadget.speed) { + case USB_SPEED_UNKNOWN: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_0; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(0); + break; + + case USB_SPEED_LOW: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_8; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); + break; + + case USB_SPEED_FULL: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_HIGH: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_WIRELESS: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_SUPER: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + break; + + case USB_SPEED_SUPER_PLUS: + dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n"); + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + break; + } + + /* init ep out */ + select_ep(usb_ss, USB_DIR_OUT); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, + EP_CFG__ENABLE__MASK | + EP_CFG__MAXPKTSIZE__WRITE(max_packet_size)); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__SETUPEN__MASK | + EP_STS_EN__DESCMISEN__MASK | + EP_STS_EN__TRBERREN__MASK); + + /* init ep in */ + select_ep(usb_ss, USB_DIR_IN); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, + EP_CFG__ENABLE__MASK | + EP_CFG__MAXPKTSIZE__WRITE(max_packet_size)); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__SETUPEN__MASK | + EP_STS_EN__TRBERREN__MASK); + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + reg |= USB_CONF__U1DS__MASK | USB_CONF__U2DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + + cdns_prepare_setup_packet(usb_ss); +} + +/** + * cdns_gadget_unconfig - Unconfigures device controller + * @usb_ss: extended gadget object + */ +static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss) +{ + /* RESET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGRST__MASK); + + cdns_enable_l1(usb_ss, 0); + usb_ss->hw_configured_flag = 0; + usb_ss->onchip_mem_allocated_size = 0; + usb_ss->out_mem_is_allocated = 0; +} + +/** + * cdns_ep0_run_transfer - Do transfer on default endpoint hardware + * @usb_ss: extended gadget object + * @dma_addr: physical address where data is/will be stored + * @length: data length + * @erdy: set it to 1 when ERDY packet should be sent - + * exit from flow control state + */ +static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss, + dma_addr_t dma_addr, unsigned int length, int erdy) +{ + usb_ss->trb_ep0[0] = TRB_SET_DATA_BUFFER_POINTER(dma_addr); + usb_ss->trb_ep0[1] = TRB_SET_TRANSFER_LENGTH((u32)length); + usb_ss->trb_ep0[2] = TRB_SET_CYCLE_BIT | + TRB_SET_INT_ON_COMPLETION | TRB_TYPE_NORMAL; + + dev_dbg(&usb_ss->dev, "DRBL(%02X)\n", + usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT); + + select_ep(usb_ss, usb_ss->ep0_data_dir + ? USB_DIR_IN : USB_DIR_OUT); + + gadget_writel(usb_ss, &usb_ss->regs->ep_traddr, + EP_TRADDR__TRADDR__WRITE(usb_ss->trb_ep0_dma)); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DRDY__MASK); /* drbl */ + + if (erdy) + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK); +} + +/** + * cdns_ep_run_transfer - Do transfer on no-default endpoint hardware + * @usb_ss_ep: extended endpoint object + * + * Returns zero on success or negative value on failure + */ +static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep) +{ + dma_addr_t trb_dma; + struct usb_request *request = next_request(&usb_ss_ep->request_list); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + int sg_iter = 0; + struct usb_ss_trb *trb; + + if (request == NULL) + return -EINVAL; + + if (request->num_sgs > USB_SS_TRBS_NUM) + return -EINVAL; + + dev_dbg(&usb_ss->dev, "DRBL(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + + usb_ss_ep->hw_pending_flag = 1; + trb_dma = request->dma; + + /* must allocate buffer aligned to 8 */ + if ((request->dma % ADDR_MODULO_8)) { + if (request->length <= CDNS3_UNALIGNED_BUF_SIZE) { + memcpy(usb_ss_ep->cpu_addr, request->buf, + request->length); + trb_dma = usb_ss_ep->dma_addr; + } else { + return -ENOMEM; + } + } + + trb = usb_ss_ep->trb_pool; + + do { + /* fill TRB */ + trb->offset0 = TRB_SET_DATA_BUFFER_POINTER(request->num_sgs == 0 + ? trb_dma : request->sg[sg_iter].dma_address); + + trb->offset4 = TRB_SET_BURST_LENGTH(16) | + TRB_SET_TRANSFER_LENGTH(request->num_sgs == 0 ? + request->length : request->sg[sg_iter].length); + + trb->offset8 = TRB_SET_CYCLE_BIT + | TRB_SET_INT_ON_COMPLETION + | TRB_SET_INT_ON_SHORT_PACKET + | TRB_TYPE_NORMAL; + + ++sg_iter; + ++trb; + + } while (sg_iter < request->num_sgs); + + /* arm transfer on selected endpoint */ + select_ep(usb_ss_ep->usb_ss, + usb_ss_ep->endpoint.desc->bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_traddr, + EP_TRADDR__TRADDR__WRITE(usb_ss_ep->trb_pool_dma)); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DRDY__MASK); /* DRDY */ + return 0; +} + +/** + * cdns_get_setup_ret - Returns status of handling setup packet + * Setup is handled by gadget driver + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns zero on success or negative value on failure + */ +static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + spin_unlock(&usb_ss->lock); + usb_ss->setup_pending = 1; + ret = usb_ss->gadget_driver->setup(&usb_ss->gadget, ctrl_req); + usb_ss->setup_pending = 0; + spin_lock(&usb_ss->lock); + return ret; +} + +static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss) +{ + usb_ss->ep0_data_dir = 0; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 8, 0); +} + +/** + * cdns_req_ep0_set_address - Handling of SET_ADDRESS standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = usb_ss->gadget.state; + u32 reg; + u32 addr; + + addr = le16_to_cpu(ctrl_req->wValue); + + if (addr > DEVICE_ADDRESS_MAX) { + dev_err(&usb_ss->dev, + "Device address (%d) cannot be greater than %d\n", + addr, DEVICE_ADDRESS_MAX); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(&usb_ss->dev, "USB device already configured\n"); + return -EINVAL; + } + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_cmd); + + gadget_writel(usb_ss, &usb_ss->regs->usb_cmd, reg + | USB_CMD__FADDR__WRITE(addr) + | USB_CMD__SET_ADDR__MASK); + + usb_gadget_set_state(&usb_ss->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + cdns_prepare_setup_packet(usb_ss); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return 0; +} + +/** + * cdns_req_ep0_get_status - Handling of GET_STATUS standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + u16 usb_status = 0; + unsigned int length = 2; + u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK; + u32 reg; + + switch (recip) { + + case USB_RECIP_DEVICE: + /* handling otg features */ + if (ctrl_req->wIndex == OTG_STS_SELECTOR) { + length = 1; + usb_status = usb_ss->gadget.host_request_flag; + } else { + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + + if (reg & USB_STS__U1ENS__MASK) + usb_status |= 1uL << USB_DEV_STAT_U1_ENABLED; + + if (reg & USB_STS__U2ENS__MASK) + usb_status |= 1uL << USB_DEV_STAT_U2_ENABLED; + + if (usb_ss->wake_up_flag) + usb_status |= 1uL << USB_DEVICE_REMOTE_WAKEUP; + + /* self powered */ + usb_status |= usb_ss->gadget.is_selfpowered; + } + break; + + case USB_RECIP_INTERFACE: + return cdns_get_setup_ret(usb_ss, ctrl_req); + + case USB_RECIP_ENDPOINT: + /* check if endpoint is stalled */ + select_ep(usb_ss, ctrl_req->wIndex); + if (gadget_readl(usb_ss, &usb_ss->regs->ep_sts) + & EP_STS__STALL__MASK) + usb_status = 1; + break; + + default: + return -EINVAL; + } + + *(u16 *)usb_ss->setup = cpu_to_le16(usb_status); + + usb_ss->actual_ep0_request = NULL; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, length, 1); + return 0; +} + +/** + * cdns_req_ep0_handle_feature - + * Handling of GET/SET_FEATURE standard USB request + * + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * @set: must be set to 1 for SET_FEATURE request + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req, int set) +{ + u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK; + struct usb_ss_endpoint *usb_ss_ep; + u32 reg; + u8 tmode = 0; + int ret = 0; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (ctrl_req->wValue) { + case USB_DEVICE_U1_ENABLE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_SUPER) + return -EINVAL; + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + if (set) + /* set U1EN */ + reg |= USB_CONF__U1EN__MASK; + else + /* set U1 disable */ + reg |= USB_CONF__U1DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + break; + case USB_DEVICE_U2_ENABLE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_SUPER) + return -EINVAL; + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + if (set) + /* set U2EN */ + reg |= USB_CONF__U2EN__MASK; + else + /* set U2 disable */ + reg |= USB_CONF__U2DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + break; + case USB_DEVICE_A_HNP_SUPPORT: + break; + case USB_DEVICE_B_HNP_ENABLE: + if (!usb_ss->gadget.b_hnp_enable && set) + usb_ss->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_REMOTE_WAKEUP: + usb_ss->wake_up_flag = !!set; + break; + case USB_DEVICE_TEST_MODE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_HIGH && + usb_ss->gadget.speed != USB_SPEED_FULL) + return -EINVAL; + if (ctrl_req->wLength != 0 || + ctrl_req->bRequestType & USB_DIR_IN) { + dev_err(&usb_ss->dev, "req is error\n"); + return -EINVAL; + } + tmode = le16_to_cpu(ctrl_req->wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + reg = gadget_readl(usb_ss, + &usb_ss->regs->usb_cmd); + tmode -= 1; + reg |= USB_CMD__STMODE | + USB_CMD__TMODE_SEL(tmode); + gadget_writel(usb_ss, &usb_ss->regs->usb_cmd, + reg); + dev_info(&usb_ss->dev, + "set test mode, val=0x%x", reg); + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + + } + break; + case USB_RECIP_INTERFACE: + return cdns_get_setup_ret(usb_ss, ctrl_req); + case USB_RECIP_ENDPOINT: + select_ep(usb_ss, ctrl_req->wIndex); + if (set) { + /* set stall */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__SSTALL__MASK); + + /* handle non zero endpoint software endpoint */ + if (ctrl_req->wIndex & 0x7F) { + usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]; + usb_ss_ep->stalled_flag = 1; + } + } else { + struct usb_request *request; + + if (ctrl_req->wIndex & 0x7F) { + if (usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]->wedge_flag) + goto jmp_wedge; + } + + /* clear stall */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK); + /* wait for EPRST cleared */ + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + + /* handle non zero endpoint software endpoint */ + if (ctrl_req->wIndex & 0x7F) { + usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]; + usb_ss_ep->stalled_flag = 0; + + request = next_request( + &usb_ss_ep->request_list); + if (request) + cdns_ep_run_transfer(usb_ss_ep); + } + } +jmp_wedge: + select_ep(usb_ss, 0x00); + break; + + default: + return -EINVAL; + } + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return ret; +} + +/** + * cdns_req_ep0_set_sel - Handling of SET_SEL standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + if (usb_ss->gadget.state < USB_STATE_ADDRESS) + return -EINVAL; + + if (ctrl_req->wLength != 6) { + dev_err(&usb_ss->dev, "Set SEL should be 6 bytes, got %d\n", + ctrl_req->wLength); + return -EINVAL; + } + + usb_ss->ep0_data_dir = 0; + usb_ss->actual_ep0_request = NULL; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 6, 1); + return 0; +} + +/** + * cdns_req_ep0_set_isoch_delay - + * Handling of GET_ISOCH_DELAY standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + if (ctrl_req->wIndex || ctrl_req->wLength) + return -EINVAL; + + usb_ss->isoch_delay = ctrl_req->wValue; + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return 0; +} + +static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable) +{ + if (enable) + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__L1EN__MASK); + else + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__L1DS__MASK); +} + +/** + * cdns_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, 0x7FFF on deferred status stage, error code on error + */ +static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = usb_ss->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep; + int i, result = 0; + + switch (device_state) { + case USB_STATE_ADDRESS: + /* Configure non-control EPs */ + list_for_each_entry_safe(usb_ss_ep, temp_ss_ep, + &usb_ss->ep_match_list, ep_match_pending_list) + cdns_ep_config(usb_ss_ep); + + result = cdns_get_setup_ret(usb_ss, ctrl_req); + + if (result != 0) + return result; + + if (config) { + if (!usb_ss->hw_configured_flag) { + /* SET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGSET__MASK); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | + EP_CMD__REQ_CMPL__MASK); + /* wait until configuration set */ + result = wait_reg_bit_set(usb_ss, + &usb_ss->regs->usb_sts, + USB_STS__CFGSTS__MASK, 100); + usb_ss->hw_configured_flag = 1; + cdns_enable_l1(usb_ss, 1); + + list_for_each_entry(ep, + &usb_ss->gadget.ep_list, + ep_list) { + if (ep->enabled) + cdns_ep_run_transfer( + to_usb_ss_ep(ep)); + } + } + } else { + cdns_gadget_unconfig(usb_ss); + for (i = 0; i < usb_ss->ep_nums; i++) + usb_ss->eps[i]->endpoint.enabled = 0; + usb_gadget_set_state(&usb_ss->gadget, + USB_STATE_ADDRESS); + } + break; + case USB_STATE_CONFIGURED: + result = cdns_get_setup_ret(usb_ss, ctrl_req); + if (!config && !result) { + cdns_gadget_unconfig(usb_ss); + for (i = 0; i < usb_ss->ep_nums; i++) + usb_ss->eps[i]->endpoint.enabled = 0; + usb_gadget_set_state(&usb_ss->gadget, + USB_STATE_ADDRESS); + } + break; + default: + result = -EINVAL; + } + + return result; +} + +/** + * cdns_ep0_standard_request - Handling standard USB requests + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + switch (ctrl_req->bRequest) { + case USB_REQ_SET_ADDRESS: + return cdns_req_ep0_set_address(usb_ss, ctrl_req); + case USB_REQ_SET_CONFIGURATION: + return cdns_req_ep0_set_configuration(usb_ss, ctrl_req); + case USB_REQ_GET_STATUS: + return cdns_req_ep0_get_status(usb_ss, ctrl_req); + case USB_REQ_CLEAR_FEATURE: + return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 0); + case USB_REQ_SET_FEATURE: + return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 1); + case USB_REQ_SET_SEL: + return cdns_req_ep0_set_sel(usb_ss, ctrl_req); + case USB_REQ_SET_ISOCH_DELAY: + return cdns_req_ep0_set_isoch_delay(usb_ss, ctrl_req); + default: + return cdns_get_setup_ret(usb_ss, ctrl_req); + } +} + +/** + * cdns_ep0_setup_phase - Handling setup USB requests + * @usb_ss: extended gadget object + */ +static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss) +{ + int result; + struct usb_ctrlrequest *ctrl_req = + (struct usb_ctrlrequest *)usb_ss->setup; + + if ((ctrl_req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + result = cdns_ep0_standard_request(usb_ss, ctrl_req); + else + result = cdns_get_setup_ret(usb_ss, ctrl_req); + + if (result != 0 && result != USB_GADGET_DELAYED_STATUS) { + dev_dbg(&usb_ss->dev, "STALL(00) %d\n", result); + /* set_stall on ep0 */ + select_ep(usb_ss, 0x00); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__SSTALL__MASK); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + } +} + +/** + * cdns_check_ep_interrupt_proceed - Processes interrupt related to endpoint + * @usb_ss_ep: extended endpoint object + * + * Returns 0 + */ +static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + struct usb_request *request; + u32 ep_sts_reg; + + select_ep(usb_ss, usb_ss_ep->endpoint.address); + ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts); + + dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg); + + if (ep_sts_reg & EP_STS__TRBERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK); + + dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__ISOERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__ISOERR__MASK); + dev_dbg(&usb_ss->dev, "ISOERR(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__OUTSMM__MASK) { + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__OUTSMM__MASK); + dev_dbg(&usb_ss->dev, "OUTSMM(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__NRDY__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__NRDY__MASK); + dev_dbg(&usb_ss->dev, "NRDY(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if ((ep_sts_reg & EP_STS__IOC__MASK) + || (ep_sts_reg & EP_STS__ISP__MASK)) { + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__IOC__MASK | EP_STS__ISP__MASK); + + /* get just completed request */ + request = next_request(&usb_ss_ep->request_list); + if (!request) + return 0; + + if ((request->dma % ADDR_MODULO_8) && + (usb_ss_ep->dir == USB_DIR_OUT)) + memcpy(request->buf, usb_ss_ep->cpu_addr, + request->length); + + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + usb_ss_ep->endpoint.desc->bEndpointAddress + & ENDPOINT_DIR_MASK); + + request->status = 0; + request->actual = + le32_to_cpu(((u32 *) usb_ss_ep->trb_pool)[1]) + & ACTUAL_TRANSFERRED_BYTES_MASK; + + dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n", + usb_ss_ep->endpoint.desc->bEndpointAddress, + request->actual); + + list_del(&request->list); + + usb_ss_ep->hw_pending_flag = 0; + if (request->complete) { + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request(&usb_ss_ep->endpoint, + request); + spin_lock(&usb_ss->lock); + } + + if (request->buf == usb_ss->zlp_buf) + kfree(request); + + /* handle deferred STALL */ + if (usb_ss_ep->stalled_flag) { + cdns_ep_stall_flush(usb_ss_ep); + return 0; + } + + /* exit if hardware transfer already started */ + if (usb_ss_ep->hw_pending_flag) + return 0; + + /* if any request queued run it! */ + if (!list_empty(&usb_ss_ep->request_list)) + cdns_ep_run_transfer(usb_ss_ep); + } + + if (ep_sts_reg & EP_STS__DESCMIS__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK); + dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + return 0; +} + +/** + * cdns_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0 + * @usb_ss: extended gadget object + * @dir: 1 for IN direction, 0 for OUT direction + */ +static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss, int dir) +{ + u32 ep_sts_reg; + int i; + + select_ep(usb_ss, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT)); + ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts); + + dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg); + + __pending_setup_status_handler(usb_ss); + + if ((ep_sts_reg & EP_STS__SETUP__MASK) && (dir == 0)) { + dev_dbg(&usb_ss->dev, "SETUP(%02X)\n", 0x00); + + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__SETUP__MASK | + EP_STS__IOC__MASK | EP_STS__ISP__MASK); + + dev_dbg(&usb_ss->dev, "SETUP: "); + for (i = 0; i < 8; i++) + dev_dbg(&usb_ss->dev, "%02X ", usb_ss->setup[i]); + dev_dbg(&usb_ss->dev, "\nSTATE: %d\n", usb_ss->gadget.state); + usb_ss->ep0_data_dir = usb_ss->setup[0] & USB_DIR_IN; + cdns_ep0_setup_phase(usb_ss); + ep_sts_reg &= ~(EP_STS__SETUP__MASK | + EP_STS__IOC__MASK | + EP_STS__ISP__MASK); + } + + if (ep_sts_reg & EP_STS__TRBERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK); + dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n", + dir ? USB_DIR_IN : USB_DIR_OUT); + } + + if (ep_sts_reg & EP_STS__DESCMIS__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK); + + dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n", + dir ? USB_DIR_IN : USB_DIR_OUT); + + if (dir == 0 && !usb_ss->setup_pending) { + usb_ss->ep0_data_dir = 0; + cdns_ep0_run_transfer(usb_ss, + usb_ss->setup_dma, 8, 0); + } + } + + if ((ep_sts_reg & EP_STS__IOC__MASK) + || (ep_sts_reg & EP_STS__ISP__MASK)) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__IOC__MASK); + if (usb_ss->actual_ep0_request) { + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, + usb_ss->actual_ep0_request, + usb_ss->ep0_data_dir); + + usb_ss->actual_ep0_request->actual = + le32_to_cpu((usb_ss->trb_ep0)[1]) + & ACTUAL_TRANSFERRED_BYTES_MASK; + + dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n", + dir ? USB_DIR_IN : USB_DIR_OUT, + usb_ss->actual_ep0_request->actual); + list_del_init(&usb_ss->actual_ep0_request->list); + } + + if (usb_ss->actual_ep0_request + && usb_ss->actual_ep0_request->complete) { + spin_unlock(&usb_ss->lock); + usb_ss->actual_ep0_request->complete(usb_ss->gadget.ep0, + usb_ss->actual_ep0_request); + spin_lock(&usb_ss->lock); + } + cdns_prepare_setup_packet(usb_ss); + gadget_writel(usb_ss, + &usb_ss->regs->ep_cmd, EP_CMD__REQ_CMPL__MASK); + } +} + +/** + * cdns_check_usb_interrupt_proceed - Processes interrupt related to device + * @usb_ss: extended gadget object + * @usb_ists: bitmap representation of device's reported interrupts + * (usb_ists register value) + */ +static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss, + u32 usb_ists) +{ + int interrupt_bit = ffs(usb_ists) - 1; + int speed; + u32 val; + + dev_dbg(&usb_ss->dev, "USB interrupt detected\n"); + + switch (interrupt_bit) { + case USB_ISTS__CON2I__SHIFT: + /* FS/HS Connection detected */ + dev_dbg(&usb_ss->dev, + "[Interrupt] FS/HS Connection detected\n"); + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + speed = USB_STS__USBSPEED__READ(val); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n", + usb_speed_string(speed), speed, val); + usb_ss->gadget.speed = speed; + usb_ss->is_connected = 1; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED); + cdns_ep0_config(usb_ss); + break; + case USB_ISTS__CONI__SHIFT: + /* SS Connection detected */ + dev_dbg(&usb_ss->dev, "[Interrupt] SS Connection detected\n"); + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + speed = USB_STS__USBSPEED__READ(val); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n", + usb_speed_string(speed), speed, val); + usb_ss->gadget.speed = speed; + usb_ss->is_connected = 1; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED); + cdns_ep0_config(usb_ss); + break; + case USB_ISTS__DIS2I__SHIFT: + case USB_ISTS__DISI__SHIFT: + /* SS Disconnection detected */ + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + dev_dbg(&usb_ss->dev, + "[Interrupt] Disconnection detected: usbsts:0x%x\n", + val); + if (usb_ss->gadget_driver + && usb_ss->gadget_driver->disconnect) { + + spin_unlock(&usb_ss->lock); + usb_ss->gadget_driver->disconnect(&usb_ss->gadget); + spin_lock(&usb_ss->lock); + } + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_NOTATTACHED); + usb_ss->is_connected = 0; + cdns_gadget_unconfig(usb_ss); + break; + case USB_ISTS__L2ENTI__SHIFT: + dev_dbg(&usb_ss->dev, + "[Interrupt] Device suspended\n"); + break; + case USB_ISTS__L2EXTI__SHIFT: + dev_dbg(&usb_ss->dev, "[Interrupt] L2 exit detected\n"); + /* + * Exit from standby mode + * on L2 exit (Suspend in HS/FS or SS) + */ + break; + case USB_ISTS__U3EXTI__SHIFT: + /* + * Exit from standby mode + * on U3 exit (Suspend in HS/FS or SS) + */ + dev_dbg(&usb_ss->dev, "[Interrupt] U3 exit detected\n"); + break; + + /* resets cases */ + case USB_ISTS__UWRESI__SHIFT: + case USB_ISTS__UHRESI__SHIFT: + case USB_ISTS__U2RESI__SHIFT: + dev_dbg(&usb_ss->dev, "[Interrupt] Reset detected\n"); + speed = USB_STS__USBSPEED__READ( + gadget_readl(usb_ss, &usb_ss->regs->usb_sts)); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_DEFAULT); + usb_ss->gadget.speed = speed; + cdns_gadget_unconfig(usb_ss); + cdns_ep0_config(usb_ss); + break; + default: + break; + } + + /* Clear interrupt bit */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ists, (1uL << interrupt_bit)); +} + +/** + * cdns_irq_handler - irq line interrupt handler + * @cdns: cdns3 instance + * + * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV, + * IRQ_NONE when interrupt raised by other device connected + * to the irq line + */ +static irqreturn_t cdns_irq_handler_thread(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss = + container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + u32 reg; + enum irqreturn ret = IRQ_NONE; + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + + /* check USB device interrupt */ + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_ists); + if (reg) { + dev_dbg(&usb_ss->dev, "usb_ists: %08X\n", reg); + cdns_check_usb_interrupt_proceed(usb_ss, reg); + ret = IRQ_HANDLED; + } + + /* check endpoint interrupt */ + reg = gadget_readl(usb_ss, &usb_ss->regs->ep_ists); + if (reg != 0) { + dev_dbg(&usb_ss->dev, "ep_ists: %08X\n", reg); + } else { + if (gadget_readl(usb_ss, &usb_ss->regs->usb_sts) & + USB_STS__CFGSTS__MASK) + ret = IRQ_HANDLED; + goto irqend; + } + + /* handle default endpoint OUT */ + if (reg & EP_ISTS__EOUT0__MASK) { + cdns_check_ep0_interrupt_proceed(usb_ss, 0); + ret = IRQ_HANDLED; + } + + /* handle default endpoint IN */ + if (reg & EP_ISTS__EIN0__MASK) { + cdns_check_ep0_interrupt_proceed(usb_ss, 1); + ret = IRQ_HANDLED; + } + + /* check if interrupt from non default endpoint, if no exit */ + reg &= ~(EP_ISTS__EOUT0__MASK | EP_ISTS__EIN0__MASK); + if (!reg) + goto irqend; + + do { + unsigned int bit_pos = ffs(reg); + u32 bit_mask = 1 << (bit_pos - 1); + + dev_dbg(&usb_ss->dev, "Interrupt on index: %d bitmask %08X\n", + CAST_EP_REG_POS_TO_INDEX(bit_pos), bit_mask); + cdns_check_ep_interrupt_proceed( + usb_ss->eps[CAST_EP_REG_POS_TO_INDEX(bit_pos)]); + reg &= ~bit_mask; + ret = IRQ_HANDLED; + } while (reg); + +irqend: + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep0_enable + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int usb_ss_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/** + * usb_ss_gadget_ep0_disable + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int usb_ss_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/** + * usb_ss_gadget_ep0_set_halt + * @ep: pointer to endpoint zero object + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 + */ +static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + /* TODO */ + return 0; +} + +static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss) +{ + struct usb_request *request = usb_ss->pending_status_request; + + if (usb_ss->status_completion_no_call && request && request->complete) { + request->complete(usb_ss->gadget.ep0, request); + usb_ss->status_completion_no_call = 0; + } +} + +static void pending_setup_status_handler(struct work_struct *work) +{ + struct usb_ss_dev *usb_ss = container_of(work, struct usb_ss_dev, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + __pending_setup_status_handler(usb_ss); + spin_unlock_irqrestore(&usb_ss->lock, flags); +} + +/** + * usb_ss_gadget_ep0_queue Transfer data on endpoint zero + * @ep: pointer to endpoint zero object + * @request: pointer to request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + int ret = 0; + unsigned long flags; + int erdy_sent = 0; + /* get extended endpoint */ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n", + usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT, + request->length); + + /* send STATUS stage */ + if (request->length == 0 && request->zero == 0) { + spin_lock_irqsave(&usb_ss->lock, flags); + select_ep(usb_ss, 0x00); + if (!usb_ss->hw_configured_flag) { + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGSET__MASK); /* SET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + /* wait until configuration set */ + ret = wait_reg_bit_set(usb_ss, &usb_ss->regs->usb_sts, + USB_STS__CFGSTS__MASK, 100); + erdy_sent = 1; + usb_ss->hw_configured_flag = 1; + cdns_enable_l1(usb_ss, 1); + + list_for_each_entry(ep, + &usb_ss->gadget.ep_list, + ep_list) { + + if (ep->enabled) + cdns_ep_run_transfer( + to_usb_ss_ep(ep)); + } + } + if (!erdy_sent) + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + + cdns_prepare_setup_packet(usb_ss); + request->actual = 0; + usb_ss->status_completion_no_call = true; + usb_ss->pending_status_request = request; + spin_unlock_irqrestore(&usb_ss->lock, flags); + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * ep0_queue is back. + */ + queue_work(system_freezable_wq, &usb_ss->pending_status_wq); + return 0; + } + + spin_lock_irqsave(&usb_ss->lock, flags); + if (!list_empty(&usb_ss_ep->request_list)) { + dev_err(&usb_ss->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return -EOPNOTSUPP; + } + + ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request, + usb_ss->ep0_data_dir); + if (ret) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_err(&usb_ss->dev, "failed to map request\n"); + return -EINVAL; + } + + usb_ss->actual_ep0_request = request; + cdns_ep0_run_transfer(usb_ss, request->dma, request->length, 1); + list_add_tail(&request->list, &usb_ss_ep->request_list); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} +/** + * ep_onchip_buffer_alloc - Try to allocate onchip buf for EP + * + * The real allocation will occur during write to EP_CFG register, + * this function is used to check if the 'size' allocation is allowed. + * + * @usb_ss: extended gadget object + * @size: the size (KB) for EP would like to allocate + * @is_in: the direction for EP + * + * Return 0 if the later allocation is allowed or negative value on failure + */ + +static int ep_onchip_buffer_alloc(struct usb_ss_dev *usb_ss, + int size, int is_in) +{ + if (is_in) { + usb_ss->onchip_mem_allocated_size += size; + } else if (!usb_ss->out_mem_is_allocated) { + /* ALL OUT EPs are shared the same chunk onchip memory */ + usb_ss->onchip_mem_allocated_size += size; + usb_ss->out_mem_is_allocated = 1; + } + + if (usb_ss->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) { + usb_ss->onchip_mem_allocated_size -= size; + return -EPERM; + } else { + return 0; + } +} + +/** + * cdns_ep_config Configure hardware endpoint + * @usb_ss_ep: extended endpoint object + */ +static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + u32 ep_cfg = 0; + u32 max_packet_size = 0; + u32 bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir; + u32 interrupt_mask = 0; + int is_in = !!usb_ss_ep->dir; + bool is_iso_ep = (usb_ss_ep->type == USB_ENDPOINT_XFER_ISOC); + int default_buf_size = CDNS3_EP_BUF_SIZE; + + dev_dbg(&usb_ss->dev, "%s: %s addr=0x%x\n", __func__, + usb_ss_ep->name, bEndpointAddress); + + if (is_iso_ep) { + ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_ISOC); + interrupt_mask = INTERRUPT_MASK; + } else { + ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_BULK); + } + + switch (usb_ss->gadget.speed) { + case USB_SPEED_UNKNOWN: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0; + break; + case USB_SPEED_LOW: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8; + break; + case USB_SPEED_FULL: + max_packet_size = (is_iso_ep ? + ENDPOINT_MAX_PACKET_SIZE_1023 : + ENDPOINT_MAX_PACKET_SIZE_64); + break; + case USB_SPEED_HIGH: + max_packet_size = (is_iso_ep ? + ENDPOINT_MAX_PACKET_SIZE_1024 : + ENDPOINT_MAX_PACKET_SIZE_512); + break; + case USB_SPEED_WIRELESS: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + break; + case USB_SPEED_SUPER: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024; + break; + case USB_SPEED_SUPER_PLUS: + dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n"); + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024; + break; + } + + if (ep_onchip_buffer_alloc(usb_ss, default_buf_size, is_in)) { + dev_err(&usb_ss->dev, "onchip mem is full, ep is invalid\n"); + return; + } + + ep_cfg |= EP_CFG__MAXPKTSIZE__WRITE(max_packet_size) | + EP_CFG__BUFFERING__WRITE(default_buf_size - 1) | + EP_CFG__MAXBURST__WRITE(usb_ss_ep->endpoint.maxburst); + + select_ep(usb_ss, bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__TRBERREN__MASK | interrupt_mask); + + /* enable interrupt for selected endpoint */ + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_ien); + ep_cfg |= CAST_EP_ADDR_TO_BIT_POS(bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_ien, ep_cfg); +} + +/** + * usb_ss_gadget_ep_enable Enable endpoint + * @ep: endpoint object + * @desc: endpoint descriptor + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usb_ss_endpoint *usb_ss_ep; + struct usb_ss_dev *usb_ss; + unsigned long flags; + int ret; + u32 ep_cfg; + + usb_ss_ep = to_usb_ss_ep(ep); + usb_ss = usb_ss_ep->usb_ss; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + dev_err(&usb_ss->dev, "usb-ss: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + dev_err(&usb_ss->dev, "usb-ss: missing wMaxPacketSize\n"); + return -EINVAL; + } + + ret = usb_ss_allocate_trb_pool(usb_ss_ep); + if (ret) + return ret; + + dev_dbg(&usb_ss->dev, "Enabling endpoint: %s, addr=0x%x\n", + ep->name, desc->bEndpointAddress); + spin_lock_irqsave(&usb_ss->lock, flags); + select_ep(usb_ss, desc->bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK); + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg); + ep_cfg |= EP_CFG__ENABLE__MASK; + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + + ep->enabled = 1; + ep->desc = desc; + usb_ss_ep->hw_pending_flag = 0; + usb_ss_ep->stalled_flag = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +/* Find correct direction for HW endpoint according to description */ +static int ep_dir_is_correct(struct usb_endpoint_descriptor *desc, + struct usb_ss_endpoint *usb_ss_ep) +{ + return (usb_ss_ep->endpoint.caps.dir_in && + !!(desc->bEndpointAddress & USB_DIR_IN)) + || (usb_ss_ep->endpoint.caps.dir_out + && ((desc->bEndpointAddress & 0x80) == USB_DIR_OUT)); +} + +static struct usb_ss_endpoint *find_available_ss_ep( + struct usb_ss_dev *usb_ss, + struct usb_endpoint_descriptor *desc) +{ + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep; + + list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + + usb_ss_ep = to_usb_ss_ep(ep); + if (ep_dir_is_correct(desc, usb_ss_ep)) { + if (!usb_ss_ep->used) { + usb_ss_ep->num = num; + usb_ss_ep->used = true; + return usb_ss_ep; + } + } + } + return ERR_PTR(-ENOENT); +} + +static struct usb_ep *usb_ss_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + struct usb_ss_endpoint *usb_ss_ep; + unsigned long flags; + + usb_ss_ep = find_available_ss_ep(usb_ss, desc); + if (IS_ERR(usb_ss_ep)) { + dev_err(&usb_ss->dev, "no available ep\n"); + return NULL; + } + + dev_dbg(&usb_ss->dev, "match endpoint: %s\n", usb_ss_ep->name); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss_ep->endpoint.desc = desc; + usb_ss_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT; + usb_ss_ep->type = usb_endpoint_type(desc); + + list_add_tail(&usb_ss_ep->ep_match_pending_list, + &usb_ss->ep_match_list); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return &usb_ss_ep->endpoint; +} + +static void usb_ss_free_trb_pool(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + if (usb_ss_ep->trb_pool) { + dma_free_coherent(usb_ss->sysdev, + sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM, + usb_ss_ep->trb_pool, usb_ss_ep->trb_pool_dma); + usb_ss_ep->trb_pool = NULL; + } + + if (usb_ss_ep->cpu_addr) { + dma_free_coherent(usb_ss->sysdev, CDNS3_UNALIGNED_BUF_SIZE, + usb_ss_ep->cpu_addr, usb_ss_ep->dma_addr); + usb_ss_ep->cpu_addr = NULL; + } +} + +/** + * usb_ss_gadget_ep_disable Disable endpoint + * @ep: endpoint object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_disable(struct usb_ep *ep) +{ + struct usb_ss_endpoint *usb_ss_ep; + struct usb_ss_dev *usb_ss; + unsigned long flags; + int ret = 0; + struct usb_request *request; + u32 ep_cfg; + + if (!ep) { + pr_debug("usb-ss: invalid parameters\n"); + return -EINVAL; + } + + usb_ss_ep = to_usb_ss_ep(ep); + usb_ss = usb_ss_ep->usb_ss; + + spin_lock_irqsave(&usb_ss->lock, flags); + if (!usb_ss->start_gadget) { + dev_dbg(&usb_ss->dev, + "Disabling endpoint at disconnection: %s\n", ep->name); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + dev_dbg(&usb_ss->dev, + "Disabling endpoint: %s\n", ep->name); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + ret = cdns_data_flush(usb_ss_ep); + while (!list_empty(&usb_ss_ep->request_list)) { + + request = next_request(&usb_ss_ep->request_list); + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + ep->desc->bEndpointAddress & USB_DIR_IN); + request->status = -ESHUTDOWN; + list_del(&request->list); + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request(ep, request); + spin_lock(&usb_ss->lock); + } + + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg); + ep_cfg &= ~EP_CFG__ENABLE__MASK; + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + ep->desc = NULL; + ep->enabled = 0; + + spin_unlock_irqrestore(&usb_ss->lock, flags); + + return ret; +} + +/** + * usb_ss_gadget_ep_alloc_request Allocates request + * @ep: endpoint object associated with request + * @gfp_flags: gfp flags + * + * Returns allocated request address, NULL on allocation error + */ +static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usb_request *request; + + request = kzalloc(sizeof(struct usb_request), gfp_flags); + if (!request) + return NULL; + + return request; +} + +/** + * usb_ss_gadget_ep_free_request Free memory occupied by request + * @ep: endpoint object associated with request + * @request: request to free memory + */ +static void usb_ss_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + kfree(request); +} + +/** + * usb_ss_gadget_ep_queue Transfer data on endpoint + * @ep: endpoint object + * @request: request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int __usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + int ret = 0; + int empty_list = 0; + + request->actual = 0; + request->status = -EINPROGRESS; + + dev_dbg(&usb_ss->dev, + "Queuing endpoint: %s\n", usb_ss_ep->name); + + dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n", + ep->desc->bEndpointAddress, request->length); + + ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request, + ep->desc->bEndpointAddress & USB_DIR_IN); + + if (ret) + return ret; + + empty_list = list_empty(&usb_ss_ep->request_list); + list_add_tail(&request->list, &usb_ss_ep->request_list); + + if (!usb_ss->hw_configured_flag) + return 0; + + if (empty_list) { + if (!usb_ss_ep->stalled_flag) + ret = cdns_ep_run_transfer(usb_ss_ep); + } + + return ret; +} + +static int usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + struct usb_request *zlp_request; + unsigned long flags; + int ret; + + spin_lock_irqsave(&usb_ss->lock, flags); + + ret = __usb_ss_gadget_ep_queue(ep, request, gfp_flags); + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + zlp_request = usb_ss_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->length = 0; + zlp_request->buf = usb_ss->zlp_buf; + + dev_dbg(&usb_ss->dev, "Queuing ZLP for endpoint: %s\n", + usb_ss_ep->name); + ret = __usb_ss_gadget_ep_queue(ep, zlp_request, gfp_flags); + } + + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep_dequeue Remove request from transfer queue + * @ep: endpoint object associated with request + * @request: request object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + unsigned long flags; + struct usb_request *req, *req_temp; + int ret = 0; + + if (ep == NULL || request == NULL || ep->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "DEQUEUE(%02X) %d\n", + ep->address, request->length); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + if (usb_ss->start_gadget) + ret = cdns_data_flush(usb_ss_ep); + + list_for_each_entry_safe(req, req_temp, + &usb_ss_ep->request_list, list) { + if (request == req) { + request->status = -ECONNRESET; + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + ep->address & USB_DIR_IN); + list_del_init(&request->list); + if (request->complete) { + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request + (&usb_ss_ep->endpoint, request); + spin_lock(&usb_ss->lock); + } + break; + } + } + + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep_set_halt Sets/clears stall on selected endpoint + * @ep: endpoint object to set/clear stall on + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + unsigned long flags; + int ret = 0; + + /* return error when endpoint disabled */ + if (!ep->enabled) + return -EPERM; + + /* if actual transfer is pending defer setting stall on this endpoint */ + if (usb_ss_ep->hw_pending_flag && value) { + usb_ss_ep->stalled_flag = 1; + return 0; + } + + dev_dbg(&usb_ss->dev, "HALT(%02X) %d\n", ep->address, value); + + spin_lock_irqsave(&usb_ss->lock, flags); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + if (value) { + cdns_ep_stall_flush(usb_ss_ep); + } else { + /* + * TODO: + * epp->wedgeFlag = 0; + */ + usb_ss_ep->wedge_flag = 0; + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK); + /* wait for EPRST cleared */ + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + usb_ss_ep->stalled_flag = 0; + } + usb_ss_ep->hw_pending_flag = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + + return ret; +} + +/** + * usb_ss_gadget_ep_set_wedge Set wedge on selected endpoint + * @ep: endpoint object + * + * Returns 0 + */ +static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + dev_dbg(&usb_ss->dev, "WEDGE(%02X)\n", ep->address); + usb_ss_gadget_ep_set_halt(ep, 1); + usb_ss_ep->wedge_flag = 1; + return 0; +} + +static const struct usb_ep_ops usb_ss_gadget_ep0_ops = { + .enable = usb_ss_gadget_ep0_enable, + .disable = usb_ss_gadget_ep0_disable, + .alloc_request = usb_ss_gadget_ep_alloc_request, + .free_request = usb_ss_gadget_ep_free_request, + .queue = usb_ss_gadget_ep0_queue, + .dequeue = usb_ss_gadget_ep_dequeue, + .set_halt = usb_ss_gadget_ep0_set_halt, + .set_wedge = usb_ss_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops usb_ss_gadget_ep_ops = { + .enable = usb_ss_gadget_ep_enable, + .disable = usb_ss_gadget_ep_disable, + .alloc_request = usb_ss_gadget_ep_alloc_request, + .free_request = usb_ss_gadget_ep_free_request, + .queue = usb_ss_gadget_ep_queue, + .dequeue = usb_ss_gadget_ep_dequeue, + .set_halt = usb_ss_gadget_ep_set_halt, + .set_wedge = usb_ss_gadget_ep_set_wedge, +}; + +/** + * usb_ss_gadget_get_frame Returns number of actual ITP frame + * @gadget: gadget object + * + * Returns number of actual ITP frame + */ +static int usb_ss_gadget_get_frame(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_get_frame\n"); + return gadget_readl(usb_ss, &usb_ss->regs->usb_iptn); +} + +static int usb_ss_gadget_wakeup(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_wakeup\n"); + return 0; +} + +static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + unsigned long flags; + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_set_selfpowered: %d\n", + is_selfpowered); + + spin_lock_irqsave(&usb_ss->lock, flags); + gadget->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + if (!usb_ss->start_gadget) + return 0; + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_pullup: %d\n", is_on); + + if (is_on) + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__DEVEN__MASK); + else + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__DEVDS__MASK); + + return 0; +} + +/** + * usb_ss_gadget_udc_start Gadget start + * @gadget: gadget object + * @driver: driver which operates on this gadget + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + unsigned long flags; + + if (usb_ss->gadget_driver) { + dev_err(&usb_ss->dev, "%s is already bound to %s\n", + usb_ss->gadget.name, + usb_ss->gadget_driver->driver.name); + return -EBUSY; + } + + dev_dbg(&usb_ss->dev, "%s begins\n", __func__); + + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->gadget_driver = driver; + if (!usb_ss->start_gadget) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "%s ends\n", __func__); + return 0; +} + +/** + * usb_ss_gadget_udc_stop Stops gadget + * @gadget: gadget object + * + * Returns 0 + */ +static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep; + int i; + u32 bEndpointAddress; + int ret = 0; + + usb_ss->gadget_driver = NULL; + list_for_each_entry_safe(usb_ss_ep, temp_ss_ep, + &usb_ss->ep_match_list, ep_match_pending_list) { + list_del(&usb_ss_ep->ep_match_pending_list); + usb_ss_ep->used = false; + } + + usb_ss->onchip_mem_allocated_size = 0; + usb_ss->out_mem_is_allocated = 0; + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + for (i = 0; i < usb_ss->ep_nums ; i++) + usb_ss_free_trb_pool(usb_ss->eps[i]); + + if (!usb_ss->start_gadget) + return 0; + + list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) { + usb_ss_ep = to_usb_ss_ep(ep); + bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir; + select_ep(usb_ss, bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK); + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + } + + /* disable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0); + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + + return ret; +} + +static const struct usb_gadget_ops usb_ss_gadget_ops = { + .get_frame = usb_ss_gadget_get_frame, + .wakeup = usb_ss_gadget_wakeup, + .set_selfpowered = usb_ss_gadget_set_selfpowered, + .pullup = usb_ss_gadget_pullup, + .udc_start = usb_ss_gadget_udc_start, + .udc_stop = usb_ss_gadget_udc_stop, + .match_ep = usb_ss_gadget_match_ep, +}; + +/** + * usb_ss_init_ep Initializes software endpoints of gadget + * @usb_ss: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_init_ep(struct usb_ss_dev *usb_ss) +{ + struct usb_ss_endpoint *usb_ss_ep; + u32 ep_enabled_reg, iso_ep_reg, bulk_ep_reg; + int i; + int ep_reg_pos, ep_dir, ep_number; + int found_endpoints = 0; + + /* Read it from USB_CAP3 to USB_CAP5 */ + ep_enabled_reg = 0x00ff00ff; + iso_ep_reg = 0x00fe00fe; + bulk_ep_reg = 0x00fe00fe; + + dev_dbg(&usb_ss->dev, "Initializing non-zero endpoints\n"); + + for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) { + ep_number = (i / 2) + 1; + ep_dir = i % 2; + ep_reg_pos = (16 * ep_dir) + ep_number; + + if (!(ep_enabled_reg & (1uL << ep_reg_pos))) + continue; + + /* create empty endpoint object */ + usb_ss_ep = devm_kzalloc(&usb_ss->dev, sizeof(*usb_ss_ep), + GFP_KERNEL); + if (!usb_ss_ep) + return -ENOMEM; + + /* set parent of endpoint object */ + usb_ss_ep->usb_ss = usb_ss; + + /* set index of endpoint in endpoints container */ + usb_ss->eps[found_endpoints++] = usb_ss_ep; + + /* set name of endpoint */ + snprintf(usb_ss_ep->name, sizeof(usb_ss_ep->name), "ep%d%s", + ep_number, !!ep_dir ? "in" : "out"); + usb_ss_ep->endpoint.name = usb_ss_ep->name; + dev_dbg(&usb_ss->dev, "Initializing endpoint: %s\n", + usb_ss_ep->name); + + usb_ep_set_maxpacket_limit(&usb_ss_ep->endpoint, + ENDPOINT_MAX_PACKET_LIMIT); + usb_ss_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS; + usb_ss_ep->endpoint.ops = &usb_ss_gadget_ep_ops; + if (ep_dir) + usb_ss_ep->endpoint.caps.dir_in = 1; + else + usb_ss_ep->endpoint.caps.dir_out = 1; + + /* check endpoint type */ + if (iso_ep_reg & (1uL << ep_reg_pos)) + usb_ss_ep->endpoint.caps.type_iso = 1; + + if (bulk_ep_reg & (1uL << ep_reg_pos)) { + usb_ss_ep->endpoint.caps.type_bulk = 1; + usb_ss_ep->endpoint.caps.type_int = 1; + usb_ss_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1; + } + + list_add_tail(&usb_ss_ep->endpoint.ep_list, + &usb_ss->gadget.ep_list); + INIT_LIST_HEAD(&usb_ss_ep->request_list); + INIT_LIST_HEAD(&usb_ss_ep->ep_match_pending_list); + } + + usb_ss->ep_nums = found_endpoints; + return 0; +} + +/** + * usb_ss_init_ep0 Initializes software endpoint 0 of gadget + * @usb_ss: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss) +{ + struct usb_ss_endpoint *ep0; + + dev_dbg(&usb_ss->dev, "Initializing EP0\n"); + ep0 = devm_kzalloc(&usb_ss->dev, sizeof(struct usb_ss_endpoint), + GFP_KERNEL); + + if (!ep0) + return -ENOMEM; + + /* fill CDNS fields */ + ep0->usb_ss = usb_ss; + sprintf(ep0->name, "ep0"); + + /* fill linux fields */ + ep0->endpoint.ops = &usb_ss_gadget_ep0_ops; + ep0->endpoint.maxburst = 1; + usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT); + ep0->endpoint.address = 0; + ep0->endpoint.enabled = 1; + ep0->endpoint.caps.type_control = 1; + ep0->endpoint.caps.dir_in = 1; + ep0->endpoint.caps.dir_out = 1; + ep0->endpoint.name = ep0->name; + ep0->endpoint.desc = &cdns3_gadget_ep0_desc; + usb_ss->gadget.ep0 = &ep0->endpoint; + INIT_LIST_HEAD(&ep0->request_list); + + return 0; +} + +static void cdns3_gadget_release(struct device *dev) +{ + struct usb_ss_dev *usb_ss = container_of(dev, struct usb_ss_dev, dev); + + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(usb_ss); +} + +static int __cdns3_gadget_init(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + int ret; + struct device *dev; + + usb_ss = kzalloc(sizeof(*usb_ss), GFP_KERNEL); + if (!usb_ss) + return -ENOMEM; + + dev = &usb_ss->dev; + dev->release = cdns3_gadget_release; + dev->parent = cdns->dev; + dev_set_name(dev, "gadget-cdns3"); + cdns->gadget_dev = dev; + usb_ss->sysdev = cdns->dev; + ret = device_register(dev); + if (ret) + goto err1; + + usb_ss->regs = cdns->dev_regs; + + /* fill gadget fields */ + usb_ss->gadget.ops = &usb_ss_gadget_ops; + usb_ss->gadget.max_speed = USB_SPEED_SUPER; + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + usb_ss->gadget.name = "usb-ss-gadget"; + usb_ss->gadget.sg_supported = 1; + usb_ss->is_connected = 0; + spin_lock_init(&usb_ss->lock); + INIT_WORK(&usb_ss->pending_status_wq, pending_setup_status_handler); + + usb_ss->in_standby_mode = 1; + + /* initialize endpoint container */ + INIT_LIST_HEAD(&usb_ss->gadget.ep_list); + INIT_LIST_HEAD(&usb_ss->ep_match_list); + ret = usb_ss_init_ep0(usb_ss); + if (ret) { + dev_err(dev, "Failed to create endpoint 0\n"); + ret = -ENOMEM; + goto err2; + } + + ret = usb_ss_init_ep(usb_ss); + if (ret) { + dev_err(dev, "Failed to create non zero endpoints\n"); + ret = -ENOMEM; + goto err2; + } + + /* allocate memory for default endpoint TRB */ + usb_ss->trb_ep0 = (u32 *)dma_alloc_coherent(usb_ss->sysdev, 20, + &usb_ss->trb_ep0_dma, GFP_DMA); + if (!usb_ss->trb_ep0) { + dev_err(dev, "Failed to allocate memory for ep0 TRB\n"); + ret = -ENOMEM; + goto err2; + } + + /* allocate memory for setup packet buffer */ + usb_ss->setup = (u8 *)dma_alloc_coherent(usb_ss->sysdev, 8, + &usb_ss->setup_dma, + GFP_DMA); + if (!usb_ss->setup) { + dev_err(dev, "Failed to allocate memory for SETUP buffer\n"); + ret = -ENOMEM; + goto err3; + } + + /* add USB gadget device */ + ret = usb_add_gadget_udc(&usb_ss->dev, &usb_ss->gadget); + if (ret < 0) { + dev_err(dev, "Failed to register USB device controller\n"); + goto err4; + } + + usb_ss->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL); + if (!usb_ss->zlp_buf) { + ret = -ENOMEM; + goto err4; + } + + return 0; +err4: + dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup, + usb_ss->setup_dma); +err3: + dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0, + usb_ss->trb_ep0_dma); +err2: + device_del(dev); +err1: + put_device(dev); + cdns->gadget_dev = NULL; + return ret; +} + +/** + * cdns3_gadget_remove: parent must call this to remove UDC + * + * cdns: cdns3 instance + * + */ +void cdns3_gadget_remove(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + + if (!cdns->roles[CDNS3_ROLE_GADGET]) + return; + + usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + usb_del_gadget_udc(&usb_ss->gadget); + dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup, usb_ss->setup_dma); + dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0, + usb_ss->trb_ep0_dma); + device_unregister(cdns->gadget_dev); + cdns->gadget_dev = NULL; + kfree(usb_ss->zlp_buf); +} + +static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss) +{ + + /* configure endpoint 0 hardware */ + cdns_ep0_config(usb_ss); + + /* enable interrupts for endpoint 0 (in and out) */ + gadget_writel(usb_ss, &usb_ss->regs->ep_ien, + EP_IEN__EOUTEN0__MASK | EP_IEN__EINEN0__MASK); + + /* enable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, + USB_IEN__U2RESIEN__MASK + | USB_ISTS__DIS2I__MASK + | USB_IEN__CON2IEN__MASK + | USB_IEN__UHRESIEN__MASK + | USB_IEN__UWRESIEN__MASK + | USB_IEN__DISIEN__MASK + | USB_IEN__CONIEN__MASK + | USB_IEN__U3EXTIEN__MASK + | USB_IEN__L2ENTIEN__MASK + | USB_IEN__L2EXTIEN__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CLK2OFFDS__MASK + /* | USB_CONF__USB3DIS__MASK */ + | USB_CONF__L1DS__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__U1DS__MASK + | USB_CONF__U2DS__MASK + ); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVEN__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->dbg_link1, + DBG_LINK1__LFPS_MIN_GEN_U1_EXIT_SET__MASK | + DBG_LINK1__LFPS_MIN_GEN_U1_EXIT__WRITE(0x3C)); +} + +static int cdns3_gadget_start(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss = container_of(cdns->gadget_dev, + struct usb_ss_dev, dev); + unsigned long flags; + + dev_dbg(&usb_ss->dev, "%s begins\n", __func__); + + pm_runtime_get_sync(cdns->dev); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->start_gadget = 1; + if (!usb_ss->gadget_driver) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + usb_ss->in_standby_mode = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "%s ends\n", __func__); + return 0; +} + +static void __cdns3_gadget_stop(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + unsigned long flags; + + usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + if (usb_ss->gadget_driver) + usb_ss->gadget_driver->disconnect(&usb_ss->gadget); + usb_gadget_disconnect(&usb_ss->gadget); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + /* disable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0); + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + usb_ss->start_gadget = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); +} + +static void cdns3_gadget_stop(struct cdns3 *cdns) +{ + if (cdns->role == CDNS3_ROLE_GADGET) + __cdns3_gadget_stop(cdns); + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); +} + +static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup) +{ + __cdns3_gadget_stop(cdns); + return 0; +} + +static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated) +{ + struct usb_ss_dev *usb_ss = container_of(cdns->gadget_dev, + struct usb_ss_dev, dev); + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->start_gadget = 1; + if (!usb_ss->gadget_driver) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + usb_ss->in_standby_mode = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +/** + * cdns3_gadget_init - initialize device structure + * + * cdns: cdns3 instance + * + * This function initializes the gadget. + */ +int cdns3_gadget_init(struct cdns3 *cdns) +{ + struct cdns3_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = cdns3_gadget_start; + rdrv->stop = cdns3_gadget_stop; + rdrv->suspend = cdns3_gadget_suspend; + rdrv->resume = cdns3_gadget_resume; + rdrv->irq = cdns_irq_handler_thread; + rdrv->name = "gadget"; + cdns->roles[CDNS3_ROLE_GADGET] = rdrv; + return __cdns3_gadget_init(cdns); +} |