| /* |
| * linux/drivers/usb/gadget/ambarella_udc.c |
| * driver for High/Full speed USB device controller on Ambarella processors |
| * |
| * History: |
| * 2008/06/12 - [Cao Rongrong] created file |
| * 2009/03/16 - [Cao Rongrong] Change DMA descriptor allocate mode |
| * |
| * Copyright (C) 2008 by Ambarella, Inc. |
| * http://www.ambarella.com |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/dmapool.h> |
| #include <linux/usb.h> |
| #include <linux/usb/gadget.h> |
| #include <linux/proc_fs.h> |
| |
| #include <mach/hardware.h> |
| #include <plat/udc.h> |
| #include "ambarella_udc.h" |
| |
| #define DRIVER_DESC "Ambarella USB Device Controller Gadget" |
| #define DRIVER_VERSION "2 August 2011" |
| #define DRIVER_AUTHOR "Cao Rongrong <rrcao@ambarella.com>" |
| |
| #define DMA_ADDR_INVALID (~(dma_addr_t)0) |
| |
| static const char gadget_name[] = "ambarella_udc"; |
| static const char driver_desc[] = DRIVER_DESC; |
| static const char ep0name [] = "ep0"; |
| |
| static struct ambarella_udc *the_controller; |
| |
| static const char *amb_ep_string[] = { |
| ep0name, |
| "ep1in-bulk", "ep2in-bulk", "ep3in-bulk", "ep4in-int", |
| "ep5in-bulk", "ep6in-bulk", "ep7in-bulk", "ep8in-bulk", |
| "ep9in-bulk", "ep10in-bulk", "ep11in-bulk", "ep12in-bulk", |
| "ep13in-bulk", "ep14in-bulk", "ep15in-bulk", |
| |
| "ep0-out", "ep1out-bulk", "ep2out-bulk", "ep3out-bulk", |
| "ep4out-bulk", "ep5out-bulk", "ep6out-bulk", "ep7out-bulk", |
| "ep8out-bulk", "ep9out-bulk", "ep10out-bulk", "ep11out-bulk", |
| "ep12out-bulk", "ep13out-bulk", "ep14out-bulk", "ep15out-bulk" |
| }; |
| |
| /* must keep consistent with enum ambarella_udc_status */ |
| static const char *amb_udc_status_str [] = { |
| "Unknown", "Enabled", "Configured", "Suspend", "Resume", |
| "Disabled", "Bussuspend" |
| }; |
| |
| /*************************** DEBUG FUNCTION ***************************/ |
| #define DEBUG_ERR 0 |
| #define DEBUG_NORMAL 1 |
| #define DEBUG_VERBOSE 2 |
| |
| #define DEBUG_BULK_OUT 3 |
| #define DEBUG_BULK_IN 3 |
| #define DEBUG_INTR_IN 3 |
| |
| #define AMBARELLA_USB_DEBUG 1 |
| #if AMBARELLA_USB_DEBUG |
| |
| #define dprintk(level,format,args...) \ |
| do { \ |
| if(level < 1) \ |
| printk(KERN_DEBUG "%s: " format, __FUNCTION__,## args); \ |
| } while(0) |
| |
| #else |
| #define dprintk(args...) do { } while(0) |
| #endif |
| |
| |
| static inline struct ambarella_ep *to_ambarella_ep(struct usb_ep *ep) |
| { |
| return container_of(ep, struct ambarella_ep, ep); |
| } |
| |
| static inline struct ambarella_udc *to_ambarella_udc(struct usb_gadget *gadget) |
| { |
| return container_of(gadget, struct ambarella_udc, gadget); |
| } |
| |
| static inline struct ambarella_request *to_ambarella_req(struct usb_request *req) |
| { |
| return container_of(req, struct ambarella_request, req); |
| } |
| |
| |
| /************** PROC FILESYSTEM BEGIN *****************/ |
| |
| static void ambarella_uevent_work(struct work_struct *data) |
| { |
| struct ambarella_udc *udc; |
| char buf_status[64]; |
| char buf_vbus[64]; |
| char buf_driver[64]; |
| char *envp[4]; |
| |
| udc = container_of(data, struct ambarella_udc, uevent_work); |
| buf_status[0] = '\0'; |
| buf_vbus[0] = '\0'; |
| buf_driver[0] = '\0'; |
| |
| spin_lock_irq(&udc->lock); |
| if (udc->pre_uevent_status == amb_udc_status && |
| udc->pre_uevent_vbus == udc->vbus_status) { |
| spin_unlock_irq(&udc->lock); |
| return; |
| } |
| udc->pre_uevent_status = amb_udc_status; |
| udc->pre_uevent_vbus = udc->vbus_status; |
| snprintf(buf_status, sizeof(buf_status), "AMBUDC_STATUS=%s", |
| amb_udc_status_str[amb_udc_status + 1]); |
| snprintf(buf_vbus, sizeof(buf_vbus), "AMBUDC_VBUS=%s", |
| ambarella_check_connected(udc) ? "Connected" : "Disconnected"); |
| snprintf(buf_driver, sizeof(buf_driver), "AMBUDC_DRIVER=%s", |
| udc->driver ? udc->driver->driver.name : "NULL"); |
| spin_unlock_irq(&udc->lock); |
| envp[0] = buf_status; |
| envp[1] = buf_vbus; |
| envp[2] = buf_driver; |
| envp[3] = NULL; |
| kobject_uevent_env(&udc->dev->kobj, KOBJ_CHANGE, envp); |
| } |
| |
| static int ambarella_proc_udc_read(char *page, char **start, |
| off_t off, int count, int *eof, void *data) |
| { |
| struct ambarella_udc *udc; |
| int len; |
| char buf[64]; |
| |
| if (off != 0) |
| return 0; |
| |
| udc = (struct ambarella_udc *)data; |
| buf[0] = '\0'; |
| |
| snprintf(buf, sizeof(buf), "AMBUDC_STATUS=%s", |
| amb_udc_status_str[amb_udc_status + 1]); |
| |
| *start = page + off; |
| len = sprintf(*start, "%s (%s: %s)\n", buf, |
| udc->driver ? udc->driver->driver.name : "NULL", |
| ambarella_check_connected(udc) ? "Connected" : "Disconnected"); |
| |
| *eof = 1; |
| |
| return len; |
| } |
| |
| #ifdef CONFIG_USB_GADGET_DEBUG_FILES |
| |
| static const char proc_node_name[] = "driver/udc"; |
| |
| static int ambarella_debugfs_udc_read(char *page, char **start, |
| off_t off, int count, int *eof, void *_dev) |
| { |
| char *buf = page; |
| struct ambarella_udc *udc = _dev; |
| char *next = buf; |
| unsigned size = count; |
| int t; |
| struct usb_ctrlrequest *crq = (struct usb_ctrlrequest *)&udc->setup[0]; |
| |
| if (off != 0) |
| return 0; |
| |
| /* basic device status */ |
| t = scnprintf(next, size, |
| DRIVER_DESC "\n" |
| "Name: %s\n" |
| "Version: %s\n" |
| "Author: %s\n\n" |
| "Gadget driver: %s\n" |
| "Host: %s\n\n", |
| gadget_name, |
| DRIVER_VERSION, |
| DRIVER_AUTHOR, |
| udc->driver ? udc->driver->driver.name : "(none)", |
| ambarella_check_connected(udc)? |
| (udc->gadget.speed == USB_SPEED_HIGH ?"high speed" : "full speed") : |
| "disconnected"); |
| size -= t; |
| next += t; |
| |
| t = scnprintf(next, size, "the last setup packet is: \n" |
| "bRequestType = 0x%02x, bRequest = 0x%02x,\n" |
| "wValue = 0x%04x, wIndex = 0x%04x, wLength = 0x%04x\n\n", |
| crq->bRequestType, crq->bRequest, crq->wValue, crq->wIndex, |
| crq->wLength); |
| size -= t; |
| next += t; |
| |
| /* |
| t = scnprintf(next, size, "max_cmd_num = %d\tmax_ep0_cmd_num = %d\n\n", |
| udc->max_cmd_num, udc->max_ep0_cmd_num); |
| size -= t; |
| next += t; |
| */ |
| *eof = 1; |
| return count - size; |
| } |
| |
| #define create_proc_files() create_proc_read_entry(proc_node_name, 0, NULL, ambarella_debugfs_udc_read, udc) |
| #define remove_proc_files() remove_proc_entry(proc_node_name, NULL) |
| |
| #else /* !CONFIG_USB_GADGET_DEBUG_FILES */ |
| |
| #define create_proc_files() do {} while (0) |
| #define remove_proc_files() do {} while (0) |
| |
| #endif /* CONFIG_USB_GADGET_DEBUG_FILES */ |
| |
| /************** PROC FILESYSTEM END*****************/ |
| |
| static void ambarella_regaddr_map(struct ambarella_udc *udc) |
| { |
| u32 i; |
| |
| for(i = 0; i < EP_NUM_MAX; i++) { |
| struct ambarella_ep *ep; |
| ep = &udc->ep[i]; |
| if(ep->dir == USB_DIR_IN){ |
| ep->ep_reg.ctrl_reg = USB_EP_IN_CTRL_REG(ep->id); |
| ep->ep_reg.sts_reg = USB_EP_IN_STS_REG(ep->id); |
| ep->ep_reg.buf_sz_reg = USB_EP_IN_BUF_SZ_REG(ep->id); |
| ep->ep_reg.max_pkt_sz_reg = |
| USB_EP_IN_MAX_PKT_SZ_REG(ep->id); |
| ep->ep_reg.dat_desc_ptr_reg = |
| USB_EP_IN_DAT_DESC_PTR_REG(ep->id); |
| } else { |
| ep->ep_reg.ctrl_reg = USB_EP_OUT_CTRL_REG(ep->id - 16); |
| ep->ep_reg.sts_reg = USB_EP_OUT_STS_REG(ep->id - 16); |
| ep->ep_reg.buf_sz_reg = |
| USB_EP_OUT_PKT_FRM_NUM_REG(ep->id - 16); |
| ep->ep_reg.max_pkt_sz_reg = |
| USB_EP_OUT_MAX_PKT_SZ_REG(ep->id - 16); |
| ep->ep_reg.dat_desc_ptr_reg = |
| USB_EP_OUT_DAT_DESC_PTR_REG(ep->id - 16); |
| } |
| |
| ep->ep_reg.setup_buf_ptr_reg = (i == CTRL_OUT) ? |
| USB_EP_OUT_SETUP_BUF_PTR_REG(ep->id - 16) : (u32)NULL; |
| } |
| } |
| |
| |
| static void ambarella_disable_all_intr(void) |
| { |
| /* device interrupt mask register */ |
| amba_writel(USB_DEV_INTR_MSK_REG, 0x0000007f); |
| amba_writel(USB_DEV_EP_INTR_MSK_REG, 0xffffffff); |
| amba_writel(USB_DEV_INTR_REG, 0x0000007f); |
| amba_writel(USB_DEV_EP_INTR_REG, 0xffffffff); |
| } |
| |
| static void ambarella_ep_fifo_flush(struct ambarella_ep *ep) |
| { |
| if(ep->dir == USB_DIR_IN) /* Empty Tx FIFO */ |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_FLUSH); |
| else { /* Empty RX FIFO */ |
| if (!(amba_readl(USB_DEV_STS_REG) & USB_DEV_RXFIFO_EMPTY_STS)) |
| ep->udc->controller_info->flush_rxfifo(); |
| } |
| } |
| |
| |
| /* |
| * Name: ambarella_flush_fifo |
| * Description: |
| * Empty Tx/Rx FIFO of DMA |
| */ |
| static void ambarella_udc_fifo_flush(void) |
| { |
| struct ambarella_udc *udc = the_controller; |
| struct ambarella_ep *ep; |
| u32 ep_id; |
| |
| for (ep_id = 0; ep_id < EP_NUM_MAX; ep_id++) { |
| ep = &udc->ep[ep_id]; |
| if(ep->desc == NULL && !IS_EP0(ep)) |
| continue; |
| ambarella_ep_fifo_flush(ep); |
| } |
| } |
| |
| static void ambarella_init_usb(void) |
| { |
| u32 value; |
| |
| /* disable all interrupts */ |
| ambarella_disable_all_intr(); |
| /* disable Tx and Rx DMA */ |
| amba_clrbitsl(USB_DEV_CTRL_REG, USB_DEV_RCV_DMA_EN | USB_DEV_TRN_DMA_EN); |
| /* flush dma fifo, may used in AMboot */ |
| ambarella_udc_fifo_flush(); |
| |
| /* device config register */ |
| value = USB_DEV_SPD_HI | |
| USB_DEV_SELF_POWER | |
| USB_DEV_PHY_8BIT | |
| USB_DEV_UTMI_DIR_BI | |
| USB_DEV_HALT_ACK | |
| USB_DEV_SET_DESC_STALL | |
| USB_DEV_DDR | |
| USB_DEV_CSR_PRG_EN | |
| USB_DEV_REMOTE_WAKEUP_EN; |
| |
| amba_setbitsl(USB_DEV_CFG_REG, value); |
| |
| /* device control register */ |
| value = USB_DEV_DESC_UPD_PYL | |
| USB_DEV_LITTLE_ENDN | |
| USB_DEV_DMA_MD | |
| USB_DEV_BURST_EN | |
| USB_DEV_BURST_LEN; |
| |
| amba_setbitsl(USB_DEV_CTRL_REG, value); |
| |
| // udelay(200); // FIXME: how long to wait is the best? |
| } |
| |
| |
| /* |
| * Name: init_setup_descriptor |
| * Description: |
| * Config the setup packet to specific endpoint register |
| */ |
| static void init_setup_descriptor(struct ambarella_udc *udc) |
| { |
| struct ambarella_ep *ep = &udc->ep[CTRL_OUT]; |
| |
| udc->setup_buf->status = USB_DMA_BUF_HOST_RDY; |
| udc->setup_buf->reserved = 0xffffffff; |
| udc->setup_buf->data0 = 0xffffffff; |
| udc->setup_buf->data1 = 0xffffffff; |
| |
| amba_writel(ep->ep_reg.setup_buf_ptr_reg, udc->setup_addr); |
| } |
| |
| |
| static int init_null_pkt_desc(struct ambarella_udc *udc) |
| { |
| udc->dummy_desc = dma_pool_alloc(udc->desc_dma_pool, GFP_KERNEL, |
| &udc->dummy_desc_addr); |
| if(udc->dummy_desc == NULL){ |
| printk(KERN_ERR "No memory to DMA\n"); |
| return -ENOMEM; |
| } |
| |
| udc->dummy_desc->data_ptr = udc->dummy_desc_addr; |
| udc->dummy_desc->reserved = 0xffffffff; |
| udc->dummy_desc->next_desc_ptr = udc->dummy_desc_addr; |
| udc->dummy_desc->status = USB_DMA_BUF_HOST_RDY | USB_DMA_LAST; |
| |
| return 0; |
| } |
| |
| static void init_ep0(struct ambarella_udc *udc) |
| { |
| struct ambarella_ep_reg *ep_reg; |
| |
| ep_reg = &udc->ep[CTRL_IN].ep_reg; |
| amba_writel(ep_reg->ctrl_reg, USB_EP_TYPE_CTRL); |
| amba_writel(ep_reg->buf_sz_reg, USB_TXFIFO_DEPTH_CTRLIN); |
| amba_writel(ep_reg->max_pkt_sz_reg, USB_EP_CTRLIN_MAX_PKT_SZ); |
| udc->ep[CTRL_IN].ctrl_sts_phase = 0; |
| |
| ep_reg = &udc->ep[CTRL_OUT].ep_reg; |
| amba_writel(ep_reg->ctrl_reg, USB_EP_TYPE_CTRL); |
| amba_writel(ep_reg->max_pkt_sz_reg, USB_EP_CTRL_MAX_PKT_SZ); |
| init_setup_descriptor(udc); |
| udc->ep[CTRL_OUT].ctrl_sts_phase = 0; |
| |
| /* This should be set after gadget->bind */ |
| udc->ep[CTRL_OUT].ep.driver_data = udc->ep[CTRL_IN].ep.driver_data; |
| |
| /* FIXME: For A5S, this bit must be set, |
| * or USB_UDC_REG can't be read or write */ |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_REMOTE_WAKEUP); |
| |
| /* setup ep0 CSR. Note: ep0-in and ep0-out share the same CSR reg */ |
| amba_clrbitsl(USB_UDC_REG(CTRL_IN), 0x7ff << 19); |
| amba_setbitsl(USB_UDC_REG(CTRL_IN), USB_EP_CTRL_MAX_PKT_SZ << 19); |
| /* the direction bit should not take effect for ep0 */ |
| amba_setbitsl(USB_UDC_REG(CTRL_IN), 0x1 << 4); |
| } |
| |
| static int ambarella_map_dma_buffer(struct ambarella_ep *ep, |
| struct ambarella_request *req) |
| { |
| struct ambarella_udc *udc = ep->udc; |
| enum dma_data_direction dmadir; |
| |
| dmadir = (ep->dir & USB_DIR_IN) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; |
| |
| if (likely(!req->use_aux_buf)) { |
| /* map req.buf, and get req's dma address */ |
| if (req->req.dma == DMA_ADDR_INVALID) { |
| req->req.dma = dma_map_single(udc->dev, |
| req->req.buf, req->req.length, dmadir); |
| /* dma address isn't 8-byte align */ |
| if(req->req.dma & 0x7){ |
| printk("dma address isn't 8-byte align\n"); |
| BUG(); |
| } |
| req->mapped = 1; |
| } else { |
| dma_sync_single_for_device(udc->dev, |
| req->req.dma, req->req.length, dmadir); |
| req->mapped = 0; |
| } |
| } else { |
| if (req->dma_aux == DMA_ADDR_INVALID) { |
| req->dma_aux = dma_map_single(udc->dev, |
| req->buf_aux, req->req.length, dmadir); |
| /* dma address isn't 8-byte align */ |
| if(req->dma_aux & 0x7){ |
| printk("dma address isn't 8-byte align\n"); |
| BUG(); |
| } |
| req->mapped = 1; |
| } else { |
| dma_sync_single_for_device(udc->dev, |
| req->dma_aux, req->req.length, dmadir); |
| req->mapped = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| static int ambarella_unmap_dma_buffer(struct ambarella_ep *ep, |
| struct ambarella_request *req) |
| { |
| struct ambarella_udc *udc = ep->udc; |
| enum dma_data_direction dmadir; |
| |
| dmadir = (ep->dir & USB_DIR_IN) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; |
| |
| if (likely(!req->use_aux_buf)) { |
| if (req->mapped) { |
| dma_unmap_single(udc->dev, |
| req->req.dma, req->req.length, dmadir); |
| req->req.dma = DMA_ADDR_INVALID; |
| req->mapped = 0; |
| } else { |
| dma_sync_single_for_cpu(udc->dev, |
| req->req.dma, req->req.length, dmadir); |
| } |
| } else { |
| if (req->mapped) { |
| dma_unmap_single(udc->dev, |
| req->dma_aux, req->req.length, dmadir); |
| req->dma_aux = DMA_ADDR_INVALID; |
| req->mapped = 0; |
| } else { |
| dma_sync_single_for_cpu(udc->dev, |
| req->dma_aux, req->req.length, dmadir); |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Name: ambarella_check_connected |
| * Description: |
| * Check if USB cable is connected to a host |
| */ |
| static int ambarella_check_connected(struct ambarella_udc *udc) |
| { |
| return udc->vbus_status; |
| } |
| |
| |
| /* |
| * Name: ambarella_check_softdis |
| * Description: |
| * Check if udc is soft disconnected |
| */ |
| static int ambarella_check_softdis(void) |
| { |
| return !!(amba_readl(USB_DEV_CTRL_REG) & USB_DEV_SOFT_DISCON); |
| } |
| |
| /* |
| * Name: ambarella_set_softdis |
| * Description: |
| * If force = 0, set soft disconnected when USB cable inserted. |
| * If force = 1, set soft disconnected in any case. |
| */ |
| static void ambarella_set_softdis(int force) |
| { |
| if(force || (amba_readl(VIC_RAW_STA_REG) & 1)) { |
| /* note don't change the order */ |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_SOFT_DISCON); |
| amba_setbitsl(USB_DEV_CFG_REG, USB_DEV_REMOTE_WAKEUP_EN); |
| } |
| } |
| |
| |
| static struct ambarella_data_desc * |
| ambarella_get_last_desc(struct ambarella_ep *ep) |
| { |
| struct ambarella_data_desc *desc = ep->data_desc; |
| int retry_count = 1000; |
| |
| while(desc && (desc->status & USB_DMA_LAST) == 0) { |
| if (retry_count-- < 0) { |
| printk(KERN_ERR "Can't find the last descriptor\n"); |
| break; |
| } |
| |
| if (desc->last_aux == 1) |
| break; |
| |
| desc = desc->next_desc_virt; |
| }; |
| |
| return desc; |
| } |
| |
| static u32 ambarella_check_bna_error (struct ambarella_ep *ep) |
| { |
| u32 ep_sts, retval = 0; |
| |
| ep_sts = amba_readl(ep->ep_reg.sts_reg); |
| |
| /* Error: Buffer Not Available */ |
| if (ep_sts & USB_EP_BUF_NOT_AVAIL) { |
| printk(KERN_ERR "[USB]:BNA error in %s\n", ep->ep.name); |
| amba_writel(ep->ep_reg.sts_reg, USB_EP_BUF_NOT_AVAIL); |
| retval = 1; |
| } |
| |
| return retval; |
| } |
| |
| static u32 ambarella_check_he_error (struct ambarella_ep *ep) |
| { |
| u32 ep_sts, retval = 0; |
| |
| ep_sts = amba_readl(ep->ep_reg.sts_reg); |
| |
| /* Error: Host Error */ |
| if (ep_sts & USB_EP_HOST_ERR) { |
| printk(KERN_ERR "[USB]:HE error in %s\n", ep->ep.name); |
| amba_writel(ep->ep_reg.sts_reg, USB_EP_HOST_ERR); |
| retval = 1; |
| } |
| |
| return retval; |
| } |
| |
| static u32 ambarella_check_dma_error (struct ambarella_ep *ep) |
| { |
| u32 retval = 0, sts_tmp1, sts_tmp2; |
| |
| if(ep->last_data_desc){ |
| sts_tmp1 = ep->last_data_desc->status & USB_DMA_BUF_STS; |
| sts_tmp2 = ep->last_data_desc->status & USB_DMA_RXTX_STS; |
| if ((sts_tmp1 != USB_DMA_BUF_DMA_DONE) || (sts_tmp2 != USB_DMA_RXTX_SUCC)){ |
| printk(KERN_ERR "%s: DMA failed\n", ep->ep.name); |
| retval = 1; |
| } |
| } |
| |
| |
| return retval; |
| } |
| |
| |
| static void ambarella_free_descriptor(struct ambarella_ep *ep, |
| struct ambarella_request *req) |
| { |
| struct ambarella_data_desc *data_desc, *next_data_desc; |
| struct dma_pool *desc_dma_pool = ep->udc->desc_dma_pool; |
| int i; |
| |
| next_data_desc = req->data_desc; |
| for(i = 0; i < req->desc_count; i++){ |
| data_desc = next_data_desc; |
| data_desc->status = USB_DMA_BUF_HOST_BUSY | USB_DMA_LAST; |
| data_desc->data_ptr = 0xffffffff; |
| data_desc->next_desc_ptr = 0; |
| data_desc->last_aux = 1; |
| next_data_desc = data_desc->next_desc_virt; |
| dma_pool_free(desc_dma_pool, data_desc, data_desc->cur_desc_addr); |
| } |
| |
| req->desc_count = 0; |
| req->data_desc = NULL; |
| req->data_desc_addr = 0; |
| } |
| |
| static int ambarella_reuse_descriptor(struct ambarella_ep *ep, |
| struct ambarella_request *req, u32 desc_count, dma_addr_t start_address) |
| { |
| struct ambarella_data_desc *data_desc, *next_data_desc; |
| u32 data_transmit, rest_bytes, i; |
| |
| next_data_desc = req->data_desc; |
| for(i = 0; i < desc_count; i++){ |
| rest_bytes = req->req.length - i * ep->ep.maxpacket; |
| if(ep->dir == USB_DIR_IN) |
| data_transmit = rest_bytes < ep->ep.maxpacket ? |
| rest_bytes : ep->ep.maxpacket; |
| else |
| data_transmit = 0; |
| |
| data_desc = next_data_desc; |
| data_desc->status = USB_DMA_BUF_HOST_RDY | data_transmit; |
| data_desc->reserved = 0xffffffff; |
| data_desc->data_ptr = start_address + i * ep->ep.maxpacket; |
| data_desc->last_aux = 0; |
| |
| next_data_desc = data_desc->next_desc_virt; |
| } |
| |
| /* Patch last one. */ |
| data_desc->status |= USB_DMA_LAST; |
| data_desc->last_aux = 1; |
| |
| return 0; |
| } |
| |
| static int ambarella_prepare_descriptor(struct ambarella_ep *ep, |
| struct ambarella_request *req, gfp_t gfp) |
| { |
| struct ambarella_udc *udc = ep->udc; |
| struct ambarella_data_desc *data_desc = NULL; |
| struct ambarella_data_desc *prev_data_desc = NULL; |
| dma_addr_t desc_phys, start_address; |
| u32 desc_count, data_transmit, rest_bytes, i; |
| |
| if (likely(!req->use_aux_buf)) |
| start_address = req->req.dma; |
| else |
| start_address = req->dma_aux; |
| |
| desc_count = (req->req.length + ep->ep.maxpacket - 1) / ep->ep.maxpacket; |
| if(req->req.zero && (req->req.length % ep->ep.maxpacket == 0)) |
| desc_count++; |
| if(desc_count == 0) |
| desc_count = 1; |
| |
| if (desc_count <= req->desc_count) { |
| ambarella_reuse_descriptor(ep, req, desc_count, start_address); |
| return 0; |
| } |
| |
| if(req->desc_count > 0) |
| ambarella_free_descriptor(ep, req); |
| |
| req->desc_count = desc_count; |
| |
| for(i = 0; i < desc_count; i++){ |
| rest_bytes = req->req.length - i * ep->ep.maxpacket; |
| if(ep->dir == USB_DIR_IN) |
| data_transmit = rest_bytes < ep->ep.maxpacket ? |
| rest_bytes : ep->ep.maxpacket; |
| else |
| data_transmit = 0; |
| |
| data_desc = dma_pool_alloc(udc->desc_dma_pool, gfp, &desc_phys); |
| if (!data_desc) { |
| req->desc_count = i; |
| if(req->desc_count > 0) |
| ambarella_free_descriptor(ep, req); |
| return -ENOMEM; |
| } |
| |
| data_desc->status = USB_DMA_BUF_HOST_RDY | data_transmit; |
| data_desc->reserved = 0xffffffff; |
| data_desc->data_ptr = start_address + i * ep->ep.maxpacket; |
| data_desc->next_desc_ptr = 0; |
| data_desc->rsvd1 = 0xffffffff; |
| data_desc->last_aux = 0; |
| data_desc->cur_desc_addr = desc_phys; |
| |
| if(prev_data_desc){ |
| prev_data_desc->next_desc_ptr = desc_phys; |
| prev_data_desc->next_desc_virt = data_desc; |
| } |
| |
| prev_data_desc = data_desc; |
| |
| if(i == 0){ |
| req->data_desc = data_desc; |
| req->data_desc_addr = desc_phys; |
| } |
| } |
| |
| /* Patch last one */ |
| data_desc->status |= USB_DMA_LAST; |
| data_desc->next_desc_ptr = 0; |
| data_desc->next_desc_virt = NULL; |
| data_desc->last_aux = 1; |
| |
| return 0; |
| } |
| |
| |
| static void ambarella_clr_ep_nak(struct ambarella_ep *ep) |
| { |
| struct ambarella_ep_reg *ep_reg = &ep->ep_reg; |
| |
| amba_setbitsl(ep_reg->ctrl_reg, USB_EP_CLR_NAK); |
| if (amba_readl(ep_reg->ctrl_reg) & USB_EP_NAK_STS) { |
| /* can't clear NAK, let somebody clear it after Rx DMA is done. */ |
| ep->need_cnak = 1; |
| }else{ |
| ep->need_cnak = 0; |
| } |
| } |
| |
| static void ambarella_enable_rx_dma(struct ambarella_ep *ep) |
| { |
| ep->dma_going = 1; |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_RCV_DMA_EN); |
| } |
| |
| |
| /* |
| * Name: ambarella_set_tx_dma |
| * Description: |
| * Set data descriptor and Prepare Tx-FIFO for IN endpoint |
| */ |
| static void ambarella_set_tx_dma(struct ambarella_ep *ep, |
| struct ambarella_request * req) |
| { |
| struct ambarella_ep_reg *ep_reg = &ep->ep_reg; |
| |
| dprintk(DEBUG_NORMAL, "Enter. %s Tx DMA\n", ep->ep.name); |
| |
| ep->data_desc = req->data_desc; |
| amba_writel(ep_reg->dat_desc_ptr_reg, req->data_desc_addr); |
| /* set Poll command to transfer data to Tx FIFO */ |
| amba_setbitsl(ep_reg->ctrl_reg, USB_EP_POLL_DEMAND); |
| ep->dma_going = 1; |
| |
| dprintk(DEBUG_NORMAL, "Exit\n"); |
| } |
| |
| |
| static void ambarella_set_rx_dma(struct ambarella_ep *ep, |
| struct ambarella_request * req) |
| { |
| struct ambarella_ep_reg *ep_reg = &ep->ep_reg; |
| struct ambarella_udc *udc = ep->udc; |
| u32 i; |
| |
| dprintk(DEBUG_NORMAL, "Enter. %s Rx DMA\n", ep->ep.name); |
| |
| if(req){ |
| ep->data_desc = req->data_desc; |
| amba_writel(ep_reg->dat_desc_ptr_reg, req->data_desc_addr); |
| } else { |
| /* receive zero-length-packet */ |
| amba_writel(ep_reg->dat_desc_ptr_reg, udc->dummy_desc_addr); |
| } |
| |
| /* enable dma completion interrupt for next RX data */ |
| amba_clrbitsl(USB_DEV_EP_INTR_MSK_REG, 1 << ep->id); |
| /* re-enable DMA read */ |
| ambarella_enable_rx_dma(ep); |
| |
| if (amba_readl(USB_DEV_STS_REG) & USB_DEV_RXFIFO_EMPTY_STS) { |
| /* clear NAK for TX dma */ |
| for (i = 0; i < EP_NUM_MAX; i++) { |
| struct ambarella_ep *_ep = &udc->ep[i]; |
| if (_ep->need_cnak == 1) |
| ambarella_clr_ep_nak(_ep); |
| } |
| } |
| |
| /* clear NAK */ |
| ambarella_clr_ep_nak(ep); |
| |
| dprintk(DEBUG_NORMAL, "Exit\n"); |
| } |
| |
| static int ambarella_handle_ep_stall(struct ambarella_ep *ep, u32 ep_status) |
| { |
| int ret = 0; |
| |
| if (ep_status & USB_EP_RCV_CLR_STALL) { |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_CLR_NAK); |
| amba_clrbitsl(ep->ep_reg.ctrl_reg, USB_EP_STALL); |
| amba_setbitsl(ep->ep_reg.sts_reg, USB_EP_RCV_CLR_STALL); |
| ret = 1; |
| } |
| |
| if (ep_status & USB_EP_RCV_SET_STALL) { |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_STALL); |
| if (ep->dir == USB_DIR_IN) |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_FLUSH); |
| amba_setbitsl(ep->ep_reg.sts_reg, USB_EP_RCV_SET_STALL); |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| static void ambarella_handle_request_packet(struct ambarella_udc *udc) |
| { |
| struct usb_ctrlrequest *crq; |
| int ret; |
| |
| dprintk(DEBUG_NORMAL, "Enter\n"); |
| |
| ambarella_ep_nuke(&udc->ep[CTRL_OUT], -EPROTO); // Todo |
| |
| /* read out setup packet */ |
| udc->setup[0] = udc->setup_buf->data0; |
| udc->setup[1] = udc->setup_buf->data1; |
| /* reinitialize setup packet */ |
| init_setup_descriptor(udc); |
| |
| crq = (struct usb_ctrlrequest *) &udc->setup[0]; |
| |
| dprintk(DEBUG_NORMAL, "bRequestType = 0x%02x, bRequest = 0x%02x, " |
| "wValue = 0x%04x, wIndex = 0x%04x, wLength = 0x%04x\n", |
| crq->bRequestType, crq->bRequest, crq->wValue, crq->wIndex, |
| crq->wLength); |
| |
| if((crq->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD){ |
| switch(crq->bRequest) |
| { |
| case USB_REQ_GET_STATUS: |
| case USB_REQ_SET_ADDRESS: |
| case USB_REQ_CLEAR_FEATURE: |
| case USB_REQ_SET_FEATURE: |
| dprintk(DEBUG_ERR, "This bRequest is not implemented!\n" |
| "\tbRequestType = 0x%02x, bRequest = 0x%02x,\n" |
| "\twValue = 0x%04x, wIndex = 0x%04x, wLength = 0x%04x\n", |
| crq->bRequestType, crq->bRequest, crq->wValue, crq->wIndex, |
| crq->wLength); |
| } |
| } |
| |
| if (crq->bRequestType & USB_DIR_IN) |
| udc->gadget.ep0 = &udc->ep[CTRL_IN].ep; |
| |
| else |
| udc->gadget.ep0 = &udc->ep[CTRL_OUT].ep; |
| |
| spin_unlock(&udc->lock); |
| ret = udc->driver->setup(&udc->gadget, crq); |
| spin_lock(&udc->lock); |
| if (ret < 0) { |
| dprintk(DEBUG_ERR, "SETUP request failed (%d)\n", ret); |
| amba_setbitsl(udc->ep[CTRL_IN].ep_reg.ctrl_reg, USB_EP_STALL | USB_EP_FLUSH); |
| /* Re-enable Rx DMA to receive next setup packet */ |
| ambarella_enable_rx_dma(&udc->ep[CTRL_OUT]); |
| ambarella_clr_ep_nak(&udc->ep[CTRL_OUT]); |
| } |
| |
| dprintk(DEBUG_NORMAL, "Exit\n"); |
| |
| return; |
| |
| } |
| |
| static void ambarella_handle_data_in(struct ambarella_ep *ep) |
| { |
| struct ambarella_request *req = NULL; |
| struct ambarella_udc *udc = ep->udc; |
| |
| /* get request */ |
| if (list_empty(&ep->queue)) { |
| printk(KERN_DEBUG "%s: req NULL\n", __func__); |
| return ; |
| } |
| |
| req = list_first_entry(&ep->queue, struct ambarella_request,queue); |
| |
| /* If error happened, issue the request again */ |
| if (ambarella_check_dma_error(ep)) |
| req->req.status = -EPROTO; |
| else { |
| /* No error happened, so all the data has been sent to host */ |
| req->req.actual = req->req.length; |
| } |
| |
| ambarella_udc_done(ep, req, 0); |
| |
| if(ep->id == CTRL_IN){ |
| /* For STATUS-OUT stage */ |
| udc->ep[CTRL_OUT].ctrl_sts_phase = 1; |
| udc->dummy_desc->status = USB_DMA_BUF_HOST_RDY | USB_DMA_LAST; |
| ambarella_set_rx_dma(&udc->ep[CTRL_OUT], NULL); |
| } |
| } |
| |
| static int ambarella_handle_data_out(struct ambarella_ep *ep) |
| { |
| struct ambarella_request *req = NULL; |
| struct ambarella_udc *udc = ep->udc; |
| u32 recv_size = 0, req_size; |
| |
| /* get request */ |
| if (list_empty(&ep->queue)) { |
| printk(KERN_DEBUG "%s: req NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| req = list_first_entry(&ep->queue, struct ambarella_request,queue); |
| |
| /* If error happened, issue the request again */ |
| if (ambarella_check_dma_error(ep)) |
| req->req.status = -EPROTO; |
| |
| recv_size = ep->last_data_desc->status & USB_DMA_RXTX_BYTES; |
| if (!recv_size && req->req.length == UDC_DMA_MAXPACKET) { |
| /* on 64k packets the RXBYTES field is zero */ |
| recv_size = UDC_DMA_MAXPACKET; |
| } |
| |
| req_size = req->req.length - req->req.actual; |
| if (recv_size > req_size) { |
| if ((req_size % ep->ep.maxpacket) != 0) |
| req->req.status = -EOVERFLOW; |
| recv_size = req_size; |
| } |
| |
| req->req.actual += recv_size; |
| |
| ambarella_udc_done(ep, req, 0); |
| |
| if(ep->id == CTRL_OUT) { |
| /* For STATUS-IN stage */ |
| ambarella_clr_ep_nak(&udc->ep[CTRL_IN]); |
| /* Re-enable Rx DMA to receive next setup packet */ |
| ambarella_enable_rx_dma(ep); |
| ep->dma_going = 0; |
| } |
| |
| return 0; |
| } |
| |
| |
| static void ambarella_udc_done(struct ambarella_ep *ep, |
| struct ambarella_request *req, int status) |
| { |
| unsigned halted_tmp, need_queue = 0; |
| struct ambarella_request *next_req; |
| |
| halted_tmp = ep->halted; |
| |
| list_del_init(&req->queue); |
| |
| if(!list_empty(&ep->queue) && !ep->halted && !ep->cancel_transfer){ |
| need_queue = 1; |
| } else if (!IS_EP0(ep) && (ep->dir == USB_DIR_IN) && !ep->cancel_transfer) { |
| amba_setbitsl(USB_DEV_EP_INTR_MSK_REG, 1 << ep->id); |
| } |
| |
| if (likely (req->req.status == -EINPROGRESS)) |
| req->req.status = status; |
| else |
| status = req->req.status; |
| |
| ambarella_unmap_dma_buffer(ep, req); |
| |
| if (req->use_aux_buf && ep->dir != USB_DIR_IN) |
| memcpy(req->req.buf, req->buf_aux, req->req.actual); |
| |
| ep->data_desc = NULL; |
| ep->last_data_desc = NULL; |
| |
| ep->halted = 1; |
| spin_unlock(&ep->udc->lock); |
| req->req.complete(&ep->ep, &req->req); |
| spin_lock(&ep->udc->lock); |
| ep->halted = halted_tmp; |
| |
| if(need_queue){ |
| next_req = list_first_entry (&ep->queue, |
| struct ambarella_request, queue); |
| |
| switch(ep->dir) { |
| case USB_DIR_IN: |
| ambarella_clr_ep_nak(ep); |
| break; |
| case USB_DIR_OUT: |
| ambarella_set_rx_dma(ep, next_req); |
| break; |
| default: |
| return; |
| } |
| } |
| } |
| |
| static void ambarella_ep_nuke(struct ambarella_ep *ep, int status) |
| { |
| while (!list_empty (&ep->queue)) { |
| struct ambarella_request *req; |
| req = list_first_entry(&ep->queue, |
| struct ambarella_request, queue); |
| ambarella_udc_done(ep, req, status); |
| } |
| } |
| |
| /* |
| * Name: device_interrupt |
| * Description: |
| * Process related device interrupt |
| */ |
| static void udc_device_interrupt(struct ambarella_udc *udc, u32 int_value) |
| { |
| /* case 1. Get set_config or set_interface request from host */ |
| if (int_value & (USB_DEV_SET_CFG | USB_DEV_SET_INTF)) { |
| struct usb_ctrlrequest crq; |
| u32 i, ret, csr_config; |
| |
| dprintk(DEBUG_NORMAL, "set_config or set_interface IRQ" |
| " - device intr reg = 0x%x\n", amba_readl(USB_DEV_INTR_REG)); |
| |
| if(int_value & USB_DEV_SET_CFG) { |
| /* Ack the SC interrupt */ |
| amba_writel(USB_DEV_INTR_REG, USB_DEV_SET_CFG); |
| udc->cur_config = (u16)(amba_readl(USB_DEV_STS_REG) & USB_DEV_CFG_NUM); |
| |
| crq.bRequestType = 0x00; |
| crq.bRequest = USB_REQ_SET_CONFIGURATION; |
| crq.wValue = cpu_to_le16(udc->cur_config); |
| crq.wIndex = 0x0000; |
| crq.wLength = 0x0000; |
| } else if(int_value & USB_DEV_SET_INTF){ |
| printk("set interface\n"); |
| /* Ack the SI interrupt */ |
| amba_writel(USB_DEV_INTR_REG, USB_DEV_SET_INTF); |
| udc->cur_intf = (amba_readl(USB_DEV_STS_REG) & USB_DEV_INTF_NUM) >> 4; |
| udc->cur_alt = (amba_readl(USB_DEV_STS_REG) & USB_DEV_ALT_SET) >> 8; |
| |
| crq.bRequestType = 0x01; |
| crq.bRequest = USB_REQ_SET_INTERFACE; |
| crq.wValue = cpu_to_le16(udc->cur_alt); |
| crq.wIndex = cpu_to_le16(udc->cur_intf); |
| crq.wLength = 0x0000; |
| } |
| |
| for (i = 0; i < EP_NUM_MAX; i++){ |
| udc->ep[i].halted = 0; |
| amba_clrbitsl(udc->ep[i].ep_reg.ctrl_reg, USB_EP_STALL); |
| } |
| |
| /* setup ep0 CSR. Note: ep0-in and ep0-out share the same CSR reg */ |
| csr_config = (udc->cur_config << 7) | (udc->cur_intf << 11) | |
| (udc->cur_alt << 15); |
| amba_clrbitsl(USB_UDC_REG(CTRL_IN), 0xfff << 7); |
| amba_setbitsl(USB_UDC_REG(CTRL_IN), csr_config); |
| |
| udc->auto_ack_0_pkt = 1; |
| ambarella_ep_nuke(&udc->ep[CTRL_OUT], -EPROTO); |
| spin_unlock(&udc->lock); |
| ret = udc->driver->setup(&udc->gadget, &crq); |
| spin_lock(&udc->lock); |
| if(ret < 0) |
| printk(KERN_ERR "set config failed. (%d)\n", ret); |
| |
| /* told UDC the configuration is done, and to ack HOST */ |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_CSR_DONE); |
| udelay(150); |
| amb_udc_status = AMBARELLA_UDC_STATUS_CONFIGURED; |
| schedule_work(&udc->uevent_work); |
| |
| } |
| |
| /* case 2. Get reset Interrupt */ |
| else if (int_value & USB_DEV_RESET) { |
| |
| dprintk(DEBUG_NORMAL, "USB reset IRQ\n"); |
| |
| amba_writel(USB_DEV_INTR_REG, USB_DEV_RESET); |
| |
| // ambarella_disable_all_intr(); |
| |
| if (udc->host_suspended && udc->driver && udc->driver->resume){ |
| spin_unlock(&udc->lock); |
| udc->driver->resume(&udc->gadget); |
| spin_lock(&udc->lock); |
| udc->host_suspended = 0; |
| } |
| |
| if (udc->reset_by_host == 0) { |
| udc->reset_by_host = 1; |
| udc->controller_info->reset_usb(); |
| ambarella_init_usb(); |
| } |
| |
| ambarella_stop_activity(udc); |
| |
| udc->gadget.speed = USB_SPEED_UNKNOWN; |
| udc->auto_ack_0_pkt = 0; |
| udc->remote_wakeup_en = 0; |
| |
| /* Clear all pending endpoint interrupts */ |
| amba_writel(USB_DEV_EP_INTR_REG, 0xffffffff); |
| |
| udc->udc_is_enabled = 0; |
| ambarella_udc_enable(udc); |
| #if 0 |
| /* enable suspend interrupt */ |
| amba_clrbitsl(USB_DEV_INTR_MSK_REG, UDC_INTR_MSK_US); |
| #endif |
| } |
| |
| /* case 3. Get suspend Interrupt */ |
| else if (int_value & USB_DEV_SUSP) { |
| |
| dprintk(DEBUG_ERR, "USB suspend IRQ\n"); |
| |
| amba_writel(USB_DEV_INTR_REG, USB_DEV_SUSP); |
| |
| if (udc->driver->suspend) { |
| udc->host_suspended = 1; |
| spin_unlock(&udc->lock); |
| udc->driver->suspend(&udc->gadget); |
| spin_lock(&udc->lock); |
| } |
| |
| amb_udc_status = AMBARELLA_UDC_STATUS_BUSSUSPEND; |
| schedule_work(&udc->uevent_work); |
| } |
| |
| /* case 4. enumeration complete */ |
| else if(int_value & USB_DEV_ENUM_CMPL) { |
| u32 value = 0; |
| |
| udc->reset_by_host = 0; |
| |
| /* Ack the CMPL interrupt */ |
| amba_writel(USB_DEV_INTR_REG, USB_DEV_ENUM_CMPL); |
| |
| value = amba_readl(USB_DEV_STS_REG) & USB_DEV_ENUM_SPD; |
| |
| if(value == USB_DEV_ENUM_SPD_HI) { /* high speed */ |
| |
| dprintk(DEBUG_NORMAL,"enumeration IRQ - " |
| "High-speed bus detected\n"); |
| udc->gadget.speed = USB_SPEED_HIGH; |
| } else if (value == USB_DEV_ENUM_SPD_FU) { /* full speed */ |
| |
| dprintk(DEBUG_NORMAL,"enumeration IRQ - " |
| "Full-speed bus detected\n"); |
| udc->gadget.speed = USB_SPEED_FULL; |
| } else { |
| printk(KERN_ERR "Not supported speed!" |
| "USB_DEV_STS_REG = 0x%x\n", value); |
| udc->gadget.speed = USB_SPEED_UNKNOWN; |
| |
| } |
| } /* ENUM COMPLETE */ |
| else { |
| printk(KERN_ERR "Unknown Interrupt:0x%08x\n", int_value); |
| /* Ack the Unknown interrupt */ |
| amba_writel(USB_DEV_INTR_REG, int_value); |
| } |
| } |
| |
| |
| /* |
| * Name: udc_epin_interrupt |
| * Description: |
| * Process IN(CTRL or BULK) endpoint interrupt |
| */ |
| static void udc_epin_interrupt(struct ambarella_udc *udc, u32 ep_id) |
| { |
| u32 ep_status = 0; |
| struct ambarella_ep *ep = &udc->ep[ep_id]; |
| struct ambarella_request *req; |
| |
| ep_status = amba_readl(ep->ep_reg.sts_reg); |
| |
| dprintk(DEBUG_NORMAL, "ep_status = 0x%x\n", ep_status); |
| |
| if (ambarella_handle_ep_stall(ep, ep_status)) |
| return; |
| |
| if ((ambarella_check_bna_error(ep) || ambarella_check_he_error(ep)) |
| && !list_empty(&ep->queue)) { |
| struct ambarella_request *req = NULL; |
| ep->dma_going = 0; |
| ep->cancel_transfer = 0; |
| ep->need_cnak = 0; |
| req = list_first_entry(&ep->queue, struct ambarella_request,queue); |
| req->req.status = -EPROTO; |
| ambarella_udc_done(ep, req, 0); |
| return; |
| } |
| |
| if (ep_status & USB_EP_TRN_DMA_CMPL) { |
| if(ep->halted || ep->dma_going == 0 || ep->cancel_transfer == 1 |
| || list_empty(&ep->queue)) { |
| ep_status &= (USB_EP_IN_PKT | USB_EP_TRN_DMA_CMPL); |
| amba_writel(ep->ep_reg.sts_reg, ep_status); |
| ep->dma_going = 0; |
| ep->cancel_transfer = 0; |
| return; |
| } |
| |
| ep->dma_going = 0; |
| ep->cancel_transfer = 0; |
| ep->need_cnak = 0; |
| |
| ep->last_data_desc = ambarella_get_last_desc(ep); |
| if(ep->last_data_desc == NULL){ |
| printk(KERN_ERR "%s: last_data_desc is NULL\n", ep->ep.name); |
| BUG(); |
| return; |
| } |
| ambarella_handle_data_in(&udc->ep[ep_id]); |
| } else if(ep_status & USB_EP_IN_PKT) { |
| if(!ep->halted && !ep->cancel_transfer && !list_empty(&ep->queue)){ |
| req = list_first_entry(&ep->queue, |
| struct ambarella_request, queue); |
| ambarella_set_tx_dma(ep, req); |
| } else if (ep->dma_going == 0 || ep->halted || ep->cancel_transfer) { |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_SET_NAK); |
| } |
| ep->cancel_transfer = 0; |
| } else if (ep_status != 0){ |
| if (ep_status != 0x08000000) { |
| dprintk(DEBUG_ERR, "%s: Unknown int source(0x%08x)\n", |
| ep->ep.name, ep_status); |
| } |
| amba_writel(ep->ep_reg.sts_reg, ep_status); |
| return; |
| } |
| |
| if (ep_status != 0) { |
| ep_status &= (USB_EP_IN_PKT | USB_EP_TRN_DMA_CMPL); |
| // ep_status &= (USB_EP_IN_PKT | USB_EP_TRN_DMA_CMPL | USB_EP_RCV_CLR_STALL); |
| amba_writel(ep->ep_reg.sts_reg, ep_status); |
| } |
| } |
| |
| |
| /* |
| * Name: udc_epout_interrupt |
| * Description: |
| * Process OUT endpoint interrupt |
| */ |
| static void udc_epout_interrupt(struct ambarella_udc *udc, u32 ep_id) |
| { |
| struct ambarella_ep *ep = &udc->ep[ep_id]; |
| u32 desc_status, ep_status, i; |
| |
| /* check the status bits for what kind of packets in */ |
| ep_status = amba_readl(ep->ep_reg.sts_reg); |
| |
| if (ambarella_handle_ep_stall(ep, ep_status)) |
| return; |
| |
| |
| if(ep_id == CTRL_OUT) { |
| /* Cope with setup-data packet */ |
| if((ep_status & USB_EP_OUT_PKT_MSK) == USB_EP_SETUP_PKT){ |
| |
| amba_writel(ep->ep_reg.sts_reg, USB_EP_SETUP_PKT); |
| ep->ctrl_sts_phase = 0; |
| ep->dma_going = 0; |
| ambarella_handle_request_packet(udc); |
| } |
| } |
| |
| /* Cope with normal data packet */ |
| if((ep_status & USB_EP_OUT_PKT_MSK) == USB_EP_OUT_PKT) { |
| |
| amba_writel(ep->ep_reg.sts_reg, USB_EP_OUT_PKT); |
| ep->dma_going = 0; |
| |
| /* Just cope with the zero-length packet */ |
| if(ep->ctrl_sts_phase == 1) { |
| ep->ctrl_sts_phase = 0; |
| ambarella_enable_rx_dma(ep); |
| ep->dma_going = 0; |
| return; |
| } |
| |
| if(ep->halted || ep->cancel_transfer || list_empty(&ep->queue)) { |
| amba_writel(ep->ep_reg.sts_reg, ep_status); |
| return; |
| } |
| |
| if (ambarella_check_bna_error(ep)) |
| return; |
| |
| if(ambarella_check_he_error(ep) && !list_empty(&ep->queue)) { |
| struct ambarella_request *req = NULL; |
| req = list_first_entry(&ep->queue, |
| struct ambarella_request, queue); |
| req->req.status = -EPROTO; |
| ambarella_udc_done(ep, req, 0); |
| return; |
| } |
| |
| ep->last_data_desc = ambarella_get_last_desc(ep); |
| if(ep->last_data_desc == NULL){ |
| dprintk(DEBUG_ERR, "%s: last_data_desc is NULL\n", |
| ep->ep.name); |
| BUG(); |
| return; |
| } |
| |
| if(ep_id != CTRL_OUT){ |
| desc_status = ep->last_data_desc->status; |
| /* received data */ |
| if((desc_status & USB_DMA_BUF_STS) == USB_DMA_BUF_DMA_DONE) { |
| amba_setbitsl(USB_DEV_EP_INTR_MSK_REG, 1 << ep_id); |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_SET_NAK); |
| } |
| } |
| |
| ambarella_handle_data_out(ep); |
| |
| /* clear NAK for TX dma */ |
| if (amba_readl(USB_DEV_STS_REG) & USB_DEV_RXFIFO_EMPTY_STS) { |
| for (i = 0; i < EP_NUM_MAX; i++) { |
| struct ambarella_ep *_ep = &udc->ep[i]; |
| if (_ep->need_cnak == 1) |
| ambarella_clr_ep_nak(_ep); |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| static irqreturn_t ambarella_udc_irq(int irq, void *_dev) |
| { |
| struct ambarella_udc *udc = _dev; |
| u32 value, handled = 0, i, ep_irq; |
| |
| spin_lock(&udc->lock); |
| /* If gadget driver is not connected, do not handle the interrupt */ |
| if (!udc->driver) { |
| amba_writel(USB_DEV_INTR_REG, amba_readl(USB_DEV_INTR_REG)); |
| amba_writel(USB_DEV_EP_INTR_REG, amba_readl(USB_DEV_EP_INTR_REG)); |
| } |
| |
| /* 1. check if device interrupt */ |
| value = amba_readl(USB_DEV_INTR_REG); |
| if(value) { |
| |
| dprintk(DEBUG_NORMAL, "device int value = 0x%x\n", value); |
| |
| handled = 1; |
| udc_device_interrupt(udc, value); |
| |
| } |
| /* 2. check if endpoint interrupt */ |
| value = amba_readl(USB_DEV_EP_INTR_REG); |
| if(value) { |
| handled = 1; |
| |
| for(i = 0; i < EP_NUM_MAX; i++){ |
| ep_irq = 1 << i; |
| if (!(value & ep_irq)) |
| continue; |
| |
| /* ack the endpoint interrupt */ |
| amba_writel(USB_DEV_EP_INTR_REG, ep_irq); |
| |
| /* irq for out ep ? */ |
| if (i >= EP_IN_NUM) |
| udc_epout_interrupt(udc, i); |
| else |
| udc_epin_interrupt(udc, i); |
| |
| value &= ~ep_irq; |
| if(value == 0) |
| break; |
| } |
| } |
| |
| spin_unlock(&udc->lock); |
| |
| return IRQ_RETVAL(handled); |
| } |
| |
| |
| #if 0 |
| /* |
| * ambarella_udc_vbus_irq - VBUS interrupt handler |
| */ |
| static irqreturn_t ambarella_udc_vbus_irq(int irq, void *_dev) |
| { |
| struct ambarella_udc *udc = _dev; |
| //unsigned int value; |
| |
| dprintk(DEBUG_NORMAL, "%s()\n", __func__); |
| |
| amba_writel(VIC_INTEN_CLR_REG, (0x1 << USBVBUS_IRQ)); |
| |
| /* make sure vbus register is intialized before calling this */ |
| if(ambarella_check_connected(udc)&& !udc->pre_vbus_status) { |
| dprintk(DEBUG_NORMAL, "usb cable is inserted\n"); |
| udc->pre_vbus_status = 1; |
| ambarella_udc_vbus_session(&udc->gadget, 1); |
| |
| } else if (!ambarella_check_connected(udc) && udc->pre_vbus_status) { |
| dprintk(DEBUG_NORMAL, "usb cable is removed\n"); |
| udc->pre_vbus_status = 0; |
| ambarella_udc_vbus_session(&udc->gadget, 0); |
| }else |
| printk(KERN_ERR "Incorrect vbus interrupt!\n"); |
| |
| //amba_writel(VIC_INTEN_REG, (0x1 << USBVBUS_IRQ)); |
| |
| return IRQ_HANDLED; |
| } |
| #endif |
| |
| static void ambarella_vbus_timer(unsigned long data) |
| { |
| struct ambarella_udc *udc = (struct ambarella_udc *)data; |
| u32 connected; |
| |
| connected = !!(amba_readl(VIC_RAW_STA_REG) & 0x1); |
| |
| if (udc->vbus_status != connected) { |
| udc->vbus_status = connected; |
| ambarella_udc_vbus_session(&udc->gadget, udc->vbus_status); |
| } |
| |
| mod_timer(&udc->vbus_timer, jiffies + VBUS_POLL_TIMEOUT); |
| } |
| |
| static void ambarella_stop_activity(struct ambarella_udc *udc) |
| { |
| struct usb_gadget_driver *driver = udc->driver; |
| struct ambarella_ep *ep; |
| u32 i; |
| |
| dprintk(DEBUG_VERBOSE, "Enter\n"); |
| |
| /* Disable Tx and Rx DMA */ |
| amba_clrbitsl(USB_DEV_CTRL_REG, USB_DEV_RCV_DMA_EN | USB_DEV_TRN_DMA_EN); |
| |
| if (udc->gadget.speed == USB_SPEED_UNKNOWN) |
| driver = NULL; |
| udc->gadget.speed = USB_SPEED_UNKNOWN; |
| |
| for (i = 0; i < EP_NUM_MAX; i++) { |
| ep = &udc->ep[i]; |
| |
| if(ep->desc == NULL && !IS_EP0(ep)) |
| continue; |
| |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_SET_NAK); |
| |
| ep->halted = 1; |
| ambarella_ep_nuke(ep, -ESHUTDOWN); |
| } |
| if (driver) { |
| spin_unlock(&udc->lock); |
| driver->disconnect(&udc->gadget); |
| spin_lock(&udc->lock); |
| } |
| |
| ambarella_udc_reinit(udc); |
| |
| dprintk(DEBUG_VERBOSE, "Exit\n"); |
| } |
| |
| |
| /* |
| * Name: ambarella_udc_ep_enable |
| * Description: |
| * Enable endpoint |
| */ |
| static int ambarella_udc_ep_enable(struct usb_ep *_ep, |
| const struct usb_endpoint_descriptor *desc) |
| { |
| struct ambarella_udc *udc; |
| struct ambarella_ep *ep = to_ambarella_ep(_ep); |
| u32 max_packet, tmp, type, idx = 0; |
| unsigned long flags; |
| |
| /* Sanity check */ |
| if (!_ep || !desc || ep->desc || IS_EP0(ep) |
| || desc->bDescriptorType != USB_DT_ENDPOINT) |
| return -EINVAL; |
| |
| udc = ep->udc; |
| if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) |
| return -ESHUTDOWN; |
| |
| max_packet = le16_to_cpu(desc->wMaxPacketSize) & 0x7ff; |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ep->ep.maxpacket = max_packet; |
| ep->desc = desc; |
| ep->halted = 0; |
| ep->data_desc = NULL; |
| ep->last_data_desc = NULL; |
| ep->ctrl_sts_phase = 0; |
| ep->dma_going = 0; |
| ep->cancel_transfer = 0; |
| |
| if(ep->dir == USB_DIR_IN){ |
| idx = ep->id; |
| } else { |
| idx = ep->id - CTRL_OUT_UDC_IDX; |
| } |
| |
| /* setup CSR */ |
| amba_clrbitsl(USB_UDC_REG(idx), 0x3fffffff); |
| tmp = (desc->bEndpointAddress & 0xf) << 0; |
| tmp |= (desc->bEndpointAddress >> 7) << 4; |
| tmp |= (desc->bmAttributes & 0x3) << 5; |
| tmp |= udc->cur_config << 7; |
| tmp |= udc->cur_intf << 11; |
| tmp |= udc->cur_alt << 15; |
| tmp |= max_packet << 19; |
| amba_setbitsl(USB_UDC_REG(idx), tmp); |
| |
| type = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) << 4; |
| amba_writel(ep->ep_reg.ctrl_reg, type | USB_EP_SET_NAK); |
| |
| if(ep->dir == USB_DIR_IN) |
| amba_writel(ep->ep_reg.buf_sz_reg, max_packet / 4); |
| amba_writel(ep->ep_reg.max_pkt_sz_reg, max_packet); |
| |
| /* print some debug message */ |
| dprintk (DEBUG_NORMAL, "enable %s, address = 0x%02x, max_packet = 0x%02x\n", |
| ep->ep.name, desc->bEndpointAddress, max_packet); |
| |
| spin_unlock_irqrestore(&udc->lock, flags); |
| ambarella_udc_set_halt(_ep, 0); |
| |
| return 0; |
| } |
| |
| |
| static int ambarella_udc_ep_disable(struct usb_ep *_ep) |
| { |
| struct ambarella_ep *ep = to_ambarella_ep(_ep); |
| unsigned long flags; |
| |
| dprintk(DEBUG_NORMAL, "ep_disable: %s\n", _ep->name); |
| |
| if (!_ep || !ep->desc) { |
| dprintk(DEBUG_NORMAL, "%s not enabled\n", |
| _ep ? ep->ep.name : NULL); |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&ep->udc->lock, flags); |
| |
| ep->desc = NULL; |
| ep->halted = 1; |
| ambarella_ep_nuke(ep, -ESHUTDOWN); |
| |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_SET_NAK); |
| |
| if(ep->dir == USB_DIR_IN){ |
| /* clear DMA poll demand bit */ |
| amba_clrbitsl(ep->ep_reg.ctrl_reg, USB_EP_POLL_DEMAND); |
| /* clear status register */ |
| amba_setbitsl(ep->ep_reg.sts_reg, USB_EP_IN_PKT); |
| /* flush the fifo */ |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_FLUSH); |
| } |
| |
| /* disable irqs */ |
| amba_setbitsl(USB_DEV_EP_INTR_MSK_REG, 1 << ep->id); |
| |
| spin_unlock_irqrestore(&ep->udc->lock, flags); |
| |
| dprintk(DEBUG_NORMAL, "%s disabled\n", _ep->name); |
| |
| return 0; |
| } |
| |
| |
| static struct usb_request * |
| ambarella_udc_alloc_request(struct usb_ep *_ep, gfp_t mem_flags) |
| { |
| struct ambarella_request *req; |
| |
| if (!_ep) |
| return NULL; |
| |
| req = kzalloc (sizeof(struct ambarella_request), mem_flags); |
| if (!req) |
| return NULL; |
| |
| req->req.dma = DMA_ADDR_INVALID; |
| INIT_LIST_HEAD (&req->queue); |
| req->desc_count = 0; |
| |
| req->buf_aux = NULL; |
| req->dma_aux = DMA_ADDR_INVALID; |
| req->use_aux_buf = 0; |
| |
| return &req->req; |
| } |
| |
| |
| static void |
| ambarella_udc_free_request(struct usb_ep *_ep, struct usb_request *_req) |
| { |
| struct ambarella_ep *ep = to_ambarella_ep(_ep); |
| struct ambarella_request *req = to_ambarella_req(_req); |
| |
| dprintk(DEBUG_VERBOSE, "(%p,%p)\n", _ep, _req); |
| |
| if (!ep || !_req || (!ep->desc && !IS_EP0(ep))) |
| return; |
| |
| if(req->desc_count > 0) |
| ambarella_free_descriptor(ep, req); |
| |
| if (req->buf_aux) { |
| kfree(req->buf_aux); |
| req->buf_aux = NULL; |
| } |
| |
| WARN_ON (!list_empty (&req->queue)); |
| kfree(req); |
| } |
| |
| |
| static int ambarella_udc_queue(struct usb_ep *_ep, struct usb_request *_req, |
| gfp_t gfp_flags) |
| { |
| struct ambarella_request *req = NULL; |
| struct ambarella_ep *ep = NULL; |
| struct ambarella_udc *udc; |
| unsigned long flags; |
| |
| if (unlikely (!_ep)) { |
| dprintk(DEBUG_ERR, "_ep is NULL\n"); |
| return -EINVAL; |
| } |
| |
| ep = to_ambarella_ep(_ep); |
| if (unlikely (!ep->desc && !IS_EP0(ep))) { |
| dprintk(DEBUG_ERR, "%s: invalid args\n", _ep->name); |
| return -EINVAL; |
| } |
| |
| udc = ep->udc; |
| if( unlikely( !udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN)){ |
| dprintk(DEBUG_NORMAL, "%s: %01d %01d\n", _ep->name, |
| !udc->driver, udc->gadget.speed==USB_SPEED_UNKNOWN); |
| return -ESHUTDOWN; |
| } |
| |
| if (unlikely(!_req )) { |
| dprintk(DEBUG_ERR, "%s: _req is NULL\n", _ep->name); |
| return -EINVAL; |
| } |
| |
| req = to_ambarella_req(_req); |
| if (unlikely(!req->req.complete || !req->req.buf |
| || !list_empty(&req->queue))) { |
| dprintk(DEBUG_ERR, "%s: %01d %01d %01d\n", _ep->name, |
| !_req->complete, !_req->buf, !list_empty(&req->queue)); |
| |
| return -EINVAL; |
| } |
| |
| if(IS_EP0(ep) && (udc->auto_ack_0_pkt == 1)){ |
| /* It's status stage in setup packet. And A2/A3 will |
| * automatically send the zero-length packet to Host */ |
| udc->auto_ack_0_pkt = 0; |
| req->req.actual = 0; |
| req->req.complete(&ep->ep, &req->req); |
| return 0; |
| } |
| |
| /* check whether USB is suspended */ |
| if(amba_readl(USB_DEV_STS_REG) & USB_DEV_SUSP_STS){ |
| printk(KERN_ERR "UDC is suspended!\n"); |
| return -ESHUTDOWN; |
| } |
| |
| if (unlikely((unsigned long)req->req.buf & 0x7)) { |
| req->use_aux_buf = 1; |
| |
| if (req->buf_aux == NULL) { |
| req->buf_aux = kmalloc(UDC_DMA_MAXPACKET, GFP_ATOMIC); |
| if (req->buf_aux == NULL) |
| return -ENOMEM; |
| } |
| |
| if (ep->dir == USB_DIR_IN) |
| memcpy(req->buf_aux, req->req.buf, req->req.length); |
| } else { |
| req->use_aux_buf = 0; |
| } |
| |
| ambarella_map_dma_buffer(ep, req); |
| |
| /* disable IRQ handler's bottom-half */ |
| spin_lock_irqsave(&udc->lock, flags); |
| |
| _req->status = -EINPROGRESS; |
| _req->actual = 0; |
| |
| ambarella_prepare_descriptor(ep, req, gfp_flags); |
| |
| /* kickstart this i/o queue? */ |
| //if (list_empty(&ep->queue) && !ep->halted) { |
| if (list_empty(&ep->queue)) { |
| /* when the data length in ctrl-out transfer is zero, we just |
| * need to implement the STATUS-IN stage. But we don't |
| * implement the case that the data length in ctrl-in transfer |
| * is zero. */ |
| if(req->req.length == 0) { |
| if(ep->id == CTRL_OUT) { |
| ambarella_udc_done(ep, req, 0); |
| /* told UDC the configuration is done, and to ack HOST */ |
| //amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_CSR_DONE); |
| //udelay(150); |
| /* For STATUS-IN stage */ |
| ambarella_clr_ep_nak(&udc->ep[CTRL_IN]); |
| /* Re-enable Rx DMA to receive next setup packet */ |
| ambarella_enable_rx_dma(ep); |
| ep->dma_going = 0; |
| goto finish; |
| } else if (ep->id == CTRL_IN) { |
| printk("the data length of ctrl-in is zero\n"); |
| BUG(); |
| } |
| } |
| |
| switch(ep->dir) { |
| case USB_DIR_IN: |
| /* enable dma completion interrupt for current TX data */ |
| amba_clrbitsl(USB_DEV_EP_INTR_MSK_REG, 1 << ep->id); |
| ambarella_clr_ep_nak(ep); |
| break; |
| case USB_DIR_OUT: |
| ambarella_set_rx_dma(ep, req); |
| break; |
| default: |
| dprintk(DEBUG_ERR, "bulk or intr queue error\n"); |
| BUG(); |
| return -EL2HLT; |
| } |
| } |
| |
| list_add_tail(&req->queue, &ep->queue); |
| |
| finish: |
| /* enable IRQ handler's bottom-half */ |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| return 0; |
| } |
| |
| static int ambarella_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req) |
| { |
| struct ambarella_ep *ep = to_ambarella_ep(_ep); |
| struct ambarella_request *req; |
| unsigned long flags; |
| unsigned halted; |
| |
| if (!the_controller->driver) |
| return -ESHUTDOWN; |
| |
| if (!_ep || !_req) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&ep->udc->lock, flags); |
| |
| /* make sure the request is actually queued on this endpoint */ |
| list_for_each_entry (req, &ep->queue, queue) { |
| if (&req->req == _req) |
| break; |
| } |
| if (&req->req != _req) { |
| spin_unlock_irqrestore(&ep->udc->lock, flags); |
| return -EINVAL; |
| } |
| |
| halted = ep->halted; |
| ep->halted = 1; |
| |
| /* request in processing */ |
| if((ep->data_desc == req->data_desc) && (ep->dma_going == 1)) { |
| if (ep->dir == USB_DIR_IN) |
| ep->cancel_transfer = 1; |
| else { |
| u32 tmp, desc_status; |
| /* stop potential receive DMA */ |
| tmp = amba_readl(USB_DEV_CTRL_REG); |
| amba_clrbitsl(USB_DEV_CTRL_REG, USB_DEV_RCV_DMA_EN); |
| |
| /* cancel transfer later in ISR if descriptor was touched. */ |
| desc_status = req->data_desc->status; |
| if (desc_status != USB_DMA_BUF_HOST_RDY) |
| ep->cancel_transfer = 1; |
| |
| amba_writel(USB_DEV_CTRL_REG, tmp); |
| } |
| } |
| |
| ambarella_udc_done(ep, req, -ECONNRESET); |
| |
| ep->halted = halted; |
| spin_unlock_irqrestore(&ep->udc->lock, flags); |
| |
| return 0; |
| } |
| |
| /* |
| * ambarella_udc_set_halt |
| */ |
| static int ambarella_udc_set_halt(struct usb_ep *_ep, int value) |
| { |
| struct ambarella_ep *ep = to_ambarella_ep(_ep); |
| unsigned long flags; |
| |
| dprintk(DEBUG_NORMAL, "Enter, %s: value = %d\n", _ep->name, value); |
| |
| if (unlikely (!_ep || (!ep->desc && !IS_EP0(ep)))) { |
| dprintk(DEBUG_ERR, "%s: -EINVAL 1\n",_ep->name); |
| return -EINVAL; |
| } |
| if (!ep->udc->driver || ep->udc->gadget.speed == USB_SPEED_UNKNOWN){ |
| dprintk(DEBUG_ERR, "%s: -ESHUTDOWN\n",_ep->name); |
| return -ESHUTDOWN; |
| } |
| if (ep->desc /* not ep0 */ |
| && (ep->desc->bmAttributes & 0x03) == USB_ENDPOINT_XFER_ISOC){ |
| dprintk(DEBUG_ERR, "%s: -EINVAL 2\n",_ep->name); |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&ep->udc->lock, flags); |
| |
| /* set/clear, then synch memory views with the device */ |
| if (value) { /* stall endpoint */ |
| if (ep->dir == USB_DIR_IN) { |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_STALL | USB_EP_FLUSH); |
| } else { |
| int retry_count = 10000; |
| /* Wait Rx-FIFO to be empty */ |
| while(!(amba_readl(USB_DEV_STS_REG) & USB_DEV_RXFIFO_EMPTY_STS)){ |
| if (retry_count-- < 0) { |
| printk(KERN_ERR"[USB] stall_endpoint:failed"); |
| break; |
| } |
| } |
| amba_setbitsl(ep->ep_reg.ctrl_reg, USB_EP_STALL); |
| } |
| } else { /* clear stall endpoint */ |
| amba_clrbitsl(ep->ep_reg.ctrl_reg, USB_EP_STALL); |
| } |
| |
| ep->halted = value ? 1 : 0; |
| |
| spin_unlock_irqrestore(&ep->udc->lock, flags); |
| |
| dprintk(DEBUG_NORMAL, "Exit\n"); |
| |
| return 0; |
| } |
| |
| |
| static const struct usb_ep_ops ambarella_ep_ops = { |
| .enable = ambarella_udc_ep_enable, |
| .disable = ambarella_udc_ep_disable, |
| |
| .alloc_request = ambarella_udc_alloc_request, |
| .free_request = ambarella_udc_free_request, |
| |
| .queue = ambarella_udc_queue, |
| .dequeue = ambarella_udc_dequeue, |
| |
| .set_halt = ambarella_udc_set_halt, |
| /* fifo ops not implemented */ |
| }; |
| |
| /*------------------------- usb_gadget_ops ----------------------------------*/ |
| |
| |
| static int ambarella_udc_get_frame(struct usb_gadget *_gadget) |
| { |
| return amba_readl(USB_DEV_STS_REG) >> 18; |
| } |
| |
| |
| static int ambarella_udc_wakeup(struct usb_gadget *_gadget) |
| { |
| struct ambarella_udc *udc = to_ambarella_udc(_gadget); |
| u32 tmp; |
| |
| dprintk(DEBUG_VERBOSE, "Enter\n"); |
| |
| tmp = amba_readl(USB_DEV_CFG_REG); |
| /* Remote wakeup feature not enabled by host */ |
| if ((!udc->remote_wakeup_en) || (!(tmp & USB_DEV_REMOTE_WAKEUP_EN))) |
| return -ENOTSUPP; |
| |
| tmp = amba_readl(USB_DEV_STS_REG); |
| /* not suspended? */ |
| if (!(tmp & USB_DEV_SUSP_STS)) |
| return 0; |
| |
| /* trigger force resume */ |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_REMOTE_WAKEUP); |
| |
| dprintk(DEBUG_VERBOSE, "Exit\n"); |
| |
| return 0; |
| } |
| |
| static int ambarella_udc_set_pullup(struct ambarella_udc *udc, int is_on) |
| { |
| if (is_on) |
| ambarella_udc_enable(udc); |
| else { |
| if (udc->gadget.speed != USB_SPEED_UNKNOWN) |
| ambarella_stop_activity(udc); |
| |
| ambarella_udc_disable(udc); |
| } |
| |
| return 0; |
| } |
| |
| static int ambarella_udc_vbus_session(struct usb_gadget *gadget, int is_active) |
| { |
| unsigned long flags; |
| struct ambarella_udc *udc = to_ambarella_udc(gadget); |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| if (udc->driver) |
| ambarella_udc_set_pullup(udc, is_active); |
| else |
| ambarella_udc_set_pullup(udc, 0); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| return 0; |
| } |
| |
| static int ambarella_udc_pullup(struct usb_gadget *gadget, int is_on) |
| { |
| unsigned long flags; |
| struct ambarella_udc *udc = to_ambarella_udc(gadget); |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_udc_set_pullup(udc, is_on); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| return 0; |
| } |
| |
| |
| static const struct usb_gadget_ops ambarella_ops = { |
| .get_frame = ambarella_udc_get_frame, |
| .wakeup = ambarella_udc_wakeup, |
| .pullup = ambarella_udc_pullup, |
| .vbus_session = ambarella_udc_vbus_session, |
| /*.set_selfpowered: Always selfpowered */ |
| }; |
| |
| /*------------------------- gadget driver handling---------------------------*/ |
| |
| /* Tears down device */ |
| static void ambarella_gadget_release(struct device *dev) |
| { |
| struct ambarella_udc *udc = dev_get_drvdata(dev); |
| kfree(udc); |
| } |
| |
| static void ambarella_init_gadget(struct ambarella_udc *udc, |
| struct platform_device *pdev) |
| { |
| struct ambarella_ep *ep; |
| u32 i; |
| |
| spin_lock_init (&udc->lock); |
| |
| dev_set_name(&udc->gadget.dev, "gadget"); |
| udc->gadget.ops = &ambarella_ops; |
| udc->gadget.name = gadget_name; |
| udc->gadget.is_dualspeed = 1; |
| udc->gadget.ep0 = &udc->ep[CTRL_IN].ep; |
| udc->gadget.dev.parent = &pdev->dev; |
| udc->gadget.dev.release = ambarella_gadget_release; |
| pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); |
| |
| device_initialize(&udc->gadget.dev); |
| |
| /* set basic ep parameters */ |
| for (i = 0; i < EP_NUM_MAX; i++) { |
| ep = &udc->ep[i]; |
| ep->ep.name = amb_ep_string[i]; |
| ep->id = i; |
| ep->ep.ops = &ambarella_ep_ops; |
| ep->ep.maxpacket = (unsigned short) ~0; |
| |
| if (i < EP_IN_NUM) { |
| ep->dir = USB_DIR_IN; |
| } else { |
| ep->dir = USB_DIR_OUT; |
| } |
| } |
| |
| udc->ep[CTRL_IN].ep.maxpacket = USB_EP_CTRL_MAX_PKT_SZ; |
| udc->ep[CTRL_OUT].ep.maxpacket = USB_EP_CTRL_MAX_PKT_SZ; |
| |
| return; |
| } |
| |
| |
| /* |
| * ambarella_udc_enable |
| */ |
| static void ambarella_udc_enable(struct ambarella_udc *udc) |
| { |
| if (udc->udc_is_enabled) |
| return; |
| |
| udc->udc_is_enabled = 1; |
| |
| /* Disable Tx and Rx DMA */ |
| amba_clrbitsl(USB_DEV_CTRL_REG, |
| USB_DEV_RCV_DMA_EN | USB_DEV_TRN_DMA_EN); |
| |
| /* flush all of dma fifo */ |
| ambarella_udc_fifo_flush(); |
| |
| /* initialize ep0 register */ |
| init_ep0(udc); |
| |
| /* enable ep0 interrupt. */ |
| amba_clrbitsl(USB_DEV_EP_INTR_MSK_REG, |
| USB_DEV_MSK_EP0_IN | USB_DEV_MSK_EP0_OUT); |
| |
| /* enable Tx and Rx DMA */ |
| amba_setbitsl(USB_DEV_CTRL_REG, |
| USB_DEV_RCV_DMA_EN | USB_DEV_TRN_DMA_EN); |
| |
| /* enable device interrupt: |
| * Set_Configure, Set_Interface, Speed Enumerate Complete, Reset */ |
| amba_clrbitsl(USB_DEV_INTR_MSK_REG, USB_DEV_MSK_SET_CFG | USB_DEV_MSK_SET_INTF | |
| USB_DEV_MSK_SPD_ENUM_CMPL | USB_DEV_MSK_RESET); |
| |
| /* Reconnect if udc is soft-disconnect */ |
| if(ambarella_check_softdis()) |
| amba_clrbitsl(USB_DEV_CTRL_REG, USB_DEV_SOFT_DISCON); |
| |
| /* Resume if udc is connected to host */ |
| if(ambarella_check_connected(udc)) |
| amba_setbitsl(USB_DEV_CTRL_REG, USB_DEV_REMOTE_WAKEUP); |
| |
| amb_udc_status = AMBARELLA_UDC_STATUS_ENABLED; |
| schedule_work(&udc->uevent_work); |
| } |
| |
| /* |
| * ambarella_udc_disable |
| */ |
| static void ambarella_udc_disable(struct ambarella_udc *udc) |
| { |
| /* Disable all interrupts and Clear the interrupt registers */ |
| ambarella_disable_all_intr(); |
| |
| /* Disable Tx and Rx DMA */ |
| amba_clrbitsl(USB_DEV_CTRL_REG, USB_DEV_RCV_DMA_EN | USB_DEV_TRN_DMA_EN); |
| |
| /* Good bye, cruel world - Set soft disconnect */ |
| ambarella_set_softdis(1); |
| |
| udc->gadget.speed = USB_SPEED_UNKNOWN; |
| |
| amb_udc_status = AMBARELLA_UDC_STATUS_DISABLED; |
| schedule_work(&udc->uevent_work); |
| |
| udc->udc_is_enabled = 0; |
| } |
| |
| |
| /* |
| * ambarella_udc_reinit |
| */ |
| static void ambarella_udc_reinit(struct ambarella_udc *udc) |
| { |
| u32 i; |
| |
| dprintk(DEBUG_NORMAL, "Enter\n"); |
| |
| /* device/ep0 records init */ |
| INIT_LIST_HEAD (&udc->gadget.ep_list); |
| INIT_LIST_HEAD (&udc->gadget.ep0->ep_list); |
| udc->auto_ack_0_pkt = 0; |
| udc->remote_wakeup_en = 0; |
| |
| for (i = 0; i < EP_NUM_MAX; i++) { |
| struct ambarella_ep *ep = &udc->ep[i]; |
| |
| if (!IS_EP0(ep)) |
| list_add_tail (&ep->ep.ep_list, &udc->gadget.ep_list); |
| |
| ep->udc = udc; |
| ep->halted = 0; |
| ep->data_desc = NULL; |
| ep->last_data_desc = NULL; |
| INIT_LIST_HEAD (&ep->queue); |
| } |
| |
| dprintk(DEBUG_NORMAL, "Exit\n"); |
| } |
| |
| /* Called by gadget driver to register itself */ |
| int usb_gadget_probe_driver(struct usb_gadget_driver *driver, |
| int (*bind)(struct usb_gadget *)) |
| { |
| unsigned long flags; |
| struct ambarella_udc *udc = the_controller; |
| int retval; |
| |
| /* Sanity checks */ |
| if (!udc) |
| return -ENODEV; |
| |
| if (udc->driver) |
| return -EBUSY; |
| |
| if (!driver || !bind || !driver->setup |
| || driver->speed != USB_SPEED_HIGH) { |
| printk(KERN_ERR "Invalid driver: bind %p setup %p speed %d\n", |
| bind, driver->setup, driver->speed); |
| return -EINVAL; |
| } |
| #if defined(MODULE) |
| if (!driver->unbind) { |
| printk(KERN_ERR "Invalid driver: no unbind method\n"); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Hook the driver */ |
| driver->driver.bus = NULL; |
| udc->driver = driver; |
| udc->gadget.dev.driver = &driver->driver; |
| |
| /* Bind the driver */ |
| if ((retval = device_add(&udc->gadget.dev)) != 0) { |
| printk(KERN_ERR "Error in device_add() : %d\n",retval); |
| goto register_error; |
| } |
| |
| dprintk(DEBUG_NORMAL, "binding gadget driver '%s'\n", |
| driver->driver.name); |
| |
| if ((retval = bind(&udc->gadget)) != 0) { |
| device_del(&udc->gadget.dev); |
| goto register_error; |
| } |
| |
| /* Enable udc */ |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_udc_enable(udc); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| return 0; |
| |
| register_error: |
| udc->driver = NULL; |
| udc->gadget.dev.driver = NULL; |
| return retval; |
| } |
| EXPORT_SYMBOL(usb_gadget_probe_driver); |
| |
| /* |
| * usb_gadget_unregister_driver |
| */ |
| int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) |
| { |
| unsigned long flags; |
| struct ambarella_udc *udc = the_controller; |
| |
| if (!udc) |
| return -ENODEV; |
| |
| if (!driver || driver != udc->driver || !driver->unbind) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_stop_activity(udc); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| if (driver->unbind) |
| driver->unbind(&udc->gadget); |
| |
| device_del(&udc->gadget.dev); |
| udc->driver = NULL; |
| udc->gadget.dev.driver = NULL; |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_udc_disable(udc); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(usb_gadget_unregister_driver); |
| |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * Name: ambarella_udc_probe |
| * Description: |
| * Probe udc driver. |
| */ |
| static int __devinit ambarella_udc_probe(struct platform_device *pdev) |
| { |
| struct ambarella_udc *udc; |
| int retval; |
| |
| dprintk(DEBUG_NORMAL, "%s()\n", __func__); |
| |
| udc = kzalloc(sizeof(struct ambarella_udc), GFP_KERNEL); |
| if (!udc) { |
| retval = -ENOMEM; |
| goto out; |
| } |
| the_controller = udc; |
| platform_set_drvdata(pdev, udc); |
| |
| udc->controller_info = pdev->dev.platform_data; |
| if (udc->controller_info == NULL) { |
| retval = -ENODEV; |
| goto err_out1; |
| } |
| |
| udc->dev = &pdev->dev; |
| udc->pre_uevent_status = AMBARELLA_UDC_STATUS_UNKNOWN; |
| udc->pre_uevent_vbus = 0; |
| udc->udc_is_enabled = 0; |
| udc->reset_by_host = 0; |
| udc->vbus_status = 0; |
| /* Initial USB PLL */ |
| udc->controller_info->init_pll(); |
| /* Reset USB */ |
| udc->controller_info->reset_usb(); |
| |
| ambarella_init_gadget(udc, pdev); |
| ambarella_udc_reinit(udc); |
| ambarella_regaddr_map(udc); |
| |
| /*initial usb hardware, and set soft disconnect */ |
| ambarella_init_usb(); |
| ambarella_set_softdis(1); |
| udelay(50); |
| |
| /* DMA pool create */ |
| udc->desc_dma_pool = dma_pool_create("desc_dma_pool", NULL, |
| sizeof(struct ambarella_data_desc), 16, 0); |
| if (!udc->desc_dma_pool) { |
| dprintk(DEBUG_ERR, "can't get descriptor dma pool\n"); |
| retval = -ENOMEM; |
| goto err_out1; |
| } |
| |
| udc->setup_buf = dma_pool_alloc(udc->desc_dma_pool, GFP_KERNEL, |
| &udc->setup_addr); |
| if(udc->setup_buf == NULL) { |
| printk(KERN_ERR "No memory to DMA\n"); |
| retval = -ENOMEM; |
| goto err_out2; |
| } |
| |
| retval = init_null_pkt_desc(udc); |
| if(retval){ |
| goto err_out3; |
| } |
| |
| /* irq setup after old hardware state is cleaned up */ |
| retval = request_irq(USBC_IRQ, ambarella_udc_irq, |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| dev_name(&pdev->dev), udc); |
| if (retval != 0) { |
| dprintk(DEBUG_NORMAL, "cannot get irq %i, err %d\n", USBC_IRQ, retval); |
| goto err_out4; |
| } |
| |
| #if 0 |
| retval = request_irq(USBVBUS_IRQ, ambarella_udc_vbus_irq, |
| 0, dev_name(&pdev->dev), udc); |
| if (retval != 0) { |
| dprintk(DEBUG_NORMAL, "can't get vbus irq %i, err %d\n", |
| USBVBUS_IRQ, retval); |
| retval = -EBUSY; |
| free_irq(USBC_IRQ, udc); |
| return retval; |
| } |
| #endif |
| |
| if (udc->controller_info->vbus_polled) { |
| setup_timer(&udc->vbus_timer, |
| ambarella_vbus_timer, (unsigned long)udc); |
| mod_timer(&udc->vbus_timer, jiffies + VBUS_POLL_TIMEOUT); |
| } |
| |
| INIT_WORK(&udc->uevent_work, ambarella_uevent_work); |
| |
| udc->proc_file = create_proc_entry("udc", S_IRUGO, |
| get_ambarella_proc_dir()); |
| if (udc->proc_file == NULL) { |
| dev_err(&pdev->dev, "%s: create proc file (mode) fail!\n", |
| dev_name(&pdev->dev)); |
| retval = -ENOMEM; |
| goto err_out5; |
| } else { |
| udc->proc_file->read_proc = ambarella_proc_udc_read; |
| udc->proc_file->data = udc; |
| } |
| create_proc_files(); |
| |
| dprintk(DEBUG_NORMAL, "probe ok\n"); |
| |
| goto out; |
| |
| err_out5: |
| free_irq(USBC_IRQ, udc); |
| err_out4: |
| dma_pool_free(udc->desc_dma_pool, udc->dummy_desc, udc->dummy_desc_addr); |
| err_out3: |
| dma_pool_free(udc->desc_dma_pool, udc->setup_buf, udc->setup_addr); |
| err_out2: |
| dma_pool_destroy(udc->desc_dma_pool); |
| err_out1: |
| kfree(udc); |
| out: |
| return retval; |
| } |
| |
| |
| /* |
| * Name: ambarella_udc_remove |
| * Description: |
| * Remove udc driver. |
| */ |
| static int __devexit ambarella_udc_remove(struct platform_device *pdev) |
| { |
| struct ambarella_udc *udc = platform_get_drvdata(pdev); |
| |
| dprintk(DEBUG_NORMAL, "%s()\n", __func__); |
| if (udc->driver) |
| return -EBUSY; |
| |
| if (udc->controller_info->vbus_polled) |
| del_timer_sync(&udc->vbus_timer); |
| |
| remove_proc_entry("udc", get_ambarella_proc_dir()); |
| remove_proc_files(); |
| |
| free_irq(USBC_IRQ, udc); |
| |
| dma_pool_free(udc->desc_dma_pool, udc->dummy_desc, udc->dummy_desc_addr); |
| dma_pool_free(udc->desc_dma_pool, udc->setup_buf, udc->setup_addr); |
| dma_pool_destroy(udc->desc_dma_pool); |
| |
| platform_set_drvdata(pdev, NULL); |
| |
| kfree(udc); |
| |
| dprintk(DEBUG_NORMAL, "%s: remove ok\n", __func__); |
| |
| return 0; |
| } |
| |
| |
| #ifdef CONFIG_PM |
| static int ambarella_udc_suspend(struct platform_device *pdev, pm_message_t message) |
| { |
| unsigned long flags; |
| int retval = 0; |
| struct ambarella_udc *udc; |
| |
| udc = platform_get_drvdata(pdev); |
| udc->sys_suspended = 1; |
| disable_irq(USBC_IRQ); |
| |
| if (udc->controller_info->vbus_polled) |
| del_timer_sync(&udc->vbus_timer); |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_udc_set_pullup(udc, 0); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| amb_udc_status = AMBARELLA_UDC_STATUS_SUSPEND; |
| schedule_work(&udc->uevent_work); |
| |
| dev_dbg(&pdev->dev, "%s exit with %d @ %d\n", |
| __func__, retval, message.event); |
| |
| return retval; |
| } |
| |
| static int ambarella_udc_resume(struct platform_device *pdev) |
| { |
| unsigned long flags; |
| int retval = 0; |
| struct ambarella_udc *udc; |
| |
| udc = platform_get_drvdata(pdev); |
| udc->sys_suspended = 0; |
| |
| /* Initial USB PLL */ |
| udc->controller_info->init_pll(); |
| /* Reset USB */ |
| udc->controller_info->reset_usb(); |
| /*initial usb hardware */ |
| ambarella_init_usb(); |
| /* Set soft disconnected and delay 50 microseconds at least, |
| * or it will report full speed device to host. */ |
| ambarella_set_softdis(1); |
| udelay(50); |
| |
| enable_irq(USBC_IRQ); |
| |
| spin_lock_irqsave(&udc->lock, flags); |
| ambarella_udc_set_pullup(udc, 1); |
| amb_udc_status = AMBARELLA_UDC_STATUS_RESUME; |
| schedule_work(&udc->uevent_work); |
| spin_unlock_irqrestore(&udc->lock, flags); |
| |
| if (udc->controller_info->vbus_polled) { |
| setup_timer(&udc->vbus_timer, |
| ambarella_vbus_timer, (unsigned long)udc); |
| mod_timer(&udc->vbus_timer, jiffies + VBUS_POLL_TIMEOUT); |
| } |
| |
| dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, retval); |
| |
| return retval; |
| } |
| #endif |
| |
| static struct platform_driver ambarella_udc_driver = { |
| .driver = { |
| .name = "ambarella-udc", |
| .owner = THIS_MODULE, |
| }, |
| .probe = ambarella_udc_probe, |
| .remove = __devexit_p(ambarella_udc_remove), |
| #ifdef CONFIG_PM |
| .suspend = ambarella_udc_suspend, |
| .resume = ambarella_udc_resume, |
| #endif |
| }; |
| |
| |
| static int __init udc_init(void) |
| { |
| int retval = 0; |
| |
| printk(KERN_INFO "%s: version %s\n", gadget_name, DRIVER_VERSION); |
| retval = platform_driver_register(&ambarella_udc_driver); |
| |
| return retval; |
| } |
| |
| static void __exit udc_exit(void) |
| { |
| platform_driver_unregister(&ambarella_udc_driver); |
| } |
| |
| module_init(udc_init); |
| module_exit(udc_exit); |
| |
| MODULE_AUTHOR(DRIVER_AUTHOR); |
| MODULE_DESCRIPTION(DRIVER_DESC); |
| MODULE_VERSION(DRIVER_VERSION); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:ambarella-usbgadget"); |
| |