From patchwork Mon May 13 08:28:13 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kuo-Jung Su X-Patchwork-Id: 243328 X-Patchwork-Delegate: marek.vasut@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id C92582C008C for ; Mon, 13 May 2013 18:27:33 +1000 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 50F444A029; Mon, 13 May 2013 10:27:23 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id b19K6JuVX7DA; Mon, 13 May 2013 10:27:23 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 8981B4A058; Mon, 13 May 2013 10:27:05 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 4D06C4A088 for ; Mon, 13 May 2013 10:27:02 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id wsE2dydf3A-V for ; Mon, 13 May 2013 10:26:57 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from mail-pa0-f41.google.com (mail-pa0-f41.google.com [209.85.220.41]) by theia.denx.de (Postfix) with ESMTPS id 55CD14A023 for ; Mon, 13 May 2013 10:26:36 +0200 (CEST) Received: by mail-pa0-f41.google.com with SMTP id rl6so4445150pac.0 for ; Mon, 13 May 2013 01:26:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=x-received:from:to:cc:subject:date:message-id:x-mailer:in-reply-to :references:in-reply-to:references; bh=lUpz3NduEOKFZt8g/UF7lA8iLRgh6ah8O7WZbGp2HEM=; b=ShSrME3Zo4EgcZlIey3Si8FRgFjvCfNG7GxD5IB33KUg8n/7hC4+TTBo8m7TNu959M IZyiz47SU0dQvQZrTLUuZQG2yAXGuzOuWodsymBB3tdHaUOCVrrgHCiZIRAPFdYYlYnC NRlmnIFXVCjLmm3gj1OYSGiEOd7P4iOFuggdXpjpgJshMdvUh9f7mulflVMQxUsMdKBZ fwlk/pBxmNQTSYe7SlG6DdguqehuOCETOKtj5aJRks10qm7DxGa7SvYtBTWDMtps3KAE d560ccazMdL7J+CScD1yX+SSILQet4qTI6g7Z/Yi2sF6Tc9wESKdtHy2JS4JLIw1nTum TuVw== X-Received: by 10.68.209.232 with SMTP id mp8mr27960157pbc.44.1368433594917; Mon, 13 May 2013 01:26:34 -0700 (PDT) Received: from localhost.localdomain ([220.132.37.35]) by mx.google.com with ESMTPSA id qh4sm14045970pac.8.2013.05.13.01.26.32 for (version=TLSv1 cipher=DES-CBC3-SHA bits=168/168); Mon, 13 May 2013 01:26:34 -0700 (PDT) From: Kuo-Jung Su To: u-boot@lists.denx.de Date: Mon, 13 May 2013 16:28:13 +0800 Message-Id: <1368433693-18599-5-git-send-email-dantesu@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1368433693-18599-1-git-send-email-dantesu@gmail.com> References: <1368433693-18599-1-git-send-email-dantesu@gmail.com> In-Reply-To: <1368410846-18038-2-git-send-email-dantesu@gmail.com> References: <1368410846-18038-2-git-send-email-dantesu@gmail.com> Cc: Marek Vasut , Kuo-Jung Su Subject: [U-Boot] [PATCH v7 4/4] usb: gadget: add Faraday FOTG210 USB gadget support X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de From: Kuo-Jung Su The Faraday FOTG210 is an OTG chip which could operate as either an EHCI Host or a USB Device at a time. Signed-off-by: Kuo-Jung Su CC: Marek Vasut --- Changes for v7: - Nothing updates Changes for v6: - Nothing updates Changes for v5: - Coding Style cleanup. - Drop postfix '__iomem' from struct fotg210_regs - Use permanent delay for hardware reset - Drop '#ifndef CONFIG_SYS_DCACHE_OFF' - Drop magic numbers Changes for v4: - Use only macro constants and named bit/mask - Remove dcache_enable() from usb_gadget_register_driver() Changes for v3: - Coding Style cleanup. - Drop bit fields from c struct. - Drop macros for wirtel()/readl(), call them directly. - Always insert a blank line between declarations and code. - Replace all the infinite wait loop with a timeout. - Add '__iomem' to all the declaration of HW register pointers. Changes for v2: - Coding Style cleanup. - Use readl(), writel(), clrsetbits_le32() to replace REG() macros. - Use structure based hardware registers to replace the macro constants. - Replace BIT() with BIT_MASK(). - echi-faraday: Remove debug codes. drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/fotg210.c | 948 +++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/gadget_chips.h | 8 + 3 files changed, 957 insertions(+) create mode 100644 drivers/usb/gadget/fotg210.c -- 1.7.9.5 diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index e545b6b..432cf17 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -35,6 +35,7 @@ endif # new USB gadget layer dependencies ifdef CONFIG_USB_GADGET COBJS-$(CONFIG_USB_GADGET_S3C_UDC_OTG) += s3c_udc_otg.o +COBJS-$(CONFIG_USB_GADGET_FOTG210) += fotg210.o COBJS-$(CONFIG_USBDOWNLOAD_GADGET) += g_dnl.o COBJS-$(CONFIG_DFU_FUNCTION) += f_dfu.o endif diff --git a/drivers/usb/gadget/fotg210.c b/drivers/usb/gadget/fotg210.c new file mode 100644 index 0000000..d003331 --- /dev/null +++ b/drivers/usb/gadget/fotg210.c @@ -0,0 +1,948 @@ +/* + * Faraday USB 2.0 OTG Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CFG_NUM_ENDPOINTS 4 +#define CFG_EP0_MAX_PACKET_SIZE 64 +#define CFG_EPX_MAX_PACKET_SIZE 512 + +#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 2) /* 250 ms */ + +struct fotg210_chip; + +struct fotg210_ep { + struct usb_ep ep; + + uint maxpacket; + uint id; + uint stopped; + + struct list_head queue; + struct fotg210_chip *chip; + const struct usb_endpoint_descriptor *desc; +}; + +struct fotg210_request { + struct usb_request req; + struct list_head queue; + struct fotg210_ep *ep; +}; + +struct fotg210_chip { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct fotg210_regs *regs; + uint8_t irq; + uint16_t addr; + int pullup; + enum usb_device_state state; + struct fotg210_ep ep[1 + CFG_NUM_ENDPOINTS]; +}; + +static struct usb_endpoint_descriptor ep0_desc = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static inline int fifo_to_ep(struct fotg210_chip *chip, int id, int in) +{ + return (id < 0) ? 0 : ((id & 0x03) + 1); +} + +static inline int ep_to_fifo(struct fotg210_chip *chip, int id) +{ + return (id <= 0) ? -1 : ((id - 1) & 0x03); +} + +static inline int ep_reset(struct fotg210_chip *chip, uint8_t ep_addr) +{ + int ep = ep_addr & USB_ENDPOINT_NUMBER_MASK; + struct fotg210_regs *regs = chip->regs; + + if (ep_addr & USB_DIR_IN) { + /* reset endpoint */ + setbits_le32(®s->iep[ep - 1], IEP_RESET); + mdelay(1); + clrbits_le32(®s->iep[ep - 1], IEP_RESET); + /* clear endpoint stall */ + clrbits_le32(®s->iep[ep - 1], IEP_STALL); + } else { + /* reset endpoint */ + setbits_le32(®s->oep[ep - 1], OEP_RESET); + mdelay(1); + clrbits_le32(®s->oep[ep - 1], OEP_RESET); + /* clear endpoint stall */ + clrbits_le32(®s->oep[ep - 1], OEP_STALL); + } + + return 0; +} + +static int fotg210_reset(struct fotg210_chip *chip) +{ + struct fotg210_regs *regs = chip->regs; + uint32_t i; + + chip->state = USB_STATE_POWERED; + + /* chip enable */ + writel(DEVCTRL_EN, ®s->dev_ctrl); + + /* device address reset */ + chip->addr = 0; + writel(0, ®s->dev_addr); + + /* set idle counter to 7ms */ + writel(7, ®s->idle); + + /* disable all interrupts */ + writel(IMR_MASK, ®s->imr); + writel(GIMR_MASK, ®s->gimr); + writel(GIMR0_MASK, ®s->gimr0); + writel(GIMR1_MASK, ®s->gimr1); + writel(GIMR2_MASK, ®s->gimr2); + + /* clear interrupts */ + writel(ISR_MASK, ®s->isr); + writel(0, ®s->gisr); + writel(0, ®s->gisr0); + writel(0, ®s->gisr1); + writel(0, ®s->gisr2); + + /* chip reset */ + setbits_le32(®s->dev_ctrl, DEVCTRL_RESET); + mdelay(10); + if (readl(®s->dev_ctrl) & DEVCTRL_RESET) { + printf("fotg210: chip reset failed\n"); + return -1; + } + + /* CX FIFO reset */ + setbits_le32(®s->cxfifo, CXFIFO_CXFIFOCLR); + mdelay(10); + if (readl(®s->cxfifo) & CXFIFO_CXFIFOCLR) { + printf("fotg210: ep0 fifo reset failed\n"); + return -1; + } + + /* create static ep-fifo map (EP1 <-> FIFO0, EP2 <-> FIFO1 ...) */ + writel(EPMAP14_DEFAULT, ®s->epmap14); + writel(EPMAP58_DEFAULT, ®s->epmap58); + writel(FIFOMAP_DEFAULT, ®s->fifomap); + writel(0, ®s->fifocfg); + for (i = 0; i < 8; ++i) { + writel(CFG_EPX_MAX_PACKET_SIZE, ®s->iep[i]); + writel(CFG_EPX_MAX_PACKET_SIZE, ®s->oep[i]); + } + + /* FIFO reset */ + for (i = 0; i < 4; ++i) { + writel(FIFOCSR_RESET, ®s->fifocsr[i]); + mdelay(10); + if (readl(®s->fifocsr[i]) & FIFOCSR_RESET) { + printf("fotg210: fifo%d reset failed\n", i); + return -1; + } + } + + /* enable only device interrupt and triggered at level-high */ + writel(IMR_IRQLH | IMR_HOST | IMR_OTG, ®s->imr); + writel(ISR_MASK, ®s->isr); + /* disable EP0 IN/OUT interrupt */ + writel(GIMR0_CXOUT | GIMR0_CXIN, ®s->gimr0); + /* disable EPX IN+SPK+OUT interrupts */ + writel(GIMR1_MASK, ®s->gimr1); + /* disable wakeup+idle+dma+zlp interrupts */ + writel(GIMR2_WAKEUP | GIMR2_IDLE | GIMR2_DMAERR | GIMR2_DMAFIN + | GIMR2_ZLPRX | GIMR2_ZLPTX, ®s->gimr2); + /* enable all group interrupt */ + writel(0, ®s->gimr); + + /* suspend delay = 3 ms */ + writel(3, ®s->idle); + + /* turn-on device interrupts */ + setbits_le32(®s->dev_ctrl, DEVCTRL_GIRQ_EN); + + return 0; +} + +static inline int fotg210_cxwait(struct fotg210_chip *chip, uint32_t mask) +{ + struct fotg210_regs *regs = chip->regs; + int ret = -1; + ulong ts; + + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + if ((readl(®s->cxfifo) & mask) != mask) + continue; + ret = 0; + break; + } + + if (ret) + printf("fotg210: cx/ep0 timeout\n"); + + return ret; +} + +static int fotg210_dma(struct fotg210_ep *ep, struct fotg210_request *req) +{ + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + uint32_t tmp, ts; + uint8_t *buf = req->req.buf + req->req.actual; + uint32_t len = req->req.length - req->req.actual; + int fifo = ep_to_fifo(chip, ep->id); + int ret = -EBUSY; + + /* 1. init dma buffer */ + if (len > ep->maxpacket) + len = ep->maxpacket; + + /* 2. wait for dma ready (hardware) */ + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + if (!(readl(®s->dma_ctrl) & DMACTRL_START)) { + ret = 0; + break; + } + } + if (ret) { + printf("fotg210: dma busy\n"); + req->req.status = ret; + return ret; + } + + /* 3. DMA target setup */ + if (ep->desc->bEndpointAddress & USB_DIR_IN) + flush_dcache_range((ulong)buf, (ulong)buf + len); + else + invalidate_dcache_range((ulong)buf, (ulong)buf + len); + + writel(virt_to_phys(buf), ®s->dma_addr); + + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + if (ep->id == 0) { + /* Wait until cx/ep0 fifo empty */ + fotg210_cxwait(chip, CXFIFO_CXFIFOE); + writel(DMAFIFO_CX, ®s->dma_fifo); + } else { + /* Wait until epx fifo empty */ + fotg210_cxwait(chip, CXFIFO_FIFOE(fifo)); + writel(DMAFIFO_FIFO(fifo), ®s->dma_fifo); + } + writel(DMACTRL_LEN(len) | DMACTRL_MEM2FIFO, ®s->dma_ctrl); + } else { + uint32_t blen; + + if (ep->id == 0) { + writel(DMAFIFO_CX, ®s->dma_fifo); + do { + blen = CXFIFO_BYTES(readl(®s->cxfifo)); + } while (blen < len); + } else { + writel(DMAFIFO_FIFO(fifo), ®s->dma_fifo); + blen = FIFOCSR_BYTES(readl(®s->fifocsr[fifo])); + } + len = (len < blen) ? len : blen; + writel(DMACTRL_LEN(len) | DMACTRL_FIFO2MEM, ®s->dma_ctrl); + } + + /* 4. DMA start */ + setbits_le32(®s->dma_ctrl, DMACTRL_START); + + /* 5. DMA wait */ + ret = -EBUSY; + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + tmp = readl(®s->gisr2); + /* DMA complete */ + if (tmp & GISR2_DMAFIN) { + ret = 0; + break; + } + /* DMA error */ + if (tmp & GISR2_DMAERR) { + printf("fotg210: dma error\n"); + break; + } + /* resume, suspend, reset */ + if (tmp & (GISR2_RESUME | GISR2_SUSPEND | GISR2_RESET)) { + printf("fotg210: dma reset by host\n"); + break; + } + } + + /* 7. DMA target reset */ + if (ret) + writel(DMACTRL_ABORT | DMACTRL_CLRFF, ®s->dma_ctrl); + + writel(0, ®s->gisr2); + writel(0, ®s->dma_fifo); + + req->req.status = ret; + if (!ret) + req->req.actual += len; + else + printf("fotg210: ep%d dma error(code=%d)\n", ep->id, ret); + + return len; +} + +/* + * result of setup packet + */ +#define CX_IDLE 0 +#define CX_FINISH 1 +#define CX_STALL 2 + +static void fotg210_setup(struct fotg210_chip *chip) +{ + int id, ret = CX_IDLE; + uint32_t tmp[2]; + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)tmp; + struct fotg210_regs *regs = chip->regs; + + /* + * If this is the first Cx 8 byte command, + * we can now query USB mode (high/full speed; USB 2.0/USB 1.0) + */ + if (chip->state == USB_STATE_POWERED) { + chip->state = USB_STATE_DEFAULT; + if (readl(®s->otgcsr) & OTGCSR_DEV_B) { + /* Mini-B */ + if (readl(®s->dev_ctrl) & DEVCTRL_HS) { + puts("fotg210: HS\n"); + chip->gadget.speed = USB_SPEED_HIGH; + /* SOF mask timer = 1100 ticks */ + writel(SOFMTR_TMR(1100), ®s->sof_mtr); + } else { + puts("fotg210: FS\n"); + chip->gadget.speed = USB_SPEED_FULL; + /* SOF mask timer = 10000 ticks */ + writel(SOFMTR_TMR(10000), ®s->sof_mtr); + } + } else { + printf("fotg210: mini-A?\n"); + } + } + + /* switch data port to ep0 */ + writel(DMAFIFO_CX, ®s->dma_fifo); + /* fetch 8 bytes setup packet */ + tmp[0] = readl(®s->ep0_data); + tmp[1] = readl(®s->ep0_data); + /* release data port */ + writel(0, ®s->dma_fifo); + + if (req->bRequestType & USB_DIR_IN) + ep0_desc.bEndpointAddress = USB_DIR_IN; + else + ep0_desc.bEndpointAddress = USB_DIR_OUT; + + ret = CX_IDLE; + + if ((req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (req->bRequest) { + case USB_REQ_SET_CONFIGURATION: + debug("fotg210: set_cfg(%d)\n", req->wValue & 0x00FF); + if (!(req->wValue & 0x00FF)) { + chip->state = USB_STATE_ADDRESS; + writel(chip->addr, ®s->dev_addr); + } else { + chip->state = USB_STATE_CONFIGURED; + writel(chip->addr | DEVADDR_CONF, + ®s->dev_addr); + } + ret = CX_IDLE; + break; + + case USB_REQ_SET_ADDRESS: + debug("fotg210: set_addr(0x%04X)\n", req->wValue); + chip->state = USB_STATE_ADDRESS; + chip->addr = req->wValue & DEVADDR_ADDR_MASK; + ret = CX_FINISH; + writel(chip->addr, ®s->dev_addr); + break; + + case USB_REQ_CLEAR_FEATURE: + debug("fotg210: clr_feature(%d, %d)\n", + req->bRequestType & 0x03, req->wValue); + switch (req->wValue) { + case 0: /* [Endpoint] halt */ + ep_reset(chip, req->wIndex); + ret = CX_FINISH; + break; + case 1: /* [Device] remote wake-up */ + case 2: /* [Device] test mode */ + default: + ret = CX_STALL; + break; + } + break; + + case USB_REQ_SET_FEATURE: + debug("fotg210: set_feature(%d, %d)\n", + req->wValue, req->wIndex & 0xf); + switch (req->wValue) { + case 0: /* Endpoint Halt */ + id = req->wIndex & 0xf; + setbits_le32(®s->iep[id - 1], IEP_STALL); + setbits_le32(®s->oep[id - 1], OEP_STALL); + ret = CX_FINISH; + break; + case 1: /* Remote Wakeup */ + case 2: /* Test Mode */ + default: + ret = CX_STALL; + break; + } + break; + + case USB_REQ_GET_STATUS: + debug("fotg210: get_status\n"); + ret = CX_STALL; + break; + + case USB_REQ_SET_DESCRIPTOR: + debug("fotg210: set_descriptor\n"); + ret = CX_STALL; + break; + + case USB_REQ_SYNCH_FRAME: + debug("fotg210: sync frame\n"); + ret = CX_STALL; + break; + } + } /* if ((req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) */ + + if (ret == CX_IDLE && chip->driver->setup) { + if (chip->driver->setup(&chip->gadget, req) < 0) + ret = CX_STALL; + else + ret = CX_FINISH; + } + + switch (ret) { + case CX_FINISH: + setbits_le32(®s->cxfifo, CXFIFO_CXFIN); + break; + + case CX_STALL: + setbits_le32(®s->cxfifo, CXFIFO_CXSTALL | CXFIFO_CXFIN); + printf("fotg210: cx_stall!\n"); + break; + + case CX_IDLE: + debug("fotg210: cx_idle?\n"); + default: + break; + } +} + +/* + * fifo - FIFO id + * zlp - zero length packet + */ +static void fotg210_recv(struct fotg210_chip *chip, int ep_id) +{ + struct fotg210_regs *regs = chip->regs; + struct fotg210_ep *ep = chip->ep + ep_id; + struct fotg210_request *req; + int len; + + if (ep->stopped || (ep->desc->bEndpointAddress & USB_DIR_IN)) { + printf("fotg210: ep%d recv, invalid!\n", ep->id); + return; + } + + if (list_empty(&ep->queue)) { + printf("fotg210: ep%d recv, drop!\n", ep->id); + return; + } + + req = list_first_entry(&ep->queue, struct fotg210_request, queue); + len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket || req->req.length <= req->req.actual) { + list_del_init(&req->queue); + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + } + + if (ep->id > 0 && list_empty(&ep->queue)) { + setbits_le32(®s->gimr1, + GIMR1_FIFO_RX(ep_to_fifo(chip, ep->id))); + } +} + +/* + * USB Gadget Layer + */ +static int fotg210_ep_enable( + struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int id = ep_to_fifo(chip, ep->id); + int in = (desc->bEndpointAddress & USB_DIR_IN) ? 1 : 0; + + if (!_ep || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT + || le16_to_cpu(desc->wMaxPacketSize) == 0) { + printf("fotg210: bad ep or descriptor\n"); + return -EINVAL; + } + + ep->desc = desc; + ep->stopped = 0; + + if (in) + setbits_le32(®s->fifomap, FIFOMAP(id, FIFOMAP_IN)); + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + return -EINVAL; + + case USB_ENDPOINT_XFER_ISOC: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_ISOC)); + break; + + case USB_ENDPOINT_XFER_BULK: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_BULK)); + break; + + case USB_ENDPOINT_XFER_INT: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_INTR)); + break; + } + + return 0; +} + +static int fotg210_ep_disable(struct usb_ep *_ep) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int id = ep_to_fifo(chip, ep->id); + + ep->desc = NULL; + ep->stopped = 1; + + clrbits_le32(®s->fifocfg, FIFOCFG(id, FIFOCFG_CFG_MASK)); + clrbits_le32(®s->fifomap, FIFOMAP(id, FIFOMAP_DIR_MASK)); + + return 0; +} + +static struct usb_request *fotg210_ep_alloc_request( + struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fotg210_request *req = malloc(sizeof(*req)); + + if (req) { + memset(req, 0, sizeof(*req)); + INIT_LIST_HEAD(&req->queue); + } + return &req->req; +} + +static void fotg210_ep_free_request( + struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + free(req); +} + +static int fotg210_ep_queue( + struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + printf("fotg210: invalid request to ep%d\n", ep->id); + return -EINVAL; + } + + if (!chip || chip->state == USB_STATE_SUSPENDED) { + printf("fotg210: request while chip suspended\n"); + return -EINVAL; + } + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (req->req.length == 0) { + req->req.status = 0; + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + return 0; + } + + if (ep->id == 0) { + do { + int len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket) + break; + if (ep->desc->bEndpointAddress & USB_DIR_IN) + udelay(100); + } while (req->req.length > req->req.actual); + } else { + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + do { + int len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket) + break; + } while (req->req.length > req->req.actual); + } else { + list_add_tail(&req->queue, &ep->queue); + clrbits_le32(®s->gimr1, + GIMR1_FIFO_RX(ep_to_fifo(chip, ep->id))); + } + } + + if (ep->id == 0 || (ep->desc->bEndpointAddress & USB_DIR_IN)) { + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + } + + return 0; +} + +static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_request *req; + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) + return -EINVAL; + + /* remove the request */ + list_del_init(&req->queue); + + /* update status & invoke complete callback */ + if (req->req.status == -EINPROGRESS) { + req->req.status = -ECONNRESET; + if (req->req.complete) + req->req.complete(_ep, &req->req); + } + + return 0; +} + +static int fotg210_ep_halt(struct usb_ep *_ep, int halt) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int ret = -1; + + debug("fotg210: ep%d halt=%d\n", ep->id, halt); + + /* Endpoint STALL */ + if (ep->id > 0 && ep->id <= CFG_NUM_ENDPOINTS) { + if (halt) { + /* wait until all ep fifo empty */ + fotg210_cxwait(chip, 0xf00); + /* stall */ + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + setbits_le32(®s->iep[ep->id - 1], + IEP_STALL); + } else { + setbits_le32(®s->oep[ep->id - 1], + OEP_STALL); + } + } else { + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + clrbits_le32(®s->iep[ep->id - 1], + IEP_STALL); + } else { + clrbits_le32(®s->oep[ep->id - 1], + OEP_STALL); + } + } + ret = 0; + } + + return ret; +} + +/* + * activate/deactivate link with host. + */ +static void pullup(struct fotg210_chip *chip, int is_on) +{ + struct fotg210_regs *regs = chip->regs; + + if (is_on) { + if (!chip->pullup) { + chip->state = USB_STATE_POWERED; + chip->pullup = 1; + /* enable the chip */ + setbits_le32(®s->dev_ctrl, DEVCTRL_EN); + /* clear unplug bit (BIT0) */ + clrbits_le32(®s->phy_tmsr, PHYTMSR_UNPLUG); + } + } else { + chip->state = USB_STATE_NOTATTACHED; + chip->pullup = 0; + chip->addr = 0; + writel(chip->addr, ®s->dev_addr); + /* set unplug bit (BIT0) */ + setbits_le32(®s->phy_tmsr, PHYTMSR_UNPLUG); + /* disable the chip */ + clrbits_le32(®s->dev_ctrl, DEVCTRL_EN); + } +} + +static int fotg210_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct fotg210_chip *chip; + + chip = container_of(_gadget, struct fotg210_chip, gadget); + + debug("fotg210: pullup=%d\n", is_on); + + pullup(chip, is_on); + + return 0; +} + +static int fotg210_get_frame(struct usb_gadget *_gadget) +{ + struct fotg210_chip *chip; + struct fotg210_regs *regs; + + chip = container_of(_gadget, struct fotg210_chip, gadget); + regs = chip->regs; + + return SOFFNR_FNR(readl(®s->sof_fnr)); +} + +static struct usb_gadget_ops fotg210_gadget_ops = { + .get_frame = fotg210_get_frame, + .pullup = fotg210_pullup, +}; + +static struct usb_ep_ops fotg210_ep_ops = { + .enable = fotg210_ep_enable, + .disable = fotg210_ep_disable, + .queue = fotg210_ep_queue, + .dequeue = fotg210_ep_dequeue, + .set_halt = fotg210_ep_halt, + .alloc_request = fotg210_ep_alloc_request, + .free_request = fotg210_ep_free_request, +}; + +static struct fotg210_chip controller = { + .regs = (void __iomem *)CONFIG_FOTG210_BASE, + .gadget = { + .name = "fotg210_udc", + .ops = &fotg210_gadget_ops, + .ep0 = &controller.ep[0].ep, + .speed = USB_SPEED_UNKNOWN, + .is_dualspeed = 1, + .is_otg = 0, + .is_a_peripheral = 0, + .b_hnp_enable = 0, + .a_hnp_support = 0, + .a_alt_hnp_support = 0, + }, + .ep[0] = { + .id = 0, + .ep = { + .name = "ep0", + .ops = &fotg210_ep_ops, + }, + .desc = &ep0_desc, + .chip = &controller, + .maxpacket = CFG_EP0_MAX_PACKET_SIZE, + }, + .ep[1] = { + .id = 1, + .ep = { + .name = "ep1", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[2] = { + .id = 2, + .ep = { + .name = "ep2", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[3] = { + .id = 3, + .ep = { + .name = "ep3", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[4] = { + .id = 4, + .ep = { + .name = "ep4", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, +}; + +int usb_gadget_handle_interrupts(void) +{ + struct fotg210_chip *chip = &controller; + struct fotg210_regs *regs = chip->regs; + uint32_t id, st, isr, gisr; + + isr = readl(®s->isr) & (~readl(®s->imr)); + gisr = readl(®s->gisr) & (~readl(®s->gimr)); + if (!(isr & ISR_DEV) || !gisr) + return 0; + + writel(ISR_DEV, ®s->isr); + + /* CX interrupts */ + if (gisr & GISR_GRP0) { + st = readl(®s->gisr0); + writel(0, ®s->gisr0); + + if (st & GISR0_CXERR) + printf("fotg210: cmd error\n"); + + if (st & GISR0_CXABORT) + printf("fotg210: cmd abort\n"); + + if (st & GISR0_CXSETUP) /* setup */ + fotg210_setup(chip); + else if (st & GISR0_CXEND) /* command finish */ + setbits_le32(®s->cxfifo, CXFIFO_CXFIN); + } + + /* FIFO interrupts */ + if (gisr & GISR_GRP1) { + st = readl(®s->gisr1); + for (id = 0; id < 4; ++id) { + if (st & GISR1_RX_FIFO(id)) + fotg210_recv(chip, fifo_to_ep(chip, id, 0)); + } + } + + /* Device Status Interrupts */ + if (gisr & GISR_GRP2) { + st = readl(®s->gisr2); + writel(0, ®s->gisr2); + + if (st & GISR2_RESET) + printf("fotg210: reset by host\n"); + else if (st & GISR2_SUSPEND) + printf("fotg210: suspend/removed\n"); + else if (st & GISR2_RESUME) + printf("fotg210: resume\n"); + + /* Errors */ + if (st & GISR2_ISOCERR) + printf("fotg210: iso error\n"); + if (st & GISR2_ISOCABT) + printf("fotg210: iso abort\n"); + if (st & GISR2_DMAERR) + printf("fotg210: dma error\n"); + } + + return 0; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int i, ret = 0; + struct fotg210_chip *chip = &controller; + + if (!driver || !driver->bind || !driver->setup) { + puts("fotg210: bad parameter.\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&chip->gadget.ep_list); + for (i = 0; i < CFG_NUM_ENDPOINTS + 1; ++i) { + struct fotg210_ep *ep = chip->ep + i; + + ep->ep.maxpacket = ep->maxpacket; + INIT_LIST_HEAD(&ep->queue); + + if (ep->id == 0) { + ep->stopped = 0; + } else { + ep->stopped = 1; + list_add_tail(&ep->ep.ep_list, &chip->gadget.ep_list); + } + } + + if (fotg210_reset(chip)) { + puts("fotg210: reset failed.\n"); + return -EINVAL; + } + + ret = driver->bind(&chip->gadget); + if (ret) { + debug("fotg210: driver->bind() returned %d\n", ret); + return ret; + } + chip->driver = driver; + + return ret; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct fotg210_chip *chip = &controller; + + driver->unbind(&chip->gadget); + chip->driver = NULL; + + pullup(chip, 0); + + return 0; +} diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index e570142..f038747 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -150,6 +150,12 @@ #define gadget_is_mv(g) 0 #endif +#ifdef CONFIG_USB_GADGET_FOTG210 +#define gadget_is_fotg210(g) (!strcmp("fotg210_udc", (g)->name)) +#else +#define gadget_is_fotg210(g) 0 +#endif + /* * CONFIG_USB_GADGET_SX2 * CONFIG_USB_GADGET_AU1X00 @@ -215,5 +221,7 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x20; else if (gadget_is_mv(gadget)) return 0x21; + else if (gadget_is_fotg210(gadget)) + return 0x22; return -ENOENT; }