mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-20 17:08:52 +09:00
![Lukasz Majewski](/assets/img/avatar_default.png)
The Samsung's UDC driver is not anymore copying data from USB requests to aligned internal buffers. Now it works directly in data allocated in the upper layers like UMS, DFU, THOR. This change is possible since those gadgets now must take care to allocate buffers aligned to cache line (CONFIG_SYS_CACHELINE_SIZE). This can be achieved by using DEFINE_CACHE_ALIGN_BUFFER() or ALLOC_CACHE_ALIGN_BUFFER() macros. Those take care to allocate buffer aligned to cache line in both starting address and its size. Sometimes it is enough to just use memalign() with size being a multiplication of cache line size. Test condition - test HW + measurement: Trats - Exynos4210 rev.1 - test HW Trats2 - Exynos4412 rev.1 400 MiB compressed rootfs image download with `thor 0 mmc 0` Measurement: Transmission speed: 27.04 MiB/s Signed-off-by: Lukasz Majewski <l.majewski@samsung.com> Cc: Marek Vasut <marex@denx.de>
1481 lines
36 KiB
C
1481 lines
36 KiB
C
/*
|
|
* drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
|
|
* Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers
|
|
*
|
|
* Copyright (C) 2009 for Samsung Electronics
|
|
*
|
|
* BSP Support for Samsung's UDC driver
|
|
* available at:
|
|
* git://git.kernel.org/pub/scm/linux/kernel/git/kki_ap/linux-2.6-samsung.git
|
|
*
|
|
* State machine bugfixes:
|
|
* Marek Szyprowski <m.szyprowski@samsung.com>
|
|
*
|
|
* Ported to u-boot:
|
|
* Marek Szyprowski <m.szyprowski@samsung.com>
|
|
* Lukasz Majewski <l.majewski@samsumg.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
static u8 clear_feature_num;
|
|
int clear_feature_flag;
|
|
|
|
/* Bulk-Only Mass Storage Reset (class-specific request) */
|
|
#define GET_MAX_LUN_REQUEST 0xFE
|
|
#define BOT_RESET_REQUEST 0xFF
|
|
|
|
static inline void s3c_udc_ep0_zlp(struct s3c_udc *dev)
|
|
{
|
|
u32 ep_ctrl;
|
|
|
|
writel(usb_ctrl_dma_addr, ®->in_endp[EP0_CON].diepdma);
|
|
writel(DIEPT_SIZ_PKT_CNT(1), ®->in_endp[EP0_CON].dieptsiz);
|
|
|
|
ep_ctrl = readl(®->in_endp[EP0_CON].diepctl);
|
|
writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK,
|
|
®->in_endp[EP0_CON].diepctl);
|
|
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->in_endp[EP0_CON].diepctl));
|
|
dev->ep0state = WAIT_FOR_IN_COMPLETE;
|
|
}
|
|
|
|
void s3c_udc_pre_setup(void)
|
|
{
|
|
u32 ep_ctrl;
|
|
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s : Prepare Setup packets.\n", __func__);
|
|
|
|
writel(DOEPT_SIZ_PKT_CNT(1) | sizeof(struct usb_ctrlrequest),
|
|
®->out_endp[EP0_CON].doeptsiz);
|
|
writel(usb_ctrl_dma_addr, ®->out_endp[EP0_CON].doepdma);
|
|
|
|
ep_ctrl = readl(®->out_endp[EP0_CON].doepctl);
|
|
writel(ep_ctrl|DEPCTL_EPENA, ®->out_endp[EP0_CON].doepctl);
|
|
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->in_endp[EP0_CON].diepctl));
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->out_endp[EP0_CON].doepctl));
|
|
|
|
}
|
|
|
|
static inline void s3c_ep0_complete_out(void)
|
|
{
|
|
u32 ep_ctrl;
|
|
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->in_endp[EP0_CON].diepctl));
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->out_endp[EP0_CON].doepctl));
|
|
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s : Prepare Complete Out packet.\n", __func__);
|
|
|
|
writel(DOEPT_SIZ_PKT_CNT(1) | sizeof(struct usb_ctrlrequest),
|
|
®->out_endp[EP0_CON].doeptsiz);
|
|
writel(usb_ctrl_dma_addr, ®->out_endp[EP0_CON].doepdma);
|
|
|
|
ep_ctrl = readl(®->out_endp[EP0_CON].doepctl);
|
|
writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK,
|
|
®->out_endp[EP0_CON].doepctl);
|
|
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->in_endp[EP0_CON].diepctl));
|
|
debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n",
|
|
__func__, readl(®->out_endp[EP0_CON].doepctl));
|
|
|
|
}
|
|
|
|
|
|
static int setdma_rx(struct s3c_ep *ep, struct s3c_request *req)
|
|
{
|
|
u32 *buf, ctrl;
|
|
u32 length, pktcnt;
|
|
u32 ep_num = ep_index(ep);
|
|
|
|
buf = req->req.buf + req->req.actual;
|
|
length = min(req->req.length - req->req.actual,
|
|
ep_num ? DMA_BUFFER_SIZE : ep->ep.maxpacket);
|
|
|
|
ep->len = length;
|
|
ep->dma_buf = buf;
|
|
|
|
if (ep_num == EP0_CON || length == 0)
|
|
pktcnt = 1;
|
|
else
|
|
pktcnt = (length - 1)/(ep->ep.maxpacket) + 1;
|
|
|
|
ctrl = readl(®->out_endp[ep_num].doepctl);
|
|
|
|
writel((unsigned int) ep->dma_buf, ®->out_endp[ep_num].doepdma);
|
|
writel(DOEPT_SIZ_PKT_CNT(pktcnt) | DOEPT_SIZ_XFER_SIZE(length),
|
|
®->out_endp[ep_num].doeptsiz);
|
|
writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, ®->out_endp[ep_num].doepctl);
|
|
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: EP%d RX DMA start : DOEPDMA = 0x%x,"
|
|
"DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n"
|
|
"\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n",
|
|
__func__, ep_num,
|
|
readl(®->out_endp[ep_num].doepdma),
|
|
readl(®->out_endp[ep_num].doeptsiz),
|
|
readl(®->out_endp[ep_num].doepctl),
|
|
buf, pktcnt, length);
|
|
return 0;
|
|
|
|
}
|
|
|
|
int setdma_tx(struct s3c_ep *ep, struct s3c_request *req)
|
|
{
|
|
u32 *buf, ctrl = 0;
|
|
u32 length, pktcnt;
|
|
u32 ep_num = ep_index(ep);
|
|
|
|
buf = req->req.buf + req->req.actual;
|
|
length = req->req.length - req->req.actual;
|
|
|
|
if (ep_num == EP0_CON)
|
|
length = min(length, (u32)ep_maxpacket(ep));
|
|
|
|
ep->len = length;
|
|
ep->dma_buf = buf;
|
|
|
|
flush_dcache_range((unsigned long) ep->dma_buf,
|
|
(unsigned long) ep->dma_buf +
|
|
ROUND(ep->len, CONFIG_SYS_CACHELINE_SIZE));
|
|
|
|
if (length == 0)
|
|
pktcnt = 1;
|
|
else
|
|
pktcnt = (length - 1)/(ep->ep.maxpacket) + 1;
|
|
|
|
/* Flush the endpoint's Tx FIFO */
|
|
writel(TX_FIFO_NUMBER(ep->fifo_num), ®->grstctl);
|
|
writel(TX_FIFO_NUMBER(ep->fifo_num) | TX_FIFO_FLUSH, ®->grstctl);
|
|
while (readl(®->grstctl) & TX_FIFO_FLUSH)
|
|
;
|
|
|
|
writel((unsigned long) ep->dma_buf, ®->in_endp[ep_num].diepdma);
|
|
writel(DIEPT_SIZ_PKT_CNT(pktcnt) | DIEPT_SIZ_XFER_SIZE(length),
|
|
®->in_endp[ep_num].dieptsiz);
|
|
|
|
ctrl = readl(®->in_endp[ep_num].diepctl);
|
|
|
|
/* Write the FIFO number to be used for this endpoint */
|
|
ctrl &= DIEPCTL_TX_FIFO_NUM_MASK;
|
|
ctrl |= DIEPCTL_TX_FIFO_NUM(ep->fifo_num);
|
|
|
|
/* Clear reserved (Next EP) bits */
|
|
ctrl = (ctrl&~(EP_MASK<<DEPCTL_NEXT_EP_BIT));
|
|
|
|
writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, ®->in_endp[ep_num].diepctl);
|
|
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s:EP%d TX DMA start : DIEPDMA0 = 0x%x,"
|
|
"DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n"
|
|
"\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n",
|
|
__func__, ep_num,
|
|
readl(®->in_endp[ep_num].diepdma),
|
|
readl(®->in_endp[ep_num].dieptsiz),
|
|
readl(®->in_endp[ep_num].diepctl),
|
|
buf, pktcnt, length);
|
|
|
|
return length;
|
|
}
|
|
|
|
static void complete_rx(struct s3c_udc *dev, u8 ep_num)
|
|
{
|
|
struct s3c_ep *ep = &dev->ep[ep_num];
|
|
struct s3c_request *req = NULL;
|
|
u32 ep_tsr = 0, xfer_size = 0, is_short = 0;
|
|
|
|
if (list_empty(&ep->queue)) {
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: RX DMA done : NULL REQ on OUT EP-%d\n",
|
|
__func__, ep_num);
|
|
return;
|
|
|
|
}
|
|
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
ep_tsr = readl(®->out_endp[ep_num].doeptsiz);
|
|
|
|
if (ep_num == EP0_CON)
|
|
xfer_size = (ep_tsr & DOEPT_SIZ_XFER_SIZE_MAX_EP0);
|
|
else
|
|
xfer_size = (ep_tsr & DOEPT_SIZ_XFER_SIZE_MAX_EP);
|
|
|
|
xfer_size = ep->len - xfer_size;
|
|
|
|
/*
|
|
* NOTE:
|
|
*
|
|
* Please be careful with proper buffer allocation for USB request,
|
|
* which needs to be aligned to CONFIG_SYS_CACHELINE_SIZE, not only
|
|
* with starting address, but also its size shall be a cache line
|
|
* multiplication.
|
|
*
|
|
* This will prevent from corruption of data allocated immediatelly
|
|
* before or after the buffer.
|
|
*
|
|
* For armv7, the cache_v7.c provides proper code to emit "ERROR"
|
|
* message to warn users.
|
|
*/
|
|
invalidate_dcache_range((unsigned long) ep->dma_buf,
|
|
(unsigned long) ep->dma_buf +
|
|
ROUND(xfer_size, CONFIG_SYS_CACHELINE_SIZE));
|
|
|
|
req->req.actual += min(xfer_size, req->req.length - req->req.actual);
|
|
is_short = (xfer_size < ep->ep.maxpacket);
|
|
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: RX DMA done : ep = %d, rx bytes = %d/%d, "
|
|
"is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n",
|
|
__func__, ep_num, req->req.actual, req->req.length,
|
|
is_short, ep_tsr, xfer_size);
|
|
|
|
if (is_short || req->req.actual == req->req.length) {
|
|
if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) {
|
|
debug_cond(DEBUG_OUT_EP != 0, " => Send ZLP\n");
|
|
s3c_udc_ep0_zlp(dev);
|
|
/* packet will be completed in complete_tx() */
|
|
dev->ep0state = WAIT_FOR_IN_COMPLETE;
|
|
} else {
|
|
done(ep, req, 0);
|
|
|
|
if (!list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next,
|
|
struct s3c_request, queue);
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: Next Rx request start...\n",
|
|
__func__);
|
|
setdma_rx(ep, req);
|
|
}
|
|
}
|
|
} else
|
|
setdma_rx(ep, req);
|
|
}
|
|
|
|
static void complete_tx(struct s3c_udc *dev, u8 ep_num)
|
|
{
|
|
struct s3c_ep *ep = &dev->ep[ep_num];
|
|
struct s3c_request *req;
|
|
u32 ep_tsr = 0, xfer_size = 0, is_short = 0;
|
|
u32 last;
|
|
|
|
if (dev->ep0state == WAIT_FOR_NULL_COMPLETE) {
|
|
dev->ep0state = WAIT_FOR_OUT_COMPLETE;
|
|
s3c_ep0_complete_out();
|
|
return;
|
|
}
|
|
|
|
if (list_empty(&ep->queue)) {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: TX DMA done : NULL REQ on IN EP-%d\n",
|
|
__func__, ep_num);
|
|
return;
|
|
|
|
}
|
|
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
|
|
ep_tsr = readl(®->in_endp[ep_num].dieptsiz);
|
|
|
|
xfer_size = ep->len;
|
|
is_short = (xfer_size < ep->ep.maxpacket);
|
|
req->req.actual += min(xfer_size, req->req.length - req->req.actual);
|
|
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: TX DMA done : ep = %d, tx bytes = %d/%d, "
|
|
"is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n",
|
|
__func__, ep_num, req->req.actual, req->req.length,
|
|
is_short, ep_tsr, xfer_size);
|
|
|
|
if (ep_num == 0) {
|
|
if (dev->ep0state == DATA_STATE_XMIT) {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: ep_num = %d, ep0stat =="
|
|
"DATA_STATE_XMIT\n",
|
|
__func__, ep_num);
|
|
last = write_fifo_ep0(ep, req);
|
|
if (last)
|
|
dev->ep0state = WAIT_FOR_COMPLETE;
|
|
} else if (dev->ep0state == WAIT_FOR_IN_COMPLETE) {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: ep_num = %d, completing request\n",
|
|
__func__, ep_num);
|
|
done(ep, req, 0);
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
} else if (dev->ep0state == WAIT_FOR_COMPLETE) {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: ep_num = %d, completing request\n",
|
|
__func__, ep_num);
|
|
done(ep, req, 0);
|
|
dev->ep0state = WAIT_FOR_OUT_COMPLETE;
|
|
s3c_ep0_complete_out();
|
|
} else {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: ep_num = %d, invalid ep state\n",
|
|
__func__, ep_num);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (req->req.actual == req->req.length)
|
|
done(ep, req, 0);
|
|
|
|
if (!list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: Next Tx request start...\n", __func__);
|
|
setdma_tx(ep, req);
|
|
}
|
|
}
|
|
|
|
static inline void s3c_udc_check_tx_queue(struct s3c_udc *dev, u8 ep_num)
|
|
{
|
|
struct s3c_ep *ep = &dev->ep[ep_num];
|
|
struct s3c_request *req;
|
|
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: Check queue, ep_num = %d\n", __func__, ep_num);
|
|
|
|
if (!list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: Next Tx request(0x%p) start...\n",
|
|
__func__, req);
|
|
|
|
if (ep_is_in(ep))
|
|
setdma_tx(ep, req);
|
|
else
|
|
setdma_rx(ep, req);
|
|
} else {
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: NULL REQ on IN EP-%d\n", __func__, ep_num);
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
static void process_ep_in_intr(struct s3c_udc *dev)
|
|
{
|
|
u32 ep_intr, ep_intr_status;
|
|
u8 ep_num = 0;
|
|
|
|
ep_intr = readl(®->daint);
|
|
debug_cond(DEBUG_IN_EP,
|
|
"*** %s: EP In interrupt : DAINT = 0x%x\n", __func__, ep_intr);
|
|
|
|
ep_intr &= DAINT_MASK;
|
|
|
|
while (ep_intr) {
|
|
if (ep_intr & DAINT_IN_EP_INT(1)) {
|
|
ep_intr_status = readl(®->in_endp[ep_num].diepint);
|
|
debug_cond(DEBUG_IN_EP,
|
|
"\tEP%d-IN : DIEPINT = 0x%x\n",
|
|
ep_num, ep_intr_status);
|
|
|
|
/* Interrupt Clear */
|
|
writel(ep_intr_status, ®->in_endp[ep_num].diepint);
|
|
|
|
if (ep_intr_status & TRANSFER_DONE) {
|
|
complete_tx(dev, ep_num);
|
|
|
|
if (ep_num == 0) {
|
|
if (dev->ep0state ==
|
|
WAIT_FOR_IN_COMPLETE)
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
if (dev->ep0state == WAIT_FOR_SETUP)
|
|
s3c_udc_pre_setup();
|
|
|
|
/* continue transfer after
|
|
set_clear_halt for DMA mode */
|
|
if (clear_feature_flag == 1) {
|
|
s3c_udc_check_tx_queue(dev,
|
|
clear_feature_num);
|
|
clear_feature_flag = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ep_num++;
|
|
ep_intr >>= 1;
|
|
}
|
|
}
|
|
|
|
static void process_ep_out_intr(struct s3c_udc *dev)
|
|
{
|
|
u32 ep_intr, ep_intr_status;
|
|
u8 ep_num = 0;
|
|
|
|
ep_intr = readl(®->daint);
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"*** %s: EP OUT interrupt : DAINT = 0x%x\n",
|
|
__func__, ep_intr);
|
|
|
|
ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK;
|
|
|
|
while (ep_intr) {
|
|
if (ep_intr & 0x1) {
|
|
ep_intr_status = readl(®->out_endp[ep_num].doepint);
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"\tEP%d-OUT : DOEPINT = 0x%x\n",
|
|
ep_num, ep_intr_status);
|
|
|
|
/* Interrupt Clear */
|
|
writel(ep_intr_status, ®->out_endp[ep_num].doepint);
|
|
|
|
if (ep_num == 0) {
|
|
if (ep_intr_status & TRANSFER_DONE) {
|
|
if (dev->ep0state !=
|
|
WAIT_FOR_OUT_COMPLETE)
|
|
complete_rx(dev, ep_num);
|
|
else {
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
s3c_udc_pre_setup();
|
|
}
|
|
}
|
|
|
|
if (ep_intr_status &
|
|
CTRL_OUT_EP_SETUP_PHASE_DONE) {
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"SETUP packet arrived\n");
|
|
s3c_handle_ep0(dev);
|
|
}
|
|
} else {
|
|
if (ep_intr_status & TRANSFER_DONE)
|
|
complete_rx(dev, ep_num);
|
|
}
|
|
}
|
|
ep_num++;
|
|
ep_intr >>= 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* usb client interrupt handler.
|
|
*/
|
|
static int s3c_udc_irq(int irq, void *_dev)
|
|
{
|
|
struct s3c_udc *dev = _dev;
|
|
u32 intr_status;
|
|
u32 usb_status, gintmsk;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
intr_status = readl(®->gintsts);
|
|
gintmsk = readl(®->gintmsk);
|
|
|
|
debug_cond(DEBUG_ISR,
|
|
"\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK : 0x%x,"
|
|
"DAINT : 0x%x, DAINTMSK : 0x%x\n",
|
|
__func__, intr_status, state_names[dev->ep0state], gintmsk,
|
|
readl(®->daint), readl(®->daintmsk));
|
|
|
|
if (!intr_status) {
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (intr_status & INT_ENUMDONE) {
|
|
debug_cond(DEBUG_ISR, "\tSpeed Detection interrupt\n");
|
|
|
|
writel(INT_ENUMDONE, ®->gintsts);
|
|
usb_status = (readl(®->dsts) & 0x6);
|
|
|
|
if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) {
|
|
debug_cond(DEBUG_ISR,
|
|
"\t\tFull Speed Detection\n");
|
|
set_max_pktsize(dev, USB_SPEED_FULL);
|
|
|
|
} else {
|
|
debug_cond(DEBUG_ISR,
|
|
"\t\tHigh Speed Detection : 0x%x\n",
|
|
usb_status);
|
|
set_max_pktsize(dev, USB_SPEED_HIGH);
|
|
}
|
|
}
|
|
|
|
if (intr_status & INT_EARLY_SUSPEND) {
|
|
debug_cond(DEBUG_ISR, "\tEarly suspend interrupt\n");
|
|
writel(INT_EARLY_SUSPEND, ®->gintsts);
|
|
}
|
|
|
|
if (intr_status & INT_SUSPEND) {
|
|
usb_status = readl(®->dsts);
|
|
debug_cond(DEBUG_ISR,
|
|
"\tSuspend interrupt :(DSTS):0x%x\n", usb_status);
|
|
writel(INT_SUSPEND, ®->gintsts);
|
|
|
|
if (dev->gadget.speed != USB_SPEED_UNKNOWN
|
|
&& dev->driver) {
|
|
if (dev->driver->suspend)
|
|
dev->driver->suspend(&dev->gadget);
|
|
|
|
/* HACK to let gadget detect disconnected state */
|
|
if (dev->driver->disconnect) {
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
dev->driver->disconnect(&dev->gadget);
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (intr_status & INT_RESUME) {
|
|
debug_cond(DEBUG_ISR, "\tResume interrupt\n");
|
|
writel(INT_RESUME, ®->gintsts);
|
|
|
|
if (dev->gadget.speed != USB_SPEED_UNKNOWN
|
|
&& dev->driver
|
|
&& dev->driver->resume) {
|
|
|
|
dev->driver->resume(&dev->gadget);
|
|
}
|
|
}
|
|
|
|
if (intr_status & INT_RESET) {
|
|
usb_status = readl(®->gotgctl);
|
|
debug_cond(DEBUG_ISR,
|
|
"\tReset interrupt - (GOTGCTL):0x%x\n", usb_status);
|
|
writel(INT_RESET, ®->gintsts);
|
|
|
|
if ((usb_status & 0xc0000) == (0x3 << 18)) {
|
|
if (reset_available) {
|
|
debug_cond(DEBUG_ISR,
|
|
"\t\tOTG core got reset (%d)!!\n",
|
|
reset_available);
|
|
reconfig_usbd();
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
reset_available = 0;
|
|
s3c_udc_pre_setup();
|
|
} else
|
|
reset_available = 1;
|
|
|
|
} else {
|
|
reset_available = 1;
|
|
debug_cond(DEBUG_ISR,
|
|
"\t\tRESET handling skipped\n");
|
|
}
|
|
}
|
|
|
|
if (intr_status & INT_IN_EP)
|
|
process_ep_in_intr(dev);
|
|
|
|
if (intr_status & INT_OUT_EP)
|
|
process_ep_out_intr(dev);
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/** Queue one request
|
|
* Kickstart transfer if needed
|
|
*/
|
|
static int s3c_queue(struct usb_ep *_ep, struct usb_request *_req,
|
|
gfp_t gfp_flags)
|
|
{
|
|
struct s3c_request *req;
|
|
struct s3c_ep *ep;
|
|
struct s3c_udc *dev;
|
|
unsigned long flags;
|
|
u32 ep_num, gintsts;
|
|
|
|
req = container_of(_req, struct s3c_request, req);
|
|
if (unlikely(!_req || !_req->complete || !_req->buf
|
|
|| !list_empty(&req->queue))) {
|
|
|
|
debug("%s: bad params\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ep = container_of(_ep, struct s3c_ep, ep);
|
|
|
|
if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
|
|
|
|
debug("%s: bad ep: %s, %d, %p\n", __func__,
|
|
ep->ep.name, !ep->desc, _ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ep_num = ep_index(ep);
|
|
dev = ep->dev;
|
|
if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
|
|
|
|
debug("%s: bogus device state %p\n", __func__, dev->driver);
|
|
return -ESHUTDOWN;
|
|
}
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
_req->status = -EINPROGRESS;
|
|
_req->actual = 0;
|
|
|
|
/* kickstart this i/o queue? */
|
|
debug("\n*** %s: %s-%s req = %p, len = %d, buf = %p"
|
|
"Q empty = %d, stopped = %d\n",
|
|
__func__, _ep->name, ep_is_in(ep) ? "in" : "out",
|
|
_req, _req->length, _req->buf,
|
|
list_empty(&ep->queue), ep->stopped);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int i, len = _req->length;
|
|
|
|
printf("pkt = ");
|
|
if (len > 64)
|
|
len = 64;
|
|
for (i = 0; i < len; i++) {
|
|
printf("%02x", ((u8 *)_req->buf)[i]);
|
|
if ((i & 7) == 7)
|
|
printf(" ");
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
if (list_empty(&ep->queue) && !ep->stopped) {
|
|
|
|
if (ep_num == 0) {
|
|
/* EP0 */
|
|
list_add_tail(&req->queue, &ep->queue);
|
|
s3c_ep0_kick(dev, ep);
|
|
req = 0;
|
|
|
|
} else if (ep_is_in(ep)) {
|
|
gintsts = readl(®->gintsts);
|
|
debug_cond(DEBUG_IN_EP,
|
|
"%s: ep_is_in, S3C_UDC_OTG_GINTSTS=0x%x\n",
|
|
__func__, gintsts);
|
|
|
|
setdma_tx(ep, req);
|
|
} else {
|
|
gintsts = readl(®->gintsts);
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s:ep_is_out, S3C_UDC_OTG_GINTSTS=0x%x\n",
|
|
__func__, gintsts);
|
|
|
|
setdma_rx(ep, req);
|
|
}
|
|
}
|
|
|
|
/* pio or dma irq handler advances the queue. */
|
|
if (likely(req != 0))
|
|
list_add_tail(&req->queue, &ep->queue);
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************/
|
|
/* End Point 0 related functions */
|
|
/****************************************************************/
|
|
|
|
/* return: 0 = still running, 1 = completed, negative = errno */
|
|
static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req)
|
|
{
|
|
u32 max;
|
|
unsigned count;
|
|
int is_last;
|
|
|
|
max = ep_maxpacket(ep);
|
|
|
|
debug_cond(DEBUG_EP0 != 0, "%s: max = %d\n", __func__, max);
|
|
|
|
count = setdma_tx(ep, req);
|
|
|
|
/* last packet is usually short (or a zlp) */
|
|
if (likely(count != max))
|
|
is_last = 1;
|
|
else {
|
|
if (likely(req->req.length != req->req.actual + count)
|
|
|| req->req.zero)
|
|
is_last = 0;
|
|
else
|
|
is_last = 1;
|
|
}
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: wrote %s %d bytes%s %d left %p\n", __func__,
|
|
ep->ep.name, count,
|
|
is_last ? "/L" : "",
|
|
req->req.length - req->req.actual - count, req);
|
|
|
|
/* requests complete when all IN data is in the FIFO */
|
|
if (is_last) {
|
|
ep->dev->ep0state = WAIT_FOR_SETUP;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int s3c_fifo_read(struct s3c_ep *ep, u32 *cp, int max)
|
|
{
|
|
invalidate_dcache_range((unsigned long)cp, (unsigned long)cp +
|
|
ROUND(max, CONFIG_SYS_CACHELINE_SIZE));
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: bytes=%d, ep_index=%d 0x%p\n", __func__,
|
|
max, ep_index(ep), cp);
|
|
|
|
return max;
|
|
}
|
|
|
|
/**
|
|
* udc_set_address - set the USB address for this device
|
|
* @address:
|
|
*
|
|
* Called from control endpoint function
|
|
* after it decodes a set address setup packet.
|
|
*/
|
|
static void udc_set_address(struct s3c_udc *dev, unsigned char address)
|
|
{
|
|
u32 ctrl = readl(®->dcfg);
|
|
writel(DEVICE_ADDRESS(address) | ctrl, ®->dcfg);
|
|
|
|
s3c_udc_ep0_zlp(dev);
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n",
|
|
__func__, address, readl(®->dcfg));
|
|
|
|
dev->usb_address = address;
|
|
}
|
|
|
|
static inline void s3c_udc_ep0_set_stall(struct s3c_ep *ep)
|
|
{
|
|
struct s3c_udc *dev;
|
|
u32 ep_ctrl = 0;
|
|
|
|
dev = ep->dev;
|
|
ep_ctrl = readl(®->in_endp[EP0_CON].diepctl);
|
|
|
|
/* set the disable and stall bits */
|
|
if (ep_ctrl & DEPCTL_EPENA)
|
|
ep_ctrl |= DEPCTL_EPDIS;
|
|
|
|
ep_ctrl |= DEPCTL_STALL;
|
|
|
|
writel(ep_ctrl, ®->in_endp[EP0_CON].diepctl);
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: set ep%d stall, DIEPCTL0 = 0x%p\n",
|
|
__func__, ep_index(ep), ®->in_endp[EP0_CON].diepctl);
|
|
/*
|
|
* The application can only set this bit, and the core clears it,
|
|
* when a SETUP token is received for this endpoint
|
|
*/
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
s3c_udc_pre_setup();
|
|
}
|
|
|
|
static void s3c_ep0_read(struct s3c_udc *dev)
|
|
{
|
|
struct s3c_request *req;
|
|
struct s3c_ep *ep = &dev->ep[0];
|
|
|
|
if (!list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
|
|
} else {
|
|
debug("%s: ---> BUG\n", __func__);
|
|
BUG();
|
|
return;
|
|
}
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n",
|
|
__func__, req, req->req.length, req->req.actual);
|
|
|
|
if (req->req.length == 0) {
|
|
/* zlp for Set_configuration, Set_interface,
|
|
* or Bulk-Only mass storge reset */
|
|
|
|
ep->len = 0;
|
|
s3c_udc_ep0_zlp(dev);
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: req.length = 0, bRequest = %d\n",
|
|
__func__, usb_ctrl->bRequest);
|
|
return;
|
|
}
|
|
|
|
setdma_rx(ep, req);
|
|
}
|
|
|
|
/*
|
|
* DATA_STATE_XMIT
|
|
*/
|
|
static int s3c_ep0_write(struct s3c_udc *dev)
|
|
{
|
|
struct s3c_request *req;
|
|
struct s3c_ep *ep = &dev->ep[0];
|
|
int ret, need_zlp = 0;
|
|
|
|
if (list_empty(&ep->queue))
|
|
req = 0;
|
|
else
|
|
req = list_entry(ep->queue.next, struct s3c_request, queue);
|
|
|
|
if (!req) {
|
|
debug_cond(DEBUG_EP0 != 0, "%s: NULL REQ\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n",
|
|
__func__, req, req->req.length, req->req.actual);
|
|
|
|
if (req->req.length - req->req.actual == ep0_fifo_size) {
|
|
/* Next write will end with the packet size, */
|
|
/* so we need Zero-length-packet */
|
|
need_zlp = 1;
|
|
}
|
|
|
|
ret = write_fifo_ep0(ep, req);
|
|
|
|
if ((ret == 1) && !need_zlp) {
|
|
/* Last packet */
|
|
dev->ep0state = WAIT_FOR_COMPLETE;
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: finished, waiting for status\n", __func__);
|
|
|
|
} else {
|
|
dev->ep0state = DATA_STATE_XMIT;
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: not finished\n", __func__);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int s3c_udc_get_status(struct s3c_udc *dev,
|
|
struct usb_ctrlrequest *crq)
|
|
{
|
|
u8 ep_num = crq->wIndex & 0x7F;
|
|
u16 g_status = 0;
|
|
u32 ep_ctrl;
|
|
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_GET_STATUS\n", __func__);
|
|
printf("crq->brequest:0x%x\n", crq->bRequestType & USB_RECIP_MASK);
|
|
switch (crq->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_INTERFACE:
|
|
g_status = 0;
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tGET_STATUS:USB_RECIP_INTERFACE, g_stauts = %d\n",
|
|
g_status);
|
|
break;
|
|
|
|
case USB_RECIP_DEVICE:
|
|
g_status = 0x1; /* Self powered */
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tGET_STATUS: USB_RECIP_DEVICE, g_stauts = %d\n",
|
|
g_status);
|
|
break;
|
|
|
|
case USB_RECIP_ENDPOINT:
|
|
if (crq->wLength > 2) {
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tGET_STATUS:Not support EP or wLength\n");
|
|
return 1;
|
|
}
|
|
|
|
g_status = dev->ep[ep_num].stopped;
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tGET_STATUS: USB_RECIP_ENDPOINT, g_stauts = %d\n",
|
|
g_status);
|
|
|
|
break;
|
|
|
|
default:
|
|
return 1;
|
|
}
|
|
|
|
memcpy(usb_ctrl, &g_status, sizeof(g_status));
|
|
|
|
flush_dcache_range((unsigned long) usb_ctrl,
|
|
(unsigned long) usb_ctrl +
|
|
ROUND(sizeof(g_status), CONFIG_SYS_CACHELINE_SIZE));
|
|
|
|
writel(usb_ctrl_dma_addr, ®->in_endp[EP0_CON].diepdma);
|
|
writel(DIEPT_SIZ_PKT_CNT(1) | DIEPT_SIZ_XFER_SIZE(2),
|
|
®->in_endp[EP0_CON].dieptsiz);
|
|
|
|
ep_ctrl = readl(®->in_endp[EP0_CON].diepctl);
|
|
writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK,
|
|
®->in_endp[EP0_CON].diepctl);
|
|
dev->ep0state = WAIT_FOR_NULL_COMPLETE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void s3c_udc_set_nak(struct s3c_ep *ep)
|
|
{
|
|
u8 ep_num;
|
|
u32 ep_ctrl = 0;
|
|
|
|
ep_num = ep_index(ep);
|
|
debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type);
|
|
|
|
if (ep_is_in(ep)) {
|
|
ep_ctrl = readl(®->in_endp[ep_num].diepctl);
|
|
ep_ctrl |= DEPCTL_SNAK;
|
|
writel(ep_ctrl, ®->in_endp[ep_num].diepctl);
|
|
debug("%s: set NAK, DIEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->in_endp[ep_num].diepctl));
|
|
} else {
|
|
ep_ctrl = readl(®->out_endp[ep_num].doepctl);
|
|
ep_ctrl |= DEPCTL_SNAK;
|
|
writel(ep_ctrl, ®->out_endp[ep_num].doepctl);
|
|
debug("%s: set NAK, DOEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->out_endp[ep_num].doepctl));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void s3c_udc_ep_set_stall(struct s3c_ep *ep)
|
|
{
|
|
u8 ep_num;
|
|
u32 ep_ctrl = 0;
|
|
|
|
ep_num = ep_index(ep);
|
|
debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type);
|
|
|
|
if (ep_is_in(ep)) {
|
|
ep_ctrl = readl(®->in_endp[ep_num].diepctl);
|
|
|
|
/* set the disable and stall bits */
|
|
if (ep_ctrl & DEPCTL_EPENA)
|
|
ep_ctrl |= DEPCTL_EPDIS;
|
|
|
|
ep_ctrl |= DEPCTL_STALL;
|
|
|
|
writel(ep_ctrl, ®->in_endp[ep_num].diepctl);
|
|
debug("%s: set stall, DIEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->in_endp[ep_num].diepctl));
|
|
|
|
} else {
|
|
ep_ctrl = readl(®->out_endp[ep_num].doepctl);
|
|
|
|
/* set the stall bit */
|
|
ep_ctrl |= DEPCTL_STALL;
|
|
|
|
writel(ep_ctrl, ®->out_endp[ep_num].doepctl);
|
|
debug("%s: set stall, DOEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->out_endp[ep_num].doepctl));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void s3c_udc_ep_clear_stall(struct s3c_ep *ep)
|
|
{
|
|
u8 ep_num;
|
|
u32 ep_ctrl = 0;
|
|
|
|
ep_num = ep_index(ep);
|
|
debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type);
|
|
|
|
if (ep_is_in(ep)) {
|
|
ep_ctrl = readl(®->in_endp[ep_num].diepctl);
|
|
|
|
/* clear stall bit */
|
|
ep_ctrl &= ~DEPCTL_STALL;
|
|
|
|
/*
|
|
* USB Spec 9.4.5: For endpoints using data toggle, regardless
|
|
* of whether an endpoint has the Halt feature set, a
|
|
* ClearFeature(ENDPOINT_HALT) request always results in the
|
|
* data toggle being reinitialized to DATA0.
|
|
*/
|
|
if (ep->bmAttributes == USB_ENDPOINT_XFER_INT
|
|
|| ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
|
|
ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */
|
|
}
|
|
|
|
writel(ep_ctrl, ®->in_endp[ep_num].diepctl);
|
|
debug("%s: cleared stall, DIEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->in_endp[ep_num].diepctl));
|
|
|
|
} else {
|
|
ep_ctrl = readl(®->out_endp[ep_num].doepctl);
|
|
|
|
/* clear stall bit */
|
|
ep_ctrl &= ~DEPCTL_STALL;
|
|
|
|
if (ep->bmAttributes == USB_ENDPOINT_XFER_INT
|
|
|| ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
|
|
ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */
|
|
}
|
|
|
|
writel(ep_ctrl, ®->out_endp[ep_num].doepctl);
|
|
debug("%s: cleared stall, DOEPCTL%d = 0x%x\n",
|
|
__func__, ep_num, readl(®->out_endp[ep_num].doepctl));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static int s3c_udc_set_halt(struct usb_ep *_ep, int value)
|
|
{
|
|
struct s3c_ep *ep;
|
|
struct s3c_udc *dev;
|
|
unsigned long flags;
|
|
u8 ep_num;
|
|
|
|
ep = container_of(_ep, struct s3c_ep, ep);
|
|
ep_num = ep_index(ep);
|
|
|
|
if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON ||
|
|
ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) {
|
|
debug("%s: %s bad ep or descriptor\n", __func__, ep->ep.name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Attempt to halt IN ep will fail if any transfer requests
|
|
* are still queue */
|
|
if (value && ep_is_in(ep) && !list_empty(&ep->queue)) {
|
|
debug("%s: %s queue not empty, req = %p\n",
|
|
__func__, ep->ep.name,
|
|
list_entry(ep->queue.next, struct s3c_request, queue));
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
dev = ep->dev;
|
|
debug("%s: ep_num = %d, value = %d\n", __func__, ep_num, value);
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
if (value == 0) {
|
|
ep->stopped = 0;
|
|
s3c_udc_ep_clear_stall(ep);
|
|
} else {
|
|
if (ep_num == 0)
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
ep->stopped = 1;
|
|
s3c_udc_ep_set_stall(ep);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void s3c_udc_ep_activate(struct s3c_ep *ep)
|
|
{
|
|
u8 ep_num;
|
|
u32 ep_ctrl = 0, daintmsk = 0;
|
|
|
|
ep_num = ep_index(ep);
|
|
|
|
/* Read DEPCTLn register */
|
|
if (ep_is_in(ep)) {
|
|
ep_ctrl = readl(®->in_endp[ep_num].diepctl);
|
|
daintmsk = 1 << ep_num;
|
|
} else {
|
|
ep_ctrl = readl(®->out_endp[ep_num].doepctl);
|
|
daintmsk = (1 << ep_num) << DAINT_OUT_BIT;
|
|
}
|
|
|
|
debug("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n",
|
|
__func__, ep_num, ep_ctrl, ep_is_in(ep));
|
|
|
|
/* If the EP is already active don't change the EP Control
|
|
* register. */
|
|
if (!(ep_ctrl & DEPCTL_USBACTEP)) {
|
|
ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) |
|
|
(ep->bmAttributes << DEPCTL_TYPE_BIT);
|
|
ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) |
|
|
(ep->ep.maxpacket << DEPCTL_MPS_BIT);
|
|
ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP | DEPCTL_SNAK);
|
|
|
|
if (ep_is_in(ep)) {
|
|
writel(ep_ctrl, ®->in_endp[ep_num].diepctl);
|
|
debug("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n",
|
|
__func__, ep_num, ep_num,
|
|
readl(®->in_endp[ep_num].diepctl));
|
|
} else {
|
|
writel(ep_ctrl, ®->out_endp[ep_num].doepctl);
|
|
debug("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n",
|
|
__func__, ep_num, ep_num,
|
|
readl(®->out_endp[ep_num].doepctl));
|
|
}
|
|
}
|
|
|
|
/* Unmask EP Interrtupt */
|
|
writel(readl(®->daintmsk)|daintmsk, ®->daintmsk);
|
|
debug("%s: DAINTMSK = 0x%x\n", __func__, readl(®->daintmsk));
|
|
|
|
}
|
|
|
|
static int s3c_udc_clear_feature(struct usb_ep *_ep)
|
|
{
|
|
struct s3c_udc *dev;
|
|
struct s3c_ep *ep;
|
|
u8 ep_num;
|
|
|
|
ep = container_of(_ep, struct s3c_ep, ep);
|
|
ep_num = ep_index(ep);
|
|
|
|
dev = ep->dev;
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n",
|
|
__func__, ep_num, ep_is_in(ep), clear_feature_flag);
|
|
|
|
if (usb_ctrl->wLength != 0) {
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tCLEAR_FEATURE: wLength is not zero.....\n");
|
|
return 1;
|
|
}
|
|
|
|
switch (usb_ctrl->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
switch (usb_ctrl->wValue) {
|
|
case USB_DEVICE_REMOTE_WAKEUP:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tOFF:USB_DEVICE_REMOTE_WAKEUP\n");
|
|
break;
|
|
|
|
case USB_DEVICE_TEST_MODE:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tCLEAR_FEATURE: USB_DEVICE_TEST_MODE\n");
|
|
/** @todo Add CLEAR_FEATURE for TEST modes. */
|
|
break;
|
|
}
|
|
|
|
s3c_udc_ep0_zlp(dev);
|
|
break;
|
|
|
|
case USB_RECIP_ENDPOINT:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tCLEAR_FEATURE:USB_RECIP_ENDPOINT, wValue = %d\n",
|
|
usb_ctrl->wValue);
|
|
|
|
if (usb_ctrl->wValue == USB_ENDPOINT_HALT) {
|
|
if (ep_num == 0) {
|
|
s3c_udc_ep0_set_stall(ep);
|
|
return 0;
|
|
}
|
|
|
|
s3c_udc_ep0_zlp(dev);
|
|
|
|
s3c_udc_ep_clear_stall(ep);
|
|
s3c_udc_ep_activate(ep);
|
|
ep->stopped = 0;
|
|
|
|
clear_feature_num = ep_num;
|
|
clear_feature_flag = 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int s3c_udc_set_feature(struct usb_ep *_ep)
|
|
{
|
|
struct s3c_udc *dev;
|
|
struct s3c_ep *ep;
|
|
u8 ep_num;
|
|
|
|
ep = container_of(_ep, struct s3c_ep, ep);
|
|
ep_num = ep_index(ep);
|
|
dev = ep->dev;
|
|
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_SET_FEATURE , ep_num = %d\n",
|
|
__func__, ep_num);
|
|
|
|
if (usb_ctrl->wLength != 0) {
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE: wLength is not zero.....\n");
|
|
return 1;
|
|
}
|
|
|
|
switch (usb_ctrl->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
switch (usb_ctrl->wValue) {
|
|
case USB_DEVICE_REMOTE_WAKEUP:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE:USB_DEVICE_REMOTE_WAKEUP\n");
|
|
break;
|
|
case USB_DEVICE_B_HNP_ENABLE:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n");
|
|
break;
|
|
|
|
case USB_DEVICE_A_HNP_SUPPORT:
|
|
/* RH port supports HNP */
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE:USB_DEVICE_A_HNP_SUPPORT\n");
|
|
break;
|
|
|
|
case USB_DEVICE_A_ALT_HNP_SUPPORT:
|
|
/* other RH port does */
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET: USB_DEVICE_A_ALT_HNP_SUPPORT\n");
|
|
break;
|
|
}
|
|
|
|
s3c_udc_ep0_zlp(dev);
|
|
return 0;
|
|
|
|
case USB_RECIP_INTERFACE:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE: USB_RECIP_INTERFACE\n");
|
|
break;
|
|
|
|
case USB_RECIP_ENDPOINT:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tSET_FEATURE: USB_RECIP_ENDPOINT\n");
|
|
if (usb_ctrl->wValue == USB_ENDPOINT_HALT) {
|
|
if (ep_num == 0) {
|
|
s3c_udc_ep0_set_stall(ep);
|
|
return 0;
|
|
}
|
|
ep->stopped = 1;
|
|
s3c_udc_ep_set_stall(ep);
|
|
}
|
|
|
|
s3c_udc_ep0_zlp(dev);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* WAIT_FOR_SETUP (OUT_PKT_RDY)
|
|
*/
|
|
void s3c_ep0_setup(struct s3c_udc *dev)
|
|
{
|
|
struct s3c_ep *ep = &dev->ep[0];
|
|
int i;
|
|
u8 ep_num;
|
|
|
|
/* Nuke all previous transfers */
|
|
nuke(ep, -EPROTO);
|
|
|
|
/* read control req from fifo (8 bytes) */
|
|
s3c_fifo_read(ep, (u32 *)usb_ctrl, 8);
|
|
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: bRequestType = 0x%x(%s), bRequest = 0x%x"
|
|
"\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n",
|
|
__func__, usb_ctrl->bRequestType,
|
|
(usb_ctrl->bRequestType & USB_DIR_IN) ? "IN" : "OUT",
|
|
usb_ctrl->bRequest,
|
|
usb_ctrl->wLength, usb_ctrl->wValue, usb_ctrl->wIndex);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int i, len = sizeof(*usb_ctrl);
|
|
char *p = (char *)usb_ctrl;
|
|
|
|
printf("pkt = ");
|
|
for (i = 0; i < len; i++) {
|
|
printf("%02x", ((u8 *)p)[i]);
|
|
if ((i & 7) == 7)
|
|
printf(" ");
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
if (usb_ctrl->bRequest == GET_MAX_LUN_REQUEST &&
|
|
usb_ctrl->wLength != 1) {
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\t%s:GET_MAX_LUN_REQUEST:invalid",
|
|
__func__);
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"wLength = %d, setup returned\n",
|
|
usb_ctrl->wLength);
|
|
|
|
s3c_udc_ep0_set_stall(ep);
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
return;
|
|
} else if (usb_ctrl->bRequest == BOT_RESET_REQUEST &&
|
|
usb_ctrl->wLength != 0) {
|
|
/* Bulk-Only *mass storge reset of class-specific request */
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s:BOT Rest:invalid wLength =%d, setup returned\n",
|
|
__func__, usb_ctrl->wLength);
|
|
|
|
s3c_udc_ep0_set_stall(ep);
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Set direction of EP0 */
|
|
if (likely(usb_ctrl->bRequestType & USB_DIR_IN)) {
|
|
ep->bEndpointAddress |= USB_DIR_IN;
|
|
} else {
|
|
ep->bEndpointAddress &= ~USB_DIR_IN;
|
|
}
|
|
/* cope with automagic for some standard requests. */
|
|
dev->req_std = (usb_ctrl->bRequestType & USB_TYPE_MASK)
|
|
== USB_TYPE_STANDARD;
|
|
|
|
dev->req_pending = 1;
|
|
|
|
/* Handle some SETUP packets ourselves */
|
|
if (dev->req_std) {
|
|
switch (usb_ctrl->bRequest) {
|
|
case USB_REQ_SET_ADDRESS:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_SET_ADDRESS (%d)\n",
|
|
__func__, usb_ctrl->wValue);
|
|
if (usb_ctrl->bRequestType
|
|
!= (USB_TYPE_STANDARD | USB_RECIP_DEVICE))
|
|
break;
|
|
|
|
udc_set_address(dev, usb_ctrl->wValue);
|
|
return;
|
|
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"=====================================\n");
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: USB_REQ_SET_CONFIGURATION (%d)\n",
|
|
__func__, usb_ctrl->wValue);
|
|
|
|
if (usb_ctrl->bRequestType == USB_RECIP_DEVICE)
|
|
reset_available = 1;
|
|
|
|
break;
|
|
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_GET_DESCRIPTOR\n",
|
|
__func__);
|
|
break;
|
|
|
|
case USB_REQ_SET_INTERFACE:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_SET_INTERFACE (%d)\n",
|
|
__func__, usb_ctrl->wValue);
|
|
|
|
if (usb_ctrl->bRequestType == USB_RECIP_INTERFACE)
|
|
reset_available = 1;
|
|
|
|
break;
|
|
|
|
case USB_REQ_GET_CONFIGURATION:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** USB_REQ_GET_CONFIGURATION\n",
|
|
__func__);
|
|
break;
|
|
|
|
case USB_REQ_GET_STATUS:
|
|
if (!s3c_udc_get_status(dev, usb_ctrl))
|
|
return;
|
|
|
|
break;
|
|
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
ep_num = usb_ctrl->wIndex & 0x7f;
|
|
|
|
if (!s3c_udc_clear_feature(&dev->ep[ep_num].ep))
|
|
return;
|
|
|
|
break;
|
|
|
|
case USB_REQ_SET_FEATURE:
|
|
ep_num = usb_ctrl->wIndex & 0x7f;
|
|
|
|
if (!s3c_udc_set_feature(&dev->ep[ep_num].ep))
|
|
return;
|
|
|
|
break;
|
|
|
|
default:
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s: *** Default of usb_ctrl->bRequest=0x%x"
|
|
"happened.\n", __func__, usb_ctrl->bRequest);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (likely(dev->driver)) {
|
|
/* device-2-host (IN) or no data setup command,
|
|
* process immediately */
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"%s:usb_ctrlreq will be passed to fsg_setup()\n",
|
|
__func__);
|
|
|
|
spin_unlock(&dev->lock);
|
|
i = dev->driver->setup(&dev->gadget, usb_ctrl);
|
|
spin_lock(&dev->lock);
|
|
|
|
if (i < 0) {
|
|
/* setup processing failed, force stall */
|
|
s3c_udc_ep0_set_stall(ep);
|
|
dev->ep0state = WAIT_FOR_SETUP;
|
|
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tdev->driver->setup failed (%d),"
|
|
" bRequest = %d\n",
|
|
i, usb_ctrl->bRequest);
|
|
|
|
|
|
} else if (dev->req_pending) {
|
|
dev->req_pending = 0;
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tdev->req_pending...\n");
|
|
}
|
|
|
|
debug_cond(DEBUG_SETUP != 0,
|
|
"\tep0state = %s\n", state_names[dev->ep0state]);
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handle ep0 interrupt
|
|
*/
|
|
static void s3c_handle_ep0(struct s3c_udc *dev)
|
|
{
|
|
if (dev->ep0state == WAIT_FOR_SETUP) {
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: WAIT_FOR_SETUP\n", __func__);
|
|
s3c_ep0_setup(dev);
|
|
|
|
} else {
|
|
debug_cond(DEBUG_OUT_EP != 0,
|
|
"%s: strange state!!(state = %s)\n",
|
|
__func__, state_names[dev->ep0state]);
|
|
}
|
|
}
|
|
|
|
static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep)
|
|
{
|
|
debug_cond(DEBUG_EP0 != 0,
|
|
"%s: ep_is_in = %d\n", __func__, ep_is_in(ep));
|
|
if (ep_is_in(ep)) {
|
|
dev->ep0state = DATA_STATE_XMIT;
|
|
s3c_ep0_write(dev);
|
|
|
|
} else {
|
|
dev->ep0state = DATA_STATE_RECV;
|
|
s3c_ep0_read(dev);
|
|
}
|
|
}
|