| /* |
| * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved. |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <asm/io.h> |
| #include <asm/types.h> |
| #include <malloc.h> |
| #include <command.h> |
| #include <asm/errno.h> |
| #include <usbdevice.h> |
| #include <usb/imx_udc.h> |
| |
| #include "ep0.h" |
| |
| #ifdef DEBUG |
| #define DBG(x...) printf(x) |
| #else |
| #define DBG(x...) do {} while (0) |
| #endif |
| |
| #define mdelay(n) udelay((n)*1000) |
| |
| #define EP_TQ_ITEM_SIZE 16 |
| |
| #define inc_index(x) (x = ((x+1) % EP_TQ_ITEM_SIZE)) |
| |
| #define ep_is_in(e, tx) ((e == 0) ? (mxc_udc.ep0_dir == USB_DIR_IN) : tx) |
| |
| #define USB_RECIP_MASK 0x03 |
| #define USB_TYPE_MASK (0x03 << 5) |
| #define USB_MEM_ALIGN_BYTE 4096 |
| #define USB_DEV_DQH_ALIGN 64 |
| #define USB_DEV_DTD_ALIGN 64 |
| |
| /*fixed the dtd buffer to 4096 bytes, even it could be 20KB*/ |
| #define USB_DEV_DTD_MAX_BUFFER_SIZE 4096 |
| |
| #define CACHE_ALIGNED_END(start, length) \ |
| (ALIGN((uint32_t)start + length, ARCH_DMA_MINALIGN)) |
| |
| struct mxc_ep_t{ |
| int epnum; |
| int dir; |
| int max_pkt_size; |
| struct usb_endpoint_instance *epi; |
| struct ep_queue_item *ep_dtd[EP_TQ_ITEM_SIZE]; |
| int index; /* to index the free tx tqi */ |
| int done; /* to index the complete rx tqi */ |
| struct ep_queue_item *tail; /* last item in the dtd chain */ |
| struct ep_queue_head *ep_qh; |
| } ; |
| |
| struct mxc_udc_ctrl{ |
| int max_ep; |
| int ep0_dir; |
| int setaddr; |
| struct ep_queue_head *ep_qh; |
| struct mxc_ep_t *mxc_ep; |
| u32 qh_unaligned; |
| }; |
| |
| static int usb_highspeed; |
| static int usb_inited; |
| static struct mxc_udc_ctrl mxc_udc; |
| static struct usb_device_instance *udc_device; |
| static struct urb *ep0_urb; |
| /* |
| * malloc an aligned memory |
| * unaligned_addr: return a unaligned address for memory free |
| * size : memory size |
| * align : alignment for this memroy |
| * return : aligned address(NULL when malloc failt) |
| */ |
| static void *malloc_aligned_buffer(u32 *unaligned_addr, |
| int size, int align) |
| { |
| int msize = (size + align - 1); |
| u32 vir, vir_align; |
| |
| /* force the allocated memory size to be aligned with min cache operation unit. |
| * So it is safe to flush/invalidate the cache. |
| */ |
| msize = (msize + ARCH_DMA_MINALIGN - 1) / ARCH_DMA_MINALIGN; |
| msize = msize * ARCH_DMA_MINALIGN; |
| |
| vir = (u32)malloc(msize); |
| memset((void *)vir, 0, msize); |
| vir_align = (vir + align - 1) & (~(align - 1)); |
| *unaligned_addr = vir; |
| |
| DBG("alloc aligned vir addr %x\n", vir_align); |
| return (void *)vir_align; |
| } |
| |
| int is_usb_disconnected() |
| { |
| int ret = 0; |
| |
| ret = readl(USB_OTGSC) & OTGSC_B_SESSION_VALID ? 0 : 1; |
| return ret; |
| } |
| |
| static int mxc_init_usb_qh(void) |
| { |
| int size; |
| memset(&mxc_udc, 0, sizeof(mxc_udc)); |
| mxc_udc.max_ep = (readl(USB_DCCPARAMS) & DCCPARAMS_DEN_MASK) * 2; |
| DBG("udc max ep = %d\n", mxc_udc.max_ep); |
| size = mxc_udc.max_ep * sizeof(struct ep_queue_head); |
| mxc_udc.ep_qh = malloc_aligned_buffer(&mxc_udc.qh_unaligned, |
| size, USB_MEM_ALIGN_BYTE); |
| if (!mxc_udc.ep_qh) { |
| printf("malloc ep qh dma buffer failure\n"); |
| return -1; |
| } |
| memset(mxc_udc.ep_qh, 0, size); |
| |
| /*flush cache to physical memory*/ |
| flush_dcache_range((unsigned long)mxc_udc.ep_qh, |
| CACHE_ALIGNED_END(mxc_udc.ep_qh, size)); |
| |
| writel(virt_to_phys(mxc_udc.ep_qh) & 0xfffff800, USB_ENDPOINTLISTADDR); |
| return 0; |
| } |
| |
| static int mxc_destroy_usb_qh(void) |
| { |
| if (mxc_udc.ep_qh && mxc_udc.qh_unaligned) { |
| free((void *)mxc_udc.qh_unaligned); |
| mxc_udc.ep_qh = 0; |
| mxc_udc.qh_unaligned = 0; |
| mxc_udc.max_ep = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int mxc_init_ep_struct(void) |
| { |
| int i; |
| |
| DBG("init mxc ep\n"); |
| mxc_udc.mxc_ep = malloc(mxc_udc.max_ep * sizeof(struct mxc_ep_t)); |
| if (!mxc_udc.mxc_ep) { |
| printf("malloc ep struct failure\n"); |
| return -1; |
| } |
| memset((void *)mxc_udc.mxc_ep, 0, |
| sizeof(struct mxc_ep_t) * mxc_udc.max_ep); |
| for (i = 0; i < mxc_udc.max_ep / 2; i++) { |
| struct mxc_ep_t *ep; |
| ep = mxc_udc.mxc_ep + i * 2; |
| ep->epnum = i; |
| ep->index = ep->done = 0; |
| ep->dir = USB_RECV; /* data from host to device */ |
| ep->ep_qh = &mxc_udc.ep_qh[i * 2]; |
| |
| ep = mxc_udc.mxc_ep + (i * 2 + 1); |
| ep->epnum = i; |
| ep->index = ep->done = 0; |
| ep->dir = USB_SEND; /* data to host from device */ |
| ep->ep_qh = &mxc_udc.ep_qh[(i * 2 + 1)]; |
| } |
| return 0; |
| } |
| |
| static int mxc_destroy_ep_struct(void) |
| { |
| if (mxc_udc.mxc_ep) { |
| free(mxc_udc.mxc_ep); |
| mxc_udc.mxc_ep = 0; |
| } |
| return 0; |
| } |
| |
| static int mxc_init_ep_dtd(u8 index) |
| { |
| struct mxc_ep_t *ep; |
| u32 unaligned_addr; |
| int i; |
| |
| if (index >= mxc_udc.max_ep) |
| DBG("%s ep %d is not valid\n", __func__, index); |
| |
| ep = mxc_udc.mxc_ep + index; |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| ep->ep_dtd[i] = malloc_aligned_buffer(&unaligned_addr, |
| sizeof(struct ep_queue_item), USB_DEV_DTD_ALIGN); |
| ep->ep_dtd[i]->item_unaligned_addr = unaligned_addr; |
| |
| if (NULL == ep->ep_dtd[i]) { |
| printf("%s malloc tq item failure\n", __func__); |
| |
| /*free other already allocated dtd*/ |
| while (i) { |
| i--; |
| free((void *)(ep->ep_dtd[i]->item_unaligned_addr)); |
| ep->ep_dtd[i] = 0; |
| } |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| static int mxc_destroy_ep_dtd(u8 index) |
| { |
| struct mxc_ep_t *ep; |
| int i; |
| |
| if (index >= mxc_udc.max_ep) |
| DBG("%s ep %d is not valid\n", __func__, index); |
| |
| ep = mxc_udc.mxc_ep + index; |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| if (ep->ep_dtd[i]) { |
| free((void *)(ep->ep_dtd[i]->item_unaligned_addr)); |
| ep->ep_dtd[i] = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void mxc_ep_qh_setup(u8 ep_num, u8 dir, u8 ep_type, |
| u32 max_pkt_len, u32 zlt, u8 mult) |
| { |
| struct ep_queue_head *p_qh = mxc_udc.ep_qh + (2 * ep_num + dir); |
| u32 tmp = 0; |
| |
| tmp = max_pkt_len << 16; |
| switch (ep_type) { |
| case USB_ENDPOINT_XFER_CONTROL: |
| tmp |= (1 << 15); |
| break; |
| case USB_ENDPOINT_XFER_ISOC: |
| tmp |= (mult << 30); |
| break; |
| case USB_ENDPOINT_XFER_BULK: |
| case USB_ENDPOINT_XFER_INT: |
| break; |
| default: |
| DBG("error ep type is %d\n", ep_type); |
| return; |
| } |
| if (zlt) |
| tmp |= (1<<29); |
| |
| p_qh->config = tmp; |
| |
| /*flush qh's config field to physical memory*/ |
| flush_dcache_range((unsigned long)p_qh, |
| CACHE_ALIGNED_END(p_qh, sizeof(struct ep_queue_head))); |
| } |
| |
| static void mxc_ep_setup(u8 ep_num, u8 dir, u8 ep_type) |
| { |
| u32 epctrl = 0; |
| epctrl = readl(USB_ENDPTCTRL(ep_num)); |
| if (dir) { |
| if (ep_num) |
| epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; |
| epctrl |= EPCTRL_TX_ENABLE; |
| epctrl |= ((u32)(ep_type) << EPCTRL_TX_EP_TYPE_SHIFT); |
| } else { |
| if (ep_num) |
| epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; |
| epctrl |= EPCTRL_RX_ENABLE; |
| epctrl |= ((u32)(ep_type) << EPCTRL_RX_EP_TYPE_SHIFT); |
| } |
| writel(epctrl, USB_ENDPTCTRL(ep_num)); |
| } |
| |
| static void mxc_ep_destroy(u8 ep_num, u8 dir) |
| { |
| u32 epctrl = 0; |
| epctrl = readl(USB_ENDPTCTRL(ep_num)); |
| if (dir) |
| epctrl &= ~EPCTRL_TX_ENABLE; |
| else |
| epctrl &= ~EPCTRL_RX_ENABLE; |
| |
| writel(epctrl, USB_ENDPTCTRL(ep_num)); |
| } |
| |
| |
| static void mxc_tqi_init_page(struct ep_queue_item *tqi) |
| { |
| tqi->page0 = virt_to_phys((void *)(tqi->page_vir)); |
| tqi->page1 = tqi->page0 + 0x1000; |
| tqi->page2 = tqi->page1 + 0x1000; |
| tqi->page3 = tqi->page2 + 0x1000; |
| tqi->page4 = tqi->page3 + 0x1000; |
| } |
| |
| static int mxc_malloc_ep0_ptr(struct mxc_ep_t *ep) |
| { |
| int i; |
| struct ep_queue_item *tqi; |
| int max_pkt_size = USB_MAX_CTRL_PAYLOAD; |
| |
| ep->max_pkt_size = max_pkt_size; |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| tqi = ep->ep_dtd[i]; |
| tqi->page_vir = (u32)malloc_aligned_buffer(&tqi->page_unaligned, |
| max_pkt_size, |
| USB_MEM_ALIGN_BYTE); |
| if ((void *)tqi->page_vir == NULL) { |
| printf("malloc ep's dtd bufer failure, i=%d\n", i); |
| return -1; |
| } |
| mxc_tqi_init_page(tqi); |
| |
| /*flush dtd's config field to physical memory*/ |
| flush_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| } |
| return 0; |
| } |
| |
| static int mxc_free_ep0_ptr(struct mxc_ep_t *ep) |
| { |
| int i; |
| struct ep_queue_item *tqi; |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| tqi = ep->ep_dtd[i]; |
| if (tqi->page_vir) { |
| free((void *)(tqi->page_unaligned)); |
| tqi->page_vir = 0; |
| tqi->page_unaligned = 0; |
| tqi->page0 = 0; |
| tqi->page1 = 0; |
| tqi->page2 = 0; |
| tqi->page3 = 0; |
| tqi->page4 = 0; |
| |
| /*flush dtd's config field to physical memory*/ |
| flush_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void ep0_setup(void) |
| { |
| mxc_ep_qh_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, |
| USB_MAX_CTRL_PAYLOAD, 0, 0); |
| mxc_ep_qh_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, |
| USB_MAX_CTRL_PAYLOAD, 0, 0); |
| mxc_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); |
| mxc_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); |
| mxc_init_ep_dtd(0 * 2 + USB_RECV); |
| mxc_init_ep_dtd(0 * 2 + USB_SEND); |
| mxc_malloc_ep0_ptr(mxc_udc.mxc_ep + (USB_RECV)); |
| mxc_malloc_ep0_ptr(mxc_udc.mxc_ep + (USB_SEND)); |
| } |
| |
| static void ep0_destroy(void) |
| { |
| mxc_ep_destroy(0, USB_RECV); |
| mxc_ep_destroy(0, USB_SEND); |
| mxc_free_ep0_ptr(mxc_udc.mxc_ep + (USB_RECV)); |
| mxc_free_ep0_ptr(mxc_udc.mxc_ep + (USB_SEND)); |
| mxc_destroy_ep_dtd(0 * 2 + USB_RECV); |
| mxc_destroy_ep_dtd(0 * 2 + USB_SEND); |
| } |
| |
| static int mxc_tqi_is_busy(struct ep_queue_item *tqi) |
| { |
| /* bit 7 is set by software when send, clear by controller |
| when finish */ |
| /*Invalidate cache to gain dtd content from physical memory*/ |
| invalidate_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| return tqi->info & (1 << 7); |
| } |
| |
| static int mxc_ep_xfer_is_working(struct mxc_ep_t *ep, u32 in) |
| { |
| /* in: means device -> host */ |
| u32 bitmask = 1 << (ep->epnum + in * 16); |
| u32 temp, prime, tstat; |
| |
| prime = (bitmask & readl(USB_ENDPTPRIME)); |
| if (prime) |
| return 1; |
| do { |
| temp = readl(USB_USBCMD); |
| writel(temp|USB_CMD_ATDTW, USB_USBCMD); |
| tstat = readl(USB_ENDPTSTAT) & bitmask; |
| } while (!(readl(USB_USBCMD) & USB_CMD_ATDTW)); |
| writel(temp & (~USB_CMD_ATDTW), USB_USBCMD); |
| |
| if (tstat) |
| return 1; |
| return 0; |
| } |
| |
| static void mxc_update_qh(struct mxc_ep_t *ep, |
| struct ep_queue_item *tqi, u32 in) |
| { |
| /* in: means device -> host */ |
| struct ep_queue_head *qh = ep->ep_qh; |
| u32 bitmask = 1 << (ep->epnum + in * 16); |
| DBG("%s, line %d, epnum=%d, in=%d\n", __func__, |
| __LINE__, ep->epnum, in); |
| qh->next_queue_item = virt_to_phys(tqi); |
| qh->info = 0; |
| |
| /*flush qh''s config field to physical memory*/ |
| flush_dcache_range((unsigned long)qh, |
| CACHE_ALIGNED_END(qh, sizeof(struct ep_queue_head))); |
| |
| writel(bitmask, USB_ENDPTPRIME); |
| } |
| |
| static void _dump_buf(u8 *buf, u32 len) |
| { |
| #ifdef DEBUG |
| char *data = (char *)buf; |
| int i; |
| for (i = 0; i < len; i++) { |
| printf("0x%02x ", data[i]); |
| if (i%16 == 15) |
| printf("\n"); |
| } |
| printf("\n"); |
| #endif |
| } |
| |
| static void mxc_udc_queue_update(u8 epnum, |
| u8 *data, u32 len, u32 tx) |
| { |
| struct mxc_ep_t *ep; |
| struct ep_queue_item *tqi, *head, *last; |
| int send = 0; |
| int in; |
| |
| head = last = NULL; |
| in = ep_is_in(epnum, tx); |
| ep = mxc_udc.mxc_ep + (epnum * 2 + in); |
| DBG("epnum = %d, in = %d\n", epnum, in); |
| do { |
| tqi = ep->ep_dtd[ep->index]; |
| DBG("%s, index = %d, tqi = %p\n", __func__, ep->index, tqi); |
| while (mxc_tqi_is_busy(tqi)) |
| ; |
| mxc_tqi_init_page(tqi); |
| DBG("%s, line = %d, len = %d\n", __func__, __LINE__, len); |
| inc_index(ep->index); |
| send = MIN(len, ep->max_pkt_size); |
| if (data) { |
| memcpy((void *)tqi->page_vir, (void *)data, send); |
| _dump_buf((u8 *)(tqi->page_vir), send); |
| |
| flush_dcache_range((unsigned long)(tqi->page_vir), |
| CACHE_ALIGNED_END(tqi->page_vir, send)); |
| } |
| if (!head) |
| last = head = tqi; |
| else { |
| last->next_item_ptr = virt_to_phys(tqi); |
| last->next_item_vir = tqi; |
| last = tqi; |
| } |
| if (!tx) |
| tqi->reserved[0] = send; |
| /* we set IOC for every dtd */ |
| tqi->info = ((send << 16) | (1 << 15) | (1 << 7)); |
| data += send; |
| len -= send; |
| |
| flush_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| } while (len); |
| |
| last->next_item_ptr = 0x1; /* end */ |
| flush_dcache_range((unsigned long)last, |
| CACHE_ALIGNED_END(last, sizeof(struct ep_queue_item))); |
| |
| if (ep->tail) { |
| ep->tail->next_item_ptr = virt_to_phys(head); |
| ep->tail->next_item_vir = head; |
| flush_dcache_range((unsigned long)(ep->tail), |
| CACHE_ALIGNED_END(ep->tail, sizeof(struct ep_queue_item))); |
| |
| if (mxc_ep_xfer_is_working(ep, in)) { |
| DBG("ep is working\n"); |
| goto out; |
| } |
| } |
| mxc_update_qh(ep, head, in); |
| out: |
| ep->tail = last; |
| } |
| |
| static void mxc_udc_txqueue_update(u8 ep, u8 *data, u32 len) |
| { |
| printf("[SEND DATA] EP= %d, Len = 0x%x\n", ep, len); |
| _dump_buf(data, len); |
| mxc_udc_queue_update(ep, data, len, 1); |
| } |
| |
| void mxc_udc_rxqueue_update(u8 ep, u32 len) |
| { |
| mxc_udc_queue_update(ep, NULL, len, 0); |
| } |
| |
| static void mxc_ep0_stall(void) |
| { |
| u32 temp; |
| temp = readl(USB_ENDPTCTRL(0)); |
| temp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; |
| writel(temp, USB_ENDPTCTRL(0)); |
| } |
| |
| static void mxc_usb_run(void) |
| { |
| unsigned int temp = 0; |
| |
| /* Enable DR irq reg */ |
| temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN |
| | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN |
| | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; |
| |
| writel(temp, USB_USBINTR); |
| |
| /* Set controller to Run */ |
| temp = readl(USB_USBCMD); |
| temp |= USB_CMD_RUN_STOP; |
| writel(temp, USB_USBCMD); |
| } |
| |
| static void mxc_usb_stop(void) |
| { |
| unsigned int temp = 0; |
| |
| writel(temp, USB_USBINTR); |
| |
| /* Set controller to Stop */ |
| temp = readl(USB_USBCMD); |
| temp &= ~USB_CMD_RUN_STOP; |
| writel(temp, USB_USBCMD); |
| } |
| |
| static void usb_phy_init(void) |
| { |
| u32 temp; |
| /* select 24M clk */ |
| temp = readl(USB_PHY1_CTRL); |
| temp &= ~3; |
| temp |= 1; |
| writel(temp, USB_PHY1_CTRL); |
| /* Config PHY interface */ |
| temp = readl(USB_PORTSC1); |
| temp &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); |
| temp |= PORTSCX_PTW_16BIT; |
| writel(temp, USB_PORTSC1); |
| DBG("Config PHY END\n"); |
| } |
| |
| static void usb_set_mode_device(void) |
| { |
| u32 temp; |
| |
| /* Set controller to stop */ |
| temp = readl(USB_USBCMD); |
| temp &= ~USB_CMD_RUN_STOP; |
| writel(temp, USB_USBCMD); |
| |
| while (readl(USB_USBCMD) & USB_CMD_RUN_STOP) |
| ; |
| /* Do core reset */ |
| temp = readl(USB_USBCMD); |
| temp |= USB_CMD_CTRL_RESET; |
| writel(temp, USB_USBCMD); |
| while (readl(USB_USBCMD) & USB_CMD_CTRL_RESET) |
| ; |
| DBG("DOORE RESET END\n"); |
| |
| #if defined(CONFIG_MX6) |
| reset_usb_phy1(); |
| #endif |
| DBG("init core to device mode\n"); |
| temp = readl(USB_USBMODE); |
| temp &= ~USB_MODE_CTRL_MODE_MASK; /* clear mode bits */ |
| temp |= USB_MODE_CTRL_MODE_DEVICE; |
| /* Disable Setup Lockout */ |
| temp |= USB_MODE_SETUP_LOCK_OFF; |
| writel(temp, USB_USBMODE); |
| |
| temp = readl(USB_OTGSC); |
| temp |= (1<<3); |
| writel(temp, USB_OTGSC); |
| DBG("init core to device mode end\n"); |
| } |
| |
| static void usb_init_eps(void) |
| { |
| u32 temp; |
| DBG("Flush begin\n"); |
| temp = readl(USB_ENDPTNAKEN); |
| temp |= 0x10001; /* clear mode bits */ |
| writel(temp, USB_ENDPTNAKEN); |
| writel(readl(USB_ENDPTCOMPLETE), USB_ENDPTCOMPLETE); |
| writel(readl(USB_ENDPTSETUPSTAT), USB_ENDPTSETUPSTAT); |
| writel(0xffffffff, USB_ENDPTFLUSH); |
| DBG("FLUSH END\n"); |
| } |
| |
| static void usb_udc_init(void) |
| { |
| DBG("\n************************\n"); |
| DBG(" usb init start\n"); |
| DBG("\n************************\n"); |
| |
| usb_phy_init(); |
| usb_set_mode_device(); |
| mxc_init_usb_qh(); |
| usb_init_eps(); |
| mxc_init_ep_struct(); |
| ep0_setup(); |
| usb_inited = 1; |
| } |
| |
| static void usb_udc_destroy(void) |
| { |
| usb_set_mode_device(); |
| |
| usb_inited = 0; |
| |
| ep0_destroy(); |
| mxc_destroy_ep_struct(); |
| mxc_destroy_usb_qh(); |
| } |
| |
| void usb_shutdown(void) |
| { |
| u32 temp; |
| /* disable pullup */ |
| temp = readl(USB_USBCMD); |
| temp &= ~USB_CMD_RUN_STOP; |
| writel(temp, USB_USBCMD); |
| mdelay(2); |
| } |
| |
| static void ch9getstatus(u8 request_type, |
| u16 value, u16 index, u16 length) |
| { |
| u16 tmp; |
| |
| if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { |
| tmp = 1 << 0; /* self powerd */ |
| tmp |= 0 << 1; /* not remote wakeup able */ |
| } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { |
| tmp = 0; |
| } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { |
| tmp = 0; |
| } |
| mxc_udc.ep0_dir = USB_DIR_IN; |
| mxc_udc_queue_update(0, (u8 *)&tmp, 2, 0xffffffff); |
| } |
| |
| static void mxc_udc_read_setup_pkt(struct usb_device_request *s) |
| { |
| u32 temp; |
| temp = readl(USB_ENDPTSETUPSTAT); |
| writel(temp, USB_ENDPTSETUPSTAT); |
| DBG("setup stat %x\n", temp); |
| do { |
| temp = readl(USB_USBCMD); |
| temp |= USB_CMD_SUTW; |
| writel(temp, USB_USBCMD); |
| |
| invalidate_dcache_range((unsigned long)(mxc_udc.mxc_ep[0].ep_qh), |
| CACHE_ALIGNED_END((mxc_udc.mxc_ep[0].ep_qh), |
| sizeof(struct ep_queue_head))); |
| |
| memcpy((void *)s, |
| (void *)mxc_udc.mxc_ep[0].ep_qh->setup_data, 8); |
| } while (!(readl(USB_USBCMD) & USB_CMD_SUTW)); |
| |
| DBG("handle_setup s.type=%x req=%x len=%x\n", |
| s->bmRequestType, s->bRequest, s->wLength); |
| temp = readl(USB_USBCMD); |
| temp &= ~USB_CMD_SUTW; |
| writel(temp, USB_USBCMD); |
| |
| DBG("[SETUP] type=%x req=%x val=%x index=%x len=%x\n", |
| s->bmRequestType, s->bRequest, |
| s->wValue, s->wIndex, |
| s->wLength); |
| } |
| |
| static void mxc_udc_recv_setup(void) |
| { |
| struct usb_device_request *s = &ep0_urb->device_request; |
| |
| mxc_udc_read_setup_pkt(s); |
| if (s->wLength) { |
| /* If has a data phase, |
| * then prime a dtd for status stage which has zero length DATA0. |
| * The direction of status stage should oppsite to direction of data phase. |
| */ |
| mxc_udc.ep0_dir = (s->bmRequestType & USB_DIR_IN) ? |
| USB_DIR_OUT : USB_DIR_IN; |
| mxc_udc_queue_update(0, NULL, 0, 0xffffffff); |
| } |
| if (ep0_recv_setup(ep0_urb)) { |
| mxc_ep0_stall(); |
| return; |
| } |
| switch (s->bRequest) { |
| case USB_REQ_GET_STATUS: |
| if ((s->bmRequestType & (USB_DIR_IN | USB_TYPE_MASK)) != |
| (USB_DIR_IN | USB_TYPE_STANDARD)) |
| break; |
| ch9getstatus(s->bmRequestType, s->wValue, |
| s->wIndex, s->wLength); |
| |
| DBG("[SETUP] REQ_GET_STATUS\n"); |
| return; |
| case USB_REQ_SET_ADDRESS: |
| if (s->bmRequestType != (USB_DIR_OUT | |
| USB_TYPE_STANDARD | USB_RECIP_DEVICE)) |
| break; |
| mxc_udc.setaddr = 1; |
| mxc_udc.ep0_dir = USB_DIR_IN; |
| mxc_udc_queue_update(0, NULL, 0, 0xffffffff); |
| usbd_device_event_irq(udc_device, DEVICE_ADDRESS_ASSIGNED, 0); |
| DBG("[SETUP] REQ_SET_ADDRESS\n"); |
| return; |
| case USB_REQ_SET_CONFIGURATION: |
| usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); |
| DBG("[SETUP] REQ_SET_CONFIGURATION\n"); |
| case USB_REQ_CLEAR_FEATURE: |
| case USB_REQ_SET_FEATURE: |
| { |
| int rc = -1; |
| if ((s->bmRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) == |
| (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) |
| rc = 0; |
| else if ((s->bmRequestType & |
| (USB_RECIP_MASK | USB_TYPE_MASK)) == |
| (USB_RECIP_DEVICE | USB_TYPE_STANDARD)) |
| rc = 0; |
| else |
| break; |
| if (rc == 0) { |
| mxc_udc.ep0_dir = USB_DIR_IN; |
| mxc_udc_queue_update(0, NULL, 0, 0xffffffff); |
| } |
| return; |
| } |
| default: |
| break; |
| } |
| if (s->wLength) { |
| mxc_udc.ep0_dir = (s->bmRequestType & USB_DIR_IN) ? |
| USB_DIR_IN : USB_DIR_OUT; |
| mxc_udc_queue_update(0, ep0_urb->buffer, |
| ep0_urb->actual_length, 0xffffffff); |
| ep0_urb->actual_length = 0; |
| } else { |
| mxc_udc.ep0_dir = USB_DIR_IN; |
| mxc_udc_queue_update(0, NULL, 0, 0xffffffff); |
| } |
| } |
| |
| static int mxc_udc_tqi_empty(struct ep_queue_item *tqi) |
| { |
| int ret; |
| |
| invalidate_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| |
| ret = tqi->info & (1 << 7); |
| return ret; |
| } |
| |
| static struct usb_endpoint_instance *mxc_get_epi(u8 epnum) |
| { |
| int i; |
| for (i = 0; i < udc_device->bus->max_endpoints; i++) { |
| if ((udc_device->bus->endpoint_array[i].endpoint_address & |
| USB_ENDPOINT_NUMBER_MASK) == epnum) |
| return &udc_device->bus->endpoint_array[i]; |
| } |
| return NULL; |
| } |
| |
| static u32 _mxc_ep_recv_data(u8 epnum, struct ep_queue_item *tqi) |
| { |
| struct usb_endpoint_instance *epi = mxc_get_epi(epnum); |
| struct urb *urb; |
| u32 len = 0; |
| |
| if (!epi) |
| return 0; |
| |
| invalidate_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| |
| urb = epi->rcv_urb; |
| if (urb) { |
| u8 *data = urb->buffer + urb->actual_length; |
| int remain_len = (tqi->info >> 16) & (0xefff); |
| len = tqi->reserved[0] - remain_len; |
| DBG("recv len %d-%d-%d\n", len, tqi->reserved[0], remain_len); |
| |
| |
| invalidate_dcache_range((unsigned long)tqi->page_vir, |
| CACHE_ALIGNED_END(tqi->page_vir, len)); |
| memcpy(data, (void *)tqi->page_vir, len); |
| |
| _dump_buf(data, len); |
| } |
| return len; |
| } |
| |
| static void mxc_udc_ep_recv(u8 epnum) |
| { |
| struct mxc_ep_t *ep = mxc_udc.mxc_ep + (epnum * 2 + USB_RECV); |
| struct ep_queue_item *tqi; |
| while (1) { |
| u32 nbytes; |
| tqi = ep->ep_dtd[ep->done]; |
| if (mxc_udc_tqi_empty(tqi)) |
| break; |
| nbytes = _mxc_ep_recv_data(epnum, tqi); |
| usbd_rcv_complete(ep->epi, nbytes, 0); |
| inc_index(ep->done); |
| if (ep->done == ep->index) |
| break; |
| } |
| } |
| |
| static void mxc_udc_handle_xfer_complete(void) |
| { |
| int i; |
| u32 bitpos = readl(USB_ENDPTCOMPLETE); |
| |
| writel(bitpos, USB_ENDPTCOMPLETE); |
| |
| for (i = 0; i < mxc_udc.max_ep; i++) { |
| int epnum = i >> 1; |
| int dir = i % 2; |
| u32 bitmask = 1 << (epnum + 16 * dir); |
| if (!(bitmask & bitpos)) |
| continue; |
| DBG("ep %d, dir %d, complete\n", epnum, dir); |
| if (!epnum) { |
| if (mxc_udc.setaddr) { |
| writel(udc_device->address << 25, |
| USB_DEVICEADDR); |
| mxc_udc.setaddr = 0; |
| } |
| continue; |
| } |
| DBG("############### dir = %d ***************\n", dir); |
| if (dir == USB_SEND) |
| continue; |
| mxc_udc_ep_recv(epnum); |
| } |
| } |
| |
| static void usb_dev_hand_usbint(void) |
| { |
| if (readl(USB_ENDPTSETUPSTAT)) { |
| DBG("recv one setup packet\n"); |
| mxc_udc_recv_setup(); |
| } |
| if (readl(USB_ENDPTCOMPLETE)) { |
| DBG("Dtd complete irq\n"); |
| mxc_udc_handle_xfer_complete(); |
| } |
| } |
| |
| static void usb_dev_hand_reset(void) |
| { |
| u32 temp; |
| temp = readl(USB_DEVICEADDR); |
| temp &= ~0xfe000000; |
| writel(temp, USB_DEVICEADDR); |
| writel(readl(USB_ENDPTSETUPSTAT), USB_ENDPTSETUPSTAT); |
| writel(readl(USB_ENDPTCOMPLETE), USB_ENDPTCOMPLETE); |
| while (readl(USB_ENDPTPRIME)) |
| ; |
| writel(0xffffffff, USB_ENDPTFLUSH); |
| DBG("reset-PORTSC=%x\n", readl(USB_PORTSC1)); |
| usbd_device_event_irq(udc_device, DEVICE_RESET, 0); |
| } |
| |
| void usb_dev_hand_pci(void) |
| { |
| u32 speed; |
| while (readl(USB_PORTSC1) & PORTSCX_PORT_RESET) |
| ; |
| speed = readl(USB_PORTSC1) & PORTSCX_PORT_SPEED_MASK; |
| switch (speed) { |
| case PORTSCX_PORT_SPEED_HIGH: |
| usb_highspeed = 2; |
| break; |
| case PORTSCX_PORT_SPEED_FULL: |
| usb_highspeed = 1; |
| break; |
| case PORTSCX_PORT_SPEED_LOW: |
| usb_highspeed = 0; |
| break; |
| default: |
| break; |
| } |
| DBG("portspeed=%d, speed = %x, PORTSC = %x\n", |
| usb_highspeed, speed, readl(USB_PORTSC1)); |
| } |
| |
| void usb_dev_hand_suspend(void) |
| { |
| } |
| |
| void mxc_irq_poll(void) |
| { |
| unsigned irq_src = readl(USB_USBSTS) & readl(USB_USBINTR); |
| writel(irq_src, USB_USBSTS); |
| |
| if (irq_src == 0) |
| return; |
| |
| if (irq_src & USB_STS_INT) |
| usb_dev_hand_usbint(); |
| |
| if (irq_src & USB_STS_RESET) { |
| printf("USB_RESET\n"); |
| usb_dev_hand_reset(); |
| } |
| if (irq_src & USB_STS_PORT_CHANGE) { |
| printf("USB_PORT_CHANGE 0x%x\n", irq_src); |
| usb_dev_hand_pci(); |
| } |
| if (irq_src & USB_STS_SUSPEND) |
| printf("USB_SUSPEND\n"); |
| if (irq_src & USB_STS_ERR) |
| printf("USB_ERR\n"); |
| } |
| |
| void mxc_udc_wait_cable_insert(void) |
| { |
| u32 temp; |
| int cable_connect = 1; |
| |
| do { |
| udelay(50); |
| |
| temp = readl(USB_OTGSC); |
| if (temp & (OTGSC_B_SESSION_VALID)) { |
| printf("USB Mini b cable Connected!\n"); |
| break; |
| } else if (cable_connect == 1) { |
| printf("wait usb cable into the connector!\n"); |
| cable_connect = 0; |
| } |
| } while (1); |
| } |
| |
| void udc_disable_over_current(void) |
| { |
| u32 temp; |
| temp = readl(USB_OTG_CTRL); |
| temp |= (UCTRL_OVER_CUR_POL); |
| writel(temp, USB_OTG_CTRL); |
| } |
| |
| /* |
| * mxc_udc_init function |
| */ |
| int mxc_udc_init(void) |
| { |
| udc_pins_setting(); |
| set_usb_phy1_clk(); |
| enable_usboh3_clk(1); |
| #if defined(CONFIG_MX6) |
| udc_disable_over_current(); |
| #endif |
| enable_usb_phy1_clk(1); |
| usb_udc_init(); |
| |
| return 0; |
| } |
| |
| /* |
| * mxc_udc_init function |
| */ |
| int mxc_udc_destroy(void) |
| { |
| usb_udc_destroy(); |
| enable_usboh3_clk(0); |
| enable_usb_phy1_clk(0); |
| |
| return 0; |
| } |
| |
| void mxc_udc_poll(void) |
| { |
| mxc_irq_poll(); |
| } |
| |
| /* |
| * Functions for gadget APIs |
| */ |
| int udc_init(void) |
| { |
| mxc_udc_init(); |
| return 0; |
| } |
| |
| int udc_destroy(void) |
| { |
| udc_disable(); |
| mxc_udc_destroy(); |
| return 0; |
| } |
| |
| void udc_setup_ep(struct usb_device_instance *device, u32 index, |
| struct usb_endpoint_instance *epi) |
| { |
| u8 dir, epnum, zlt, mult; |
| u8 ep_type; |
| u32 max_pkt_size; |
| int ep_addr; |
| struct mxc_ep_t *ep; |
| |
| if (epi) { |
| zlt = 1; |
| mult = 0; |
| ep_addr = epi->endpoint_address; |
| epnum = ep_addr & USB_ENDPOINT_NUMBER_MASK; |
| DBG("setup ep %d\n", epnum); |
| if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { |
| dir = USB_SEND; |
| ep_type = epi->tx_attributes; |
| max_pkt_size = epi->tx_packetSize; |
| } else { |
| dir = USB_RECV; |
| ep_type = epi->rcv_attributes; |
| max_pkt_size = epi->rcv_packetSize; |
| } |
| if (ep_type == USB_ENDPOINT_XFER_ISOC) { |
| mult = (u32)(1 + ((max_pkt_size >> 11) & 0x03)); |
| max_pkt_size = max_pkt_size & 0x7ff; |
| DBG("mult = %d\n", mult); |
| } |
| ep = mxc_udc.mxc_ep + (epnum * 2 + dir); |
| ep->epi = epi; |
| if (epnum) { |
| struct ep_queue_item *tqi; |
| int i; |
| |
| mxc_ep_qh_setup(epnum, dir, ep_type, |
| max_pkt_size, zlt, mult); |
| mxc_ep_setup(epnum, dir, ep_type); |
| mxc_init_ep_dtd(epnum * 2 + dir); |
| |
| /* malloc endpoint's dtd's data buffer*/ |
| ep->max_pkt_size = max_pkt_size; |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| tqi = ep->ep_dtd[i]; |
| tqi->page_vir = (u32)malloc_aligned_buffer( |
| &tqi->page_unaligned, max_pkt_size, |
| USB_MEM_ALIGN_BYTE); |
| if ((void *)tqi->page_vir == NULL) { |
| printf("malloc dtd bufer failure\n"); |
| return; |
| } |
| mxc_tqi_init_page(tqi); |
| |
| flush_dcache_range((unsigned long)tqi, |
| CACHE_ALIGNED_END(tqi, sizeof(struct ep_queue_item))); |
| } |
| } |
| } |
| } |
| |
| void udc_destroy_ep(struct usb_device_instance *device, |
| struct usb_endpoint_instance *epi) |
| { |
| struct mxc_ep_t *ep; |
| int ep_addr; |
| u8 dir, epnum; |
| |
| if (epi) { |
| ep_addr = epi->endpoint_address; |
| epnum = ep_addr & USB_ENDPOINT_NUMBER_MASK; |
| |
| if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) |
| dir = USB_SEND; |
| else |
| dir = USB_RECV; |
| |
| ep = mxc_udc.mxc_ep + (epnum * 2 + dir); |
| ep->epi = 0; |
| |
| if (epnum) { |
| struct ep_queue_item *tqi; |
| int i; |
| |
| for (i = 0; i < EP_TQ_ITEM_SIZE; i++) { |
| tqi = ep->ep_dtd[i]; |
| if (tqi->page_vir) { |
| free((void *)(tqi->page_unaligned)); |
| tqi->page_unaligned = 0; |
| tqi->page_vir = 0; |
| tqi->page0 = 0; |
| tqi->page1 = 0; |
| tqi->page2 = 0; |
| tqi->page3 = 0; |
| tqi->page4 = 0; |
| } |
| } |
| |
| mxc_destroy_ep_dtd(epnum * 2 + dir); |
| mxc_ep_destroy(epnum, dir); |
| } |
| } |
| } |
| |
| int udc_endpoint_write(struct usb_endpoint_instance *epi) |
| { |
| struct urb *urb = epi->tx_urb; |
| int ep_num = epi->endpoint_address & USB_ENDPOINT_NUMBER_MASK; |
| u8 *data = (u8 *)urb->buffer + epi->sent; |
| int n = urb->actual_length - epi->sent; |
| mxc_udc_txqueue_update(ep_num, data, n); |
| epi->last = n; |
| |
| /* usbd_tx_complete will take care of updating 'sent' */ |
| usbd_tx_complete(epi); |
| return 0; |
| } |
| |
| void udc_enable(struct usb_device_instance *device) |
| { |
| udc_device = device; |
| ep0_urb = usbd_alloc_urb(udc_device, udc_device->bus->endpoint_array); |
| } |
| |
| void udc_disable(void) |
| { |
| usbd_dealloc_urb(ep0_urb); |
| udc_device = NULL; |
| } |
| |
| void udc_startup_events(struct usb_device_instance *device) |
| { |
| usbd_device_event_irq(device, DEVICE_INIT, 0); |
| usbd_device_event_irq(device, DEVICE_CREATE, 0); |
| udc_enable(device); |
| } |
| |
| void udc_irq(void) |
| { |
| mxc_irq_poll(); |
| } |
| |
| void udc_connect(void) |
| { |
| mxc_usb_run(); |
| mxc_udc_wait_cable_insert(); |
| } |
| |
| void udc_disconnect(void) |
| { |
| /* imx6 will hang if access usb register without init oh3 |
| * clock, so not access it if not init. */ |
| if (usb_inited) |
| mxc_usb_stop(); |
| } |
| |
| void udc_set_nak(int epid) |
| { |
| } |
| |
| void udc_unset_nak(int epid) |
| { |
| } |