diff options
author | Li Yang <leoli@freescale.com> | 2008-09-02 19:58:10 +0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-10-17 14:40:54 -0700 |
commit | 3948f0e0c999a6201e9898bb8fbe3c6cc1199276 (patch) | |
tree | c05ea6720321899326095cf1a74e2a4749b1c91e | |
parent | bedf0883cbe3015d21aec5ed47ddffb429f6cca7 (diff) |
usb: add Freescale QE/CPM USB peripheral controller driver
Some of Freescale SoC chips have a QE or CPM co-processor which
supports full speed USB. The driver adds device mode support
of both QE and CPM USB controller to Linux USB gadget. The
driver is tested with MPC8360 and MPC8272, and should work with
other models having QE/CPM given minor tweaks.
Signed-off-by: Xie Xiaobo <X.Xie@freescale.com>
Signed-off-by: Li Yang <leoli@freescale.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Cc: David Brownell <david-b@pacbell.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/usb/gadget/Kconfig | 19 | ||||
-rw-r--r-- | drivers/usb/gadget/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/fsl_qe_udc.c | 2723 | ||||
-rw-r--r-- | drivers/usb/gadget/fsl_qe_udc.h | 436 | ||||
-rw-r--r-- | drivers/usb/gadget/gadget_chips.h | 9 |
5 files changed, 3188 insertions, 0 deletions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 80a7c02dc95..c0dfd9be311 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -341,6 +341,25 @@ config USB_AMD5536UDC default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_FSL_QE + boolean "Freescale QE/CPM USB Device Controller" + depends on FSL_SOC && (QUICC_ENGINE || CPM) + help + Some of Freescale PowerPC processors have a Full Speed + QE/CPM2 USB controller, which support device mode with 4 + programmable endpoints. This driver supports the + controller in the MPC8360 and MPC8272, and should work with + controllers having QE or CPM2, given minor tweaks. + + Set CONFIG_USB_GADGET to "m" to build this driver as a + dynmically linked module called "fsl_qe_udc". + +config USB_FSL_QE + tristate + depends on USB_GADGET_FSL_QE + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_NET2280 boolean "NetChip 228x" depends on PCI diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 2267fa0b51b..bd4041b47dc 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/fsl_qe_udc.c b/drivers/usb/gadget/fsl_qe_udc.c new file mode 100644 index 00000000000..e9400e62f17 --- /dev/null +++ b/drivers/usb/gadget/fsl_qe_udc.c @@ -0,0 +1,2723 @@ +/* + * driver/usb/gadget/fsl_qe_udc.c + * + * Copyright (c) 2006-2008 Freescale Semiconductor, Inc. All rights reserved. + * + * Xie Xiaobo <X.Xie@freescale.com> + * Li Yang <leoli@freescale.com> + * Based on bareboard code from Shlomi Gridish. + * + * Description: + * Freescle QE/CPM USB Pheripheral Controller Driver + * The controller can be found on MPC8360, MPC8272, and etc. + * MPC8360 Rev 1.1 may need QE mircocode update + * + * 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. + */ + +#undef USB_TRACE + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/moduleparam.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <asm/qe.h> +#include <asm/cpm.h> +#include <asm/dma.h> +#include <asm/reg.h> +#include "fsl_qe_udc.h" + +#define DRIVER_DESC "Freescale QE/CPM USB Device Controller driver" +#define DRIVER_AUTHOR "Xie XiaoBo" +#define DRIVER_VERSION "1.0" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static const char driver_name[] = "fsl_qe_udc"; +static const char driver_desc[] = DRIVER_DESC; + +/*ep name is important in gadget, it should obey the convention of ep_match()*/ +static const char *const ep_name[] = { + "ep0-control", /* everyone has ep0 */ + /* 3 configurable endpoints */ + "ep1", + "ep2", + "ep3", +}; + +static struct usb_endpoint_descriptor qe_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +/* it is initialized in probe() */ +static struct qe_udc *udc_controller; + +/******************************************************************** + * Internal Used Function Start +********************************************************************/ +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + *--------------------------------------------------------------*/ +static void done(struct qe_ep *ep, struct qe_req *req, int status) +{ + struct qe_udc *udc = ep->udc; + unsigned char stopped = ep->stopped; + + /* the req->queue pointer is used by ep_queue() func, in which + * the request will be added into a udc_ep->queue 'd tail + * so here the req will be dropped from the ep->queue + */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (req->mapped) { + dma_unmap_single(udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else + dma_sync_single_for_cpu(udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + if (status && (status != -ESHUTDOWN)) + dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&udc->lock); + + /* this complete() should a func implemented by gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&udc->lock); + + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + *--------------------------------------------------------------*/ +static void nuke(struct qe_ep *ep, int status) +{ + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct qe_req *req = NULL; + req = list_entry(ep->queue.next, struct qe_req, queue); + + done(ep, req, status); + } +} + +/*---------------------------------------------------------------------------* + * USB and Endpoint manipulate process, include parameter and register * + *---------------------------------------------------------------------------*/ +/* @value: 1--set stall 0--clean stall */ +static int qe_eprx_stall_change(struct qe_ep *ep, int value) +{ + u16 tem_usep; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + tem_usep = in_be16(&udc->usb_regs->usb_usep[epnum]); + tem_usep = tem_usep & ~USB_RHS_MASK; + if (value == 1) + tem_usep |= USB_RHS_STALL; + else if (ep->dir == USB_DIR_IN) + tem_usep |= USB_RHS_IGNORE_OUT; + + out_be16(&udc->usb_regs->usb_usep[epnum], tem_usep); + return 0; +} + +static int qe_eptx_stall_change(struct qe_ep *ep, int value) +{ + u16 tem_usep; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + tem_usep = in_be16(&udc->usb_regs->usb_usep[epnum]); + tem_usep = tem_usep & ~USB_THS_MASK; + if (value == 1) + tem_usep |= USB_THS_STALL; + else if (ep->dir == USB_DIR_OUT) + tem_usep |= USB_THS_IGNORE_IN; + + out_be16(&udc->usb_regs->usb_usep[epnum], tem_usep); + + return 0; +} + +static int qe_ep0_stall(struct qe_udc *udc) +{ + qe_eptx_stall_change(&udc->eps[0], 1); + qe_eprx_stall_change(&udc->eps[0], 1); + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + return 0; +} + +static int qe_eprx_nack(struct qe_ep *ep) +{ + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + if (ep->state == EP_STATE_IDLE) { + /* Set the ep's nack */ + clrsetbits_be16(&udc->usb_regs->usb_usep[epnum], + USB_RHS_MASK, USB_RHS_NACK); + + /* Mask Rx and Busy interrupts */ + clrbits16(&udc->usb_regs->usb_usbmr, + (USB_E_RXB_MASK | USB_E_BSY_MASK)); + + ep->state = EP_STATE_NACK; + } + return 0; +} + +static int qe_eprx_normal(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + + if (ep->state == EP_STATE_NACK) { + clrsetbits_be16(&udc->usb_regs->usb_usep[ep->epnum], + USB_RTHS_MASK, USB_THS_IGNORE_IN); + + /* Unmask RX interrupts */ + out_be16(&udc->usb_regs->usb_usber, + USB_E_BSY_MASK | USB_E_RXB_MASK); + setbits16(&udc->usb_regs->usb_usbmr, + (USB_E_RXB_MASK | USB_E_BSY_MASK)); + + ep->state = EP_STATE_IDLE; + ep->has_data = 0; + } + + return 0; +} + +static int qe_ep_cmd_stoptx(struct qe_ep *ep) +{ + if (ep->udc->soc_type == PORT_CPM) + cpm_command(CPM_USB_STOP_TX | (ep->epnum << CPM_USB_EP_SHIFT), + CPM_USB_STOP_TX_OPCODE); + else + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, + ep->epnum, 0); + + return 0; +} + +static int qe_ep_cmd_restarttx(struct qe_ep *ep) +{ + if (ep->udc->soc_type == PORT_CPM) + cpm_command(CPM_USB_RESTART_TX | (ep->epnum << + CPM_USB_EP_SHIFT), CPM_USB_RESTART_TX_OPCODE); + else + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, + ep->epnum, 0); + + return 0; +} + +static int qe_ep_flushtxfifo(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + int i; + + i = (int)ep->epnum; + + qe_ep_cmd_stoptx(ep); + out_8(&udc->usb_regs->usb_uscom, + USB_CMD_FLUSH_FIFO | (USB_CMD_EP_MASK & (ep->epnum))); + out_be16(&udc->ep_param[i]->tbptr, in_be16(&udc->ep_param[i]->tbase)); + out_be32(&udc->ep_param[i]->tstate, 0); + out_be16(&udc->ep_param[i]->tbcnt, 0); + + ep->c_txbd = ep->txbase; + ep->n_txbd = ep->txbase; + qe_ep_cmd_restarttx(ep); + return 0; +} + +static int qe_ep_filltxfifo(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + + out_8(&udc->usb_regs->usb_uscom, + USB_CMD_STR_FIFO | (USB_CMD_EP_MASK & (ep->epnum))); + return 0; +} + +static int qe_epbds_reset(struct qe_udc *udc, int pipe_num) +{ + struct qe_ep *ep; + u32 bdring_len; + struct qe_bd __iomem *bd; + int i; + + ep = &udc->eps[pipe_num]; + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + bd = ep->rxbase; + for (i = 0; i < (bdring_len - 1); i++) { + out_be32((u32 __iomem *)bd, R_E | R_I); + bd++; + } + out_be32((u32 __iomem *)bd, R_E | R_I | R_W); + + bd = ep->txbase; + for (i = 0; i < USB_BDRING_LEN_TX - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32((u32 __iomem *)bd, T_W); + + return 0; +} + +static int qe_ep_reset(struct qe_udc *udc, int pipe_num) +{ + struct qe_ep *ep; + u16 tmpusep; + + ep = &udc->eps[pipe_num]; + tmpusep = in_be16(&udc->usb_regs->usb_usep[pipe_num]); + tmpusep &= ~USB_RTHS_MASK; + + switch (ep->dir) { + case USB_DIR_BOTH: + qe_ep_flushtxfifo(ep); + break; + case USB_DIR_OUT: + tmpusep |= USB_THS_IGNORE_IN; + break; + case USB_DIR_IN: + qe_ep_flushtxfifo(ep); + tmpusep |= USB_RHS_IGNORE_OUT; + break; + default: + break; + } + out_be16(&udc->usb_regs->usb_usep[pipe_num], tmpusep); + + qe_epbds_reset(udc, pipe_num); + + return 0; +} + +static int qe_ep_toggledata01(struct qe_ep *ep) +{ + ep->data01 ^= 0x1; + return 0; +} + +static int qe_ep_bd_init(struct qe_udc *udc, unsigned char pipe_num) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + unsigned long tmp_addr = 0; + struct usb_ep_para __iomem *epparam; + int i; + struct qe_bd __iomem *bd; + int bdring_len; + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + epparam = udc->ep_param[pipe_num]; + /* alloc multi-ram for BD rings and set the ep parameters */ + tmp_addr = cpm_muram_alloc(sizeof(struct qe_bd) * (bdring_len + + USB_BDRING_LEN_TX), QE_ALIGNMENT_OF_BD); + out_be16(&epparam->rbase, (u16)tmp_addr); + out_be16(&epparam->tbase, (u16)(tmp_addr + + (sizeof(struct qe_bd) * bdring_len))); + + out_be16(&epparam->rbptr, in_be16(&epparam->rbase)); + out_be16(&epparam->tbptr, in_be16(&epparam->tbase)); + + ep->rxbase = cpm_muram_addr(tmp_addr); + ep->txbase = cpm_muram_addr(tmp_addr + (sizeof(struct qe_bd) + * bdring_len)); + ep->n_rxbd = ep->rxbase; + ep->e_rxbd = ep->rxbase; + ep->n_txbd = ep->txbase; + ep->c_txbd = ep->txbase; + ep->data01 = 0; /* data0 */ + + /* Init TX and RX bds */ + bd = ep->rxbase; + for (i = 0; i < bdring_len - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, R_W); + + bd = ep->txbase; + for (i = 0; i < USB_BDRING_LEN_TX - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, T_W); + + return 0; +} + +static int qe_ep_rxbd_update(struct qe_ep *ep) +{ + unsigned int size; + int i; + unsigned int tmp; + struct qe_bd __iomem *bd; + unsigned int bdring_len; + + if (ep->rxbase == NULL) + return -EINVAL; + + bd = ep->rxbase; + + ep->rxframe = kmalloc(sizeof(*ep->rxframe), GFP_ATOMIC); + if (ep->rxframe == NULL) { + dev_err(ep->udc->dev, "malloc rxframe failed\n"); + return -ENOMEM; + } + + qe_frame_init(ep->rxframe); + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + size = (ep->ep.maxpacket + USB_CRC_SIZE + 2) * (bdring_len + 1); + ep->rxbuffer = kzalloc(size, GFP_ATOMIC); + if (ep->rxbuffer == NULL) { + dev_err(ep->udc->dev, "malloc rxbuffer failed,size=%d\n", + size); + kfree(ep->rxframe); + return -ENOMEM; + } + + ep->rxbuf_d = virt_to_phys((void *)ep->rxbuffer); + if (ep->rxbuf_d == DMA_ADDR_INVALID) { + ep->rxbuf_d = dma_map_single(udc_controller->gadget.dev.parent, + ep->rxbuffer, + size, + DMA_FROM_DEVICE); + ep->rxbufmap = 1; + } else { + dma_sync_single_for_device(udc_controller->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + ep->rxbufmap = 0; + } + + size = ep->ep.maxpacket + USB_CRC_SIZE + 2; + tmp = ep->rxbuf_d; + tmp = (u32)(((tmp >> 2) << 2) + 4); + + for (i = 0; i < bdring_len - 1; i++) { + out_be32(&bd->buf, tmp); + out_be32((u32 __iomem *)bd, (R_E | R_I)); + tmp = tmp + size; + bd++; + } + out_be32(&bd->buf, tmp); + out_be32((u32 __iomem *)bd, (R_E | R_I | R_W)); + + return 0; +} + +static int qe_ep_register_init(struct qe_udc *udc, unsigned char pipe_num) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + struct usb_ep_para __iomem *epparam; + u16 usep, logepnum; + u16 tmp; + u8 rtfcr = 0; + + epparam = udc->ep_param[pipe_num]; + + usep = 0; + logepnum = (ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + usep |= (logepnum << USB_EPNUM_SHIFT); + + switch (ep->desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + usep |= USB_TRANS_BULK; + break; + case USB_ENDPOINT_XFER_ISOC: + usep |= USB_TRANS_ISO; + break; + case USB_ENDPOINT_XFER_INT: + usep |= USB_TRANS_INT; + break; + default: + usep |= USB_TRANS_CTR; + break; + } + + switch (ep->dir) { + case USB_DIR_OUT: + usep |= USB_THS_IGNORE_IN; + break; + case USB_DIR_IN: + usep |= USB_RHS_IGNORE_OUT; + break; + default: + break; + } + out_be16(&udc->usb_regs->usb_usep[pipe_num], usep); + + rtfcr = 0x30; + out_8(&epparam->rbmr, rtfcr); + out_8(&epparam->tbmr, rtfcr); + + tmp = (u16)(ep->ep.maxpacket + USB_CRC_SIZE); + /* MRBLR must be divisble by 4 */ + tmp = (u16)(((tmp >> 2) << 2) + 4); + out_be16(&epparam->mrblr, tmp); + + return 0; +} + +static int qe_ep_init(struct qe_udc *udc, + unsigned char pipe_num, + const struct usb_endpoint_descriptor *desc) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + unsigned long flags; + int reval = 0; + u16 max = 0; + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* check the max package size validate for this endpoint */ + /* Refer to USB2.0 spec table 9-13, + */ + if (pipe_num != 0) { + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if ((max == 128) || (max == 256) || (max == 512)) + break; + default: + switch (max) { + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + case USB_SPEED_LOW: + goto en_done; + } + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto en_done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto en_done; + } + break; + case USB_ENDPOINT_XFER_CONTROL: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + switch (max) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + goto en_done; + } + case USB_SPEED_LOW: + switch (max) { + case 1: + case 2: + case 4: + case 8: + break; + default: + goto en_done; + } + default: + goto en_done; + } + break; + + default: + goto en_done; + } + } /* if ep0*/ + + spin_lock_irqsave(&udc->lock, flags); + + /* initialize ep structure */ + ep->ep.maxpacket = max; + ep->tm = (u8)(desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); + ep->desc = desc; + ep->stopped = 0; + ep->init = 1; + + if (pipe_num == 0) { + ep->dir = USB_DIR_BOTH; + udc->ep0_dir = USB_DIR_OUT; + udc->ep0_state = WAIT_FOR_SETUP; + } else { + switch (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + case USB_DIR_OUT: + ep->dir = USB_DIR_OUT; + break; + case USB_DIR_IN: + ep->dir = USB_DIR_IN; + default: + break; + } + } + + /* hardware special operation */ + qe_ep_bd_init(udc, pipe_num); + if ((ep->tm == USBP_TM_CTL) || (ep->dir == USB_DIR_OUT)) { + reval = qe_ep_rxbd_update(ep); + if (reval) + goto en_done1; + } + + if ((ep->tm == USBP_TM_CTL) || (ep->dir == USB_DIR_IN)) { + ep->txframe = kmalloc(sizeof(*ep->txframe), GFP_ATOMIC); + if (ep->txframe == NULL) { + dev_err(udc->dev, "malloc txframe failed\n"); + goto en_done2; + } + qe_frame_init(ep->txframe); + } + + qe_ep_register_init(udc, pipe_num); + + /* Now HW will be NAKing transfers to that EP, + * until a buffer is queued to it. */ + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +en_done2: + kfree(ep->rxbuffer); + kfree(ep->rxframe); +en_done1: + spin_unlock_irqrestore(&udc->lock, flags); +en_done: + dev_dbg(udc->dev, "failed to initialize %s\n", ep->ep.name); + return -ENODEV; +} + +static inline void qe_usb_enable(void) +{ + setbits8(&udc_controller->usb_regs->usb_usmod, USB_MODE_EN); +} + +static inline void qe_usb_disable(void) +{ + clrbits8(&udc_controller->usb_regs->usb_usmod, USB_MODE_EN); +} + +/*----------------------------------------------------------------------------* + * USB and EP basic manipulate function end * + *----------------------------------------------------------------------------*/ + + +/****************************************************************************** + UDC transmit and receive process + ******************************************************************************/ +static void recycle_one_rxbd(struct qe_ep *ep) +{ + u32 bdstatus; + + bdstatus = in_be32((u32 __iomem *)ep->e_rxbd); + bdstatus = R_I | R_E | (bdstatus & R_W); + out_be32((u32 __iomem *)ep->e_rxbd, bdstatus); + + if (bdstatus & R_W) + ep->e_rxbd = ep->rxbase; + else + ep->e_rxbd++; +} + +static void recycle_rxbds(struct qe_ep *ep, unsigned char stopatnext) +{ + u32 bdstatus; + struct qe_bd __iomem *bd, *nextbd; + unsigned char stop = 0; + + nextbd = ep->n_rxbd; + bd = ep->e_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + + while (!(bdstatus & R_E) && !(bdstatus & BD_LENGTH_MASK) && !stop) { + bdstatus = R_E | R_I | (bdstatus & R_W); + out_be32((u32 __iomem *)bd, bdstatus); + + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + if (stopatnext && (bd == nextbd)) + stop = 1; + } + + ep->e_rxbd = bd; +} + +static void ep_recycle_rxbds(struct qe_ep *ep) +{ + struct qe_bd __iomem *bd = ep->n_rxbd; + u32 bdstatus; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + bdstatus = in_be32((u32 __iomem *)bd); + if (!(bdstatus & R_E) && !(bdstatus & BD_LENGTH_MASK)) { + bd = ep->rxbase + + ((in_be16(&udc->ep_param[epnum]->rbptr) - + in_be16(&udc->ep_param[epnum]->rbase)) + >> 3); + bdstatus = in_be32((u32 __iomem *)bd); + + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + ep->e_rxbd = bd; + recycle_rxbds(ep, 0); + ep->e_rxbd = ep->n_rxbd; + } else + recycle_rxbds(ep, 1); + + if (in_be16(&udc->usb_regs->usb_usber) & USB_E_BSY_MASK) + out_be16(&udc->usb_regs->usb_usber, USB_E_BSY_MASK); + + if (ep->has_data <= 0 && (!list_empty(&ep->queue))) + qe_eprx_normal(ep); + + ep->localnack = 0; +} + +static void setup_received_handle(struct qe_udc *udc, + struct usb_ctrlrequest *setup); +static int qe_ep_rxframe_handle(struct qe_ep *ep); +static void ep0_req_complete(struct qe_udc *udc, struct qe_req *req); +/* when BD PID is setup, handle the packet */ +static int ep0_setup_handle(struct qe_udc *udc) +{ + struct qe_ep *ep = &udc->eps[0]; + struct qe_frame *pframe; + unsigned int fsize; + u8 *cp; + + pframe = ep->rxframe; + if ((frame_get_info(pframe) & PID_SETUP) + && (udc->ep0_state == WAIT_FOR_SETUP)) { + fsize = frame_get_length(pframe); + if (unlikely(fsize != 8)) + return -EINVAL; + cp = (u8 *)&udc->local_setup_buff; + memcpy(cp, pframe->data, fsize); + ep->data01 = 1; + + /* handle the usb command base on the usb_ctrlrequest */ + setup_received_handle(udc, &udc->local_setup_buff); + return 0; + } + return -EINVAL; +} + +static int qe_ep0_rx(struct qe_udc *udc) +{ + struct qe_ep *ep = &udc->eps[0]; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + u32 bdstatus, length; + u32 vaddr; + + pframe = ep->rxframe; + + if (ep->dir == USB_DIR_IN) { + dev_err(udc->dev, "ep0 not a control endpoint\n"); + return -EINVAL; + } + + bd = ep->n_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + while (!(bdstatus & R_E) && length) { + if ((bdstatus & R_F) && (bdstatus & R_L) + && !(bdstatus & R_ERROR)) { + if (length == USB_CRC_SIZE) { + udc->ep0_state = WAIT_FOR_SETUP; + dev_vdbg(udc->dev, + "receive a ZLP in status phase\n"); + } else { + qe_frame_clean(pframe); + vaddr = (u32)phys_to_virt(in_be32(&bd->buf)); + frame_set_data(pframe, (u8 *)vaddr); + frame_set_length(pframe, + (length - USB_CRC_SIZE)); + frame_set_status(pframe, FRAME_OK); + switch (bdstatus & R_PID) { + case R_PID_SETUP: + frame_set_info(pframe, PID_SETUP); + break; + case R_PID_DATA1: + frame_set_info(pframe, PID_DATA1); + break; + default: + frame_set_info(pframe, PID_DATA0); + break; + } + + if ((bdstatus & R_PID) == R_PID_SETUP) + ep0_setup_handle(udc); + else + qe_ep_rxframe_handle(ep); + } + } else { + dev_err(udc->dev, "The receive frame with error!\n"); + } + + /* note: don't clear the rxbd's buffer address */ + recycle_one_rxbd(ep); + + /* Get next BD */ + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + } + + ep->n_rxbd = bd; + + return 0; +} + +static int qe_ep_rxframe_handle(struct qe_ep *ep) +{ + struct qe_frame *pframe; + u8 framepid = 0; + unsigned int fsize; + u8 *cp; + struct qe_req *req; + + pframe = ep->rxframe; + + if (frame_get_info(pframe) & PID_DATA1) + framepid = 0x1; + + if (framepid != ep->data01) { + dev_err(ep->udc->dev, "the data01 error!\n"); + return -EIO; + } + + fsize = frame_get_length(pframe); + if (list_empty(&ep->queue)) { + dev_err(ep->udc->dev, "the %s have no requeue!\n", ep->name); + } else { + req = list_entry(ep->queue.next, struct qe_req, queue); + + cp = (u8 *)(req->req.buf) + req->req.actual; + if (cp) { + memcpy(cp, pframe->data, fsize); + req->req.actual += fsize; + if ((fsize < ep->ep.maxpacket) || + (req->req.actual >= req->req.length)) { + if (ep->epnum == 0) + ep0_req_complete(ep->udc, req); + else + done(ep, req, 0); + if (list_empty(&ep->queue) && ep->epnum != 0) + qe_eprx_nack(ep); + } + } + } + + qe_ep_toggledata01(ep); + + return 0; +} + +static void ep_rx_tasklet(unsigned long data) +{ + struct qe_udc *udc = (struct qe_udc *)data; + struct qe_ep *ep; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + unsigned long flags; + u32 bdstatus, length; + u32 vaddr, i; + + spin_lock_irqsave(&udc->lock, flags); + + for (i = 1; i < USB_MAX_ENDPOINTS; i++) { + ep = &udc->eps[i]; + + if (ep->dir == USB_DIR_IN || ep->enable_tasklet == 0) { + dev_dbg(udc->dev, + "This is a transmit ep or disable tasklet!\n"); + continue; + } + + pframe = ep->rxframe; + bd = ep->n_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + while (!(bdstatus & R_E) && length) { + if (list_empty(&ep->queue)) { + qe_eprx_nack(ep); + dev_dbg(udc->dev, + "The rxep have noreq %d\n", + ep->has_data); + break; + } + + if ((bdstatus & R_F) && (bdstatus & R_L) + && !(bdstatus & R_ERROR)) { + qe_frame_clean(pframe); + vaddr = (u32)phys_to_virt(in_be32(&bd->buf)); + frame_set_data(pframe, (u8 *)vaddr); + frame_set_length(pframe, + (length - USB_CRC_SIZE)); + frame_set_status(pframe, FRAME_OK); + switch (bdstatus & R_PID) { + case R_PID_DATA1: + frame_set_info(pframe, PID_DATA1); + break; + case R_PID_SETUP: + frame_set_info(pframe, PID_SETUP); + break; + default: + frame_set_info(pframe, PID_DATA0); + break; + } + /* handle the rx frame */ + qe_ep_rxframe_handle(ep); + } else { + dev_err(udc->dev, + "error in received frame\n"); + } + /* note: don't clear the rxbd's buffer address */ + /*clear the length */ + out_be32((u32 __iomem *)bd, bdstatus & BD_STATUS_MASK); + ep->has_data--; + if (!(ep->localnack)) + recycle_one_rxbd(ep); + + /* Get next BD */ + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + } + + ep->n_rxbd = bd; + + if (ep->localnack) + ep_recycle_rxbds(ep); + + ep->enable_tasklet = 0; + } /* for i=1 */ + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static int qe_ep_rx(struct qe_ep *ep) +{ + struct qe_udc *udc; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + u16 swoffs, ucoffs, emptybds; + + udc = ep->udc; + pframe = ep->rxframe; + + if (ep->dir == USB_DIR_IN) { + dev_err(udc->dev, "transmit ep in rx function\n"); + return -EINVAL; + } + + bd = ep->n_rxbd; + + swoffs = (u16)(bd - ep->rxbase); + ucoffs = (u16)((in_be16(&udc->ep_param[ep->epnum]->rbptr) - + in_be16(&udc->ep_param[ep->epnum]->rbase)) >> 3); + if (swoffs < ucoffs) + emptybds = USB_BDRING_LEN_RX - ucoffs + swoffs; + else + emptybds = swoffs - ucoffs; + + if (emptybds < MIN_EMPTY_BDS) { + qe_eprx_nack(ep); + ep->localnack = 1; + dev_vdbg(udc->dev, "%d empty bds, send NACK\n", emptybds); + } + ep->has_data = USB_BDRING_LEN_RX - emptybds; + + if (list_empty(&ep->queue)) { + qe_eprx_nack(ep); + dev_vdbg(udc->dev, "The rxep have no req queued with %d BDs\n", + ep->has_data); + return 0; + } + + tasklet_schedule(&udc->rx_tasklet); + ep->enable_tasklet = 1; + + return 0; +} + +/* send data from a frame, no matter what tx_req */ +static int qe_ep_tx(struct qe_ep *ep, struct qe_frame *frame) +{ + struct qe_udc *udc = ep->udc; + struct qe_bd __iomem *bd; + u16 saveusbmr; + u32 bdstatus, pidmask; + u32 paddr; + + if (ep->dir == USB_DIR_OUT) { + dev_err(udc->dev, "receive ep passed to tx function\n"); + return -EINVAL; + } + + /* Disable the Tx interrupt */ + saveusbmr = in_be16(&udc->usb_regs->usb_usbmr); + out_be16(&udc->usb_regs->usb_usbmr, + saveusbmr & ~(USB_E_TXB_MASK | USB_E_TXE_MASK)); + + bd = ep->n_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + + if (!(bdstatus & (T_R | BD_LENGTH_MASK))) { + if (frame_get_length(frame) == 0) { + frame_set_data(frame, udc->nullbuf); + frame_set_length(frame, 2); + frame->info |= (ZLP | NO_CRC); + dev_vdbg(udc->dev, "the frame size = 0\n"); + } + paddr = virt_to_phys((void *)frame->data); + out_be32(&bd->buf, paddr); + bdstatus = (bdstatus&T_W); + if (!(frame_get_info(frame) & NO_CRC)) + bdstatus |= T_R | T_I | T_L | T_TC + | frame_get_length(frame); + else + bdstatus |= T_R | T_I | T_L | frame_get_length(frame); + + /* if the packet is a ZLP in status phase */ + if ((ep->epnum == 0) && (udc->ep0_state == DATA_STATE_NEED_ZLP)) + ep->data01 = 0x1; + + if (ep->data01) { + pidmask = T_PID_DATA1; + frame->info |= PID_DATA1; + } else { + pidmask = T_PID_DATA0; + frame->info |= PID_DATA0; + } + bdstatus |= T_CNF; + bdstatus |= pidmask; + out_be32((u32 __iomem *)bd, bdstatus); + qe_ep_filltxfifo(ep); + + /* enable the TX interrupt */ + out_be16(&udc->usb_regs->usb_usbmr, saveusbmr); + + qe_ep_toggledata01(ep); + if (bdstatus & T_W) |