blob: 9cdf3c8592459ff9c428b254a7aa7a9601f34744 [file] [log] [blame]
/*
* 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)
{
}