/* * drivers/usb/sunxi_usb/udc/sunxi_udc.c * (C) Copyright 2010-2015 * Allwinner Technology Co., Ltd. * javen, 2010-3-3, create this file * * usb device contoller driver * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_udc_config.h" #include "sunxi_udc_board.h" #include "sunxi_udc_debug.h" #include "sunxi_udc_dma.h" #include #include #include #include #if defined(CONFIG_AW_AXP) #include #endif #include #define DRIVER_DESC "SoftWinner USB Device Controller" #define DRIVER_VERSION "20080411" #define DRIVER_AUTHOR "SoftWinner USB Developer" static struct wake_lock udc_wake_lock; static const char gadget_name[] = "sunxi_usb_udc"; static const char driver_desc[] = DRIVER_DESC; static u64 sunxi_udc_mask = DMA_BIT_MASK(32); static struct sunxi_udc *the_controller; static u32 usbd_port_no; static sunxi_udc_io_t g_sunxi_udc_io; static u32 usb_connect; static u32 is_controller_alive; static u8 is_udc_enable; /* is udc enable by gadget? */ static struct platform_device *g_udc_pdev; static __u32 dma_working; #define DMA_ADDR_INVALID (~(dma_addr_t)0) static u8 crq_bRequest; static u8 crq_wIndex; static const unsigned char TestPkt[54] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E, 0x00 }; /* debug */ static int g_queue_debug; int g_dma_debug; static int g_write_debug; static int g_read_debug; static int g_irq_debug; static int g_msc_write_debug; static int g_msc_read_debug; static ssize_t show_ed_test(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_queue_debug); } static ssize_t ed_test(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { void __iomem *fifo = NULL; if (!strncmp(buf, "test_j_state", 12)) { USBC_EnterMode_Test_J(g_sunxi_udc_io.usb_bsp_hdle); DMSG_INFO("test_mode:%s\n", "test_j_state"); } else if (!strncmp(buf, "test_k_state", 12)) { USBC_EnterMode_Test_K(g_sunxi_udc_io.usb_bsp_hdle); DMSG_INFO("test_mode:%s\n", "test_k_state"); } else if (!strncmp(buf, "test_se0_nak", 12)) { USBC_EnterMode_Test_SE0_NAK(g_sunxi_udc_io.usb_bsp_hdle); DMSG_INFO("test_mode:%s\n", "test_se0_nak"); } else if (!strncmp(buf, "test_pack", 9)) { DMSG_INFO("test_mode___:%s\n", "test_pack"); fifo = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, 0); USBC_WritePacket(g_sunxi_udc_io.usb_bsp_hdle, fifo, 54, (u32 *)TestPkt); USBC_EnterMode_TestPacket(g_sunxi_udc_io.usb_bsp_hdle); USBC_Dev_WriteDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); } else if (!strncmp(buf, "disable_test_mode", 17)) { DMSG_INFO("start disable_test_mode\n"); USBC_EnterMode_Idle(g_sunxi_udc_io.usb_bsp_hdle); } else { DMSG_PANIC("ERR: test_mode Argment is invalid\n"); } return count; } static DEVICE_ATTR(otg_ed_test, 0644, show_ed_test, ed_test); static ssize_t show_udc_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_queue_debug); } static ssize_t udc_queue_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_queue_debug = debug; return count; } static DEVICE_ATTR(queue_debug, 0644, show_udc_debug, udc_queue_debug); static ssize_t show_dma_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_dma_debug); } static ssize_t udc_dma_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_dma_debug = debug; return count; } static DEVICE_ATTR(dma_debug, 0644, show_dma_debug, udc_dma_debug); static ssize_t show_write_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_write_debug); } static ssize_t udc_write_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_write_debug = debug; return count; } static DEVICE_ATTR(write_debug, 0644, show_write_debug, udc_write_debug); static ssize_t show_read_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_read_debug); } static ssize_t udc_read_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_read_debug = debug; return count; } static DEVICE_ATTR(read_debug, 0644, show_read_debug, udc_read_debug); static ssize_t show_irq_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_irq_debug); } static ssize_t udc_irq_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_irq_debug = debug; return count; } static DEVICE_ATTR(irq_debug, 0644, show_irq_debug, udc_irq_debug); static ssize_t show_msc_write_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_msc_write_debug); } static ssize_t udc_msc_write_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_msc_write_debug = debug; return count; } static DEVICE_ATTR(msc_write_debug, 0644, show_msc_write_debug, udc_msc_write_debug); static ssize_t show_msc_read_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", g_msc_read_debug); } static ssize_t udc_msc_read_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int debug = 0; int ret = 0; ret = kstrtoint(buf, 0, &debug); if (ret != 0) return ret; g_msc_read_debug = debug; return count; } static DEVICE_ATTR(msc_read_debug, 0644, show_msc_read_debug, udc_msc_read_debug); /* function defination */ static void cfg_udc_command(enum sunxi_udc_cmd_e cmd); static void cfg_vbus_draw(unsigned int ma); static __u32 is_peripheral_active(void) { return is_controller_alive; } /** * DMA transfer conditions: * 1. the driver support dma transfer * 2. not EP0 * 3. more than one packet */ #define big_req(req, ep) ((req->req.length != req->req.actual) \ ? ((req->req.length - req->req.actual) \ > ep->ep.maxpacket) \ : (req->req.length > ep->ep.maxpacket)) #define is_sunxi_udc_dma_capable(req, ep) (is_udc_support_dma() \ && big_req(req, ep) \ && req->req.dma_flag \ && ep->num) #define is_buffer_mapped(req, ep) (is_sunxi_udc_dma_capable(req, ep) \ && (req->map_state != UN_MAPPED)) /* Maps the buffer to dma */ static inline void sunxi_udc_map_dma_buffer( struct sunxi_udc_request *req, struct sunxi_udc *udc, struct sunxi_udc_ep *ep) { if (!is_sunxi_udc_dma_capable(req, ep)) { DMSG_PANIC("err: need not to dma map\n"); return; } req->map_state = UN_MAPPED; if (req->req.dma == DMA_ADDR_INVALID) { req->req.dma = dma_map_single( udc->controller, req->req.buf, req->req.length, (is_tx_ep(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); if (dma_mapping_error(udc->controller, req->req.dma)){ DMSG_PANIC("dma_mapping_error, %p, %x\n", req->req.buf, req->req.length); return; } req->map_state = SW_UDC_USB_MAPPED; } else { dma_sync_single_for_device(udc->controller, req->req.dma, req->req.length, (is_tx_ep(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); req->map_state = PRE_MAPPED; } } /* Unmap the buffer from dma and maps it back to cpu */ static inline void sunxi_udc_unmap_dma_buffer( struct sunxi_udc_request *req, struct sunxi_udc *udc, struct sunxi_udc_ep *ep) { if (!is_buffer_mapped(req, ep)) return; if (req->req.dma == DMA_ADDR_INVALID) { DMSG_PANIC("not unmapping a never mapped buffer\n"); return; } if (req->map_state == SW_UDC_USB_MAPPED) { dma_unmap_single(udc->controller, req->req.dma, req->req.length, (is_tx_ep(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); req->req.dma = DMA_ADDR_INVALID; } else { /* PRE_MAPPED */ dma_sync_single_for_cpu(udc->controller, req->req.dma, req->req.length, (is_tx_ep(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); } req->map_state = UN_MAPPED; } static void sunxi_udc_done(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req, int status) __releases(ep->dev->lock) __acquires(ep->dev->lock) { unsigned halted = ep->halted; if (g_queue_debug) { DMSG_INFO("d: (%s, %p, %d, %d)\n\n\n", ep->ep.name, &(req->req), req->req.length, req->req.actual); } list_del_init(&req->queue); if (likely(req->req.status == -EINPROGRESS)) req->req.status = status; else status = req->req.status; ep->halted = 1; if (is_sunxi_udc_dma_capable(req, ep)) { spin_unlock(&ep->dev->lock); sunxi_udc_unmap_dma_buffer(req, ep->dev, ep); req->req.complete(&ep->ep, &req->req); spin_lock(&ep->dev->lock); } else { spin_unlock(&ep->dev->lock); req->req.complete(&ep->ep, &req->req); spin_lock(&ep->dev->lock); } ep->halted = halted; } static void sunxi_udc_nuke(struct sunxi_udc *udc, struct sunxi_udc_ep *ep, int status) { /* Sanity check */ if (&ep->queue == NULL) return; while (!list_empty(&ep->queue)) { struct sunxi_udc_request *req; req = list_entry(ep->queue.next, struct sunxi_udc_request, queue); DMSG_INFO_UDC("nuke: ep num is %d\n", ep->num); sunxi_udc_done(ep, req, status); } } static inline void sunxi_udc_clear_ep_state(struct sunxi_udc *dev) { unsigned i = 0; /** * hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint * fifos, and pending transactions mustn't be continued in any case. */ for (i = 1; i < SW_UDC_ENDPOINTS; i++) sunxi_udc_nuke(dev, &dev->ep[i], -ECONNABORTED); } static inline int sunxi_udc_fifo_count_out(__hdle usb_bsp_hdle, __u8 ep_index) { if (ep_index) { return USBC_ReadLenFromFifo( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); } else { return USBC_ReadLenFromFifo( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } } static inline int sunxi_udc_write_packet(void __iomem *fifo, struct sunxi_udc_request *req, unsigned max) { unsigned len = min(req->req.length - req->req.actual, max); void *buf = req->req.buf + req->req.actual; prefetch(buf); DMSG_DBG_UDC("W: req.actual(%d), req.length(%d), len(%d), total(%d)\n", req->req.actual, req->req.length, len, req->req.actual + len); req->req.actual += len; udelay(5); USBC_WritePacket(g_sunxi_udc_io.usb_bsp_hdle, fifo, len, buf); return len; } static int pio_write_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { unsigned count = 0; int is_last = 0; u32 idx = 0; void __iomem *fifo_reg = 0; __s32 ret = 0; u8 old_ep_index = 0; idx = ep->bEndpointAddress & 0x7F; /* write data */ /* select ep */ old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); /* select fifo */ fifo_reg = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, idx); count = sunxi_udc_write_packet(fifo_reg, req, ep->ep.maxpacket); /* check if the last packet */ /* last packet is often short (sometimes a zlp) */ if (count != ep->ep.maxpacket) is_last = 1; else if (req->req.length != req->req.actual || req->req.zero) is_last = 0; else is_last = 2; if (g_write_debug) { DMSG_INFO("pw: (0x%p, %d, %d)\n", &(req->req), req->req.length, req->req.actual); } if (idx) { /* ep1~4 */ ret = USBC_Dev_WriteDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, is_last); if (ret != 0) { DMSG_PANIC("ERR: USBC_Dev_WriteDataStatus, failed\n"); req->req.status = -EOVERFLOW; } } else { /* ep0 */ ret = USBC_Dev_WriteDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, is_last); if (ret != 0) { DMSG_PANIC("ERR: USBC_Dev_WriteDataStatus, failed\n"); req->req.status = -EOVERFLOW; } } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); if (is_last) { if (!idx) { /* ep0 */ ep->dev->ep0state = EP0_IDLE; } sunxi_udc_done(ep, req, 0); is_last = 1; } return is_last; } static int dma_write_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { u32 left_len = 0; u32 idx = 0; void __iomem *fifo_reg = 0; u8 old_ep_index = 0; idx = ep->bEndpointAddress & 0x7F; /* select ep */ old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); /* select fifo */ fifo_reg = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, idx); /* auto_set, tx_mode, dma_tx_en, mode1 */ USBC_Dev_ConfigEpDma( ep->dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); /* cut fragment part(??) */ left_len = req->req.length - req->req.actual; left_len = left_len - (left_len % ep->ep.maxpacket); ep->dma_working = 1; dma_working = 1; ep->dma_transfer_len = left_len; spin_unlock(&ep->dev->lock); sunxi_udc_dma_set_config(ep, req, (__u32)req->req.dma, left_len); sunxi_udc_dma_start(ep, fifo_reg, (__u32)req->req.dma, left_len); spin_lock(&ep->dev->lock); return 0; } /* return: 0 = still running, 1 = completed, negative = errno */ static int sunxi_udc_write_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { if (ep->dma_working) { if (g_dma_debug) { struct sunxi_udc_request *req_next = NULL; DMSG_PANIC("ERR: dma is busy, write fifo. ep(0x%p, %d), req(0x%p, 0x%p, 0x%p, %d, %d)\n\n", ep, ep->num, req, &(req->req), req->req.buf, req->req.length, req->req.actual); if (likely(!list_empty(&ep->queue))) req_next = list_entry(ep->queue.next, struct sunxi_udc_request, queue); else req_next = NULL; if (req_next) { DMSG_PANIC("ERR: dma is busy, write fifo. req(0x%p, 0x%p, 0x%p, %d, %d)\n\n", req_next, &(req_next->req), req_next->req.buf, req_next->req.length, req_next->req.actual); } } return 0; } if (is_sunxi_udc_dma_capable(req, ep)) return dma_write_fifo(ep, req); else return pio_write_fifo(ep, req); } static inline int sunxi_udc_read_packet(void __iomem *fifo, void *buf, struct sunxi_udc_request *req, unsigned avail) { unsigned len = 0; len = min(req->req.length - req->req.actual, avail); req->req.actual += len; DMSG_DBG_UDC("R: req.actual(%d), req.length(%d), len(%d), total(%d)\n", req->req.actual, req->req.length, len, req->req.actual + len); USBC_ReadPacket(g_sunxi_udc_io.usb_bsp_hdle, fifo, len, buf); return len; } /* return: 0 = still running, 1 = completed, negative = errno */ static int pio_read_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { void *buf = NULL; unsigned bufferspace = 0; int is_last = 1; unsigned avail = 0; int fifo_count = 0; u32 idx = 0; void __iomem *fifo_reg = 0; __s32 ret = 0; u8 old_ep_index = 0; idx = ep->bEndpointAddress & 0x7F; /* select fifo */ fifo_reg = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, idx); if (!req->req.length) { DMSG_PANIC("ERR: req->req.length == 0\n"); return 1; } buf = req->req.buf + req->req.actual; bufferspace = req->req.length - req->req.actual; if (!bufferspace) { DMSG_PANIC("ERR: buffer full!\n"); return -1; } /* select ep */ old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); fifo_count = sunxi_udc_fifo_count_out(g_sunxi_udc_io.usb_bsp_hdle, idx); if (fifo_count > ep->ep.maxpacket) avail = ep->ep.maxpacket; else avail = fifo_count; fifo_count = sunxi_udc_read_packet(fifo_reg, buf, req, avail); /** * checking this with ep0 is not accurate as we already * read a control request */ if (idx != 0 && fifo_count < ep->ep.maxpacket) { is_last = 1; /* overflowed this request? flush extra data */ if (fifo_count != avail) req->req.status = -EOVERFLOW; } else { is_last = (req->req.length <= req->req.actual) ? 1 : 0; } if (g_read_debug) { DMSG_INFO("pr: (0x%p, %d, %d)\n", &(req->req), req->req.length, req->req.actual); } if (idx) { ret = USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX, is_last); if (ret != 0) { DMSG_PANIC("ERR: pio_read_fifo: "); DMSG_PANIC("USBC_Dev_WriteDataStatus, failed\n"); req->req.status = -EOVERFLOW; } } else { ret = USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, is_last); if (ret != 0) { DMSG_PANIC("ERR: pio_read_fifo: "); DMSG_PANIC("USBC_Dev_WriteDataStatus, failed\n"); req->req.status = -EOVERFLOW; } } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); if (is_last) { if (!idx) ep->dev->ep0state = EP0_IDLE; sunxi_udc_done(ep, req, 0); is_last = 1; } return is_last; } static int dma_read_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { u32 left_len = 0; u32 idx = 0; void __iomem *fifo_reg = 0; u8 old_ep_index = 0; idx = ep->bEndpointAddress & 0x7F; /* select ep */ old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); /* select fifo */ fifo_reg = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, idx); /* auto_set, tx_mode, dma_tx_en, mode1 */ USBC_Dev_ConfigEpDma( ep->dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_RX); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); /* cut fragment packet part */ left_len = req->req.length - req->req.actual; left_len = left_len - (left_len % ep->ep.maxpacket); if (g_dma_debug) { DMSG_INFO("dr: (0x%p, %d, %d)\n", &(req->req), req->req.length, req->req.actual); } ep->dma_working = 1; dma_working = 1; ep->dma_transfer_len = left_len; spin_unlock(&ep->dev->lock); sunxi_udc_dma_set_config(ep, req, (__u32)req->req.dma, left_len); sunxi_udc_dma_start(ep, fifo_reg, (__u32)req->req.dma, left_len); spin_lock(&ep->dev->lock); return 0; } /* return: 0 = still running, 1 = completed, negative = errno */ static int sunxi_udc_read_fifo(struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { if (ep->dma_working) { if (g_dma_debug) { struct sunxi_udc_request *req_next = NULL; DMSG_PANIC("ERR: dma is busy, read fifo. ep(0x%p, %d), req(0x%p, 0x%p, 0x%p, %d, %d)\n\n", ep, ep->num, req, &(req->req), req->req.buf, req->req.length, req->req.actual); if (likely(!list_empty(&ep->queue))) req_next = list_entry(ep->queue.next, struct sunxi_udc_request, queue); else req_next = NULL; if (req_next) { DMSG_PANIC("ERR: dma is busy, read fifo. req(0x%p, 0x%p, 0x%p, %d, %d)\n\n", req_next, &(req_next->req), req_next->req.buf, req_next->req.length, req_next->req.actual); } } return 0; } if (is_sunxi_udc_dma_capable(req, ep)) return dma_read_fifo(ep, req); else return pio_read_fifo(ep, req); } static int sunxi_udc_read_fifo_crq(struct usb_ctrlrequest *crq) { u32 fifo_count = 0; u32 i = 0; void *pOut = crq; void __iomem *fifo = 0; fifo = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, 0); fifo_count = USBC_ReadLenFromFifo( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); if (fifo_count != 8) { i = 0; while (i < 16 && (fifo_count != 8)) { fifo_count = USBC_ReadLenFromFifo( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); i++; } if (i >= 16) DMSG_PANIC("ERR: get ep0 fifo len failed\n"); } return USBC_ReadPacket(g_sunxi_udc_io.usb_bsp_hdle, fifo, fifo_count, pOut); } static int sunxi_udc_get_status(struct sunxi_udc *dev, struct usb_ctrlrequest *crq) { u16 status = 0; u8 buf[8]; u8 ep_num = crq->wIndex & 0x7F; u8 is_in = crq->wIndex & USB_DIR_IN; void __iomem *fifo = 0; u8 old_ep_index = 0; int ret = 0; switch (crq->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: buf[0] = 0x00; buf[1] = 0x00; break; case USB_RECIP_DEVICE: status = dev->devstatus; buf[0] = 0x01; buf[1] = 0x00; break; case USB_RECIP_ENDPOINT: if (ep_num > 4 || crq->wLength > 2) return 1; old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, ep_num); if (ep_num == 0) { status = USBC_Dev_IsEpStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } else { if (is_in) { ret = readw(g_sunxi_udc_io.usb_vbase + USBC_REG_o_TXCSR); status = ret & (0x1 << USBC_BP_TXCSR_D_SEND_STALL); } else { ret = readw(g_sunxi_udc_io.usb_vbase + USBC_REG_o_RXCSR); status = ret & (0x1 << USBC_BP_RXCSR_D_SEND_STALL); } } status = status ? 1 : 0; if (status) { buf[0] = 0x01; buf[1] = 0x00; } else { buf[0] = 0x00; buf[1] = 0x00; } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); break; default: return 1; } /* Seems to be needed to get it working. ouch :( */ udelay(5); USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); fifo = USBC_SelectFIFO(g_sunxi_udc_io.usb_bsp_hdle, 0); USBC_WritePacket(g_sunxi_udc_io.usb_bsp_hdle, fifo, crq->wLength, buf); USBC_Dev_WriteDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); return 0; } static int sunxi_udc_set_halt(struct usb_ep *_ep, int value); static int sunxi_udc_set_halt_ex(struct usb_ep *_ep, int value, int is_in); static void sunxi_udc_handle_ep0_idle(struct sunxi_udc *dev, struct sunxi_udc_ep *ep, struct usb_ctrlrequest *crq, u32 ep0csr) { int len = 0, ret = 0, tmp = 0; int is_in = 0; /* start control request? */ if (!USBC_Dev_IsReadDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0)) { DMSG_WRN("ERR: data is ready, can not read data.\n"); return; } sunxi_udc_nuke(dev, ep, -EPROTO); len = sunxi_udc_read_fifo_crq(crq); if (len != sizeof(*crq)) { USBC_Dev_ReadDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); USBC_Dev_EpSendStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); return; } DMSG_DBG_UDC("ep0: bRequest = %d bRequestType %d wLength = %d\n", crq->bRequest, crq->bRequestType, crq->wLength); /* cope with automagic for some standard requests. */ dev->req_std = ((crq->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD); dev->req_config = 0; dev->req_pending = 1; if (dev->req_std) { /* standard request */ switch (crq->bRequest) { case USB_REQ_SET_CONFIGURATION: DMSG_DBG_UDC("USB_REQ_SET_CONFIGURATION ...\n"); if (crq->bRequestType == USB_RECIP_DEVICE) dev->req_config = 1; break; case USB_REQ_SET_INTERFACE: DMSG_DBG_UDC("USB_REQ_SET_INTERFACE ...\n"); if (crq->bRequestType == USB_RECIP_INTERFACE) dev->req_config = 1; break; case USB_REQ_SET_ADDRESS: DMSG_DBG_UDC("USB_REQ_SET_ADDRESS ...\n"); if (crq->bRequestType == USB_RECIP_DEVICE) { tmp = crq->wValue & 0x7F; dev->address = tmp; /* rx receive over, dataend, tx_pakect ready */ USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->ep0state = EP0_END_XFER; crq_bRequest = USB_REQ_SET_ADDRESS; return; } break; case USB_REQ_GET_STATUS: DMSG_DBG_UDC("USB_REQ_GET_STATUS ...\n"); if (!sunxi_udc_get_status(dev, crq)) return; break; case USB_REQ_CLEAR_FEATURE: /* --<1>--data direction must be host to device */ if (x_test_bit(crq->bRequestType, 7)) { DMSG_PANIC("USB_REQ_CLEAR_FEATURE: "); DMSG_PANIC("data is not host to device\n"); break; } USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); /* --<3>--data stage */ if (crq->bRequestType == USB_RECIP_DEVICE) { /* wValue 0-1 */ if (crq->wValue) { dev->devstatus &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); } else { int k = 0; for (k = 0; k < SW_UDC_ENDPOINTS; k++) { is_in = crq->wIndex & USB_DIR_IN; sunxi_udc_set_halt_ex(&dev->ep[k].ep, 0, is_in); } } } else if (crq->bRequestType == USB_RECIP_INTERFACE) { /** * --<2>--token stage over * do nothing */ } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { /* --<3>--release the forbidden of ep */ /* wValue 0-1 */ if (crq->wValue) { dev->devstatus &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); } else { int k = 0; is_in = crq->wIndex & USB_DIR_IN; for (k = 0; k < SW_UDC_ENDPOINTS; k++) { if (dev->ep[k].bEndpointAddress == (crq->wIndex & 0xff)) sunxi_udc_set_halt_ex(&dev->ep[k].ep, 0, is_in); } } } else { DMSG_PANIC("PANIC : nonsupport set feature request. (%d)\n", crq->bRequestType); USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } dev->ep0state = EP0_IDLE; return; case USB_REQ_SET_FEATURE: /* --<1>--data direction must be host to device */ if (x_test_bit(crq->bRequestType, 7)) { DMSG_PANIC("USB_REQ_SET_FEATURE: data is not host to device\n"); break; } /* --<3>--data stage */ if (crq->bRequestType == USB_RECIP_DEVICE) { if (crq->wValue == USB_DEVICE_TEST_MODE) { /* setup packet receive over */ switch (crq->wIndex) { case SUNXI_UDC_TEST_J: USBC_Dev_ReadDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->ep0state = EP0_END_XFER; crq_wIndex = TEST_J; crq_bRequest = USB_REQ_SET_FEATURE; return; case SUNXI_UDC_TEST_K: USBC_Dev_ReadDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->ep0state = EP0_END_XFER; crq_wIndex = TEST_K; crq_bRequest = USB_REQ_SET_FEATURE; return; case SUNXI_UDC_TEST_SE0_NAK: USBC_Dev_ReadDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->ep0state = EP0_END_XFER; crq_wIndex = TEST_SE0_NAK; crq_bRequest = USB_REQ_SET_FEATURE; return; case SUNXI_UDC_TEST_PACKET: USBC_Dev_ReadDataStatus(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->ep0state = EP0_END_XFER; crq_wIndex = TEST_PACKET; crq_bRequest = USB_REQ_SET_FEATURE; return; default: break; } } USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); dev->devstatus |= (1 << USB_DEVICE_REMOTE_WAKEUP); } else if (crq->bRequestType == USB_RECIP_INTERFACE) { /* --<2>--token stage over */ USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); /* do nothing */ } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { /* --<3>--forbidden ep */ int k = 0; is_in = crq->wIndex & USB_DIR_IN; USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); for (k = 0; k < SW_UDC_ENDPOINTS; k++) { if (dev->ep[k].bEndpointAddress == (crq->wIndex & 0xff)) sunxi_udc_set_halt_ex(&dev->ep[k].ep, 1, is_in); } } else { DMSG_PANIC("PANIC : nonsupport set feature request. (%d)\n", crq->bRequestType); USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } dev->ep0state = EP0_IDLE; return; default: /* only receive setup_data packet, cannot set DataEnd */ USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); break; } } else { USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); #if defined(CONFIG_USB_G_WEBCAM) || defined(CONFIG_USB_CONFIGFS_F_UVC) /** * getinfo request about exposure time asolute, * iris absolute, brightness of webcam. */ if (crq->bRequest == 0x86 && crq->bRequestType == 0xa1 && crq->wLength == 0x1 && ((crq->wValue == 0x400 && crq->wIndex == 0x100) || (crq->wValue == 0x900 && crq->wIndex == 0x100) || (crq->wValue == 0x200 && crq->wIndex == 0x200))) { USBC_Dev_EpSendStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); return; } #endif } if (crq->bRequestType & USB_DIR_IN) dev->ep0state = EP0_IN_DATA_PHASE; else dev->ep0state = EP0_OUT_DATA_PHASE; if (!dev->driver) return; spin_unlock(&dev->lock); ret = dev->driver->setup(&dev->gadget, crq); spin_lock(&dev->lock); if (ret < 0) { if (dev->req_config) { DMSG_PANIC("ERR: config change %02x fail %d?\n", crq->bRequest, ret); return; } if (ret == -EOPNOTSUPP) DMSG_PANIC("ERR: Operation not supported\n"); else DMSG_PANIC("ERR: dev->driver->setup failed. (%d)\n", ret); udelay(5); USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); dev->ep0state = EP0_IDLE; /* deferred i/o == no response yet */ } else if (dev->req_pending) { dev->req_pending = 0; } if (crq->bRequest == USB_REQ_SET_CONFIGURATION || crq->bRequest == USB_REQ_SET_INTERFACE) { /* rx_packet receive over */ USBC_Dev_ReadDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 1); } } static void sunxi_udc_handle_ep0(struct sunxi_udc *dev) { u32 ep0csr = 0; struct sunxi_udc_ep *ep = &dev->ep[0]; struct sunxi_udc_request *req = NULL; struct usb_ctrlrequest crq; DMSG_DBG_UDC("sunxi_udc_handle_ep0--1--\n"); if (list_empty(&ep->queue)) req = NULL; else req = list_entry(ep->queue.next, struct sunxi_udc_request, queue); DMSG_DBG_UDC("sunxi_udc_handle_ep0--2--\n"); /** * We make the assumption that sunxi_udc_UDC_IN_CSR1_REG equal to * sunxi_udc_UDC_EP0_CSR_REG when index is zero. */ USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, 0); /* clear stall status */ if (USBC_Dev_IsEpStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0)) { DMSG_PANIC("ERR: ep0 stall\n"); sunxi_udc_nuke(dev, ep, -EPIPE); USBC_Dev_EpClearStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); dev->ep0state = EP0_IDLE; return; } /* clear setup end */ if (USBC_Dev_Ctrl_IsSetupEnd(g_sunxi_udc_io.usb_bsp_hdle)) { DMSG_PANIC("handle_ep0: ep0 setup end\n"); sunxi_udc_nuke(dev, ep, 0); USBC_Dev_Ctrl_ClearSetupEnd(g_sunxi_udc_io.usb_bsp_hdle); dev->ep0state = EP0_IDLE; } DMSG_DBG_UDC("sunxi_udc_handle_ep0--3--%d\n", dev->ep0state); ep0csr = USBC_Readw(USBC_REG_CSR0(g_sunxi_udc_io.usb_vbase)); switch (dev->ep0state) { case EP0_IDLE: sunxi_udc_handle_ep0_idle(dev, ep, &crq, ep0csr); break; case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ DMSG_DBG_UDC("EP0_IN_DATA_PHASE ... what now?\n"); if (!USBC_Dev_IsWriteDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0) && req) { sunxi_udc_write_fifo(ep, req); } break; case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ DMSG_DBG_UDC("EP0_OUT_DATA_PHASE ... what now?\n"); if (USBC_Dev_IsReadDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0) && req) { sunxi_udc_read_fifo(ep, req); } break; case EP0_END_XFER: DMSG_DBG_UDC("EP0_END_XFER ... what now?\n"); DMSG_DBG_UDC("crq_bRequest = 0x%x\n", crq_bRequest); switch (crq_bRequest) { case USB_REQ_SET_ADDRESS: USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, 0); USBC_Dev_Ctrl_ClearSetupEnd( g_sunxi_udc_io.usb_bsp_hdle); USBC_Dev_SetAddress( g_sunxi_udc_io.usb_bsp_hdle, dev->address); DMSG_INFO_UDC("Set address %d\n", dev->address); break; case USB_REQ_SET_FEATURE: switch (crq_wIndex) { case TEST_J: USBC_EnterMode_Test_J( g_sunxi_udc_io.usb_bsp_hdle); break; case TEST_K: USBC_EnterMode_Test_K( g_sunxi_udc_io.usb_bsp_hdle); break; case TEST_SE0_NAK: USBC_EnterMode_Test_SE0_NAK( g_sunxi_udc_io.usb_bsp_hdle); break; case TEST_PACKET: { void __iomem *fifo = 0; fifo = USBC_SelectFIFO( g_sunxi_udc_io.usb_bsp_hdle, 0); USBC_WritePacket( g_sunxi_udc_io.usb_bsp_hdle, fifo, 54, (u32 *)TestPkt); USBC_EnterMode_TestPacket( g_sunxi_udc_io.usb_bsp_hdle); USBC_Dev_WriteDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0, 0); } break; default: break; } crq_wIndex = 0; break; default: break; } crq_bRequest = 0; dev->ep0state = EP0_IDLE; break; case EP0_STALL: DMSG_DBG_UDC("EP0_STALL ... what now?\n"); dev->ep0state = EP0_IDLE; break; } DMSG_DBG_UDC("sunxi_udc_handle_ep0--4--%d\n", dev->ep0state); } static void sunxi_udc_handle_ep(struct sunxi_udc_ep *ep) { struct sunxi_udc_request *req = NULL; int is_in = ep->bEndpointAddress & USB_DIR_IN; u32 idx = 0; u8 old_ep_index = 0; /* see sunxi_udc_queue. */ if (likely(!list_empty(&ep->queue))) req = list_entry(ep->queue.next, struct sunxi_udc_request, queue); else req = NULL; if (g_irq_debug) { DMSG_INFO("e: (%s), tx_csr=0x%x\n", ep->ep.name, USBC_Readw(USBC_REG_TXCSR(g_sunxi_udc_io.usb_vbase))); if (req) { DMSG_INFO("req: (0x%p, %d, %d)\n", &(req->req), req->req.length, req->req.actual); } } idx = ep->bEndpointAddress & 0x7F; old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); if (is_in) { if (USBC_Dev_IsEpStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX)) { DMSG_PANIC("ERR: tx ep(%d) is stall\n", idx); USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); goto end; } } else { if (USBC_Dev_IsEpStall(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX)) { DMSG_PANIC("ERR: rx ep(%d) is stall\n", idx); USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); goto end; } } if (req) { if (is_in) { if (!USBC_Dev_IsWriteDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX)) { sunxi_udc_write_fifo(ep, req); } } else { if (USBC_Dev_IsReadDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX)) { sunxi_udc_read_fifo(ep, req); } } } end: USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); } /* mask the useless irq, save disconect, reset, resume, suspend */ static u32 filtrate_irq_misc(u32 irq_misc) { u32 irq = irq_misc; irq &= ~(USBC_INTUSB_VBUS_ERROR | USBC_INTUSB_SESSION_REQ | USBC_INTUSB_CONNECT | USBC_INTUSB_SOF); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_VBUS_ERROR); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_SESSION_REQ); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_CONNECT); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_SOF); return irq; } static void clear_all_irq(void) { USBC_INT_ClearEpPendingAll(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); USBC_INT_ClearEpPendingAll(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); USBC_INT_ClearMiscPendingAll(g_sunxi_udc_io.usb_bsp_hdle); } static void throw_away_all_urb(struct sunxi_udc *dev) { int k = 0; DMSG_INFO_UDC("irq: reset happen, throw away all urb\n"); for (k = 0; k < SW_UDC_ENDPOINTS; k++) sunxi_udc_nuke(dev, (struct sunxi_udc_ep *)&(dev->ep[k]), -ECONNRESET); } /* clear all dma status of the EP, called when dma exception */ static void sunxi_udc_clean_dma_status(struct sunxi_udc_ep *ep) { u8 ep_index = 0; u8 old_ep_index = 0; struct sunxi_udc_request *req = NULL; ep_index = ep->bEndpointAddress & 0x7F; old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, ep_index); if ((ep->bEndpointAddress) & USB_DIR_IN) { /* dma_mode1 */ /* clear ep dma status */ USBC_Dev_ClearEpDma(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); /* select bus to pio */ sunxi_udc_switch_bus_to_pio(ep, 1); } else { /* dma_mode0 */ /* clear ep dma status */ USBC_Dev_ClearEpDma(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); /* select bus to pio */ sunxi_udc_switch_bus_to_pio(ep, 0); } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); /* done req */ while (likely(!list_empty(&ep->queue))) { req = list_entry(ep->queue.next, struct sunxi_udc_request, queue); if (req) { req->req.status = -ECONNRESET; req->req.actual = 0; sunxi_udc_done(ep, req, -ECONNRESET); } } ep->dma_working = 0; dma_working = 0; } static void sunxi_udc_stop_dma_work(struct sunxi_udc *dev, u32 unlock) { __u32 i = 0; struct sunxi_udc_ep *ep = NULL; for (i = 0; i < SW_UDC_ENDPOINTS; i++) { ep = &dev->ep[i]; if (sunxi_udc_dma_is_busy(ep)) { DMSG_PANIC("wrn: ep(%d) must stop working\n", i); if (unlock) { spin_unlock(&ep->dev->lock); sunxi_udc_dma_stop(ep); spin_lock(&ep->dev->lock); } else { sunxi_udc_dma_stop(ep); } #ifdef SW_UDC_DMA_INNER ep->dev->dma_hdle = NULL; #else ep->dev->sunxi_udc_dma[ep->num].is_start = 0; #endif ep->dma_transfer_len = 0; sunxi_udc_clean_dma_status(ep); } } } void sunxi_udc_dma_completion(struct sunxi_udc *dev, struct sunxi_udc_ep *ep, struct sunxi_udc_request *req) { unsigned long flags = 0; __u8 old_ep_index = 0; __u32 dma_transmit_len = 0; int is_complete = 0; struct sunxi_udc_request *req_next = NULL; if (dev == NULL || ep == NULL || req == NULL) { DMSG_PANIC("ERR: argment invaild. (0x%p, 0x%p, 0x%p)\n", dev, ep, req); return; } if (!ep->dma_working) { DMSG_PANIC("ERR: dma is not work, can not callback\n"); return; } sunxi_udc_unmap_dma_buffer(req, dev, ep); spin_lock_irqsave(&dev->lock, flags); old_ep_index = USBC_GetActiveEp(dev->sunxi_udc_io->usb_bsp_hdle); USBC_SelectActiveEp(dev->sunxi_udc_io->usb_bsp_hdle, ep->num); if ((ep->bEndpointAddress) & USB_DIR_IN) { /* tx, dma_mode1 */ while (USBC_Dev_IsWriteDataReady_FifoEmpty( dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX)) ; USBC_Dev_ClearEpDma(dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX); } else { /* rx, dma_mode0 */ USBC_Dev_ClearEpDma(dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_RX); } dma_transmit_len = sunxi_udc_dma_transmit_length(ep); if (dma_transmit_len < req->req.length) { if ((ep->bEndpointAddress) & USB_DIR_IN) USBC_Dev_ClearEpDma(dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX); else USBC_Dev_ClearEpDma(dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_RX); } if (g_dma_debug) { DMSG_INFO("di: (0x%p, %d, %d),(%d,%d)\n", &(req->req), req->req.length, req->req.actual, ep->bEndpointAddress, USB_DIR_IN); } ep->dma_working = 0; dma_working = 0; ep->dma_transfer_len = 0; /* if current data transfer not complete, then go on */ req->req.actual += dma_transmit_len; if (req->req.length > req->req.actual) { if (((ep->bEndpointAddress & USB_DIR_IN) != 0) && !USBC_Dev_IsWriteDataReady_FifoEmpty( dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX)) { if (pio_write_fifo(ep, req)) { req = NULL; is_complete = 1; } } else if (((ep->bEndpointAddress & USB_DIR_IN) == 0) && USBC_Dev_IsReadDataReady( dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_RX)) { if (pio_read_fifo(ep, req)) { req = NULL; is_complete = 1; } } } else { /* if DMA transfer data over, then done */ sunxi_udc_done(ep, req, 0); is_complete = 1; } /* start next transfer */ if (is_complete) { if (likely(!list_empty(&ep->queue))) req_next = list_entry(ep->queue.next, struct sunxi_udc_request, queue); else req_next = NULL; if (req_next) { if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { while (USBC_Dev_IsWriteDataReady_FifoEmpty( dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_TX)) ; sunxi_udc_write_fifo(ep, req_next); } else if (((ep->bEndpointAddress & USB_DIR_IN) == 0) && USBC_Dev_IsReadDataReady( dev->sunxi_udc_io->usb_bsp_hdle, USBC_EP_TYPE_RX)) { sunxi_udc_read_fifo(ep, req_next); } } } USBC_SelectActiveEp(dev->sunxi_udc_io->usb_bsp_hdle, old_ep_index); spin_unlock_irqrestore(&dev->lock, flags); } static irqreturn_t sunxi_udc_irq(int dummy, void *_dev) { struct sunxi_udc *dev = _dev; int usb_irq = 0; int tx_irq = 0; int rx_irq = 0; int i = 0; int dma_irq = 0; u32 old_ep_idx = 0; unsigned long flags = 0; spin_lock_irqsave(&dev->lock, flags); /* Driver connected ? */ if (!dev->driver || !is_peripheral_active()) { DMSG_PANIC("ERR: functoin driver is not exist, "); DMSG_PANIC("or udc is not active.\n"); /* Clear interrupts */ clear_all_irq(); spin_unlock_irqrestore(&dev->lock, flags); return IRQ_NONE; } /* Save index */ old_ep_idx = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); /* Read status registers */ usb_irq = USBC_INT_MiscPending(g_sunxi_udc_io.usb_bsp_hdle); tx_irq = USBC_INT_EpPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); rx_irq = USBC_INT_EpPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); dma_irq = USBC_Readw(USBC_REG_DMA_INTS(dev->sunxi_udc_io->usb_vbase)); usb_irq = filtrate_irq_misc(usb_irq); if (g_irq_debug) { DMSG_INFO("\nirq: %02x,tx_irq=%02x,rx_irq=%02x, dma_irq:%x\n", usb_irq, tx_irq, rx_irq, dma_irq); } /** * Now, handle interrupts. There's two types : * - Reset, Resume, Suspend coming -> usb_int_reg * - EP -> ep_int_reg */ /* RESET */ if (usb_irq & USBC_INTUSB_RESET) { DMSG_INFO_UDC("IRQ: reset\n"); DMSG_INFO_UDC("(1:star,2:end): vfs_read:%d, vfs_write:%d,dma_working:%d,amount:%u,file_offset:%llu\n", atomic_read(&vfs_read_flag), atomic_read(&vfs_write_flag), dma_working, vfs_amount, (unsigned long long)vfs_file_offset); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_RESET); clear_all_irq(); usb_connect = 1; /* add wake lock when usb conntect on pc. */ pr_debug("usb_connecting: hold wake lock.\n"); wake_lock(&udc_wake_lock); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, 0); USBC_Dev_SetAddress_default(g_sunxi_udc_io.usb_bsp_hdle); if (is_udc_support_dma()) sunxi_udc_stop_dma_work(dev, 1); throw_away_all_urb(dev); dev->address = 0; dev->ep0state = EP0_IDLE; dev->gadget.speed = USB_SPEED_UNKNOWN; g_irq_debug = 0; g_queue_debug = 0; g_dma_debug = 0; spin_unlock_irqrestore(&dev->lock, flags); #if defined CONFIG_AW_AXP && !defined SUNXI_USB_FPGA axp_usbvol(CHARGE_USB_20); axp_usbcur(CHARGE_USB_20); #endif return IRQ_HANDLED; } /* RESUME */ if (usb_irq & USBC_INTUSB_RESUME) { DMSG_INFO_UDC("IRQ: resume\n"); /* clear interrupt */ USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_RESUME); if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver && dev->driver->resume) { spin_unlock(&dev->lock); dev->driver->resume(&dev->gadget); spin_lock(&dev->lock); usb_connect = 1; } } /* SUSPEND */ if (usb_irq & USBC_INTUSB_SUSPEND) { DMSG_INFO_UDC("IRQ: suspend\n"); /* clear interrupt */ USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_SUSPEND); if (dev->gadget.speed != USB_SPEED_UNKNOWN) { schedule_work(&dev->vbus_det_work); usb_connect = 0; wake_unlock(&udc_wake_lock); pr_debug("usb_connecting: release wake lock\n"); } else { DMSG_INFO_UDC("ERR: usb speed is unknown\n"); } if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver && dev->driver->suspend) { spin_unlock(&dev->lock); dev->driver->suspend(&dev->gadget); spin_lock(&dev->lock); } dev->ep0state = EP0_IDLE; } /* DISCONNECT */ if (usb_irq & USBC_INTUSB_DISCONNECT) { DMSG_INFO_UDC("IRQ: disconnect\n"); USBC_INT_ClearMiscPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_DISCONNECT); dev->ep0state = EP0_IDLE; usb_connect = 0; } /** * EP * control traffic * check on ep0csr != 0 is not a good idea as clearing in_pkt_ready * generate an interrupt */ if (tx_irq & USBC_INTTx_FLAG_EP0) { DMSG_DBG_UDC("USB ep0 irq\n"); /* Clear the interrupt bit by setting it to 1 */ USBC_INT_ClearEpPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, 0); if (dev->gadget.speed == USB_SPEED_UNKNOWN) { if (USBC_Dev_QueryTransferMode(g_sunxi_udc_io.usb_bsp_hdle) == USBC_TS_MODE_HS) { dev->gadget.speed = USB_SPEED_HIGH; DMSG_INFO_UDC("\n++++++++++++++++++++\n"); DMSG_INFO_UDC(" usb enter high speed.\n"); DMSG_INFO_UDC("\n++++++++++++++++++++\n"); } else { dev->gadget.speed = USB_SPEED_FULL; DMSG_INFO_UDC("\n++++++++++++++++++++\n"); DMSG_INFO_UDC(" usb enter full speed.\n"); DMSG_INFO_UDC("\n++++++++++++++++++++\n"); } } sunxi_udc_handle_ep0(dev); } /* firstly to get data */ /* rx endpoint data transfers */ for (i = 1; i <= SW_UDC_EPNUMS; i++) { u32 tmp = 1 << i; if (rx_irq & tmp) { DMSG_DBG_UDC("USB rx ep%d irq\n", i); /* Clear the interrupt bit by setting it to 1 */ USBC_INT_ClearEpPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX, i); sunxi_udc_handle_ep(&dev->ep[ep_fifo_out[i]]); } } /* tx endpoint data transfers */ for (i = 1; i <= SW_UDC_EPNUMS; i++) { u32 tmp = 1 << i; if (tx_irq & tmp) { DMSG_DBG_UDC("USB tx ep%d irq\n", i); /* Clear the interrupt bit by setting it to 1 */ USBC_INT_ClearEpPending(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, i); sunxi_udc_handle_ep(&dev->ep[ep_fifo_in[i]]); } } #ifdef SW_UDC_DMA_INNER if (is_udc_support_dma()) { struct sunxi_udc_request *req = NULL; struct sunxi_udc_ep *ep = NULL; int i = 0; /* tx endpoint data transfers */ for (i = 0; i < DMA_CHAN_TOTAL; i++) { u32 tmp = 1 << i; if (dma_irq & tmp) { DMSG_DBG_UDC("USB dma chanle%d irq\n", i); /* set 1 to clear pending */ writel(BIT(i), USBC_REG_DMA_INTS(dev->sunxi_udc_io->usb_vbase)); ep = &dev->ep[dma_chnl[i].ep_num]; sunxi_udc_dma_release((dm_hdl_t)ep->dev->dma_hdle); ep->dev->dma_hdle = NULL; if (ep) { /* find req */ if (likely(!list_empty(&ep->queue))) req = list_entry(ep->queue.next, struct sunxi_udc_request, queue); else req = NULL; /* call back */ if (req) { spin_unlock_irqrestore(&dev->lock, flags); sunxi_udc_dma_completion(dev, ep, req); spin_lock_irqsave(&dev->lock, flags); } } else { DMSG_PANIC("ERR: sunxi_udc_dma_callback: dma is remove, but dma irq is happened\n"); } } } } #endif /* Restore old index */ USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_idx); spin_unlock_irqrestore(&dev->lock, flags); return IRQ_HANDLED; } /* sunxi_udc_ep_ops */ static inline struct sunxi_udc_ep *to_sunxi_udc_ep(struct usb_ep *ep) { return container_of(ep, struct sunxi_udc_ep, ep); } static inline struct sunxi_udc *to_sunxi_udc(struct usb_gadget *gadget) { return container_of(gadget, struct sunxi_udc, gadget); } static inline struct sunxi_udc_request *to_sunxi_udc_req(struct usb_request *req) { return container_of(req, struct sunxi_udc_request, req); } static void sunxi_udc_ep_config_reset(struct sunxi_udc_ep *ep) { if (ep->bmAttributes == USB_ENDPOINT_XFER_CONTROL) ep->ep.maxpacket = EP0_FIFO_SIZE; else if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) ep->ep.maxpacket = SW_UDC_EP_ISO_FIFO_SIZE; else if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) ep->ep.maxpacket = SW_UDC_EP_FIFO_SIZE; else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT) ep->ep.maxpacket = SW_UDC_EP_FIFO_SIZE; else DMSG_PANIC("[ep_disable] ep type is invalid!\n"); } static int sunxi_udc_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) { struct sunxi_udc *dev = NULL; struct sunxi_udc_ep *ep = NULL; u32 max = 0; u32 old_ep_index = 0; __u32 fifo_addr = 0; unsigned long flags = 0; u32 ep_type = 0; u32 ts_type = 0; u32 fifo_size = 0; u8 double_fifo = 0; int i = 0; if (_ep == NULL || desc == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } if (_ep->name == ep0name || desc->bDescriptorType != USB_DT_ENDPOINT) { DMSG_PANIC("PANIC : _ep->name(%s) == ep0name || desc->bDescriptorType(%d) != USB_DT_ENDPOINT\n", _ep->name, desc->bDescriptorType); return -EINVAL; } ep = to_sunxi_udc_ep(_ep); if (ep == NULL) { DMSG_PANIC("ERR: usbd_ep_enable, ep = NULL\n"); return -EINVAL; } if (ep->desc) { DMSG_PANIC("ERR: usbd_ep_enable, ep->desc is not NULL, ep%d(%s)\n", ep->num, _ep->name); return -EINVAL; } DMSG_INFO_UDC("ep enable: ep%d(0x%p, %s, %d, %d)\n", ep->num, _ep, _ep->name, (desc->bEndpointAddress & USB_DIR_IN), _ep->maxpacket); dev = ep->dev; if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { DMSG_PANIC("PANIC : dev->driver = 0x%p ?= NULL dev->gadget->speed =%d ?= USB_SPEED_UNKNOWN\n", dev->driver, dev->gadget.speed); return -ESHUTDOWN; } max = le16_to_cpu(desc->wMaxPacketSize) & 0x1fff; spin_lock_irqsave(&ep->dev->lock, flags); _ep->maxpacket = max & 0x7ff; ep->desc = desc; ep->halted = 0; ep->bEndpointAddress = desc->bEndpointAddress; /* ep_type */ if ((ep->bEndpointAddress) & USB_DIR_IN) { /* tx */ ep_type = USBC_EP_TYPE_TX; } else { /* rx */ ep_type = USBC_EP_TYPE_RX; } /* ts_type */ switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { case USB_ENDPOINT_XFER_CONTROL: ts_type = USBC_TS_TYPE_CTRL; break; case USB_ENDPOINT_XFER_BULK: ts_type = USBC_TS_TYPE_BULK; break; case USB_ENDPOINT_XFER_ISOC: ts_type = USBC_TS_TYPE_ISO; break; case USB_ENDPOINT_XFER_INT: ts_type = USBC_TS_TYPE_INT; break; default: DMSG_PANIC("err: usbd_ep_enable, unknown ep type(%d)\n", (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); goto end; } /* fifo_addr && fifo_size && double fifo */ for (i = 0; i < SW_UDC_ENDPOINTS; i++) { if (!strcmp(_ep->name, ep_fifo[i].name)) { fifo_addr = ep_fifo[i].fifo_addr; fifo_size = ep_fifo[i].fifo_size; double_fifo = ep_fifo[i].double_fifo; break; } } DMSG_INFO_UDC("ep enable: ep%d(0x%p, %s, %d, %d), fifo(%d, %d, %d)\n", ep->num, _ep, _ep->name, (desc->bEndpointAddress & USB_DIR_IN), _ep->maxpacket, fifo_addr, fifo_size, double_fifo); if (i >= SW_UDC_ENDPOINTS) { DMSG_PANIC("err: usbd_ep_enable, config fifo failed\n"); goto end; } /* check fifo size */ if ((_ep->maxpacket & 0x7ff) > fifo_size) { DMSG_PANIC("err: usbd_ep_enable, fifo size is too small\n"); goto end; } /* check double fifo */ if (double_fifo) { if (((_ep->maxpacket & 0x7ff) * 2) > fifo_size) { DMSG_PANIC("err: usbd_ep_enable, it is double fifo, "); DMSG_PANIC("but fifo size is too small\n"); goto end; } /* ????FIFO, ????????????? */ fifo_size = _ep->maxpacket & 0x7ff; } if (!is_peripheral_active()) { DMSG_INFO_UDC("usbd_ep_enable, usb device is not active\n"); goto end; } old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, ep->num); USBC_Dev_ConfigEp_Default(g_sunxi_udc_io.usb_bsp_hdle, ep_type); USBC_Dev_FlushFifo(g_sunxi_udc_io.usb_bsp_hdle, ep_type); /** * set max packet ,type, direction, address; * reset fifo counters, enable irq */ USBC_Dev_ConfigEp(g_sunxi_udc_io.usb_bsp_hdle, ts_type, ep_type, double_fifo, (_ep->maxpacket & 0x7ff)); USBC_ConfigFifo(g_sunxi_udc_io.usb_bsp_hdle, ep_type, double_fifo, fifo_size, fifo_addr); if (ts_type == USBC_TS_TYPE_ISO) USBC_Dev_IsoUpdateEnable(g_sunxi_udc_io.usb_bsp_hdle); USBC_INT_EnableEp(g_sunxi_udc_io.usb_bsp_hdle, ep_type, ep->num); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); end: spin_unlock_irqrestore(&ep->dev->lock, flags); sunxi_udc_set_halt(_ep, 0); return 0; } static int sunxi_udc_ep_disable(struct usb_ep *_ep) { struct sunxi_udc_ep *ep = NULL; u32 old_ep_index = 0; unsigned long flags = 0; if (!_ep) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } ep = to_sunxi_udc_ep(_ep); if (ep == NULL) { DMSG_PANIC("ERR: usbd_ep_disable: ep = NULL\n"); return -EINVAL; } if (!ep->desc) { DMSG_PANIC("ERR: %s not enabled\n", _ep ? ep->ep.name : NULL); return -EINVAL; } DMSG_INFO_UDC("ep disable: ep%d(0x%p, %s, %d, %x)\n", ep->num, _ep, _ep->name, (ep->bEndpointAddress & USB_DIR_IN), _ep->maxpacket); spin_lock_irqsave(&ep->dev->lock, flags); DMSG_DBG_UDC("ep_disable: %s\n", _ep->name); ep->desc = NULL; ep->halted = 1; sunxi_udc_ep_config_reset(ep); sunxi_udc_nuke(ep->dev, ep, -ESHUTDOWN); if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); goto end; } old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, ep->num); if ((ep->bEndpointAddress) & USB_DIR_IN) { /* tx */ USBC_Dev_ConfigEp_Default(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); USBC_INT_DisableEp(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, ep->num); } else { /* rx */ USBC_Dev_ConfigEp_Default(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); USBC_INT_DisableEp(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX, ep->num); } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); end: spin_unlock_irqrestore(&ep->dev->lock, flags); DMSG_DBG_UDC("%s disabled\n", _ep->name); return 0; } static struct usb_request *sunxi_udc_alloc_request( struct usb_ep *_ep, gfp_t mem_flags) { struct sunxi_udc_request *req = NULL; if (!_ep) { DMSG_PANIC("ERR: invalid argment\n"); return NULL; } req = kzalloc(sizeof(struct sunxi_udc_request), mem_flags); if (!req) { DMSG_PANIC("ERR: kzalloc failed\n"); return NULL; } memset(req, 0, sizeof(struct sunxi_udc_request)); req->req.dma = DMA_ADDR_INVALID; INIT_LIST_HEAD(&req->queue); DMSG_INFO_UDC("alloc request: ep(0x%p, %s, %d), req(0x%p)\n", _ep, _ep->name, _ep->maxpacket, req); return &req->req; } static void sunxi_udc_free_request(struct usb_ep *_ep, struct usb_request *_req) { struct sunxi_udc_request *req = NULL; if (_ep == NULL || _req == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return; } req = to_sunxi_udc_req(_req); if (req == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return; } DMSG_INFO_UDC("free request: ep(0x%p, %s, %d), req(0x%p)\n", _ep, _ep->name, _ep->maxpacket, req); kfree(req); } static int sunxi_udc_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) { struct sunxi_udc_request *req = NULL; struct sunxi_udc_ep *ep = NULL; struct sunxi_udc *dev = NULL; unsigned long flags = 0; u8 old_ep_index = 0; if (_ep == NULL || _req == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } ep = to_sunxi_udc_ep(_ep); if ((ep == NULL || (!ep->desc && _ep->name != ep0name))) { DMSG_PANIC("ERR: sunxi_udc_queue: inval 2\n"); return -EINVAL; } dev = ep->dev; if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { DMSG_PANIC("ERR : dev->driver=0x%p, dev->gadget.speed=%x\n", dev->driver, dev->gadget.speed); return -ESHUTDOWN; } if (!_req->complete || !_req->buf) { DMSG_PANIC("ERR: usbd_queue: _req is invalid\n"); return -EINVAL; } req = to_sunxi_udc_req(_req); if (!req) { DMSG_PANIC("ERR: req is NULL\n"); return -EINVAL; } spin_lock_irqsave(&ep->dev->lock, flags); _req->status = -EINPROGRESS; _req->actual = 0; if (is_sunxi_udc_dma_capable(req, ep)) { spin_unlock_irqrestore(&ep->dev->lock, flags); sunxi_udc_map_dma_buffer(req, dev, ep); spin_lock_irqsave(&ep->dev->lock, flags); } list_add_tail(&req->queue, &ep->queue); if (!is_peripheral_active()) { DMSG_PANIC("warn: peripheral is active\n"); goto end; } if (g_queue_debug) DMSG_INFO("q:(0x%p,%d,%d)\n", _req, _req->length, _req->actual); old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); if (ep->bEndpointAddress) USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, ep->bEndpointAddress & 0x7F); else USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, 0); /* if there is only one in the queue, then execute it */ if (!ep->halted && (&req->queue == ep->queue.next)) { if (ep->bEndpointAddress == 0 /* ep0 */) { switch (dev->ep0state) { case EP0_IN_DATA_PHASE: if (!USBC_Dev_IsWriteDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0) && sunxi_udc_write_fifo(ep, req)) { dev->ep0state = EP0_IDLE; req = NULL; } break; case EP0_OUT_DATA_PHASE: if ((!_req->length) || (USBC_Dev_IsReadDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0) && sunxi_udc_read_fifo(ep, req))) { dev->ep0state = EP0_IDLE; req = NULL; } break; default: spin_unlock_irqrestore(&ep->dev->lock, flags); return -EL2HLT; } } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 && !USBC_Dev_IsWriteDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX)) { #if (defined(CONFIG_USB_G_WEBCAM) || defined(CONFIG_USB_CONFIGFS_F_UVC)) \ && defined(CONFIG_SMP) /** * not execute req when only one in the queue, otherwise * it will be deadlocked for webcam on SMP. */ if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC) { int ret = 0; ret = USBC_Dev_WriteDataStatus( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, 1); if (ret != 0) { DMSG_PANIC("ERR: USBC_Dev_WriteDataStatus, failed\n"); req->req.status = -EOVERFLOW; USBC_SelectActiveEp( g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); spin_unlock_irqrestore(&ep->dev->lock, flags); return req->req.status; } } else if (sunxi_udc_write_fifo(ep, req)) { req = NULL; } #else if (sunxi_udc_write_fifo(ep, req)) req = NULL; #endif } else if ((ep->bEndpointAddress & USB_DIR_IN) == 0 && USBC_Dev_IsReadDataReady( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX)) { if (sunxi_udc_read_fifo(ep, req)) req = NULL; } } USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); end: spin_unlock_irqrestore(&ep->dev->lock, flags); return 0; } static int sunxi_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req) { struct sunxi_udc_ep *ep = NULL; struct sunxi_udc *udc = NULL; int retval = -EINVAL; struct sunxi_udc_request *req = NULL; unsigned long flags = 0; DMSG_DBG_UDC("(%p,%p)\n", _ep, _req); if (!the_controller->driver) { DMSG_PANIC("ERR: sunxi_udc_dequeue: driver is null\n"); return -ESHUTDOWN; } if (!_ep || !_req) { DMSG_PANIC("ERR: sunxi_udc_dequeue: invalid argment\n"); return retval; } ep = to_sunxi_udc_ep(_ep); if (ep == NULL) { DMSG_PANIC("ERR: ep == NULL\n"); return -EINVAL; } udc = to_sunxi_udc(ep->gadget); if (udc == NULL) { DMSG_PANIC("ERR: ep == NULL\n"); return -EINVAL; } DMSG_INFO_UDC("dequeue: ep(0x%p, %d), _req(0x%p, %d, %d)\n", ep, ep->num, _req, _req->length, _req->actual); spin_lock_irqsave(&ep->dev->lock, flags); list_for_each_entry(req, &ep->queue, queue) { if (&req->req == _req) { list_del_init(&req->queue); _req->status = -ECONNRESET; retval = 0; break; } } if (retval == 0) { DMSG_DBG_UDC("dequeued req %p from %s, len %d buf %p\n", req, _ep->name, _req->length, _req->buf); sunxi_udc_done(ep, req, -ECONNRESET); /* * If dma is capable, we should disable the dma channel and * clean dma status, or it would cause dma hang when unexpected * abort occurs. */ if (is_sunxi_udc_dma_capable(req, ep)) { sunxi_udc_dma_chan_disable((dm_hdl_t)ep->dev->dma_hdle); sunxi_udc_clean_dma_status(ep); } } spin_unlock_irqrestore(&ep->dev->lock, flags); return retval; } static int sunxi_udc_set_halt_ex(struct usb_ep *_ep, int value, int is_in) { struct sunxi_udc_ep *ep = NULL; u32 idx = 0; __u8 old_ep_index = 0; if (_ep == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } ep = to_sunxi_udc_ep(_ep); if (ep == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } if (!ep->desc && ep->ep.name != ep0name) { DMSG_PANIC("ERR: !ep->desc && ep->ep.name != ep0name\n"); return -EINVAL; } if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } idx = ep->bEndpointAddress & 0x7F; old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); if (idx == 0) { USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } else { if (is_in) { if (value) { USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); } else { USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); } } else { if (value) USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); else USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); } } ep->halted = value ? 1 : 0; USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); return 0; } static int sunxi_udc_set_halt(struct usb_ep *_ep, int value) { struct sunxi_udc_ep *ep = NULL; unsigned long flags = 0; u32 idx = 0; __u8 old_ep_index = 0; if (_ep == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } ep = to_sunxi_udc_ep(_ep); if (ep == NULL) { DMSG_PANIC("ERR: invalid argment\n"); return -EINVAL; } if (!ep->desc && ep->ep.name != ep0name) { DMSG_PANIC("ERR: !ep->desc && ep->ep.name != ep0name\n"); return -EINVAL; } if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } spin_lock_irqsave(&ep->dev->lock, flags); idx = ep->bEndpointAddress & 0x7F; old_ep_index = USBC_GetActiveEp(g_sunxi_udc_io.usb_bsp_hdle); USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, idx); if (idx == 0) { USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_EP0); } else { if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { if (value) USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); else USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); } else { if (value) USBC_Dev_EpSendStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); else USBC_Dev_EpClearStall( g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); } } ep->halted = value ? 1 : 0; USBC_SelectActiveEp(g_sunxi_udc_io.usb_bsp_hdle, old_ep_index); spin_unlock_irqrestore(&ep->dev->lock, flags); return 0; } static const struct usb_ep_ops sunxi_udc_ep_ops = { .enable = sunxi_udc_ep_enable, .disable = sunxi_udc_ep_disable, .alloc_request = sunxi_udc_alloc_request, .free_request = sunxi_udc_free_request, .queue = sunxi_udc_queue, .dequeue = sunxi_udc_dequeue, .set_halt = sunxi_udc_set_halt, }; static int sunxi_udc_get_frame(struct usb_gadget *_gadget) { int ret = 0; if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } DMSG_INFO_UDC("sunxi_udc_get_frame is no susport\n"); return ret; } static int sunxi_udc_wakeup(struct usb_gadget *_gadget) { if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } return 0; } static int sunxi_udc_set_selfpowered(struct usb_gadget *gadget, int value) { if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } return 0; } static void sunxi_udc_disable(struct sunxi_udc *dev); static void sunxi_udc_enable(struct sunxi_udc *dev); static int sunxi_udc_set_pullup(struct sunxi_udc *udc, int is_on) { DMSG_DBG_UDC("sunxi_udc_set_pullup\n"); is_udc_enable = is_on; if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } if (is_on) { sunxi_udc_enable(udc); } else { if (udc->gadget.speed != USB_SPEED_UNKNOWN) { if (udc->driver && udc->driver->disconnect) udc->driver->disconnect(&udc->gadget); } sunxi_udc_disable(udc); } return 0; } static int sunxi_udc_vbus_session(struct usb_gadget *gadget, int is_active) { struct sunxi_udc *udc = to_sunxi_udc(gadget); DMSG_DBG_UDC("sunxi_udc_vbus_session\n"); if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } udc->vbus = (is_active != 0); sunxi_udc_set_pullup(udc, is_active); return 0; } static int sunxi_udc_pullup(struct usb_gadget *gadget, int is_on) { struct sunxi_udc *udc = to_sunxi_udc(gadget); DMSG_INFO_UDC("sunxi_udc_pullup, is_on = %d\n", is_on); sunxi_udc_set_pullup(udc, is_on); return 0; } static int sunxi_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) { if (!is_peripheral_active()) { DMSG_INFO("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } DMSG_DBG_UDC("sunxi_udc_vbus_draw\n"); cfg_vbus_draw(ma); return 0; } static int sunxi_get_udc_base(struct platform_device *pdev, sunxi_udc_io_t *sunxi_udc_io) { struct device_node *np = pdev->dev.of_node; sunxi_udc_io->usb_vbase = of_iomap(np, 0); if (sunxi_udc_io->usb_vbase == NULL) { dev_err(&pdev->dev, "can't get usb_vbase resource\n"); return -EINVAL; } return 0; } static int sunxi_get_udc_clock(struct platform_device *pdev, sunxi_udc_io_t *sunxi_udc_io) { struct device_node *np = pdev->dev.of_node; sunxi_udc_io->ahb_otg = of_clk_get(np, 1); if (IS_ERR(sunxi_udc_io->ahb_otg)) { sunxi_udc_io->ahb_otg = NULL; DMSG_PANIC("ERR: get usb ahb_otg clk failed.\n"); return -EINVAL; } sunxi_udc_io->mod_usbphy = of_clk_get(np, 0); if (IS_ERR(sunxi_udc_io->mod_usbphy)) { sunxi_udc_io->ahb_otg = NULL; DMSG_PANIC("ERR: get usb mod_usbphy failed.\n"); return -EINVAL; } return 0; } /* gadget driver handling */ static void sunxi_udc_reinit(struct sunxi_udc *dev) { u32 i = 0; /* device/ep0 records init */ INIT_LIST_HEAD(&dev->gadget.ep_list); INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); dev->ep0state = EP0_IDLE; for (i = 0; i < SW_UDC_ENDPOINTS; i++) { struct sunxi_udc_ep *ep = &dev->ep[i]; if (i != 0) list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); ep->dev = dev; ep->desc = NULL; ep->halted = 0; INIT_LIST_HEAD(&ep->queue); } } static void sunxi_udc_enable(struct sunxi_udc *dev) { DMSG_DBG_UDC("sunxi_udc_enable called\n"); dev->gadget.speed = USB_SPEED_UNKNOWN; DMSG_INFO_UDC("CONFIG_USB_GADGET_DUALSPEED: USBC_TS_MODE_HS\n"); USBC_Dev_ConfigTransferMode( g_sunxi_udc_io.usb_bsp_hdle, USBC_TS_TYPE_BULK, USBC_TS_MODE_HS); /* Enable reset and suspend interrupt interrupts */ USBC_INT_EnableUsbMiscUint(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_SUSPEND); USBC_INT_EnableUsbMiscUint(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_RESUME); USBC_INT_EnableUsbMiscUint(g_sunxi_udc_io.usb_bsp_hdle, USBC_INTUSB_RESET); /* Enable ep0 interrupt */ USBC_INT_EnableEp(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, 0); cfg_udc_command(SW_UDC_P_ENABLE); } static void sunxi_udc_disable(struct sunxi_udc *dev) { DMSG_DBG_UDC("sunxi_udc_disable\n"); /* Disable all interrupts */ USBC_INT_DisableUsbMiscAll(g_sunxi_udc_io.usb_bsp_hdle); USBC_INT_DisableEpAll(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX); USBC_INT_DisableEpAll(g_sunxi_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX); /* Clear the interrupt registers */ clear_all_irq(); cfg_udc_command(SW_UDC_P_DISABLE); /* Set speed to unknown */ dev->gadget.speed = USB_SPEED_UNKNOWN; } static s32 usbd_start_work(void) { DMSG_INFO_UDC("usbd_start_work\n"); if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } USBC_Dev_ConectSwitch(g_sunxi_udc_io.usb_bsp_hdle, USBC_DEVICE_SWITCH_ON); return 0; } static s32 usbd_stop_work(void) { DMSG_INFO_UDC("usbd_stop_work\n"); if (!is_peripheral_active()) { DMSG_INFO_UDC("%s_%d: usb device is not active\n", __func__, __LINE__); return 0; } USBC_Dev_ConectSwitch(g_sunxi_udc_io.usb_bsp_hdle, USBC_DEVICE_SWITCH_OFF); /* default is pulldown */ return 0; } static int sunxi_udc_start(struct usb_gadget *g, struct usb_gadget_driver *driver) { struct sunxi_udc *udc = the_controller; /* Sanity checks */ if (!udc) { DMSG_PANIC("ERR: udc is null\n"); return -ENODEV; } if (udc->driver) { DMSG_PANIC("ERR: udc->driver is not null\n"); return -EBUSY; } /** * the struct usb_gadget_driver has a little change * between linux 3.0 and linux-3.3, speed->max_speed */ if (!driver->setup || driver->max_speed < USB_SPEED_FULL) { DMSG_PANIC("ERR: Invalid setup %p speed %d\n", driver->setup, driver->max_speed); return -EINVAL; } #if defined(MODULE) if (!driver->unbind) { DMSG_PANIC("Invalid driver: no unbind method\n"); return -EINVAL; } #endif /* Hook the driver */ udc->driver = driver; udc->gadget.dev.driver = &driver->driver; DMSG_INFO_UDC("[%s]: binding gadget driver '%s'\n", gadget_name, driver->driver.name); return 0; } static int sunxi_udc_stop(struct usb_gadget *g) { struct sunxi_udc *udc = the_controller; struct usb_gadget_driver *driver = udc->driver; if (!udc) { DMSG_PANIC("ERR: udc is null\n"); return -ENODEV; } if (!driver || driver != udc->driver || !driver->unbind) { DMSG_PANIC("ERR: driver is null\n"); return -EINVAL; } DMSG_INFO_UDC("[%s]: usb_gadget_unregister_driver() '%s'\n", gadget_name, driver->driver.name); udc->gadget.dev.driver = NULL; udc->driver = NULL; /* Disable udc */ sunxi_udc_disable(udc); return 0; } static const struct usb_gadget_ops sunxi_udc_ops = { .get_frame = sunxi_udc_get_frame, .wakeup = sunxi_udc_wakeup, .set_selfpowered = sunxi_udc_set_selfpowered, .pullup = sunxi_udc_pullup, .vbus_session = sunxi_udc_vbus_session, .vbus_draw = sunxi_udc_vbus_draw, .udc_start = sunxi_udc_start, .udc_stop = sunxi_udc_stop, }; static struct sunxi_udc sunxi_udc = { .gadget = { .ops = &sunxi_udc_ops, .ep0 = &sunxi_udc.ep[0].ep, .name = gadget_name, .dev = { .init_name = "gadget", }, }, .ep[0] = { .num = 0, .ep = { .name = ep0name, .ops = &sunxi_udc_ep_ops, .maxpacket = EP0_FIFO_SIZE, .maxpacket_limit = EP0_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL), }, .dev = &sunxi_udc, }, .ep[1] = { .num = 1, .ep = { .name = ep1in_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_IN | 1), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[2] = { .num = 1, .ep = { .name = ep1out_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_OUT | 1), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[3] = { .num = 2, .ep = { .name = ep2in_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_IN | 2), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[4] = { .num = 2, .ep = { .name = ep2out_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_OUT | 2), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[5] = { .num = 3, .ep = { .name = ep3_iso_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_ISO_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_ISO_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_ALL), }, .dev = &sunxi_udc, .bEndpointAddress = 3, .bmAttributes = USB_ENDPOINT_XFER_ISOC, }, .ep[6] = { .num = 4, .ep = { .name = ep4_int_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_ALL), }, .dev = &sunxi_udc, .bEndpointAddress = 4, .bmAttributes = USB_ENDPOINT_XFER_INT, }, #if defined(CONFIG_ARCH_SUN50IW1) || defined(CONFIG_ARCH_SUN50IW3) \ || defined(CONFIG_ARCH_SUN8IW6) || defined(CONFIG_ARCH_SUN8IW15) .ep[7] = { .num = 5, .ep = { .name = ep5in_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_IN | 5), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[8] = { .num = 5, .ep = { .name = ep5out_bulk_name, .ops = &sunxi_udc_ep_ops, .maxpacket = SW_UDC_EP_FIFO_SIZE, .maxpacket_limit = SW_UDC_EP_FIFO_SIZE, .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT), }, .dev = &sunxi_udc, .bEndpointAddress = (USB_DIR_OUT | 5), .bmAttributes = USB_ENDPOINT_XFER_BULK, }, #endif }; static void sunxi_vbus_det_work(struct work_struct *work) { struct sunxi_udc *udc = NULL; /* wait for axp vbus detect ready */ msleep(100); udc = container_of(work, struct sunxi_udc, vbus_det_work); #if defined(CONFIG_AW_AXP) while (axp_usb_is_connected()) { msleep(1000); /* after resume */ if (usb_connect) return; } #endif if (udc->driver && udc->driver->disconnect) udc->driver->disconnect(&udc->gadget); } void __iomem *get_otgc_vbase(void) { return g_sunxi_udc_io.usb_vbase; } EXPORT_SYMBOL_GPL(get_otgc_vbase); int get_dp_dm_status_normal(void) { __u32 reg_val = 0; __u32 dp = 0; __u32 dm = 0; if (g_sunxi_udc_io.usb_vbase == NULL) return 0; /* USBC_EnableDpDmPullUp */ reg_val = USBC_Readl(USBC_REG_ISCR(g_sunxi_udc_io.usb_vbase)); reg_val |= (1 << USBC_BP_ISCR_DPDM_PULLUP_EN); USBC_Writel(reg_val, USBC_REG_ISCR(g_sunxi_udc_io.usb_vbase)); /* USBC_EnableIdPullUp */ reg_val = USBC_Readl(USBC_REG_ISCR(g_sunxi_udc_io.usb_vbase)); reg_val |= (1 << USBC_BP_ISCR_ID_PULLUP_EN); USBC_Writel(reg_val, USBC_REG_ISCR(g_sunxi_udc_io.usb_vbase)); msleep(20); reg_val = USBC_Readl(USBC_REG_ISCR(g_sunxi_udc_io.usb_vbase)); dp = (reg_val >> USBC_BP_ISCR_EXT_DP_STATUS) & 0x01; dm = (reg_val >> USBC_BP_ISCR_EXT_DM_STATUS) & 0x01; return ((dp << 1) | dm); } EXPORT_SYMBOL_GPL(get_dp_dm_status_normal); static int sunxi_get_udc_resource(struct platform_device *pdev, sunxi_udc_io_t *sunxi_udc_io); int sunxi_usb_device_enable(void) { struct platform_device *pdev = g_udc_pdev; struct sunxi_udc *udc = &sunxi_udc; int retval = 0; DMSG_INFO_UDC("sunxi_usb_device_enable start\n"); if (pdev == NULL) { DMSG_PANIC("pdev is null\n"); return -1; } usb_connect = 0; crq_bRequest = 0; is_controller_alive = 1; #if defined(CONFIG_ARCH_SUN8IW6) retval = sunxi_get_udc_resource(pdev, &g_sunxi_udc_io); if (retval != 0) { DMSG_PANIC("ERR: sunxi_get_udc_resource, is fail\n"); return -ENODEV; } retval = sunxi_udc_io_init(usbd_port_no, &g_sunxi_udc_io); if (retval != 0) { DMSG_PANIC("ERR: sunxi_udc_io_init fail\n"); return -1; } #endif retval = sunxi_udc_bsp_init(&g_sunxi_udc_io); if (retval != 0) { DMSG_PANIC("ERR: sunxi_udc_bsp_init failed\n"); return -1; } sunxi_udc_disable(udc); udc->irq_no = platform_get_irq(pdev, 0); if (udc->irq_no < 0) { DMSG_PANIC("%s,%d: error to get irq\n", __func__, __LINE__); return -EINVAL; } udc->sunxi_udc_io = &g_sunxi_udc_io; udc->usbc_no = usbd_port_no; strcpy((char *)udc->driver_name, gadget_name); udc->pdev = pdev; udc->controller = &(pdev->dev); #ifdef CONFIG_OF udc->controller->dma_mask = &sunxi_udc_mask; udc->controller->coherent_dma_mask = DMA_BIT_MASK(32); #endif if (is_udc_support_dma()) { retval = sunxi_udc_dma_probe(udc); if (retval != 0) { DMSG_PANIC("ERR: sunxi_udc_dma_probe failef\n"); retval = -EBUSY; goto err; } } INIT_WORK(&udc->vbus_det_work, sunxi_vbus_det_work); retval = request_irq(udc->irq_no, sunxi_udc_irq, 0, gadget_name, udc); if (retval != 0) { DMSG_PANIC("ERR: cannot get irq %i, err %d\n", udc->irq_no, retval); retval = -EBUSY; goto err; } if (udc->driver && is_udc_enable) { sunxi_udc_enable(udc); cfg_udc_command(SW_UDC_P_ENABLE); } DMSG_INFO_UDC("sunxi_usb_device_enable end\n"); return 0; err: if (is_udc_support_dma()) sunxi_udc_dma_remove(udc); sunxi_udc_bsp_exit(&g_sunxi_udc_io); #if defined(CONFIG_ARCH_SUN8IW6) sunxi_udc_io_exit(&g_sunxi_udc_io); #endif return retval; } EXPORT_SYMBOL_GPL(sunxi_usb_device_enable); int sunxi_usb_device_disable(void) __releases(sunxi_udc.lock) __acquires(sunxi_udc.lock) { struct platform_device *pdev = g_udc_pdev; struct sunxi_udc *udc = NULL; unsigned long flags = 0; DMSG_INFO_UDC("sunxi_usb_device_disable start\n"); if (pdev == NULL) { DMSG_PANIC("pdev is null\n"); return -1; } udc = platform_get_drvdata(pdev); if (udc == NULL) { DMSG_PANIC("udc is null\n"); return -1; } /* disable usb controller */ if (udc->driver && udc->driver->disconnect) udc->driver->disconnect(&udc->gadget); if (is_udc_support_dma()) { spin_lock_irqsave(&udc->lock, flags); sunxi_udc_stop_dma_work(udc, 0); spin_unlock_irqrestore(&udc->lock, flags); sunxi_udc_dma_remove(udc); } free_irq(udc->irq_no, udc); sunxi_udc_bsp_exit(&g_sunxi_udc_io); spin_lock_irqsave(&udc->lock, flags); usb_connect = 0; wake_unlock(&udc_wake_lock); pr_debug("usb_connecting: release wake lock\n"); crq_bRequest = 0; is_controller_alive = 0; spin_unlock_irqrestore(&udc->lock, flags); DMSG_INFO_UDC("sunxi_usb_device_disable end\n"); return 0; } EXPORT_SYMBOL_GPL(sunxi_usb_device_disable); int sunxi_udc_is_enable(struct platform_device *pdev) { struct device_node *usbc_np = NULL; int ret = 0; int is_enable = 0; const char *used_status; usbc_np = of_find_node_by_type(NULL, SET_USB0); ret = of_property_read_string(usbc_np, "status", &used_status); if (ret) { DMSG_PANIC("get sunxi_udc_is_enable is fail, %d\n", -ret); is_enable = 0; } else if (!strcmp(used_status, "okay")) { is_enable = 1; } else { is_enable = 0; } return is_enable; } static int sunxi_get_udc_resource(struct platform_device *pdev, sunxi_udc_io_t *sunxi_udc_io) { int retval = 0; memset(&g_sunxi_udc_io, 0, sizeof(sunxi_udc_io_t)); retval = sunxi_get_udc_base(pdev, sunxi_udc_io); if (retval != 0) { dev_err(&pdev->dev, "can't get udc base\n"); goto err0; } retval = sunxi_get_udc_clock(pdev, sunxi_udc_io); if (retval != 0) { dev_err(&pdev->dev, "can't get udc clock\n"); goto err0; } return 0; err0: return retval; } static const struct of_device_id sunxi_udc_match[] = { {.compatible = "allwinner,sunxi-udc", }, {}, }; MODULE_DEVICE_TABLE(of, sunxi_udc_match); static int sunxi_udc_probe_otg(struct platform_device *pdev) { struct sunxi_udc *udc = &sunxi_udc; int retval = 0; g_udc_pdev = pdev; spin_lock_init(&udc->lock); if (!sunxi_udc_is_enable(pdev)) { DMSG_INFO("sunxi udc is no enable\n"); return -ENODEV; } udc->gadget.dev.parent = &pdev->dev; #ifdef CONFIG_OF udc->gadget.dev.dma_mask = &sunxi_udc_mask; udc->gadget.dev.coherent_dma_mask = DMA_BIT_MASK(32); #endif retval = sunxi_get_udc_resource(pdev, &g_sunxi_udc_io); if (retval != 0) { DMSG_PANIC("ERR: sunxi_get_udc_resource, is fail\n"); return -ENODEV; } sunxi_udc_io_init(usbd_port_no, &g_sunxi_udc_io); sunxi_udc_reinit(udc); the_controller = udc; platform_set_drvdata(pdev, udc); retval = usb_add_gadget_udc(&pdev->dev, &udc->gadget); if (retval != 0) { dev_err(&pdev->dev, "can't usb_add_gadget_udc\n"); return retval; } device_create_file(&pdev->dev, &dev_attr_otg_ed_test); device_create_file(&pdev->dev, &dev_attr_queue_debug); device_create_file(&pdev->dev, &dev_attr_dma_debug); device_create_file(&pdev->dev, &dev_attr_write_debug); device_create_file(&pdev->dev, &dev_attr_read_debug); device_create_file(&pdev->dev, &dev_attr_irq_debug); device_create_file(&pdev->dev, &dev_attr_msc_read_debug); device_create_file(&pdev->dev, &dev_attr_msc_write_debug); return 0; } static int sunxi_udc_remove_otg(struct platform_device *pdev) { struct sunxi_udc *udc = NULL; g_udc_pdev = NULL; udc = platform_get_drvdata(pdev); if (udc->driver) { DMSG_PANIC("ERR: invalid argment, udc->driver(0x%p)\n", udc->driver); return -EBUSY; } device_remove_file(&pdev->dev, &dev_attr_otg_ed_test); device_remove_file(&pdev->dev, &dev_attr_queue_debug); device_remove_file(&pdev->dev, &dev_attr_dma_debug); device_remove_file(&pdev->dev, &dev_attr_write_debug); device_remove_file(&pdev->dev, &dev_attr_read_debug); device_remove_file(&pdev->dev, &dev_attr_irq_debug); device_remove_file(&pdev->dev, &dev_attr_msc_read_debug); device_remove_file(&pdev->dev, &dev_attr_msc_write_debug); usb_del_gadget_udc(&udc->gadget); sunxi_udc_io_exit(&g_sunxi_udc_io); memset(&g_sunxi_udc_io, 0, sizeof(sunxi_udc_io_t)); return 0; } static int sunxi_udc_probe(struct platform_device *pdev) { return sunxi_udc_probe_otg(pdev); } static int sunxi_udc_remove(struct platform_device *pdev) { return sunxi_udc_remove_otg(pdev); } #ifdef CONFIG_PM static int sunxi_udc_suspend(struct device *dev) { struct sunxi_udc *udc = the_controller; DMSG_INFO_UDC("sunxi_udc_suspend start\n"); if (udc == NULL) { DMSG_INFO_UDC("udc is NULL, need not enter to suspend\n"); return 0; } if (!is_peripheral_active()) { DMSG_INFO("udc is disable, need not enter to suspend\n"); return 0; } /* soft disconnect */ cfg_udc_command(SW_UDC_P_DISABLE); /* disable usb controller */ sunxi_udc_disable(udc); /* close USB clock */ close_usb_clock(&g_sunxi_udc_io); DMSG_INFO_UDC("sunxi_udc_suspend end\n"); return 0; } static int sunxi_udc_resume(struct device *dev) { struct sunxi_udc *udc = the_controller; DMSG_INFO_UDC("sunxi_udc_resume start\n"); if (udc == NULL) { DMSG_INFO_UDC("udc is NULL, need not enter to suspend\n"); return 0; } if (!is_peripheral_active()) { DMSG_INFO("udc is disable, need not enter to resume\n"); return 0; } sunxi_udc_bsp_init(&g_sunxi_udc_io); if (is_udc_enable) { /* enable usb controller */ sunxi_udc_enable(udc); /* soft connect */ cfg_udc_command(SW_UDC_P_ENABLE); } DMSG_INFO_UDC("sunxi_udc_resume end\n"); return 0; } static const struct dev_pm_ops sunxi_udc_pm_ops = { .suspend = sunxi_udc_suspend, .resume = sunxi_udc_resume, }; #define UDC_PM_OPS (&sunxi_udc_pm_ops) #else /* !CONFIG_PM */ #define UDC_PM_OPS NULL #endif /* CONFIG_PM */ static struct platform_driver sunxi_udc_driver = { .driver = { .name = (char *)gadget_name, .pm = UDC_PM_OPS, .bus = &platform_bus_type, .owner = THIS_MODULE, .of_match_table = sunxi_udc_match, }, .probe = sunxi_udc_probe, .remove = sunxi_udc_remove, }; static void cfg_udc_command(enum sunxi_udc_cmd_e cmd) { struct sunxi_udc *udc = the_controller; switch (cmd) { case SW_UDC_P_ENABLE: { if (udc->driver) usbd_start_work(); else DMSG_INFO_UDC("udc->driver is null, "); DMSG_INFO_UDC("udc is need not start\n"); } break; case SW_UDC_P_DISABLE: { if (udc->driver) usbd_stop_work(); else DMSG_INFO_UDC("udc->driver is null, "); DMSG_INFO_UDC("udc is need not stop\n"); } break; case SW_UDC_P_RESET: DMSG_PANIC("ERR: reset is not support\n"); break; default: DMSG_PANIC("ERR: unknown cmd(%d)\n", cmd); break; } } static void cfg_vbus_draw(unsigned int ma) { } static int udc_init(void) { int retval = 0; usb_connect = 0; atomic_set(&vfs_read_flag, 0); atomic_set(&vfs_write_flag, 0); /* driver register */ retval = platform_driver_register(&sunxi_udc_driver); if (retval) { DMSG_PANIC("ERR: platform_driver_register failed\n"); retval = -1; goto err; } pr_debug("usb_connecting: init wake lock.\n"); wake_lock_init(&udc_wake_lock, WAKE_LOCK_SUSPEND, "usb_connecting"); return 0; err: return retval; } static void udc_exit(void) { DMSG_INFO_UDC("udc_exit: version %s\n", DRIVER_VERSION); wake_lock_destroy(&udc_wake_lock); platform_driver_unregister(&sunxi_udc_driver); } fs_initcall(udc_init); module_exit(udc_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:softwinner-usbgadget");